Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -7,8 +7,10 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
opencode:
|
opencode:
|
||||||
if: |
|
if: |
|
||||||
contains(github.event.comment.body, '/oc') ||
|
contains(github.event.comment.body, ' /oc') ||
|
||||||
contains(github.event.comment.body, '/opencode')
|
startsWith(github.event.comment.body, '/oc') ||
|
||||||
|
contains(github.event.comment.body, ' /opencode') ||
|
||||||
|
startsWith(github.event.comment.body, '/opencode')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|||||||
@@ -39,6 +39,15 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
bun-version: 1.2.19
|
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
|
- name: Install makepkg
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
@@ -53,9 +62,11 @@ jobs:
|
|||||||
git config --global user.email "opencode@sst.dev"
|
git config --global user.email "opencode@sst.dev"
|
||||||
git config --global user.name "opencode"
|
git config --global user.name "opencode"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: bun install
|
||||||
|
|
||||||
- name: Publish
|
- name: Publish
|
||||||
run: |
|
run: |
|
||||||
bun install
|
|
||||||
OPENCODE_VERSION=${{ inputs.version }} ./script/publish.ts
|
OPENCODE_VERSION=${{ inputs.version }} ./script/publish.ts
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -30,3 +30,5 @@ jobs:
|
|||||||
git add STATS.md
|
git add STATS.md
|
||||||
git diff --staged --quiet || git commit -m "ignore: update download stats $(date -I)"
|
git diff --staged --quiet || git commit -m "ignore: update download stats $(date -I)"
|
||||||
git push
|
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
|
||||||
+1
-1
@@ -5,4 +5,4 @@ node_modules
|
|||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
openapi.json
|
openapi.json
|
||||||
scratch
|
playground
|
||||||
|
|||||||
@@ -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
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
## Style
|
## IMPORTANT
|
||||||
|
|
||||||
- prefer single word variable/function names
|
- Try to keep things in one function unless composable or reusable
|
||||||
- avoid try catch where possible - prefer to let exceptions bubble up
|
- DO NOT do unnecessary destructuring of variables
|
||||||
- avoid else statements where possible
|
- DO NOT use `else` statements unless necessary
|
||||||
- do not make useless helper functions - inline functionality unless the
|
- DO NOT use `try`/`catch` if it can be avoided
|
||||||
function is reusable or composable
|
- AVOID `try`/`catch` where possible
|
||||||
- prefer Bun apis
|
- AVOID `else` statements
|
||||||
|
- AVOID using `any` type
|
||||||
## Workflow
|
- AVOID `let` statements
|
||||||
|
- PREFER single word variable names where possible
|
||||||
- you can regenerate the golang sdk by calling ./scripts/stainless.ts
|
- Use as many bun apis as possible like Bun.file()
|
||||||
- we use bun for everything
|
|
||||||
|
|||||||
@@ -1,35 +1,41 @@
|
|||||||
# Download Stats
|
# Download Stats
|
||||||
|
|
||||||
| Date | GitHub Downloads | npm Downloads | Total |
|
| Date | GitHub Downloads | npm Downloads | Total |
|
||||||
| ---------- | ---------------- | ---------------- | ----------------- |
|
| ---------- | ---------------- | ---------------- | ---------------- |
|
||||||
| 2025-06-29 | 18,789 (+0) | 39,420 (+0) | 58,209 (+0) |
|
| 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-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-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-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-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-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-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-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-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-09 | 40,924 (+2,872) | 67,935 (+3,467) | 108,859 (+6,339) |
|
||||||
| 2025-07-11 | 46,982 (+3,186) | 77,462 (+6,060) | 124,444 (+9,246) |
|
| 2025-07-10 | 43,796 (+2,872) | 71,402 (+3,467) | 115,198 (+6,339) |
|
||||||
| 2025-07-12 | 49,302 (+2,320) | 82,177 (+4,715) | 131,479 (+7,035) |
|
| 2025-07-11 | 46,982 (+3,186) | 77,462 (+6,060) | 124,444 (+9,246) |
|
||||||
| 2025-07-13 | 50,803 (+1,501) | 86,394 (+4,217) | 137,197 (+5,718) |
|
| 2025-07-12 | 49,302 (+2,320) | 82,177 (+4,715) | 131,479 (+7,035) |
|
||||||
| 2025-07-14 | 53,283 (+2,480) | 87,860 (+1,466) | 141,143 (+3,946) |
|
| 2025-07-13 | 50,803 (+1,501) | 86,394 (+4,217) | 137,197 (+5,718) |
|
||||||
| 2025-07-15 | 57,590 (+4,307) | 91,036 (+3,176) | 148,626 (+7,483) |
|
| 2025-07-14 | 53,283 (+2,480) | 87,860 (+1,466) | 141,143 (+3,946) |
|
||||||
| 2025-07-16 | 62,313 (+4,723) | 95,258 (+4,222) | 157,571 (+8,945) |
|
| 2025-07-15 | 57,590 (+4,307) | 91,036 (+3,176) | 148,626 (+7,483) |
|
||||||
| 2025-07-17 | 66,684 (+4,371) | 100,048 (+4,790) | 166,732 (+9,161) |
|
| 2025-07-16 | 62,313 (+4,723) | 95,258 (+4,222) | 157,571 (+8,945) |
|
||||||
| 2025-07-18 | 70,379 (+3,695) | 102,587 (+2,539) | 172,966 (+6,234) |
|
| 2025-07-17 | 66,684 (+4,371) | 100,048 (+4,790) | 166,732 (+9,161) |
|
||||||
| 2025-07-19 | 73,497 (+3,117) | 105,904 (+3,317) | 179,401 (+6,434) |
|
| 2025-07-18 | 70,379 (+3,695) | 102,587 (+2,539) | 172,966 (+6,234) |
|
||||||
| 2025-07-20 | 76,453 (+2,956) | 109,044 (+3,140) | 185,497 (+6,096) |
|
| 2025-07-19 | 73,497 (+3,117) | 105,904 (+3,317) | 179,401 (+6,434) |
|
||||||
| 2025-07-21 | 80,197 (+3,744) | 113,537 (+4,493) | 193,734 (+8,237) |
|
| 2025-07-20 | 76,453 (+2,956) | 109,044 (+3,140) | 185,497 (+6,096) |
|
||||||
| 2025-07-22 | 84,251 (+4,054) | 118,073 (+4,536) | 202,324 (+8,590) |
|
| 2025-07-21 | 80,197 (+3,744) | 113,537 (+4,493) | 193,734 (+8,237) |
|
||||||
| 2025-07-23 | 88,589 (+4,338) | 121,436 (+3,363) | 210,025 (+7,701) |
|
| 2025-07-22 | 84,251 (+4,054) | 118,073 (+4,536) | 202,324 (+8,590) |
|
||||||
| 2025-07-24 | 92,469 (+3,880) | 124,091 (+2,655) | 216,560 (+6,535) |
|
| 2025-07-23 | 88,589 (+4,338) | 121,436 (+3,363) | 210,025 (+7,701) |
|
||||||
| 2025-07-25 | 96,417 (+3,948) | 126,985 (+2,894) | 223,402 (+6,842) |
|
| 2025-07-24 | 92,469 (+3,880) | 124,091 (+2,655) | 216,560 (+6,535) |
|
||||||
| 2025-07-26 | 100,646 (+4,229) | 131,411 (+4,426) | 232,057 (+8,655) |
|
| 2025-07-25 | 96,417 (+3,948) | 126,985 (+2,894) | 223,402 (+6,842) |
|
||||||
| 2025-07-27 | 102,644 (+1,998) | 134,736 (+3,325) | 237,380 (+5,323) |
|
| 2025-07-26 | 100,646 (+4,229) | 131,411 (+4,426) | 232,057 (+8,655) |
|
||||||
| 2025-07-28 | 105,446 (+2,802) | 136,016 (+1,280) | 241,462 (+4,082) |
|
| 2025-07-27 | 102,644 (+1,998) | 134,736 (+3,325) | 237,380 (+5,323) |
|
||||||
| 2025-07-29 | 108,998 (+3,552) | 137,542 (+1,526) | 246,540 (+5,078) |
|
| 2025-07-28 | 105,446 (+2,802) | 136,016 (+1,280) | 241,462 (+4,082) |
|
||||||
| 2025-07-30 | 113,544 (+4,546) | 140,317 (+2,775) | 253,861 (+7,321) |
|
| 2025-07-29 | 108,998 (+3,552) | 137,542 (+1,526) | 246,540 (+5,078) |
|
||||||
| 2025-07-31 | 118,339 (+4,795) | 143,344 (+3,027) | 261,683 (+7,822) |
|
| 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) |
|
||||||
|
|||||||
@@ -10,34 +10,41 @@
|
|||||||
},
|
},
|
||||||
"packages/function": {
|
"packages/function": {
|
||||||
"name": "@opencode/function",
|
"name": "@opencode/function",
|
||||||
"version": "0.0.1",
|
"version": "0.3.130",
|
||||||
"dependencies": {
|
"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/auth-app": "8.0.1",
|
||||||
"@octokit/rest": "22.0.0",
|
"@octokit/rest": "22.0.0",
|
||||||
|
"ai": "catalog:",
|
||||||
"hono": "catalog:",
|
"hono": "catalog:",
|
||||||
"jose": "6.0.11",
|
"jose": "6.0.11",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cloudflare/workers-types": "4.20250522.0",
|
"@cloudflare/workers-types": "4.20250522.0",
|
||||||
"@types/node": "catalog:",
|
"@types/node": "catalog:",
|
||||||
|
"openai": "5.11.0",
|
||||||
"typescript": "catalog:",
|
"typescript": "catalog:",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages/opencode": {
|
"packages/opencode": {
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"version": "0.0.0",
|
"version": "0.3.130",
|
||||||
"bin": {
|
"bin": {
|
||||||
"opencode": "./bin/opencode",
|
"opencode": "./bin/opencode",
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "1.11.1",
|
"@actions/core": "1.11.1",
|
||||||
"@actions/github": "6.0.1",
|
"@actions/github": "6.0.1",
|
||||||
"@clack/prompts": "0.11.0",
|
"@clack/prompts": "1.0.0-alpha.1",
|
||||||
"@hono/zod-validator": "0.4.2",
|
"@hono/zod-validator": "0.4.2",
|
||||||
"@modelcontextprotocol/sdk": "1.15.1",
|
"@modelcontextprotocol/sdk": "1.15.1",
|
||||||
"@octokit/graphql": "9.0.1",
|
"@octokit/graphql": "9.0.1",
|
||||||
"@octokit/rest": "22.0.0",
|
"@octokit/rest": "22.0.0",
|
||||||
"@openauthjs/openauth": "0.4.3",
|
"@openauthjs/openauth": "0.4.3",
|
||||||
|
"@opencode-ai/plugin": "workspace:*",
|
||||||
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@standard-schema/spec": "1.0.0",
|
"@standard-schema/spec": "1.0.0",
|
||||||
"@zip.js/zip.js": "2.7.62",
|
"@zip.js/zip.js": "2.7.62",
|
||||||
"ai": "catalog:",
|
"ai": "catalog:",
|
||||||
@@ -74,9 +81,21 @@
|
|||||||
"zod-to-json-schema": "3.24.5",
|
"zod-to-json-schema": "3.24.5",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"packages/plugin": {
|
||||||
|
"name": "@opencode-ai/plugin",
|
||||||
|
"version": "0.3.130",
|
||||||
|
"dependencies": {
|
||||||
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@hey-api/openapi-ts": "0.80.1",
|
||||||
|
"@tsconfig/node22": "catalog:",
|
||||||
|
"typescript": "catalog:",
|
||||||
|
},
|
||||||
|
},
|
||||||
"packages/sdk/js": {
|
"packages/sdk/js": {
|
||||||
"name": "@opencode-ai/sdk",
|
"name": "@opencode-ai/sdk",
|
||||||
"version": "0.0.0",
|
"version": "0.3.130",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@hey-api/openapi-ts": "0.80.1",
|
"@hey-api/openapi-ts": "0.80.1",
|
||||||
"@tsconfig/node22": "catalog:",
|
"@tsconfig/node22": "catalog:",
|
||||||
@@ -85,7 +104,7 @@
|
|||||||
},
|
},
|
||||||
"packages/web": {
|
"packages/web": {
|
||||||
"name": "@opencode/web",
|
"name": "@opencode/web",
|
||||||
"version": "0.0.1",
|
"version": "0.3.130",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/cloudflare": "^12.5.4",
|
"@astrojs/cloudflare": "^12.5.4",
|
||||||
"@astrojs/markdown-remark": "6.3.1",
|
"@astrojs/markdown-remark": "6.3.1",
|
||||||
@@ -120,10 +139,13 @@
|
|||||||
"sharp",
|
"sharp",
|
||||||
"esbuild",
|
"esbuild",
|
||||||
],
|
],
|
||||||
|
"patchedDependencies": {
|
||||||
|
"marked-shiki@1.2.0": "patches/marked-shiki@1.2.0.patch",
|
||||||
|
},
|
||||||
"catalog": {
|
"catalog": {
|
||||||
"@tsconfig/node22": "22.0.2",
|
"@tsconfig/node22": "22.0.2",
|
||||||
"@types/node": "22.13.9",
|
"@types/node": "22.13.9",
|
||||||
"ai": "5.0.0-beta.33",
|
"ai": "5.0.0-beta.34",
|
||||||
"hono": "4.7.10",
|
"hono": "4.7.10",
|
||||||
"remeda": "2.26.0",
|
"remeda": "2.26.0",
|
||||||
"typescript": "5.8.2",
|
"typescript": "5.8.2",
|
||||||
@@ -142,13 +164,17 @@
|
|||||||
|
|
||||||
"@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@2.2.10", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-icLGO7Q0NinnHIPgT+y1QjHVwH4HwV+brWbvM+FfCG2Afpa89PyKa3Ret91kGjZpBgM/xnj1B7K5eM+rRlsXQA=="],
|
"@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@2.2.10", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-icLGO7Q0NinnHIPgT+y1QjHVwH4HwV+brWbvM+FfCG2Afpa89PyKa3Ret91kGjZpBgM/xnj1B7K5eM+rRlsXQA=="],
|
||||||
|
|
||||||
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@1.2.12", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-YSzjlko7JvuiyQFmI9RN1tNZdEiZxc+6xld/0tq/VkJaHpEzGAb1yiNxxvmYVcjvfu/PcvCxAAYXmTYQQ63IHQ=="],
|
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-uyyaO4KhxoIKZztREqLPh+6/K3ZJx/rp72JKoUEL9/kC+vfQTThUfPnY/bUryUpcnawx8IY/tSoYNOi/8PCv7w=="],
|
||||||
|
|
||||||
"@ai-sdk/gateway": ["@ai-sdk/gateway@1.0.0-beta.18", "", { "dependencies": { "@ai-sdk/provider": "2.0.0-beta.2", "@ai-sdk/provider-utils": "3.0.0-beta.9" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-1K5L7mY04ZwpngkDPLaiBiCivVj1h7gDiCZjAIgXtVp0S2zQ+1efnM/K/o2Pig6rUbt559rDLLalwZUgvn0vig=="],
|
"@ai-sdk/gateway": ["@ai-sdk/gateway@1.0.0-beta.19", "", { "dependencies": { "@ai-sdk/provider": "2.0.0-beta.2", "@ai-sdk/provider-utils": "3.0.0-beta.10" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-felWPMuECZRGx8xnmvH5dW3jywKTkGnw/tXN8szphGzEDr/BfxywuXijfPBG2WBUS6frPXsvSLDRdCm5W38PXA=="],
|
||||||
|
|
||||||
"@ai-sdk/provider": ["@ai-sdk/provider@2.0.0-beta.2", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-vqhtZA7R24q1XnmfmIb1fZSmHMIaJH1BVQ+0kFnNJgqWsc+V8i+yfetZ37gUc4fXATFmBuS/6O7+RPoHsZ2Fqg=="],
|
"@ai-sdk/openai": ["@ai-sdk/openai@2.0.2", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-D4zYz2uR90aooKQvX1XnS00Z7PkbrcY+snUvPfm5bCabTG7bzLrVtD56nJ5bSaZG8lmuOMfXpyiEEArYLyWPpw=="],
|
||||||
|
|
||||||
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0-beta.9", "", { "dependencies": { "@ai-sdk/provider": "2.0.0-beta.2", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-RJMeoqFA9mGo1XOE20bpVv4/ikVbZMHo00vmF4RweN7GHS+nEXU3SHFgtcp7NBG3j8W15b9MAitOBycRMYxecg=="],
|
"@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.1", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-luHVcU+yKzwv3ekKgbP3v+elUVxb2Rt+8c6w9qi7g2NYG2/pEL21oIrnaEnc6UtTZLLZX9EFBcpq2N1FQKDIMw=="],
|
||||||
|
|
||||||
|
"@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="],
|
||||||
|
|
||||||
|
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
|
||||||
|
|
||||||
"@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
|
"@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
|
||||||
|
|
||||||
@@ -222,9 +248,9 @@
|
|||||||
|
|
||||||
"@capsizecss/unpack": ["@capsizecss/unpack@2.4.0", "", { "dependencies": { "blob-to-buffer": "^1.2.8", "cross-fetch": "^3.0.4", "fontkit": "^2.0.2" } }, "sha512-GrSU71meACqcmIUxPYOJvGKF0yryjN/L1aCuE9DViCTJI7bfkjgYDPD1zbNDcINJwSSP6UaBZY9GAbYDO7re0Q=="],
|
"@capsizecss/unpack": ["@capsizecss/unpack@2.4.0", "", { "dependencies": { "blob-to-buffer": "^1.2.8", "cross-fetch": "^3.0.4", "fontkit": "^2.0.2" } }, "sha512-GrSU71meACqcmIUxPYOJvGKF0yryjN/L1aCuE9DViCTJI7bfkjgYDPD1zbNDcINJwSSP6UaBZY9GAbYDO7re0Q=="],
|
||||||
|
|
||||||
"@clack/core": ["@clack/core@0.5.0", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow=="],
|
"@clack/core": ["@clack/core@1.0.0-alpha.1", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-rFbCU83JnN7l3W1nfgCqqme4ZZvTTgsiKQ6FM0l+r0P+o2eJpExcocBUWUIwnDzL76Aca9VhUdWmB2MbUv+Qyg=="],
|
||||||
|
|
||||||
"@clack/prompts": ["@clack/prompts@0.11.0", "", { "dependencies": { "@clack/core": "0.5.0", "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw=="],
|
"@clack/prompts": ["@clack/prompts@1.0.0-alpha.1", "", { "dependencies": { "@clack/core": "1.0.0-alpha.1", "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-07MNT0OsxjKOcyVfX8KhXBhJiyUbDP1vuIAcHc+nx5v93MJO23pX3X/k3bWz6T3rpM9dgWPq90i4Jq7gZAyMbw=="],
|
||||||
|
|
||||||
"@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.0", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA=="],
|
"@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.0", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA=="],
|
||||||
|
|
||||||
@@ -416,6 +442,8 @@
|
|||||||
|
|
||||||
"@openauthjs/openauth": ["@openauthjs/openauth@0.4.3", "", { "dependencies": { "@standard-schema/spec": "1.0.0-beta.3", "aws4fetch": "1.0.20", "jose": "5.9.6" }, "peerDependencies": { "arctic": "^2.2.2", "hono": "^4.0.0" } }, "sha512-RlnjqvHzqcbFVymEwhlUEuac4utA5h4nhSK/i2szZuQmxTIqbGUxZ+nM+avM+VV4Ing+/ZaNLKILoXS3yrkOOw=="],
|
"@openauthjs/openauth": ["@openauthjs/openauth@0.4.3", "", { "dependencies": { "@standard-schema/spec": "1.0.0-beta.3", "aws4fetch": "1.0.20", "jose": "5.9.6" }, "peerDependencies": { "arctic": "^2.2.2", "hono": "^4.0.0" } }, "sha512-RlnjqvHzqcbFVymEwhlUEuac4utA5h4nhSK/i2szZuQmxTIqbGUxZ+nM+avM+VV4Ing+/ZaNLKILoXS3yrkOOw=="],
|
||||||
|
|
||||||
|
"@opencode-ai/plugin": ["@opencode-ai/plugin@workspace:packages/plugin"],
|
||||||
|
|
||||||
"@opencode-ai/sdk": ["@opencode-ai/sdk@workspace:packages/sdk/js"],
|
"@opencode-ai/sdk": ["@opencode-ai/sdk@workspace:packages/sdk/js"],
|
||||||
|
|
||||||
"@opencode/function": ["@opencode/function@workspace:packages/function"],
|
"@opencode/function": ["@opencode/function@workspace:packages/function"],
|
||||||
@@ -594,7 +622,7 @@
|
|||||||
|
|
||||||
"acorn-walk": ["acorn-walk@8.3.2", "", {}, "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A=="],
|
"acorn-walk": ["acorn-walk@8.3.2", "", {}, "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A=="],
|
||||||
|
|
||||||
"ai": ["ai@5.0.0-beta.33", "", { "dependencies": { "@ai-sdk/gateway": "1.0.0-beta.18", "@ai-sdk/provider": "2.0.0-beta.2", "@ai-sdk/provider-utils": "3.0.0-beta.9", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-TKDOYDRhS6kSmfbTj3lLFmS8kBx8OOHsIfhYKJBKnAPwlbkI3/byZRBty8tfKBrwsUAbSro3GB7rFeSthft37Q=="],
|
"ai": ["ai@5.0.0-beta.34", "", { "dependencies": { "@ai-sdk/gateway": "1.0.0-beta.19", "@ai-sdk/provider": "2.0.0-beta.2", "@ai-sdk/provider-utils": "3.0.0-beta.10", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-AFJ4p35AxA+1KFtnoouePLaAUpoj0IxIAoq/xgIv88qzYajTg4Sac5KaV4CDHFRLoF0L2cwhlFXt/Ss/zyBKkA=="],
|
||||||
|
|
||||||
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
|
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
|
||||||
|
|
||||||
@@ -1350,6 +1378,8 @@
|
|||||||
|
|
||||||
"open": ["open@10.1.2", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "is-wsl": "^3.1.0" } }, "sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw=="],
|
"open": ["open@10.1.2", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "is-wsl": "^3.1.0" } }, "sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw=="],
|
||||||
|
|
||||||
|
"openai": ["openai@5.11.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.23.8" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-+AuTc5pVjlnTuA9zvn8rA/k+1RluPIx9AD4eDcnutv6JNwHHZxIhkFy+tmMKCvmMFDQzfA/r1ujvPWB19DQkYg=="],
|
||||||
|
|
||||||
"openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],
|
"openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],
|
||||||
|
|
||||||
"opencode": ["opencode@workspace:packages/opencode"],
|
"opencode": ["opencode@workspace:packages/opencode"],
|
||||||
@@ -1824,9 +1854,9 @@
|
|||||||
|
|
||||||
"@ai-sdk/amazon-bedrock/aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="],
|
"@ai-sdk/amazon-bedrock/aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="],
|
||||||
|
|
||||||
"@ai-sdk/anthropic/@ai-sdk/provider": ["@ai-sdk/provider@1.1.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg=="],
|
"@ai-sdk/gateway/@ai-sdk/provider": ["@ai-sdk/provider@2.0.0-beta.2", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-vqhtZA7R24q1XnmfmIb1fZSmHMIaJH1BVQ+0kFnNJgqWsc+V8i+yfetZ37gUc4fXATFmBuS/6O7+RPoHsZ2Fqg=="],
|
||||||
|
|
||||||
"@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@2.2.8", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "nanoid": "^3.3.8", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA=="],
|
"@ai-sdk/gateway/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0-beta.10", "", { "dependencies": { "@ai-sdk/provider": "2.0.0-beta.2", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-e6WSsgM01au04/1L/v5daXHn00eKjPBQXl3jq3BfvQbQ1jo8Rls2pvrdkyVc25jBW4TV4Zm+tw+v6NAh5NPXMA=="],
|
||||||
|
|
||||||
"@ampproject/remapping/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.29", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ=="],
|
"@ampproject/remapping/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.29", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ=="],
|
||||||
|
|
||||||
@@ -1858,6 +1888,10 @@
|
|||||||
|
|
||||||
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||||
|
|
||||||
|
"ai/@ai-sdk/provider": ["@ai-sdk/provider@2.0.0-beta.2", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-vqhtZA7R24q1XnmfmIb1fZSmHMIaJH1BVQ+0kFnNJgqWsc+V8i+yfetZ37gUc4fXATFmBuS/6O7+RPoHsZ2Fqg=="],
|
||||||
|
|
||||||
|
"ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0-beta.10", "", { "dependencies": { "@ai-sdk/provider": "2.0.0-beta.2", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-e6WSsgM01au04/1L/v5daXHn00eKjPBQXl3jq3BfvQbQ1jo8Rls2pvrdkyVc25jBW4TV4Zm+tw+v6NAh5NPXMA=="],
|
||||||
|
|
||||||
"ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
"ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||||
|
|
||||||
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||||
@@ -1898,6 +1932,8 @@
|
|||||||
|
|
||||||
"nypm/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
"nypm/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||||
|
|
||||||
|
"opencode/@ai-sdk/anthropic": ["@ai-sdk/anthropic@1.2.12", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-YSzjlko7JvuiyQFmI9RN1tNZdEiZxc+6xld/0tq/VkJaHpEzGAb1yiNxxvmYVcjvfu/PcvCxAAYXmTYQQ63IHQ=="],
|
||||||
|
|
||||||
"opencontrol/@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.6.1", "", { "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^4.1.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-oxzMzYCkZHMntzuyerehK3fV6A2Kwh5BD6CGEJSVDU2QNEhfLOptf2X7esQgaHZXHZY0oHmMsOtIDLP71UJXgA=="],
|
"opencontrol/@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.6.1", "", { "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^4.1.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-oxzMzYCkZHMntzuyerehK3fV6A2Kwh5BD6CGEJSVDU2QNEhfLOptf2X7esQgaHZXHZY0oHmMsOtIDLP71UJXgA=="],
|
||||||
|
|
||||||
"opencontrol/hono": ["hono@4.7.4", "", {}, "sha512-Pst8FuGqz3L7tFF+u9Pu70eI0xa5S3LPUmrNd5Jm8nTHze9FxLTK9Kaj5g/k4UcwuJSXTP65SyHOPLrffpcAJg=="],
|
"opencontrol/hono": ["hono@4.7.4", "", {}, "sha512-Pst8FuGqz3L7tFF+u9Pu70eI0xa5S3LPUmrNd5Jm8nTHze9FxLTK9Kaj5g/k4UcwuJSXTP65SyHOPLrffpcAJg=="],
|
||||||
@@ -1984,6 +2020,10 @@
|
|||||||
|
|
||||||
"gray-matter/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
|
"gray-matter/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
|
||||||
|
|
||||||
|
"opencode/@ai-sdk/anthropic/@ai-sdk/provider": ["@ai-sdk/provider@1.1.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg=="],
|
||||||
|
|
||||||
|
"opencode/@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@2.2.8", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "nanoid": "^3.3.8", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA=="],
|
||||||
|
|
||||||
"opencontrol/@modelcontextprotocol/sdk/pkce-challenge": ["pkce-challenge@4.1.0", "", {}, "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ=="],
|
"opencontrol/@modelcontextprotocol/sdk/pkce-challenge": ["pkce-challenge@4.1.0", "", {}, "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ=="],
|
||||||
|
|
||||||
"opencontrol/@modelcontextprotocol/sdk/zod": ["zod@3.25.49", "", {}, "sha512-JMMPMy9ZBk3XFEdbM3iL1brx4NUSejd6xr3ELrrGEfGb355gjhiAWtG3K5o+AViV/3ZfkIrCzXsZn6SbLwTR8Q=="],
|
"opencontrol/@modelcontextprotocol/sdk/zod": ["zod@3.25.49", "", {}, "sha512-JMMPMy9ZBk3XFEdbM3iL1brx4NUSejd6xr3ELrrGEfGb355gjhiAWtG3K5o+AViV/3ZfkIrCzXsZn6SbLwTR8Q=="],
|
||||||
|
|||||||
@@ -46,3 +46,15 @@ new sst.cloudflare.x.Astro("Web", {
|
|||||||
VITE_API_URL: api.url,
|
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",
|
"$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": {
|
"mcp": {
|
||||||
"context7": {
|
"context7": {
|
||||||
"type": "remote",
|
"type": "remote",
|
||||||
|
|||||||
+5
-3
@@ -5,7 +5,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "bun@1.2.14",
|
"packageManager": "bun@1.2.14",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bun run packages/opencode/src/index.ts",
|
"dev": "bun run --conditions=development packages/opencode/src/index.ts",
|
||||||
"typecheck": "bun run --filter='*' typecheck",
|
"typecheck": "bun run --filter='*' typecheck",
|
||||||
"stainless": "./scripts/stainless",
|
"stainless": "./scripts/stainless",
|
||||||
"postinstall": "./script/hooks"
|
"postinstall": "./script/hooks"
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
"catalog": {
|
"catalog": {
|
||||||
"@types/node": "22.13.9",
|
"@types/node": "22.13.9",
|
||||||
"@tsconfig/node22": "22.0.2",
|
"@tsconfig/node22": "22.0.2",
|
||||||
"ai": "5.0.0-beta.33",
|
"ai": "5.0.0-beta.34",
|
||||||
"hono": "4.7.10",
|
"hono": "4.7.10",
|
||||||
"typescript": "5.8.2",
|
"typescript": "5.8.2",
|
||||||
"zod": "3.25.49",
|
"zod": "3.25.49",
|
||||||
@@ -43,5 +43,7 @@
|
|||||||
"protobufjs",
|
"protobufjs",
|
||||||
"sharp"
|
"sharp"
|
||||||
],
|
],
|
||||||
"patchedDependencies": {}
|
"patchedDependencies": {
|
||||||
|
"marked-shiki@1.2.0": "patches/marked-shiki@1.2.0.patch"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,22 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode/function",
|
"name": "@opencode/function",
|
||||||
"version": "0.0.1",
|
"version": "0.3.130",
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cloudflare/workers-types": "4.20250522.0",
|
"@cloudflare/workers-types": "4.20250522.0",
|
||||||
"typescript": "catalog:",
|
"@types/node": "catalog:",
|
||||||
"@types/node": "catalog:"
|
"openai": "5.11.0",
|
||||||
|
"typescript": "catalog:"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"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/auth-app": "8.0.1",
|
||||||
"@octokit/rest": "22.0.0",
|
"@octokit/rest": "22.0.0",
|
||||||
|
"ai": "catalog:",
|
||||||
"hono": "catalog:",
|
"hono": "catalog:",
|
||||||
"jose": "6.0.11"
|
"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"))
|
||||||
Vendored
+17
@@ -6,6 +6,10 @@
|
|||||||
import "sst"
|
import "sst"
|
||||||
declare module "sst" {
|
declare module "sst" {
|
||||||
export interface Resource {
|
export interface Resource {
|
||||||
|
"ANTHROPIC_API_KEY": {
|
||||||
|
"type": "sst.sst.Secret"
|
||||||
|
"value": string
|
||||||
|
}
|
||||||
"GITHUB_APP_ID": {
|
"GITHUB_APP_ID": {
|
||||||
"type": "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
"value": string
|
"value": string
|
||||||
@@ -14,10 +18,22 @@ declare module "sst" {
|
|||||||
"type": "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
"value": string
|
"value": string
|
||||||
}
|
}
|
||||||
|
"OPENAI_API_KEY": {
|
||||||
|
"type": "sst.sst.Secret"
|
||||||
|
"value": string
|
||||||
|
}
|
||||||
|
"OPENCODE_API_KEY": {
|
||||||
|
"type": "sst.sst.Secret"
|
||||||
|
"value": string
|
||||||
|
}
|
||||||
"Web": {
|
"Web": {
|
||||||
"type": "sst.cloudflare.Astro"
|
"type": "sst.cloudflare.Astro"
|
||||||
"url": string
|
"url": string
|
||||||
}
|
}
|
||||||
|
"ZHIPU_API_KEY": {
|
||||||
|
"type": "sst.sst.Secret"
|
||||||
|
"value": string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// cloudflare
|
// cloudflare
|
||||||
@@ -26,6 +42,7 @@ declare module "sst" {
|
|||||||
export interface Resource {
|
export interface Resource {
|
||||||
"Api": cloudflare.Service
|
"Api": cloudflare.Service
|
||||||
"Bucket": cloudflare.R2Bucket
|
"Bucket": cloudflare.R2Bucket
|
||||||
|
"GatewayApi": cloudflare.Service
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,19 +17,6 @@
|
|||||||
- **Error handling**: Use Result patterns, avoid throwing exceptions in tools
|
- **Error handling**: Use Result patterns, avoid throwing exceptions in tools
|
||||||
- **File structure**: Namespace-based organization (e.g., `Tool.define()`, `Session.create()`)
|
- **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
|
## Architecture
|
||||||
|
|
||||||
- **Tools**: Implement `Tool.Info` interface with `execute()` method
|
- **Tools**: Implement `Tool.Info` interface with `execute()` method
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"version": "0.0.0",
|
"version": "0.3.130",
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"dev": "bun run ./src/index.ts"
|
"dev": "bun run --conditions=development ./src/index.ts"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"opencode": "./bin/opencode"
|
"opencode": "./bin/opencode"
|
||||||
@@ -30,12 +30,14 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "1.11.1",
|
"@actions/core": "1.11.1",
|
||||||
"@actions/github": "6.0.1",
|
"@actions/github": "6.0.1",
|
||||||
"@clack/prompts": "0.11.0",
|
"@clack/prompts": "1.0.0-alpha.1",
|
||||||
"@hono/zod-validator": "0.4.2",
|
"@hono/zod-validator": "0.4.2",
|
||||||
"@modelcontextprotocol/sdk": "1.15.1",
|
"@modelcontextprotocol/sdk": "1.15.1",
|
||||||
"@octokit/graphql": "9.0.1",
|
"@octokit/graphql": "9.0.1",
|
||||||
"@octokit/rest": "22.0.0",
|
"@octokit/rest": "22.0.0",
|
||||||
"@openauthjs/openauth": "0.4.3",
|
"@openauthjs/openauth": "0.4.3",
|
||||||
|
"@opencode-ai/plugin": "workspace:*",
|
||||||
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@standard-schema/spec": "1.0.0",
|
"@standard-schema/spec": "1.0.0",
|
||||||
"@zip.js/zip.js": "2.7.62",
|
"@zip.js/zip.js": "2.7.62",
|
||||||
"ai": "catalog:",
|
"ai": "catalog:",
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ if (!snapshot) {
|
|||||||
.filter((x: string) => {
|
.filter((x: string) => {
|
||||||
const lower = x.toLowerCase()
|
const lower = x.toLowerCase()
|
||||||
return (
|
return (
|
||||||
|
!lower.includes("release:") &&
|
||||||
!lower.includes("ignore:") &&
|
!lower.includes("ignore:") &&
|
||||||
!lower.includes("chore:") &&
|
!lower.includes("chore:") &&
|
||||||
!lower.includes("ci:") &&
|
!lower.includes("ci:") &&
|
||||||
|
|||||||
@@ -38,7 +38,10 @@ export namespace Agent {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
for (const [key, value] of Object.entries(cfg.agent ?? {})) {
|
for (const [key, value] of Object.entries(cfg.agent ?? {})) {
|
||||||
if (value.disable) continue
|
if (value.disable) {
|
||||||
|
delete result[key]
|
||||||
|
continue
|
||||||
|
}
|
||||||
let item = result[key]
|
let item = result[key]
|
||||||
if (!item)
|
if (!item)
|
||||||
item = result[key] = {
|
item = result[key] = {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { App } from "../app/app"
|
|||||||
import { ConfigHooks } from "../config/hooks"
|
import { ConfigHooks } from "../config/hooks"
|
||||||
import { Format } from "../format"
|
import { Format } from "../format"
|
||||||
import { LSP } from "../lsp"
|
import { LSP } from "../lsp"
|
||||||
|
import { Plugin } from "../plugin"
|
||||||
import { Share } from "../share/share"
|
import { Share } from "../share/share"
|
||||||
import { Snapshot } from "../snapshot"
|
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) => {
|
return App.provide(input, async (app) => {
|
||||||
Share.init()
|
Share.init()
|
||||||
Format.init()
|
Format.init()
|
||||||
|
Plugin.init()
|
||||||
ConfigHooks.init()
|
ConfigHooks.init()
|
||||||
LSP.init()
|
LSP.init()
|
||||||
Snapshot.init()
|
Snapshot.init()
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ const AgentCreateCommand = cmd({
|
|||||||
const query = await prompts.text({
|
const query = await prompts.text({
|
||||||
message: "Description",
|
message: "Description",
|
||||||
placeholder: "What should this agent do?",
|
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()
|
if (prompts.isCancel(query)) throw new UI.CancelledError()
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ export const AuthLoginCommand = cmd({
|
|||||||
type: "string",
|
type: "string",
|
||||||
}),
|
}),
|
||||||
async handler(args) {
|
async handler(args) {
|
||||||
|
UI.empty()
|
||||||
prompts.intro("Add credential")
|
prompts.intro("Add credential")
|
||||||
if (args.url) {
|
if (args.url) {
|
||||||
const wellknown = await fetch(`${args.url}/.well-known/opencode`).then((x) => x.json())
|
const wellknown = await fetch(`${args.url}/.well-known/opencode`).then((x) => x.json())
|
||||||
@@ -99,7 +100,7 @@ export const AuthLoginCommand = cmd({
|
|||||||
prompts.outro("Done")
|
prompts.outro("Done")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
UI.empty()
|
await ModelsDev.refresh().catch(() => {})
|
||||||
const providers = await ModelsDev.get()
|
const providers = await ModelsDev.get()
|
||||||
const priority: Record<string, number> = {
|
const priority: Record<string, number> = {
|
||||||
anthropic: 0,
|
anthropic: 0,
|
||||||
@@ -109,7 +110,7 @@ export const AuthLoginCommand = cmd({
|
|||||||
openrouter: 4,
|
openrouter: 4,
|
||||||
vercel: 5,
|
vercel: 5,
|
||||||
}
|
}
|
||||||
let provider = await prompts.select({
|
let provider = await prompts.autocomplete({
|
||||||
message: "Select provider",
|
message: "Select provider",
|
||||||
maxItems: 8,
|
maxItems: 8,
|
||||||
options: [
|
options: [
|
||||||
@@ -138,7 +139,7 @@ export const AuthLoginCommand = cmd({
|
|||||||
if (provider === "other") {
|
if (provider === "other") {
|
||||||
provider = await prompts.text({
|
provider = await prompts.text({
|
||||||
message: "Enter provider id",
|
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()
|
if (prompts.isCancel(provider)) throw new UI.CancelledError()
|
||||||
provider = provider.replace(/^@ai-sdk\//, "")
|
provider = provider.replace(/^@ai-sdk\//, "")
|
||||||
@@ -192,7 +193,7 @@ export const AuthLoginCommand = cmd({
|
|||||||
|
|
||||||
const code = await prompts.text({
|
const code = await prompts.text({
|
||||||
message: "Paste the authorization code here: ",
|
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()
|
if (prompts.isCancel(code)) throw new UI.CancelledError()
|
||||||
|
|
||||||
@@ -228,7 +229,7 @@ export const AuthLoginCommand = cmd({
|
|||||||
|
|
||||||
const code = await prompts.text({
|
const code = await prompts.text({
|
||||||
message: "Paste the authorization code here: ",
|
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()
|
if (prompts.isCancel(code)) throw new UI.CancelledError()
|
||||||
|
|
||||||
@@ -301,7 +302,7 @@ export const AuthLoginCommand = cmd({
|
|||||||
|
|
||||||
const key = await prompts.password({
|
const key = await prompts.password({
|
||||||
message: "Enter your API key",
|
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()
|
if (prompts.isCancel(key)) throw new UI.CancelledError()
|
||||||
await Auth.set(provider, {
|
await Auth.set(provider, {
|
||||||
|
|||||||
@@ -318,8 +318,10 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
opencode:
|
opencode:
|
||||||
if: |
|
if: |
|
||||||
contains(github.event.comment.body, '/oc') ||
|
contains(github.event.comment.body, ' /oc') ||
|
||||||
contains(github.event.comment.body, '/opencode')
|
startsWith(github.event.comment.body, '/oc') ||
|
||||||
|
contains(github.event.comment.body, ' /opencode') ||
|
||||||
|
startsWith(github.event.comment.body, '/opencode')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export const McpAddCommand = cmd({
|
|||||||
|
|
||||||
const name = await prompts.text({
|
const name = await prompts.text({
|
||||||
message: "Enter MCP server name",
|
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()
|
if (prompts.isCancel(name)) throw new UI.CancelledError()
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ export const McpAddCommand = cmd({
|
|||||||
const command = await prompts.text({
|
const command = await prompts.text({
|
||||||
message: "Enter command to run",
|
message: "Enter command to run",
|
||||||
placeholder: "e.g., opencode x @modelcontextprotocol/server-filesystem",
|
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()
|
if (prompts.isCancel(command)) throw new UI.CancelledError()
|
||||||
|
|
||||||
@@ -58,6 +58,7 @@ export const McpAddCommand = cmd({
|
|||||||
message: "Enter MCP server URL",
|
message: "Enter MCP server URL",
|
||||||
placeholder: "e.g., https://example.com/mcp",
|
placeholder: "e.g., https://example.com/mcp",
|
||||||
validate: (x) => {
|
validate: (x) => {
|
||||||
|
if (!x) return "Required"
|
||||||
if (x.length === 0) return "Required"
|
if (x.length === 0) return "Required"
|
||||||
const isValid = URL.canParse(x)
|
const isValid = URL.canParse(x)
|
||||||
return isValid ? undefined : "Invalid URL"
|
return isValid ? undefined : "Invalid URL"
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export const UpgradeCommand = {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
prompts.log.info("Using method: " + method)
|
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) {
|
if (Installation.VERSION === target) {
|
||||||
prompts.log.warn(`opencode upgrade skipped: ${target} is already installed`)
|
prompts.log.warn(`opencode upgrade skipped: ${target} is already installed`)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Log } from "../util/log"
|
import { Log } from "../util/log"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
|
import os from "os"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
import { App } from "../app/app"
|
import { App } from "../app/app"
|
||||||
import { Filesystem } from "../util/filesystem"
|
import { Filesystem } from "../util/filesystem"
|
||||||
@@ -23,13 +24,13 @@ export namespace Config {
|
|||||||
for (const file of ["opencode.jsonc", "opencode.json"]) {
|
for (const file of ["opencode.jsonc", "opencode.json"]) {
|
||||||
const found = await Filesystem.findUp(file, app.path.cwd, app.path.root)
|
const found = await Filesystem.findUp(file, app.path.cwd, app.path.root)
|
||||||
for (const resolved of found.toReversed()) {
|
for (const resolved of found.toReversed()) {
|
||||||
result = mergeDeep(result, await load(resolved))
|
result = mergeDeep(result, await loadFile(resolved))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override with custom config if provided
|
// Override with custom config if provided
|
||||||
if (Flag.OPENCODE_CONFIG) {
|
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 })
|
log.debug("loaded custom config", { path: Flag.OPENCODE_CONFIG })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +38,7 @@ export namespace Config {
|
|||||||
if (value.type === "wellknown") {
|
if (value.type === "wellknown") {
|
||||||
process.env[value.key] = value.token
|
process.env[value.key] = value.token
|
||||||
const wellknown = await fetch(`${key}/.well-known/opencode`).then((x) => x.json())
|
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()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,6 +93,14 @@ export namespace Config {
|
|||||||
throw new InvalidError({ path: item }, { cause: parsed.error })
|
throw new InvalidError({ path: item }, { cause: parsed.error })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// Handle migration from autoshare to share field
|
||||||
if (result.autoshare === true && !result.share) {
|
if (result.autoshare === true && !result.share) {
|
||||||
result.share = "auto"
|
result.share = "auto"
|
||||||
@@ -144,6 +153,7 @@ export namespace Config {
|
|||||||
.object({
|
.object({
|
||||||
model: z.string().optional(),
|
model: z.string().optional(),
|
||||||
temperature: z.number().optional(),
|
temperature: z.number().optional(),
|
||||||
|
top_p: z.number().optional(),
|
||||||
prompt: z.string().optional(),
|
prompt: z.string().optional(),
|
||||||
tools: z.record(z.string(), z.boolean()).optional(),
|
tools: z.record(z.string(), z.boolean()).optional(),
|
||||||
disable: z.boolean().optional(),
|
disable: z.boolean().optional(),
|
||||||
@@ -222,6 +232,7 @@ export namespace Config {
|
|||||||
$schema: z.string().optional().describe("JSON schema reference for configuration validation"),
|
$schema: z.string().optional().describe("JSON schema reference for configuration validation"),
|
||||||
theme: z.string().optional().describe("Theme name to use for the interface"),
|
theme: z.string().optional().describe("Theme name to use for the interface"),
|
||||||
keybinds: Keybinds.optional().describe("Custom keybind configurations"),
|
keybinds: Keybinds.optional().describe("Custom keybind configurations"),
|
||||||
|
plugin: z.string().array().optional(),
|
||||||
share: z
|
share: z
|
||||||
.enum(["manual", "auto", "disabled"])
|
.enum(["manual", "auto", "disabled"])
|
||||||
.optional()
|
.optional()
|
||||||
@@ -283,9 +294,29 @@ export namespace Config {
|
|||||||
z.string(),
|
z.string(),
|
||||||
z.object({
|
z.object({
|
||||||
disabled: z.boolean().optional(),
|
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(),
|
.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"),
|
instructions: z.array(z.string()).optional().describe("Additional instruction files or patterns to include"),
|
||||||
layout: Layout.optional().describe("@deprecated Always uses stretch layout."),
|
layout: Layout.optional().describe("@deprecated Always uses stretch layout."),
|
||||||
permission: z
|
permission: z
|
||||||
@@ -331,9 +362,9 @@ export namespace Config {
|
|||||||
export const global = lazy(async () => {
|
export const global = lazy(async () => {
|
||||||
let result: Info = pipe(
|
let result: Info = pipe(
|
||||||
{},
|
{},
|
||||||
mergeDeep(await load(path.join(Global.Path.config, "config.json"))),
|
mergeDeep(await loadFile(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, "opencode.json"))),
|
||||||
mergeDeep(await load(path.join(Global.Path.config, "opencode.jsonc"))),
|
mergeDeep(await loadFile(path.join(Global.Path.config, "opencode.jsonc"))),
|
||||||
)
|
)
|
||||||
|
|
||||||
await import(path.join(Global.Path.config, "config"), {
|
await import(path.join(Global.Path.config, "config"), {
|
||||||
@@ -354,46 +385,65 @@ export namespace Config {
|
|||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
|
|
||||||
async function load(configPath: string): Promise<Info> {
|
async function loadFile(filepath: string): Promise<Info> {
|
||||||
let text = await Bun.file(configPath)
|
log.info("loading", { path: filepath })
|
||||||
|
let text = await Bun.file(filepath)
|
||||||
.text()
|
.text()
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (err.code === "ENOENT") return
|
if (err.code === "ENOENT") return
|
||||||
throw new JsonError({ path: configPath }, { cause: err })
|
throw new JsonError({ path: filepath }, { cause: err })
|
||||||
})
|
})
|
||||||
if (!text) return {}
|
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) => {
|
text = text.replace(/\{env:([^}]+)\}/g, (_, varName) => {
|
||||||
return process.env[varName] || ""
|
return process.env[varName] || ""
|
||||||
})
|
})
|
||||||
|
|
||||||
const fileMatches = text.match(/"?\{file:([^}]+)\}"?/g)
|
const fileMatches = text.match(/\{file:[^}]+\}/g)
|
||||||
if (fileMatches) {
|
if (fileMatches) {
|
||||||
const configDir = path.dirname(configPath)
|
const configDir = path.dirname(filepath)
|
||||||
|
const lines = text.split("\n")
|
||||||
|
|
||||||
for (const match of fileMatches) {
|
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 resolvedPath = path.isAbsolute(filePath) ? filePath : path.resolve(configDir, filePath)
|
||||||
const fileContent = await Bun.file(resolvedPath).text()
|
const fileContent = (await Bun.file(resolvedPath).text()).trim()
|
||||||
text = text.replace(match, JSON.stringify(fileContent))
|
// escape newlines/quotes, strip outer quotes
|
||||||
|
text = text.replace(match, JSON.stringify(fileContent).slice(1, -1))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const errors: JsoncParseError[] = []
|
const errors: JsoncParseError[] = []
|
||||||
const data = parseJsonc(text, errors, { allowTrailingComma: true })
|
const data = parseJsonc(text, errors, { allowTrailingComma: true })
|
||||||
if (errors.length) {
|
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({
|
throw new JsonError({
|
||||||
path: configPath,
|
path: filepath,
|
||||||
message: errors
|
message: `\n--- JSONC Input ---\n${text}\n--- Errors ---\n${errorDetails}\n--- End ---`,
|
||||||
.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("; "),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,11 +451,21 @@ export namespace Config {
|
|||||||
if (parsed.success) {
|
if (parsed.success) {
|
||||||
if (!parsed.data.$schema) {
|
if (!parsed.data.$schema) {
|
||||||
parsed.data.$schema = "https://opencode.ai/config.json"
|
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(
|
export const JsonError = NamedError.create(
|
||||||
"ConfigJsonError",
|
"ConfigJsonError",
|
||||||
|
|||||||
@@ -129,7 +129,9 @@ export const clang: Info = {
|
|||||||
command: ["clang-format", "-i", "$FILE"],
|
command: ["clang-format", "-i", "$FILE"],
|
||||||
extensions: [".c", ".cc", ".cpp", ".cxx", ".c++", ".h", ".hh", ".hpp", ".hxx", ".h++", ".ino", ".C", ".H"],
|
extensions: [".c", ".cc", ".cpp", ".cxx", ".c++", ".h", ".hh", ".hpp", ".hxx", ".h++", ".ino", ".C", ".H"],
|
||||||
async enabled() {
|
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
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,20 +6,39 @@ import path from "path"
|
|||||||
|
|
||||||
import * as Formatter from "./formatter"
|
import * as Formatter from "./formatter"
|
||||||
import { Config } from "../config/config"
|
import { Config } from "../config/config"
|
||||||
|
import { mergeDeep } from "remeda"
|
||||||
|
|
||||||
export namespace Format {
|
export namespace Format {
|
||||||
const log = Log.create({ service: "format" })
|
const log = Log.create({ service: "format" })
|
||||||
|
|
||||||
const state = App.state("format", () => {
|
const state = App.state("format", async () => {
|
||||||
const enabled: Record<string, boolean> = {}
|
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 {
|
return {
|
||||||
enabled,
|
enabled,
|
||||||
|
formatters,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
async function isEnabled(item: Formatter.Info) {
|
async function isEnabled(item: Formatter.Info) {
|
||||||
const s = state()
|
const s = await state()
|
||||||
let status = s.enabled[item.name]
|
let status = s.enabled[item.name]
|
||||||
if (status === undefined) {
|
if (status === undefined) {
|
||||||
status = await item.enabled()
|
status = await item.enabled()
|
||||||
@@ -29,11 +48,11 @@ export namespace Format {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getFormatter(ext: string) {
|
async function getFormatter(ext: string) {
|
||||||
const cfg = await Config.get()
|
const formatters = await state().then((x) => x.formatters)
|
||||||
const result = []
|
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 (!item.extensions.includes(ext)) continue
|
||||||
if (cfg.formatter?.[item.name]?.disabled) continue
|
|
||||||
if (!(await isEnabled(item))) continue
|
if (!(await isEnabled(item))) continue
|
||||||
result.push(item)
|
result.push(item)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ await Promise.all([
|
|||||||
fs.mkdir(Global.Path.log, { recursive: true }),
|
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"))
|
const version = await Bun.file(path.join(Global.Path.cache, "version"))
|
||||||
.text()
|
.text()
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ const cli = yargs(hideBin(process.argv))
|
|||||||
if (msg.startsWith("Unknown argument") || msg.startsWith("Not enough non-option arguments")) {
|
if (msg.startsWith("Unknown argument") || msg.startsWith("Not enough non-option arguments")) {
|
||||||
cli.showHelp("log")
|
cli.showHelp("log")
|
||||||
}
|
}
|
||||||
|
process.exit(1)
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
|
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ export namespace Installation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const VERSION = typeof OPENCODE_VERSION === "string" ? OPENCODE_VERSION : "dev"
|
export const VERSION = typeof OPENCODE_VERSION === "string" ? OPENCODE_VERSION : "dev"
|
||||||
|
export const USER_AGENT = `opencode/${VERSION}`
|
||||||
|
|
||||||
export async function latest() {
|
export async function latest() {
|
||||||
return fetch("https://api.github.com/repos/sst/opencode/releases/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 path from "path"
|
||||||
import { LSPServer } from "./server"
|
import { LSPServer } from "./server"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
import { Config } from "../config/config"
|
||||||
|
import { spawn } from "child_process"
|
||||||
|
|
||||||
export namespace LSP {
|
export namespace LSP {
|
||||||
const log = Log.create({ service: "lsp" })
|
const log = Log.create({ service: "lsp" })
|
||||||
@@ -55,8 +57,35 @@ export namespace LSP {
|
|||||||
"lsp",
|
"lsp",
|
||||||
async () => {
|
async () => {
|
||||||
const clients: LSPClient.Info[] = []
|
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 {
|
return {
|
||||||
broken: new Set<string>(),
|
broken: new Set<string>(),
|
||||||
|
servers,
|
||||||
clients,
|
clients,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -76,7 +105,7 @@ export namespace LSP {
|
|||||||
const extension = path.parse(file).ext
|
const extension = path.parse(file).ext
|
||||||
const result: LSPClient.Info[] = []
|
const result: LSPClient.Info[] = []
|
||||||
for (const server of Object.values(LSPServer)) {
|
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())
|
const root = await server.root(file, App.info())
|
||||||
if (!root) continue
|
if (!root) continue
|
||||||
if (s.broken.has(root + server.id)) continue
|
if (s.broken.has(root + server.id)) continue
|
||||||
|
|||||||
@@ -115,7 +115,8 @@ export namespace MCP {
|
|||||||
const result: Record<string, Tool> = {}
|
const result: Record<string, Tool> = {}
|
||||||
for (const [clientName, client] of Object.entries(await clients())) {
|
for (const [clientName, client] of Object.entries(await clients())) {
|
||||||
for (const [toolName, tool] of Object.entries(await client.tools())) {
|
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
|
return result
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { z } from "zod"
|
|||||||
import { Bus } from "../bus"
|
import { Bus } from "../bus"
|
||||||
import { Log } from "../util/log"
|
import { Log } from "../util/log"
|
||||||
import { Identifier } from "../id/id"
|
import { Identifier } from "../id/id"
|
||||||
|
import { Plugin } from "../plugin"
|
||||||
|
|
||||||
export namespace Permission {
|
export namespace Permission {
|
||||||
const log = Log.create({ service: "permission" })
|
const log = Log.create({ service: "permission" })
|
||||||
@@ -67,7 +68,7 @@ export namespace Permission {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
export function ask(input: {
|
export async function ask(input: {
|
||||||
type: Info["type"]
|
type: Info["type"]
|
||||||
title: Info["title"]
|
title: Info["title"]
|
||||||
pattern?: Info["pattern"]
|
pattern?: Info["pattern"]
|
||||||
@@ -95,6 +96,18 @@ export namespace Permission {
|
|||||||
created: Date.now(),
|
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] || {}
|
pending[input.sessionID] = pending[input.sessionID] || {}
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
pending[input.sessionID][info.id] = {
|
pending[input.sessionID][info.id] = {
|
||||||
|
|||||||
@@ -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 path from "path"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
import { data } from "./models-macro" with { type: "macro" }
|
import { data } from "./models-macro" with { type: "macro" }
|
||||||
|
import { Installation } from "../installation"
|
||||||
|
|
||||||
export namespace ModelsDev {
|
export namespace ModelsDev {
|
||||||
const log = Log.create({ service: "models.dev" })
|
const log = Log.create({ service: "models.dev" })
|
||||||
@@ -50,21 +51,30 @@ export namespace ModelsDev {
|
|||||||
export type Provider = z.infer<typeof Provider>
|
export type Provider = z.infer<typeof Provider>
|
||||||
|
|
||||||
export async function get() {
|
export async function get() {
|
||||||
|
refresh()
|
||||||
const file = Bun.file(filepath)
|
const file = Bun.file(filepath)
|
||||||
const result = await file.json().catch(() => {})
|
const result = await file.json().catch(() => {})
|
||||||
if (result) {
|
if (result) return result as Record<string, Provider>
|
||||||
refresh()
|
|
||||||
return result as Record<string, Provider>
|
|
||||||
}
|
|
||||||
refresh()
|
|
||||||
const json = await data()
|
const json = await data()
|
||||||
return JSON.parse(json) as Record<string, Provider>
|
return JSON.parse(json) as Record<string, Provider>
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refresh() {
|
export async function refresh() {
|
||||||
const file = Bun.file(filepath)
|
const file = Bun.file(filepath)
|
||||||
log.info("refreshing")
|
log.info("refreshing", {
|
||||||
const result = await fetch("https://models.dev/api.json").catch(() => {})
|
file,
|
||||||
if (result && result.ok) await Bun.write(file, result)
|
})
|
||||||
|
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()
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ export namespace Provider {
|
|||||||
Array.isArray(msg.content) && msg.content.some((part: any) => part.type === "image_url"),
|
Array.isArray(msg.content) && msg.content.some((part: any) => part.type === "image_url"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} catch { }
|
} catch {}
|
||||||
const headers: Record<string, string> = {
|
const headers: Record<string, string> = {
|
||||||
...init.headers,
|
...init.headers,
|
||||||
...copilot.HEADERS,
|
...copilot.HEADERS,
|
||||||
@@ -283,26 +283,26 @@ export namespace Provider {
|
|||||||
cost:
|
cost:
|
||||||
!model.cost && !existing?.cost
|
!model.cost && !existing?.cost
|
||||||
? {
|
? {
|
||||||
input: 0,
|
input: 0,
|
||||||
output: 0,
|
output: 0,
|
||||||
cache_read: 0,
|
cache_read: 0,
|
||||||
cache_write: 0,
|
cache_write: 0,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
cache_read: 0,
|
cache_read: 0,
|
||||||
cache_write: 0,
|
cache_write: 0,
|
||||||
...existing?.cost,
|
...existing?.cost,
|
||||||
...model.cost,
|
...model.cost,
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
...existing?.options,
|
...existing?.options,
|
||||||
...model.options,
|
...model.options,
|
||||||
},
|
},
|
||||||
limit: model.limit ??
|
limit: model.limit ??
|
||||||
existing?.limit ?? {
|
existing?.limit ?? {
|
||||||
context: 0,
|
context: 0,
|
||||||
output: 0,
|
output: 0,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
parsed.models[modelID] = parsedModel
|
parsed.models[modelID] = parsedModel
|
||||||
}
|
}
|
||||||
@@ -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) {
|
export async function getModel(providerID: string, modelID: string) {
|
||||||
const key = `${providerID}/${modelID}`
|
const key = `${providerID}/${modelID}`
|
||||||
const s = await state()
|
const s = await state()
|
||||||
|
|||||||
@@ -2,47 +2,73 @@ import type { ModelMessage } from "ai"
|
|||||||
import { unique } from "remeda"
|
import { unique } from "remeda"
|
||||||
|
|
||||||
export namespace ProviderTransform {
|
export namespace ProviderTransform {
|
||||||
export function message(msgs: ModelMessage[], providerID: string, modelID: string) {
|
function normalizeToolCallIds(msgs: ModelMessage[]): ModelMessage[] {
|
||||||
if (providerID === "anthropic" || modelID.includes("anthropic") || modelID.includes("claude")) {
|
return msgs.map((msg) => {
|
||||||
const system = msgs.filter((msg) => msg.role === "system").slice(0, 2)
|
if ((msg.role === "assistant" || msg.role === "tool") && Array.isArray(msg.content)) {
|
||||||
const final = msgs.filter((msg) => msg.role !== "system").slice(-2)
|
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 = {
|
function applyCaching(msgs: ModelMessage[], providerID: string): ModelMessage[] {
|
||||||
anthropic: {
|
const system = msgs.filter((msg) => msg.role === "system").slice(0, 2)
|
||||||
cacheControl: { type: "ephemeral" },
|
const final = msgs.filter((msg) => msg.role !== "system").slice(-2)
|
||||||
},
|
|
||||||
openrouter: {
|
const providerOptions = {
|
||||||
cache_control: { type: "ephemeral" },
|
anthropic: {
|
||||||
},
|
cacheControl: { type: "ephemeral" },
|
||||||
bedrock: {
|
},
|
||||||
cachePoint: { type: "ephemeral" },
|
openrouter: {
|
||||||
},
|
cache_control: { type: "ephemeral" },
|
||||||
openaiCompatible: {
|
},
|
||||||
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])) {
|
msg.providerOptions = {
|
||||||
const shouldUseContentOptions =
|
...msg.providerOptions,
|
||||||
providerID !== "anthropic" && Array.isArray(msg.content) && msg.content.length > 0
|
...providerOptions,
|
||||||
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
return msgs
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,4 +76,9 @@ export namespace ProviderTransform {
|
|||||||
if (modelID.toLowerCase().includes("qwen")) return 0.55
|
if (modelID.toLowerCase().includes("qwen")) return 0.55
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function topP(_providerID: string, modelID: string) {
|
||||||
|
if (modelID.toLowerCase().includes("qwen")) return 1
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { MessageV2 } from "../session/message-v2"
|
|||||||
import { Mode } from "../session/mode"
|
import { Mode } from "../session/mode"
|
||||||
import { callTui, TuiRoute } from "./tui"
|
import { callTui, TuiRoute } from "./tui"
|
||||||
import { Permission } from "../permission"
|
import { Permission } from "../permission"
|
||||||
|
import { lazy } from "../util/lazy"
|
||||||
|
|
||||||
const ERRORS = {
|
const ERRORS = {
|
||||||
400: {
|
400: {
|
||||||
@@ -48,7 +49,7 @@ export namespace Server {
|
|||||||
Connected: Bus.event("server.connected", z.object({})),
|
Connected: Bus.event("server.connected", z.object({})),
|
||||||
}
|
}
|
||||||
|
|
||||||
function app() {
|
export const app = lazy(() => {
|
||||||
const app = new Hono()
|
const app = new Hono()
|
||||||
|
|
||||||
const result = app
|
const result = app
|
||||||
@@ -1022,7 +1023,7 @@ export namespace Server {
|
|||||||
.route("/tui/control", TuiRoute)
|
.route("/tui/control", TuiRoute)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
})
|
||||||
|
|
||||||
export async function openapi() {
|
export async function openapi() {
|
||||||
const a = app()
|
const a = app()
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import { LSP } from "../lsp"
|
|||||||
import { ReadTool } from "../tool/read"
|
import { ReadTool } from "../tool/read"
|
||||||
import { mergeDeep, pipe, splitWhen } from "remeda"
|
import { mergeDeep, pipe, splitWhen } from "remeda"
|
||||||
import { ToolRegistry } from "../tool/registry"
|
import { ToolRegistry } from "../tool/registry"
|
||||||
|
import { Plugin } from "../plugin"
|
||||||
|
|
||||||
export namespace Session {
|
export namespace Session {
|
||||||
const log = Log.create({ service: "session" })
|
const log = Log.create({ service: "session" })
|
||||||
@@ -120,6 +121,7 @@ export namespace Session {
|
|||||||
const sessions = new Map<string, Info>()
|
const sessions = new Map<string, Info>()
|
||||||
const messages = new Map<string, MessageV2.Info[]>()
|
const messages = new Map<string, MessageV2.Info[]>()
|
||||||
const pending = new Map<string, AbortController>()
|
const pending = new Map<string, AbortController>()
|
||||||
|
const autoCompacting = new Map<string, boolean>()
|
||||||
const queued = new Map<
|
const queued = new Map<
|
||||||
string,
|
string,
|
||||||
{
|
{
|
||||||
@@ -135,6 +137,7 @@ export namespace Session {
|
|||||||
sessions,
|
sessions,
|
||||||
messages,
|
messages,
|
||||||
pending,
|
pending,
|
||||||
|
autoCompacting,
|
||||||
queued,
|
queued,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -571,7 +574,14 @@ export namespace Session {
|
|||||||
text: PROMPT_PLAN,
|
text: PROMPT_PLAN,
|
||||||
synthetic: true,
|
synthetic: true,
|
||||||
})
|
})
|
||||||
|
await Plugin.trigger(
|
||||||
|
"chat.message",
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
message: userMsg,
|
||||||
|
parts: userParts,
|
||||||
|
},
|
||||||
|
)
|
||||||
await updateMessage(userMsg)
|
await updateMessage(userMsg)
|
||||||
for (const part of userParts) {
|
for (const part of userParts) {
|
||||||
await updatePart(part)
|
await updatePart(part)
|
||||||
@@ -606,6 +616,8 @@ export namespace Session {
|
|||||||
const tokens =
|
const tokens =
|
||||||
previous.tokens.input + previous.tokens.cache.read + previous.tokens.cache.write + previous.tokens.output
|
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)) {
|
if (model.info.limit.context && tokens > Math.max((model.info.limit.context - outputLimit) * 0.9, 0)) {
|
||||||
|
state().autoCompacting.set(input.sessionID, true)
|
||||||
|
|
||||||
await summarize({
|
await summarize({
|
||||||
sessionID: input.sessionID,
|
sessionID: input.sessionID,
|
||||||
providerID: input.providerID,
|
providerID: input.providerID,
|
||||||
@@ -614,7 +626,6 @@ export namespace Session {
|
|||||||
return chat(input)
|
return chat(input)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
using abort = lock(input.sessionID)
|
using abort = lock(input.sessionID)
|
||||||
|
|
||||||
const lastSummary = msgs.findLast((msg) => msg.info.role === "assistant" && msg.info.summary === true)
|
const lastSummary = msgs.findLast((msg) => msg.info.role === "assistant" && msg.info.summary === true)
|
||||||
@@ -716,10 +727,20 @@ export namespace Session {
|
|||||||
description: item.description,
|
description: item.description,
|
||||||
inputSchema: item.parameters as ZodSchema,
|
inputSchema: item.parameters as ZodSchema,
|
||||||
async execute(args, options) {
|
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, {
|
const result = await item.execute(args, {
|
||||||
sessionID: input.sessionID,
|
sessionID: input.sessionID,
|
||||||
abort: abort.signal,
|
abort: options.abortSignal!,
|
||||||
messageID: assistantMsg.id,
|
messageID: assistantMsg.id,
|
||||||
callID: options.toolCallId,
|
callID: options.toolCallId,
|
||||||
metadata: async (val) => {
|
metadata: async (val) => {
|
||||||
@@ -740,6 +761,15 @@ export namespace Session {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
await Plugin.trigger(
|
||||||
|
"tool.execute.after",
|
||||||
|
{
|
||||||
|
tool: item.id,
|
||||||
|
sessionID: input.sessionID,
|
||||||
|
callID: options.toolCallId,
|
||||||
|
},
|
||||||
|
result,
|
||||||
|
)
|
||||||
return result
|
return result
|
||||||
},
|
},
|
||||||
toModelOutput(result) {
|
toModelOutput(result) {
|
||||||
@@ -752,11 +782,10 @@ export namespace Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const [key, item] of Object.entries(await MCP.tools())) {
|
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
|
const execute = item.execute
|
||||||
if (!execute) continue
|
if (!execute) continue
|
||||||
item.execute = async (args, opts) => {
|
item.execute = async (args, opts) => {
|
||||||
await processor.track(opts.toolCallId)
|
|
||||||
const result = await execute(args, opts)
|
const result = await execute(args, opts)
|
||||||
const output = result.content
|
const output = result.content
|
||||||
.filter((x: any) => x.type === "text")
|
.filter((x: any) => x.type === "text")
|
||||||
@@ -776,6 +805,21 @@ export namespace Session {
|
|||||||
tools[key] = item
|
tools[key] = item
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
temperature: model.info.temperature
|
||||||
|
? (mode.temperature ?? ProviderTransform.temperature(input.providerID, input.modelID))
|
||||||
|
: undefined,
|
||||||
|
topP: mode.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({
|
const stream = streamText({
|
||||||
onError(e) {
|
onError(e) {
|
||||||
log.error("streamText error", {
|
log.error("streamText error", {
|
||||||
@@ -828,13 +872,26 @@ export namespace Session {
|
|||||||
messages,
|
messages,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async experimental_repairToolCall(input) {
|
||||||
|
return {
|
||||||
|
...input.toolCall,
|
||||||
|
input: JSON.stringify({
|
||||||
|
tool: input.toolCall.toolName,
|
||||||
|
error: input.error.message,
|
||||||
|
}),
|
||||||
|
toolName: "invalid",
|
||||||
|
}
|
||||||
|
},
|
||||||
maxRetries: 3,
|
maxRetries: 3,
|
||||||
|
activeTools: Object.keys(tools).filter((x) => x !== "invalid"),
|
||||||
maxOutputTokens: outputLimit,
|
maxOutputTokens: outputLimit,
|
||||||
abortSignal: abort.signal,
|
abortSignal: abort.signal,
|
||||||
stopWhen: stepCountIs(1000),
|
stopWhen: stepCountIs(1000),
|
||||||
providerOptions: {
|
providerOptions: {
|
||||||
[input.providerID]: model.info.options,
|
[input.providerID]: model.info.options,
|
||||||
},
|
},
|
||||||
|
temperature: params.temperature,
|
||||||
|
topP: params.topP,
|
||||||
messages: [
|
messages: [
|
||||||
...system.map(
|
...system.map(
|
||||||
(x): ModelMessage => ({
|
(x): ModelMessage => ({
|
||||||
@@ -844,9 +901,6 @@ export namespace Session {
|
|||||||
),
|
),
|
||||||
...MessageV2.toModelMessage(msgs),
|
...MessageV2.toModelMessage(msgs),
|
||||||
],
|
],
|
||||||
temperature: model.info.temperature
|
|
||||||
? (mode.temperature ?? ProviderTransform.temperature(input.providerID, input.modelID))
|
|
||||||
: undefined,
|
|
||||||
tools: model.info.tool_call === false ? undefined : tools,
|
tools: model.info.tool_call === false ? undefined : tools,
|
||||||
model: wrapLanguageModel({
|
model: wrapLanguageModel({
|
||||||
model: model.language,
|
model: model.language,
|
||||||
@@ -878,15 +932,11 @@ export namespace Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createProcessor(assistantMsg: MessageV2.Assistant, model: ModelsDev.Model) {
|
function createProcessor(assistantMsg: MessageV2.Assistant, model: ModelsDev.Model) {
|
||||||
const toolCalls: Record<string, MessageV2.ToolPart> = {}
|
const toolcalls: Record<string, MessageV2.ToolPart> = {}
|
||||||
const snapshots: Record<string, string> = {}
|
let snapshot: string | undefined
|
||||||
return {
|
return {
|
||||||
async track(toolCallID: string) {
|
|
||||||
const hash = await Snapshot.track()
|
|
||||||
if (hash) snapshots[toolCallID] = hash
|
|
||||||
},
|
|
||||||
partFromToolCall(toolCallID: string) {
|
partFromToolCall(toolCallID: string) {
|
||||||
return toolCalls[toolCallID]
|
return toolcalls[toolCallID]
|
||||||
},
|
},
|
||||||
async process(stream: StreamTextResult<Record<string, AITool>, never>) {
|
async process(stream: StreamTextResult<Record<string, AITool>, never>) {
|
||||||
try {
|
try {
|
||||||
@@ -902,7 +952,7 @@ export namespace Session {
|
|||||||
|
|
||||||
case "tool-input-start":
|
case "tool-input-start":
|
||||||
const part = await updatePart({
|
const part = await updatePart({
|
||||||
id: toolCalls[value.id]?.id ?? Identifier.ascending("part"),
|
id: toolcalls[value.id]?.id ?? Identifier.ascending("part"),
|
||||||
messageID: assistantMsg.id,
|
messageID: assistantMsg.id,
|
||||||
sessionID: assistantMsg.sessionID,
|
sessionID: assistantMsg.sessionID,
|
||||||
type: "tool",
|
type: "tool",
|
||||||
@@ -912,7 +962,7 @@ export namespace Session {
|
|||||||
status: "pending",
|
status: "pending",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
toolCalls[value.id] = part as MessageV2.ToolPart
|
toolcalls[value.id] = part as MessageV2.ToolPart
|
||||||
break
|
break
|
||||||
|
|
||||||
case "tool-input-delta":
|
case "tool-input-delta":
|
||||||
@@ -922,10 +972,11 @@ export namespace Session {
|
|||||||
break
|
break
|
||||||
|
|
||||||
case "tool-call": {
|
case "tool-call": {
|
||||||
const match = toolCalls[value.toolCallId]
|
const match = toolcalls[value.toolCallId]
|
||||||
if (match) {
|
if (match) {
|
||||||
const part = await updatePart({
|
const part = await updatePart({
|
||||||
...match,
|
...match,
|
||||||
|
tool: value.toolName,
|
||||||
state: {
|
state: {
|
||||||
status: "running",
|
status: "running",
|
||||||
input: value.input,
|
input: value.input,
|
||||||
@@ -934,12 +985,12 @@ export namespace Session {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
toolCalls[value.toolCallId] = part as MessageV2.ToolPart
|
toolcalls[value.toolCallId] = part as MessageV2.ToolPart
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case "tool-result": {
|
case "tool-result": {
|
||||||
const match = toolCalls[value.toolCallId]
|
const match = toolcalls[value.toolCallId]
|
||||||
if (match && match.state.status === "running") {
|
if (match && match.state.status === "running") {
|
||||||
await updatePart({
|
await updatePart({
|
||||||
...match,
|
...match,
|
||||||
@@ -955,27 +1006,13 @@ export namespace Session {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
delete toolCalls[value.toolCallId]
|
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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case "tool-error": {
|
case "tool-error": {
|
||||||
const match = toolCalls[value.toolCallId]
|
const match = toolcalls[value.toolCallId]
|
||||||
if (match && match.state.status === "running") {
|
if (match && match.state.status === "running") {
|
||||||
await updatePart({
|
await updatePart({
|
||||||
...match,
|
...match,
|
||||||
@@ -989,19 +1026,7 @@ export namespace Session {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
delete toolCalls[value.toolCallId]
|
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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -1016,6 +1041,7 @@ export namespace Session {
|
|||||||
sessionID: assistantMsg.sessionID,
|
sessionID: assistantMsg.sessionID,
|
||||||
type: "step-start",
|
type: "step-start",
|
||||||
})
|
})
|
||||||
|
snapshot = await Snapshot.track()
|
||||||
break
|
break
|
||||||
|
|
||||||
case "finish-step":
|
case "finish-step":
|
||||||
@@ -1031,6 +1057,20 @@ export namespace Session {
|
|||||||
cost: usage.cost,
|
cost: usage.cost,
|
||||||
})
|
})
|
||||||
await updateMessage(assistantMsg)
|
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
|
break
|
||||||
|
|
||||||
case "text-start":
|
case "text-start":
|
||||||
@@ -1115,7 +1155,7 @@ export namespace Session {
|
|||||||
}
|
}
|
||||||
const p = await getParts(assistantMsg.sessionID, assistantMsg.id)
|
const p = await getParts(assistantMsg.sessionID, assistantMsg.id)
|
||||||
for (const part of p) {
|
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({
|
updatePart({
|
||||||
...part,
|
...part,
|
||||||
state: {
|
state: {
|
||||||
@@ -1278,9 +1318,19 @@ export namespace Session {
|
|||||||
state().pending.set(sessionID, controller)
|
state().pending.set(sessionID, controller)
|
||||||
return {
|
return {
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
[Symbol.dispose]() {
|
async [Symbol.dispose]() {
|
||||||
log.info("unlocking", { sessionID })
|
log.info("unlocking", { sessionID })
|
||||||
state().pending.delete(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, {
|
Bus.publish(Event.Idle, {
|
||||||
sessionID,
|
sessionID,
|
||||||
})
|
})
|
||||||
@@ -1303,10 +1353,10 @@ export namespace Session {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
cost: new Decimal(0)
|
cost: new Decimal(0)
|
||||||
.add(new Decimal(tokens.input).mul(model.cost.input).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).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.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.cache.write).mul(model.cost?.cache_write ?? 0).div(1_000_000))
|
||||||
.toNumber(),
|
.toNumber(),
|
||||||
tokens,
|
tokens,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export namespace Mode {
|
|||||||
.object({
|
.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
temperature: z.number().optional(),
|
temperature: z.number().optional(),
|
||||||
|
topP: z.number().optional(),
|
||||||
model: z
|
model: z
|
||||||
.object({
|
.object({
|
||||||
modelID: z.string(),
|
modelID: z.string(),
|
||||||
@@ -51,7 +52,8 @@ export namespace Mode {
|
|||||||
item.name = key
|
item.name = key
|
||||||
if (value.model) item.model = Provider.parseModel(value.model)
|
if (value.model) item.model = Provider.parseModel(value.model)
|
||||||
if (value.prompt) item.prompt = value.prompt
|
if (value.prompt) item.prompt = value.prompt
|
||||||
if (value.temperature) item.temperature = value.temperature
|
if (value.temperature != undefined) item.temperature = value.temperature
|
||||||
|
if (value.top_p != undefined) item.topP = value.top_p
|
||||||
if (value.tools)
|
if (value.tools)
|
||||||
item.tools = {
|
item.tools = {
|
||||||
...value.tools,
|
...value.tools,
|
||||||
|
|||||||
@@ -60,33 +60,28 @@ export namespace SystemPrompt {
|
|||||||
export async function custom() {
|
export async function custom() {
|
||||||
const { cwd, root } = App.info().path
|
const { cwd, root } = App.info().path
|
||||||
const config = await Config.get()
|
const config = await Config.get()
|
||||||
const found = []
|
const paths = new Set<string>()
|
||||||
|
|
||||||
for (const item of CUSTOM_FILES) {
|
for (const item of CUSTOM_FILES) {
|
||||||
const matches = await Filesystem.findUp(item, cwd, root)
|
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"))
|
paths.add(path.join(Global.Path.config, "AGENTS.md"))
|
||||||
.text()
|
paths.add(path.join(os.homedir(), ".claude", "CLAUDE.md"))
|
||||||
.catch(() => ""),
|
|
||||||
)
|
|
||||||
found.push(
|
|
||||||
Bun.file(path.join(os.homedir(), ".claude", "CLAUDE.md"))
|
|
||||||
.text()
|
|
||||||
.catch(() => ""),
|
|
||||||
)
|
|
||||||
|
|
||||||
if (config.instructions) {
|
if (config.instructions) {
|
||||||
for (const instruction of config.instructions) {
|
for (const instruction of config.instructions) {
|
||||||
try {
|
const matches = await Filesystem.globUp(instruction, cwd, root).catch(() => [])
|
||||||
const matches = await Filesystem.globUp(instruction, cwd, root)
|
matches.forEach((path) => paths.add(path))
|
||||||
found.push(...matches.map((x) => Bun.file(x).text()))
|
|
||||||
} catch {
|
|
||||||
continue // Skip invalid glob patterns
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const found = Array.from(paths).map((p) =>
|
||||||
|
Bun.file(p)
|
||||||
|
.text()
|
||||||
|
.catch(() => ""),
|
||||||
|
)
|
||||||
return Promise.all(found).then((result) => result.filter(Boolean))
|
return Promise.all(found).then((result) => result.filter(Boolean))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export namespace Snapshot {
|
|||||||
log.info("initialized")
|
log.info("initialized")
|
||||||
}
|
}
|
||||||
await $`git --git-dir ${git} add .`.quiet().cwd(app.path.cwd).nothrow()
|
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()
|
return hash.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,23 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
import { exec } from "child_process"
|
||||||
|
import { text } from "stream/consumers"
|
||||||
import { Tool } from "./tool"
|
import { Tool } from "./tool"
|
||||||
import DESCRIPTION from "./bash.txt"
|
import DESCRIPTION from "./bash.txt"
|
||||||
import { App } from "../app/app"
|
import { App } from "../app/app"
|
||||||
import { Permission } from "../permission"
|
import { Permission } from "../permission"
|
||||||
import { Config } from "../config/config"
|
import { Config } from "../config/config"
|
||||||
import { Filesystem } from "../util/filesystem"
|
import { Filesystem } from "../util/filesystem"
|
||||||
import path from "path"
|
|
||||||
import { lazy } from "../util/lazy"
|
import { lazy } from "../util/lazy"
|
||||||
import { minimatch } from "minimatch"
|
import { Log } from "../util/log"
|
||||||
|
import { Wildcard } from "../util/wildcard"
|
||||||
|
import { $ } from "bun"
|
||||||
|
|
||||||
const MAX_OUTPUT_LENGTH = 30000
|
const MAX_OUTPUT_LENGTH = 30000
|
||||||
const DEFAULT_TIMEOUT = 1 * 60 * 1000
|
const DEFAULT_TIMEOUT = 1 * 60 * 1000
|
||||||
const MAX_TIMEOUT = 10 * 60 * 1000
|
const MAX_TIMEOUT = 10 * 60 * 1000
|
||||||
|
|
||||||
|
const log = Log.create({ service: "bash-tool" })
|
||||||
|
|
||||||
const parser = lazy(async () => {
|
const parser = lazy(async () => {
|
||||||
const { default: Parser } = await import("tree-sitter")
|
const { default: Parser } = await import("tree-sitter")
|
||||||
const Bash = await import("tree-sitter-bash")
|
const Bash = await import("tree-sitter-bash")
|
||||||
@@ -71,9 +76,14 @@ export const BashTool = Tool.define("bash", {
|
|||||||
// not an exhaustive list, but covers most common cases
|
// not an exhaustive list, but covers most common cases
|
||||||
if (["cd", "rm", "cp", "mv", "mkdir", "touch", "chmod", "chown"].includes(command[0])) {
|
if (["cd", "rm", "cp", "mv", "mkdir", "touch", "chmod", "chown"].includes(command[0])) {
|
||||||
for (const arg of command.slice(1)) {
|
for (const arg of command.slice(1)) {
|
||||||
if (arg.startsWith("-")) continue
|
if (arg.startsWith("-") || (command[0] === "chmod" && arg.startsWith("+"))) continue
|
||||||
const resolved = path.resolve(app.path.cwd, arg)
|
const resolved = await $`realpath ${arg}`
|
||||||
if (!Filesystem.contains(app.path.cwd, resolved)) {
|
.quiet()
|
||||||
|
.nothrow()
|
||||||
|
.text()
|
||||||
|
.then((x) => x.trim())
|
||||||
|
log.info("resolved path", { arg, resolved })
|
||||||
|
if (resolved && !Filesystem.contains(app.path.cwd, resolved)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`This command references paths outside of ${app.path.cwd} so it is not allowed to be executed.`,
|
`This command references paths outside of ${app.path.cwd} so it is not allowed to be executed.`,
|
||||||
)
|
)
|
||||||
@@ -85,9 +95,9 @@ export const BashTool = Tool.define("bash", {
|
|||||||
if (!needsAsk && command[0] !== "cd") {
|
if (!needsAsk && command[0] !== "cd") {
|
||||||
const ask = (() => {
|
const ask = (() => {
|
||||||
for (const [pattern, value] of Object.entries(permissions)) {
|
for (const [pattern, value] of Object.entries(permissions)) {
|
||||||
if (minimatch(node.text, pattern)) {
|
const match = Wildcard.match(node.text, pattern)
|
||||||
return value
|
log.info("checking", { text: node.text.trim(), pattern, match })
|
||||||
}
|
if (match) return value
|
||||||
}
|
}
|
||||||
return "ask"
|
return "ask"
|
||||||
})()
|
})()
|
||||||
@@ -108,18 +118,24 @@ export const BashTool = Tool.define("bash", {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const process = Bun.spawn({
|
const process = exec(params.command, {
|
||||||
cmd: ["bash", "-c", params.command],
|
|
||||||
cwd: app.path.cwd,
|
cwd: app.path.cwd,
|
||||||
maxBuffer: MAX_OUTPUT_LENGTH,
|
|
||||||
signal: ctx.abort,
|
signal: ctx.abort,
|
||||||
timeout: timeout,
|
maxBuffer: MAX_OUTPUT_LENGTH,
|
||||||
stdout: "pipe",
|
timeout,
|
||||||
stderr: "pipe",
|
|
||||||
})
|
})
|
||||||
await process.exited
|
|
||||||
const stdout = await new Response(process.stdout).text()
|
const stdoutPromise = text(process.stdout!)
|
||||||
const stderr = await new Response(process.stderr).text()
|
const stderrPromise = text(process.stderr!)
|
||||||
|
|
||||||
|
await new Promise<void>((resolve) => {
|
||||||
|
process.on("close", () => {
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const stdout = await stdoutPromise
|
||||||
|
const stderr = await stderrPromise
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: params.command,
|
title: params.command,
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ export const EditTool = Tool.define("edit", {
|
|||||||
file: filePath,
|
file: filePath,
|
||||||
})
|
})
|
||||||
contentNew = await file.text()
|
contentNew = await file.text()
|
||||||
|
diff = trimDiff(createTwoFilesPatch(filePath, filePath, contentOld, contentNew))
|
||||||
})()
|
})()
|
||||||
|
|
||||||
FileTime.read(ctx.sessionID, filePath)
|
FileTime.read(ctx.sessionID, filePath)
|
||||||
|
|||||||
@@ -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,11 @@ import { TaskTool } from "./task"
|
|||||||
import { TodoWriteTool, TodoReadTool } from "./todo"
|
import { TodoWriteTool, TodoReadTool } from "./todo"
|
||||||
import { WebFetchTool } from "./webfetch"
|
import { WebFetchTool } from "./webfetch"
|
||||||
import { WriteTool } from "./write"
|
import { WriteTool } from "./write"
|
||||||
|
import { InvalidTool } from "./invalid"
|
||||||
|
|
||||||
export namespace ToolRegistry {
|
export namespace ToolRegistry {
|
||||||
const ALL = [
|
const ALL = [
|
||||||
|
InvalidTool,
|
||||||
BashTool,
|
BashTool,
|
||||||
EditTool,
|
EditTool,
|
||||||
WebFetchTool,
|
WebFetchTool,
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,19 +19,21 @@ Log.init({ print: false })
|
|||||||
describe("tool.bash", () => {
|
describe("tool.bash", () => {
|
||||||
test("basic", async () => {
|
test("basic", async () => {
|
||||||
await App.provide({ cwd: projectRoot }, async () => {
|
await App.provide({ cwd: projectRoot }, async () => {
|
||||||
await bash.execute(
|
const result = await bash.execute(
|
||||||
{
|
{
|
||||||
command: "cd foo/bar && ls",
|
command: "echo 'test'",
|
||||||
description: "List files in foo/bar",
|
description: "Echo test message",
|
||||||
},
|
},
|
||||||
ctx,
|
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 () => {
|
await App.provide({ cwd: projectRoot }, async () => {
|
||||||
expect(
|
await expect(
|
||||||
bash.execute(
|
bash.execute(
|
||||||
{
|
{
|
||||||
command: "cd ../",
|
command: "cd ../",
|
||||||
@@ -39,7 +41,7 @@ describe("tool.bash", () => {
|
|||||||
},
|
},
|
||||||
ctx,
|
ctx,
|
||||||
),
|
),
|
||||||
).rejects.toThrow()
|
).rejects.toThrow("This command references paths outside of")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/tsconfig",
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
"extends": "@tsconfig/bun/tsconfig.json",
|
"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.130",
|
||||||
|
"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
|
||||||
Vendored
+9
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
-88
@@ -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
|
|
||||||
-32
@@ -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: 34
|
configured_endpoints: 34
|
||||||
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-2ebd9d5478864042a2e01b4995f42acbc39069fa7fcccd1c2e567366ee6c243d.yml
|
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-52fd0b61e84fdc1cdd31ec12e1600510e9dd2f9d4fb20c2315b4975cb763ee98.yml
|
||||||
openapi_spec_hash: 2a34451b288ea30af1cb61332c417c2a
|
openapi_spec_hash: e851b8d5a2412f5fc9be82ab88ebdfde
|
||||||
config_hash: 11a6f0803eb407367c3f677d3e524c37
|
config_hash: 11a6f0803eb407367c3f677d3e524c37
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ type Mode struct {
|
|||||||
Model ModeModel `json:"model"`
|
Model ModeModel `json:"model"`
|
||||||
Prompt string `json:"prompt"`
|
Prompt string `json:"prompt"`
|
||||||
Temperature float64 `json:"temperature"`
|
Temperature float64 `json:"temperature"`
|
||||||
|
TopP float64 `json:"topP"`
|
||||||
JSON modeJSON `json:"-"`
|
JSON modeJSON `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,6 +162,7 @@ type modeJSON struct {
|
|||||||
Model apijson.Field
|
Model apijson.Field
|
||||||
Prompt apijson.Field
|
Prompt apijson.Field
|
||||||
Temperature apijson.Field
|
Temperature apijson.Field
|
||||||
|
TopP apijson.Field
|
||||||
raw string
|
raw string
|
||||||
ExtraFields map[string]apijson.Field
|
ExtraFields map[string]apijson.Field
|
||||||
}
|
}
|
||||||
|
|||||||
+170
-3
@@ -51,14 +51,16 @@ type Config struct {
|
|||||||
// Automatically update to the latest version
|
// Automatically update to the latest version
|
||||||
Autoupdate bool `json:"autoupdate"`
|
Autoupdate bool `json:"autoupdate"`
|
||||||
// Disable providers that are loaded automatically
|
// Disable providers that are loaded automatically
|
||||||
DisabledProviders []string `json:"disabled_providers"`
|
DisabledProviders []string `json:"disabled_providers"`
|
||||||
Experimental ConfigExperimental `json:"experimental"`
|
Experimental ConfigExperimental `json:"experimental"`
|
||||||
|
Formatter map[string]ConfigFormatter `json:"formatter"`
|
||||||
// Additional instruction files or patterns to include
|
// Additional instruction files or patterns to include
|
||||||
Instructions []string `json:"instructions"`
|
Instructions []string `json:"instructions"`
|
||||||
// Custom keybind configurations
|
// Custom keybind configurations
|
||||||
Keybinds KeybindsConfig `json:"keybinds"`
|
Keybinds KeybindsConfig `json:"keybinds"`
|
||||||
// @deprecated Always uses stretch layout.
|
// @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 (Model Context Protocol) server configurations
|
||||||
Mcp map[string]ConfigMcp `json:"mcp"`
|
Mcp map[string]ConfigMcp `json:"mcp"`
|
||||||
// Modes configuration, see https://opencode.ai/docs/modes
|
// Modes configuration, see https://opencode.ai/docs/modes
|
||||||
@@ -66,6 +68,7 @@ type Config struct {
|
|||||||
// Model to use in the format of provider/model, eg anthropic/claude-2
|
// Model to use in the format of provider/model, eg anthropic/claude-2
|
||||||
Model string `json:"model"`
|
Model string `json:"model"`
|
||||||
Permission ConfigPermission `json:"permission"`
|
Permission ConfigPermission `json:"permission"`
|
||||||
|
Plugin []string `json:"plugin"`
|
||||||
// Custom provider configurations and model overrides
|
// Custom provider configurations and model overrides
|
||||||
Provider map[string]ConfigProvider `json:"provider"`
|
Provider map[string]ConfigProvider `json:"provider"`
|
||||||
// Control sharing behavior:'manual' allows manual sharing via commands, 'auto'
|
// Control sharing behavior:'manual' allows manual sharing via commands, 'auto'
|
||||||
@@ -89,13 +92,16 @@ type configJSON struct {
|
|||||||
Autoupdate apijson.Field
|
Autoupdate apijson.Field
|
||||||
DisabledProviders apijson.Field
|
DisabledProviders apijson.Field
|
||||||
Experimental apijson.Field
|
Experimental apijson.Field
|
||||||
|
Formatter apijson.Field
|
||||||
Instructions apijson.Field
|
Instructions apijson.Field
|
||||||
Keybinds apijson.Field
|
Keybinds apijson.Field
|
||||||
Layout apijson.Field
|
Layout apijson.Field
|
||||||
|
Lsp apijson.Field
|
||||||
Mcp apijson.Field
|
Mcp apijson.Field
|
||||||
Mode apijson.Field
|
Mode apijson.Field
|
||||||
Model apijson.Field
|
Model apijson.Field
|
||||||
Permission apijson.Field
|
Permission apijson.Field
|
||||||
|
Plugin apijson.Field
|
||||||
Provider apijson.Field
|
Provider apijson.Field
|
||||||
Share apijson.Field
|
Share apijson.Field
|
||||||
SmallModel apijson.Field
|
SmallModel apijson.Field
|
||||||
@@ -247,6 +253,32 @@ func (r configExperimentalHookSessionCompletedJSON) RawJSON() string {
|
|||||||
return r.raw
|
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.
|
// @deprecated Always uses stretch layout.
|
||||||
type ConfigLayout string
|
type ConfigLayout string
|
||||||
|
|
||||||
@@ -263,6 +295,139 @@ func (r ConfigLayout) IsKnown() bool {
|
|||||||
return false
|
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 ConfigMcp struct {
|
||||||
// Type of MCP server connection
|
// Type of MCP server connection
|
||||||
Type ConfigMcpType `json:"type,required"`
|
Type ConfigMcpType `json:"type,required"`
|
||||||
@@ -864,6 +1029,7 @@ type ModeConfig struct {
|
|||||||
Prompt string `json:"prompt"`
|
Prompt string `json:"prompt"`
|
||||||
Temperature float64 `json:"temperature"`
|
Temperature float64 `json:"temperature"`
|
||||||
Tools map[string]bool `json:"tools"`
|
Tools map[string]bool `json:"tools"`
|
||||||
|
TopP float64 `json:"top_p"`
|
||||||
JSON modeConfigJSON `json:"-"`
|
JSON modeConfigJSON `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -874,6 +1040,7 @@ type modeConfigJSON struct {
|
|||||||
Prompt apijson.Field
|
Prompt apijson.Field
|
||||||
Temperature apijson.Field
|
Temperature apijson.Field
|
||||||
Tools apijson.Field
|
Tools apijson.Field
|
||||||
|
TopP apijson.Field
|
||||||
raw string
|
raw string
|
||||||
ExtraFields map[string]apijson.Field
|
ExtraFields map[string]apijson.Field
|
||||||
}
|
}
|
||||||
|
|||||||
+123
-121
@@ -54,13 +54,13 @@ type EventListResponse struct {
|
|||||||
// [EventListResponseEventMessageRemovedProperties],
|
// [EventListResponseEventMessageRemovedProperties],
|
||||||
// [EventListResponseEventMessagePartUpdatedProperties],
|
// [EventListResponseEventMessagePartUpdatedProperties],
|
||||||
// [EventListResponseEventMessagePartRemovedProperties],
|
// [EventListResponseEventMessagePartRemovedProperties],
|
||||||
// [EventListResponseEventStorageWriteProperties], [Permission],
|
// [EventListResponseEventStorageWriteProperties],
|
||||||
|
// [EventListResponseEventFileEditedProperties], [interface{}], [Permission],
|
||||||
// [EventListResponseEventPermissionRepliedProperties],
|
// [EventListResponseEventPermissionRepliedProperties],
|
||||||
// [EventListResponseEventFileEditedProperties],
|
|
||||||
// [EventListResponseEventSessionUpdatedProperties],
|
// [EventListResponseEventSessionUpdatedProperties],
|
||||||
// [EventListResponseEventSessionDeletedProperties],
|
// [EventListResponseEventSessionDeletedProperties],
|
||||||
// [EventListResponseEventSessionIdleProperties],
|
// [EventListResponseEventSessionIdleProperties],
|
||||||
// [EventListResponseEventSessionErrorProperties], [interface{}],
|
// [EventListResponseEventSessionErrorProperties],
|
||||||
// [EventListResponseEventFileWatcherUpdatedProperties],
|
// [EventListResponseEventFileWatcherUpdatedProperties],
|
||||||
// [EventListResponseEventIdeInstalledProperties].
|
// [EventListResponseEventIdeInstalledProperties].
|
||||||
Properties interface{} `json:"properties,required"`
|
Properties interface{} `json:"properties,required"`
|
||||||
@@ -100,11 +100,12 @@ func (r *EventListResponse) UnmarshalJSON(data []byte) (err error) {
|
|||||||
// [EventListResponseEventMessageUpdated], [EventListResponseEventMessageRemoved],
|
// [EventListResponseEventMessageUpdated], [EventListResponseEventMessageRemoved],
|
||||||
// [EventListResponseEventMessagePartUpdated],
|
// [EventListResponseEventMessagePartUpdated],
|
||||||
// [EventListResponseEventMessagePartRemoved],
|
// [EventListResponseEventMessagePartRemoved],
|
||||||
// [EventListResponseEventStorageWrite], [EventListResponseEventPermissionUpdated],
|
// [EventListResponseEventStorageWrite], [EventListResponseEventFileEdited],
|
||||||
// [EventListResponseEventPermissionReplied], [EventListResponseEventFileEdited],
|
// [EventListResponseEventServerConnected],
|
||||||
|
// [EventListResponseEventPermissionUpdated],
|
||||||
|
// [EventListResponseEventPermissionReplied],
|
||||||
// [EventListResponseEventSessionUpdated], [EventListResponseEventSessionDeleted],
|
// [EventListResponseEventSessionUpdated], [EventListResponseEventSessionDeleted],
|
||||||
// [EventListResponseEventSessionIdle], [EventListResponseEventSessionError],
|
// [EventListResponseEventSessionIdle], [EventListResponseEventSessionError],
|
||||||
// [EventListResponseEventServerConnected],
|
|
||||||
// [EventListResponseEventFileWatcherUpdated],
|
// [EventListResponseEventFileWatcherUpdated],
|
||||||
// [EventListResponseEventIdeInstalled].
|
// [EventListResponseEventIdeInstalled].
|
||||||
func (r EventListResponse) AsUnion() EventListResponseUnion {
|
func (r EventListResponse) AsUnion() EventListResponseUnion {
|
||||||
@@ -116,11 +117,12 @@ func (r EventListResponse) AsUnion() EventListResponseUnion {
|
|||||||
// [EventListResponseEventMessageUpdated], [EventListResponseEventMessageRemoved],
|
// [EventListResponseEventMessageUpdated], [EventListResponseEventMessageRemoved],
|
||||||
// [EventListResponseEventMessagePartUpdated],
|
// [EventListResponseEventMessagePartUpdated],
|
||||||
// [EventListResponseEventMessagePartRemoved],
|
// [EventListResponseEventMessagePartRemoved],
|
||||||
// [EventListResponseEventStorageWrite], [EventListResponseEventPermissionUpdated],
|
// [EventListResponseEventStorageWrite], [EventListResponseEventFileEdited],
|
||||||
// [EventListResponseEventPermissionReplied], [EventListResponseEventFileEdited],
|
// [EventListResponseEventServerConnected],
|
||||||
|
// [EventListResponseEventPermissionUpdated],
|
||||||
|
// [EventListResponseEventPermissionReplied],
|
||||||
// [EventListResponseEventSessionUpdated], [EventListResponseEventSessionDeleted],
|
// [EventListResponseEventSessionUpdated], [EventListResponseEventSessionDeleted],
|
||||||
// [EventListResponseEventSessionIdle], [EventListResponseEventSessionError],
|
// [EventListResponseEventSessionIdle], [EventListResponseEventSessionError],
|
||||||
// [EventListResponseEventServerConnected],
|
|
||||||
// [EventListResponseEventFileWatcherUpdated] or
|
// [EventListResponseEventFileWatcherUpdated] or
|
||||||
// [EventListResponseEventIdeInstalled].
|
// [EventListResponseEventIdeInstalled].
|
||||||
type EventListResponseUnion interface {
|
type EventListResponseUnion interface {
|
||||||
@@ -166,6 +168,16 @@ func init() {
|
|||||||
Type: reflect.TypeOf(EventListResponseEventStorageWrite{}),
|
Type: reflect.TypeOf(EventListResponseEventStorageWrite{}),
|
||||||
DiscriminatorValue: "storage.write",
|
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{
|
apijson.UnionVariant{
|
||||||
TypeFilter: gjson.JSON,
|
TypeFilter: gjson.JSON,
|
||||||
Type: reflect.TypeOf(EventListResponseEventPermissionUpdated{}),
|
Type: reflect.TypeOf(EventListResponseEventPermissionUpdated{}),
|
||||||
@@ -176,11 +188,6 @@ func init() {
|
|||||||
Type: reflect.TypeOf(EventListResponseEventPermissionReplied{}),
|
Type: reflect.TypeOf(EventListResponseEventPermissionReplied{}),
|
||||||
DiscriminatorValue: "permission.replied",
|
DiscriminatorValue: "permission.replied",
|
||||||
},
|
},
|
||||||
apijson.UnionVariant{
|
|
||||||
TypeFilter: gjson.JSON,
|
|
||||||
Type: reflect.TypeOf(EventListResponseEventFileEdited{}),
|
|
||||||
DiscriminatorValue: "file.edited",
|
|
||||||
},
|
|
||||||
apijson.UnionVariant{
|
apijson.UnionVariant{
|
||||||
TypeFilter: gjson.JSON,
|
TypeFilter: gjson.JSON,
|
||||||
Type: reflect.TypeOf(EventListResponseEventSessionUpdated{}),
|
Type: reflect.TypeOf(EventListResponseEventSessionUpdated{}),
|
||||||
@@ -201,11 +208,6 @@ func init() {
|
|||||||
Type: reflect.TypeOf(EventListResponseEventSessionError{}),
|
Type: reflect.TypeOf(EventListResponseEventSessionError{}),
|
||||||
DiscriminatorValue: "session.error",
|
DiscriminatorValue: "session.error",
|
||||||
},
|
},
|
||||||
apijson.UnionVariant{
|
|
||||||
TypeFilter: gjson.JSON,
|
|
||||||
Type: reflect.TypeOf(EventListResponseEventServerConnected{}),
|
|
||||||
DiscriminatorValue: "server.connected",
|
|
||||||
},
|
|
||||||
apijson.UnionVariant{
|
apijson.UnionVariant{
|
||||||
TypeFilter: gjson.JSON,
|
TypeFilter: gjson.JSON,
|
||||||
Type: reflect.TypeOf(EventListResponseEventFileWatcherUpdated{}),
|
Type: reflect.TypeOf(EventListResponseEventFileWatcherUpdated{}),
|
||||||
@@ -649,6 +651,105 @@ func (r EventListResponseEventStorageWriteType) IsKnown() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EventListResponseEventFileEdited struct {
|
||||||
|
Properties EventListResponseEventFileEditedProperties `json:"properties,required"`
|
||||||
|
Type EventListResponseEventFileEditedType `json:"type,required"`
|
||||||
|
JSON eventListResponseEventFileEditedJSON `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// eventListResponseEventFileEditedJSON contains the JSON metadata for the struct
|
||||||
|
// [EventListResponseEventFileEdited]
|
||||||
|
type eventListResponseEventFileEditedJSON struct {
|
||||||
|
Properties apijson.Field
|
||||||
|
Type apijson.Field
|
||||||
|
raw string
|
||||||
|
ExtraFields map[string]apijson.Field
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *EventListResponseEventFileEdited) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
return apijson.UnmarshalRoot(data, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r eventListResponseEventFileEditedJSON) RawJSON() string {
|
||||||
|
return r.raw
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r EventListResponseEventFileEdited) implementsEventListResponse() {}
|
||||||
|
|
||||||
|
type EventListResponseEventFileEditedProperties struct {
|
||||||
|
File string `json:"file,required"`
|
||||||
|
JSON eventListResponseEventFileEditedPropertiesJSON `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// eventListResponseEventFileEditedPropertiesJSON contains the JSON metadata for
|
||||||
|
// the struct [EventListResponseEventFileEditedProperties]
|
||||||
|
type eventListResponseEventFileEditedPropertiesJSON struct {
|
||||||
|
File apijson.Field
|
||||||
|
raw string
|
||||||
|
ExtraFields map[string]apijson.Field
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *EventListResponseEventFileEditedProperties) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
return apijson.UnmarshalRoot(data, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r eventListResponseEventFileEditedPropertiesJSON) RawJSON() string {
|
||||||
|
return r.raw
|
||||||
|
}
|
||||||
|
|
||||||
|
type EventListResponseEventFileEditedType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
EventListResponseEventFileEditedTypeFileEdited EventListResponseEventFileEditedType = "file.edited"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r EventListResponseEventFileEditedType) IsKnown() bool {
|
||||||
|
switch r {
|
||||||
|
case EventListResponseEventFileEditedTypeFileEdited:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
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 {
|
type EventListResponseEventPermissionUpdated struct {
|
||||||
Properties Permission `json:"properties,required"`
|
Properties Permission `json:"properties,required"`
|
||||||
Type EventListResponseEventPermissionUpdatedType `json:"type,required"`
|
Type EventListResponseEventPermissionUpdatedType `json:"type,required"`
|
||||||
@@ -752,66 +853,6 @@ func (r EventListResponseEventPermissionRepliedType) IsKnown() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
type EventListResponseEventFileEdited struct {
|
|
||||||
Properties EventListResponseEventFileEditedProperties `json:"properties,required"`
|
|
||||||
Type EventListResponseEventFileEditedType `json:"type,required"`
|
|
||||||
JSON eventListResponseEventFileEditedJSON `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// eventListResponseEventFileEditedJSON contains the JSON metadata for the struct
|
|
||||||
// [EventListResponseEventFileEdited]
|
|
||||||
type eventListResponseEventFileEditedJSON struct {
|
|
||||||
Properties apijson.Field
|
|
||||||
Type apijson.Field
|
|
||||||
raw string
|
|
||||||
ExtraFields map[string]apijson.Field
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *EventListResponseEventFileEdited) UnmarshalJSON(data []byte) (err error) {
|
|
||||||
return apijson.UnmarshalRoot(data, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r eventListResponseEventFileEditedJSON) RawJSON() string {
|
|
||||||
return r.raw
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r EventListResponseEventFileEdited) implementsEventListResponse() {}
|
|
||||||
|
|
||||||
type EventListResponseEventFileEditedProperties struct {
|
|
||||||
File string `json:"file,required"`
|
|
||||||
JSON eventListResponseEventFileEditedPropertiesJSON `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// eventListResponseEventFileEditedPropertiesJSON contains the JSON metadata for
|
|
||||||
// the struct [EventListResponseEventFileEditedProperties]
|
|
||||||
type eventListResponseEventFileEditedPropertiesJSON struct {
|
|
||||||
File apijson.Field
|
|
||||||
raw string
|
|
||||||
ExtraFields map[string]apijson.Field
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *EventListResponseEventFileEditedProperties) UnmarshalJSON(data []byte) (err error) {
|
|
||||||
return apijson.UnmarshalRoot(data, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r eventListResponseEventFileEditedPropertiesJSON) RawJSON() string {
|
|
||||||
return r.raw
|
|
||||||
}
|
|
||||||
|
|
||||||
type EventListResponseEventFileEditedType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
EventListResponseEventFileEditedTypeFileEdited EventListResponseEventFileEditedType = "file.edited"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (r EventListResponseEventFileEditedType) IsKnown() bool {
|
|
||||||
switch r {
|
|
||||||
case EventListResponseEventFileEditedTypeFileEdited:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
type EventListResponseEventSessionUpdated struct {
|
type EventListResponseEventSessionUpdated struct {
|
||||||
Properties EventListResponseEventSessionUpdatedProperties `json:"properties,required"`
|
Properties EventListResponseEventSessionUpdatedProperties `json:"properties,required"`
|
||||||
Type EventListResponseEventSessionUpdatedType `json:"type,required"`
|
Type EventListResponseEventSessionUpdatedType `json:"type,required"`
|
||||||
@@ -1188,45 +1229,6 @@ func (r EventListResponseEventSessionErrorType) IsKnown() bool {
|
|||||||
return false
|
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 {
|
type EventListResponseEventFileWatcherUpdated struct {
|
||||||
Properties EventListResponseEventFileWatcherUpdatedProperties `json:"properties,required"`
|
Properties EventListResponseEventFileWatcherUpdatedProperties `json:"properties,required"`
|
||||||
Type EventListResponseEventFileWatcherUpdatedType `json:"type,required"`
|
Type EventListResponseEventFileWatcherUpdatedType `json:"type,required"`
|
||||||
@@ -1374,21 +1376,21 @@ const (
|
|||||||
EventListResponseTypeMessagePartUpdated EventListResponseType = "message.part.updated"
|
EventListResponseTypeMessagePartUpdated EventListResponseType = "message.part.updated"
|
||||||
EventListResponseTypeMessagePartRemoved EventListResponseType = "message.part.removed"
|
EventListResponseTypeMessagePartRemoved EventListResponseType = "message.part.removed"
|
||||||
EventListResponseTypeStorageWrite EventListResponseType = "storage.write"
|
EventListResponseTypeStorageWrite EventListResponseType = "storage.write"
|
||||||
|
EventListResponseTypeFileEdited EventListResponseType = "file.edited"
|
||||||
|
EventListResponseTypeServerConnected EventListResponseType = "server.connected"
|
||||||
EventListResponseTypePermissionUpdated EventListResponseType = "permission.updated"
|
EventListResponseTypePermissionUpdated EventListResponseType = "permission.updated"
|
||||||
EventListResponseTypePermissionReplied EventListResponseType = "permission.replied"
|
EventListResponseTypePermissionReplied EventListResponseType = "permission.replied"
|
||||||
EventListResponseTypeFileEdited EventListResponseType = "file.edited"
|
|
||||||
EventListResponseTypeSessionUpdated EventListResponseType = "session.updated"
|
EventListResponseTypeSessionUpdated EventListResponseType = "session.updated"
|
||||||
EventListResponseTypeSessionDeleted EventListResponseType = "session.deleted"
|
EventListResponseTypeSessionDeleted EventListResponseType = "session.deleted"
|
||||||
EventListResponseTypeSessionIdle EventListResponseType = "session.idle"
|
EventListResponseTypeSessionIdle EventListResponseType = "session.idle"
|
||||||
EventListResponseTypeSessionError EventListResponseType = "session.error"
|
EventListResponseTypeSessionError EventListResponseType = "session.error"
|
||||||
EventListResponseTypeServerConnected EventListResponseType = "server.connected"
|
|
||||||
EventListResponseTypeFileWatcherUpdated EventListResponseType = "file.watcher.updated"
|
EventListResponseTypeFileWatcherUpdated EventListResponseType = "file.watcher.updated"
|
||||||
EventListResponseTypeIdeInstalled EventListResponseType = "ide.installed"
|
EventListResponseTypeIdeInstalled EventListResponseType = "ide.installed"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r EventListResponseType) IsKnown() bool {
|
func (r EventListResponseType) IsKnown() bool {
|
||||||
switch r {
|
switch r {
|
||||||
case EventListResponseTypeInstallationUpdated, EventListResponseTypeLspClientDiagnostics, EventListResponseTypeMessageUpdated, EventListResponseTypeMessageRemoved, EventListResponseTypeMessagePartUpdated, EventListResponseTypeMessagePartRemoved, EventListResponseTypeStorageWrite, EventListResponseTypePermissionUpdated, EventListResponseTypePermissionReplied, 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 true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode-ai/sdk",
|
"name": "@opencode-ai/sdk",
|
||||||
"version": "0.0.0",
|
"version": "0.3.130",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"typecheck": "tsc --noEmit"
|
||||||
|
},
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./dist/index.js"
|
".": {
|
||||||
|
"development": "./src/index.ts",
|
||||||
|
"import": "./dist/index.js"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
|
|||||||
@@ -4,16 +4,11 @@ const dir = new URL("..", import.meta.url).pathname
|
|||||||
process.chdir(dir)
|
process.chdir(dir)
|
||||||
|
|
||||||
import { $ } from "bun"
|
import { $ } from "bun"
|
||||||
import fs from "fs/promises"
|
|
||||||
import path from "path"
|
import path from "path"
|
||||||
|
|
||||||
console.log("=== Generating JS SDK ===")
|
|
||||||
console.log()
|
|
||||||
|
|
||||||
import { createClient } from "@hey-api/openapi-ts"
|
import { createClient } from "@hey-api/openapi-ts"
|
||||||
|
|
||||||
await fs.rm(path.join(dir, "src/gen"), { recursive: true, force: true })
|
await $`bun dev generate > ${dir}/openapi.json`.cwd(path.resolve(dir, "../../opencode"))
|
||||||
await $`bun run ../../opencode/src/index.ts generate > openapi.json`
|
|
||||||
|
|
||||||
await createClient({
|
await createClient({
|
||||||
input: "./openapi.json",
|
input: "./openapi.json",
|
||||||
@@ -36,6 +31,4 @@ await createClient({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
await $`bun prettier --write src/gen`
|
||||||
await $`rm -rf dist`
|
|
||||||
await $`bun tsc`
|
|
||||||
|
|||||||
@@ -5,20 +5,15 @@ process.chdir(dir)
|
|||||||
|
|
||||||
import { $ } from "bun"
|
import { $ } from "bun"
|
||||||
|
|
||||||
const version = process.env["OPENCODE_VERSION"]
|
|
||||||
if (!version) {
|
|
||||||
throw new Error("OPENCODE_VERSION is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
await import("./generate")
|
await import("./generate")
|
||||||
|
await $`rm -rf dist`
|
||||||
|
await $`bun tsc`
|
||||||
|
|
||||||
const snapshot = process.env["OPENCODE_SNAPSHOT"] === "true"
|
const snapshot = process.env["OPENCODE_SNAPSHOT"] === "true"
|
||||||
|
|
||||||
await $`bun pm version --allow-same-version --no-git-tag-version ${version}`
|
|
||||||
if (snapshot) {
|
if (snapshot) {
|
||||||
await $`bun publish --tag snapshot`
|
await $`bun publish --tag snapshot`
|
||||||
}
|
}
|
||||||
if (!snapshot) {
|
if (!snapshot) {
|
||||||
await $`bun publish`
|
await $`bun publish`
|
||||||
}
|
}
|
||||||
await $`bun pm version 0.0.0 --no-git-tag-version`
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// This file is auto-generated by @hey-api/openapi-ts
|
// This file is auto-generated by @hey-api/openapi-ts
|
||||||
|
|
||||||
import type { ClientOptions } from './types.gen';
|
import type { ClientOptions } from "./types.gen"
|
||||||
import { type Config, type ClientOptions as DefaultClientOptions, createClient, createConfig } from './client';
|
import { type Config, type ClientOptions as DefaultClientOptions, createClient, createConfig } from "./client"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `createClientConfig()` function will be called on client initialization
|
* The `createClientConfig()` function will be called on client initialization
|
||||||
@@ -11,8 +11,12 @@ import { type Config, type ClientOptions as DefaultClientOptions, createClient,
|
|||||||
* `setConfig()`. This is useful for example if you're using Next.js
|
* `setConfig()`. This is useful for example if you're using Next.js
|
||||||
* to ensure your client always has the correct values.
|
* to ensure your client always has the correct values.
|
||||||
*/
|
*/
|
||||||
export type CreateClientConfig<T extends DefaultClientOptions = ClientOptions> = (override?: Config<DefaultClientOptions & T>) => Config<Required<DefaultClientOptions> & T>;
|
export type CreateClientConfig<T extends DefaultClientOptions = ClientOptions> = (
|
||||||
|
override?: Config<DefaultClientOptions & T>,
|
||||||
|
) => Config<Required<DefaultClientOptions> & T>
|
||||||
|
|
||||||
export const client = createClient(createConfig<ClientOptions>({
|
export const client = createClient(
|
||||||
baseUrl: 'http://localhost:4096'
|
createConfig<ClientOptions>({
|
||||||
}));
|
baseUrl: "http://localhost:4096",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Client, Config, RequestOptions } from './types';
|
import type { Client, Config, RequestOptions } from "./types"
|
||||||
import {
|
import {
|
||||||
buildUrl,
|
buildUrl,
|
||||||
createConfig,
|
createConfig,
|
||||||
@@ -7,189 +7,179 @@ import {
|
|||||||
mergeConfigs,
|
mergeConfigs,
|
||||||
mergeHeaders,
|
mergeHeaders,
|
||||||
setAuthParams,
|
setAuthParams,
|
||||||
} from './utils';
|
} from "./utils"
|
||||||
|
|
||||||
type ReqInit = Omit<RequestInit, 'body' | 'headers'> & {
|
type ReqInit = Omit<RequestInit, "body" | "headers"> & {
|
||||||
body?: any;
|
body?: any
|
||||||
headers: ReturnType<typeof mergeHeaders>;
|
headers: ReturnType<typeof mergeHeaders>
|
||||||
};
|
}
|
||||||
|
|
||||||
export const createClient = (config: Config = {}): Client => {
|
export const createClient = (config: Config = {}): Client => {
|
||||||
let _config = mergeConfigs(createConfig(), config);
|
let _config = mergeConfigs(createConfig(), config)
|
||||||
|
|
||||||
const getConfig = (): Config => ({ ..._config });
|
const getConfig = (): Config => ({ ..._config })
|
||||||
|
|
||||||
const setConfig = (config: Config): Config => {
|
const setConfig = (config: Config): Config => {
|
||||||
_config = mergeConfigs(_config, config);
|
_config = mergeConfigs(_config, config)
|
||||||
return getConfig();
|
return getConfig()
|
||||||
};
|
}
|
||||||
|
|
||||||
const interceptors = createInterceptors<
|
const interceptors = createInterceptors<Request, Response, unknown, RequestOptions>()
|
||||||
Request,
|
|
||||||
Response,
|
|
||||||
unknown,
|
|
||||||
RequestOptions
|
|
||||||
>();
|
|
||||||
|
|
||||||
const request: Client['request'] = async (options) => {
|
const request: Client["request"] = async (options) => {
|
||||||
const opts = {
|
const opts = {
|
||||||
..._config,
|
..._config,
|
||||||
...options,
|
...options,
|
||||||
fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,
|
fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,
|
||||||
headers: mergeHeaders(_config.headers, options.headers),
|
headers: mergeHeaders(_config.headers, options.headers),
|
||||||
};
|
}
|
||||||
|
|
||||||
if (opts.security) {
|
if (opts.security) {
|
||||||
await setAuthParams({
|
await setAuthParams({
|
||||||
...opts,
|
...opts,
|
||||||
security: opts.security,
|
security: opts.security,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.requestValidator) {
|
if (opts.requestValidator) {
|
||||||
await opts.requestValidator(opts);
|
await opts.requestValidator(opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.body && opts.bodySerializer) {
|
if (opts.body && opts.bodySerializer) {
|
||||||
opts.body = opts.bodySerializer(opts.body);
|
opts.body = opts.bodySerializer(opts.body)
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove Content-Type header if body is empty to avoid sending invalid requests
|
// remove Content-Type header if body is empty to avoid sending invalid requests
|
||||||
if (opts.body === undefined || opts.body === '') {
|
if (opts.body === undefined || opts.body === "") {
|
||||||
opts.headers.delete('Content-Type');
|
opts.headers.delete("Content-Type")
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = buildUrl(opts);
|
const url = buildUrl(opts)
|
||||||
const requestInit: ReqInit = {
|
const requestInit: ReqInit = {
|
||||||
redirect: 'follow',
|
redirect: "follow",
|
||||||
...opts,
|
...opts,
|
||||||
};
|
}
|
||||||
|
|
||||||
let request = new Request(url, requestInit);
|
let request = new Request(url, requestInit)
|
||||||
|
|
||||||
for (const fn of interceptors.request._fns) {
|
for (const fn of interceptors.request._fns) {
|
||||||
if (fn) {
|
if (fn) {
|
||||||
request = await fn(request, opts);
|
request = await fn(request, opts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetch must be assigned here, otherwise it would throw the error:
|
// fetch must be assigned here, otherwise it would throw the error:
|
||||||
// TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
|
// TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
|
||||||
const _fetch = opts.fetch!;
|
const _fetch = opts.fetch!
|
||||||
let response = await _fetch(request);
|
let response = await _fetch(request)
|
||||||
|
|
||||||
for (const fn of interceptors.response._fns) {
|
for (const fn of interceptors.response._fns) {
|
||||||
if (fn) {
|
if (fn) {
|
||||||
response = await fn(response, request, opts);
|
response = await fn(response, request, opts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
request,
|
request,
|
||||||
response,
|
response,
|
||||||
};
|
}
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
if (
|
if (response.status === 204 || response.headers.get("Content-Length") === "0") {
|
||||||
response.status === 204 ||
|
return opts.responseStyle === "data"
|
||||||
response.headers.get('Content-Length') === '0'
|
|
||||||
) {
|
|
||||||
return opts.responseStyle === 'data'
|
|
||||||
? {}
|
? {}
|
||||||
: {
|
: {
|
||||||
data: {},
|
data: {},
|
||||||
...result,
|
...result,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const parseAs =
|
const parseAs =
|
||||||
(opts.parseAs === 'auto'
|
(opts.parseAs === "auto" ? getParseAs(response.headers.get("Content-Type")) : opts.parseAs) ?? "json"
|
||||||
? getParseAs(response.headers.get('Content-Type'))
|
|
||||||
: opts.parseAs) ?? 'json';
|
|
||||||
|
|
||||||
let data: any;
|
let data: any
|
||||||
switch (parseAs) {
|
switch (parseAs) {
|
||||||
case 'arrayBuffer':
|
case "arrayBuffer":
|
||||||
case 'blob':
|
case "blob":
|
||||||
case 'formData':
|
case "formData":
|
||||||
case 'json':
|
case "json":
|
||||||
case 'text':
|
case "text":
|
||||||
data = await response[parseAs]();
|
data = await response[parseAs]()
|
||||||
break;
|
break
|
||||||
case 'stream':
|
case "stream":
|
||||||
return opts.responseStyle === 'data'
|
return opts.responseStyle === "data"
|
||||||
? response.body
|
? response.body
|
||||||
: {
|
: {
|
||||||
data: response.body,
|
data: response.body,
|
||||||
...result,
|
...result,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parseAs === 'json') {
|
if (parseAs === "json") {
|
||||||
if (opts.responseValidator) {
|
if (opts.responseValidator) {
|
||||||
await opts.responseValidator(data);
|
await opts.responseValidator(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.responseTransformer) {
|
if (opts.responseTransformer) {
|
||||||
data = await opts.responseTransformer(data);
|
data = await opts.responseTransformer(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return opts.responseStyle === 'data'
|
return opts.responseStyle === "data"
|
||||||
? data
|
? data
|
||||||
: {
|
: {
|
||||||
data,
|
data,
|
||||||
...result,
|
...result,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const textError = await response.text();
|
const textError = await response.text()
|
||||||
let jsonError: unknown;
|
let jsonError: unknown
|
||||||
|
|
||||||
try {
|
try {
|
||||||
jsonError = JSON.parse(textError);
|
jsonError = JSON.parse(textError)
|
||||||
} catch {
|
} catch {
|
||||||
// noop
|
// noop
|
||||||
}
|
}
|
||||||
|
|
||||||
const error = jsonError ?? textError;
|
const error = jsonError ?? textError
|
||||||
let finalError = error;
|
let finalError = error
|
||||||
|
|
||||||
for (const fn of interceptors.error._fns) {
|
for (const fn of interceptors.error._fns) {
|
||||||
if (fn) {
|
if (fn) {
|
||||||
finalError = (await fn(error, response, request, opts)) as string;
|
finalError = (await fn(error, response, request, opts)) as string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
finalError = finalError || ({} as string);
|
finalError = finalError || ({} as string)
|
||||||
|
|
||||||
if (opts.throwOnError) {
|
if (opts.throwOnError) {
|
||||||
throw finalError;
|
throw finalError
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: we probably want to return error and improve types
|
// TODO: we probably want to return error and improve types
|
||||||
return opts.responseStyle === 'data'
|
return opts.responseStyle === "data"
|
||||||
? undefined
|
? undefined
|
||||||
: {
|
: {
|
||||||
error: finalError,
|
error: finalError,
|
||||||
...result,
|
...result,
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
buildUrl,
|
buildUrl,
|
||||||
connect: (options) => request({ ...options, method: 'CONNECT' }),
|
connect: (options) => request({ ...options, method: "CONNECT" }),
|
||||||
delete: (options) => request({ ...options, method: 'DELETE' }),
|
delete: (options) => request({ ...options, method: "DELETE" }),
|
||||||
get: (options) => request({ ...options, method: 'GET' }),
|
get: (options) => request({ ...options, method: "GET" }),
|
||||||
getConfig,
|
getConfig,
|
||||||
head: (options) => request({ ...options, method: 'HEAD' }),
|
head: (options) => request({ ...options, method: "HEAD" }),
|
||||||
interceptors,
|
interceptors,
|
||||||
options: (options) => request({ ...options, method: 'OPTIONS' }),
|
options: (options) => request({ ...options, method: "OPTIONS" }),
|
||||||
patch: (options) => request({ ...options, method: 'PATCH' }),
|
patch: (options) => request({ ...options, method: "PATCH" }),
|
||||||
post: (options) => request({ ...options, method: 'POST' }),
|
post: (options) => request({ ...options, method: "POST" }),
|
||||||
put: (options) => request({ ...options, method: 'PUT' }),
|
put: (options) => request({ ...options, method: "PUT" }),
|
||||||
request,
|
request,
|
||||||
setConfig,
|
setConfig,
|
||||||
trace: (options) => request({ ...options, method: 'TRACE' }),
|
trace: (options) => request({ ...options, method: "TRACE" }),
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
export type { Auth } from '../core/auth';
|
export type { Auth } from "../core/auth"
|
||||||
export type { QuerySerializerOptions } from '../core/bodySerializer';
|
export type { QuerySerializerOptions } from "../core/bodySerializer"
|
||||||
export {
|
export { formDataBodySerializer, jsonBodySerializer, urlSearchParamsBodySerializer } from "../core/bodySerializer"
|
||||||
formDataBodySerializer,
|
export { buildClientParams } from "../core/params"
|
||||||
jsonBodySerializer,
|
export { createClient } from "./client"
|
||||||
urlSearchParamsBodySerializer,
|
|
||||||
} from '../core/bodySerializer';
|
|
||||||
export { buildClientParams } from '../core/params';
|
|
||||||
export { createClient } from './client';
|
|
||||||
export type {
|
export type {
|
||||||
Client,
|
Client,
|
||||||
ClientOptions,
|
ClientOptions,
|
||||||
@@ -18,5 +14,5 @@ export type {
|
|||||||
RequestResult,
|
RequestResult,
|
||||||
ResponseStyle,
|
ResponseStyle,
|
||||||
TDataShape,
|
TDataShape,
|
||||||
} from './types';
|
} from "./types"
|
||||||
export { createConfig, mergeHeaders } from './utils';
|
export { createConfig, mergeHeaders } from "./utils"
|
||||||
|
|||||||
@@ -1,33 +1,30 @@
|
|||||||
import type { Auth } from '../core/auth';
|
import type { Auth } from "../core/auth"
|
||||||
import type {
|
import type { Client as CoreClient, Config as CoreConfig } from "../core/types"
|
||||||
Client as CoreClient,
|
import type { Middleware } from "./utils"
|
||||||
Config as CoreConfig,
|
|
||||||
} from '../core/types';
|
|
||||||
import type { Middleware } from './utils';
|
|
||||||
|
|
||||||
export type ResponseStyle = 'data' | 'fields';
|
export type ResponseStyle = "data" | "fields"
|
||||||
|
|
||||||
export interface Config<T extends ClientOptions = ClientOptions>
|
export interface Config<T extends ClientOptions = ClientOptions>
|
||||||
extends Omit<RequestInit, 'body' | 'headers' | 'method'>,
|
extends Omit<RequestInit, "body" | "headers" | "method">,
|
||||||
CoreConfig {
|
CoreConfig {
|
||||||
/**
|
/**
|
||||||
* Base URL for all requests made by this client.
|
* Base URL for all requests made by this client.
|
||||||
*/
|
*/
|
||||||
baseUrl?: T['baseUrl'];
|
baseUrl?: T["baseUrl"]
|
||||||
/**
|
/**
|
||||||
* Fetch API implementation. You can use this option to provide a custom
|
* Fetch API implementation. You can use this option to provide a custom
|
||||||
* fetch instance.
|
* fetch instance.
|
||||||
*
|
*
|
||||||
* @default globalThis.fetch
|
* @default globalThis.fetch
|
||||||
*/
|
*/
|
||||||
fetch?: (request: Request) => ReturnType<typeof fetch>;
|
fetch?: (request: Request) => ReturnType<typeof fetch>
|
||||||
/**
|
/**
|
||||||
* Please don't use the Fetch client for Next.js applications. The `next`
|
* Please don't use the Fetch client for Next.js applications. The `next`
|
||||||
* options won't have any effect.
|
* options won't have any effect.
|
||||||
*
|
*
|
||||||
* Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead.
|
* Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead.
|
||||||
*/
|
*/
|
||||||
next?: never;
|
next?: never
|
||||||
/**
|
/**
|
||||||
* Return the response data parsed in a specified format. By default, `auto`
|
* Return the response data parsed in a specified format. By default, `auto`
|
||||||
* will infer the appropriate method from the `Content-Type` response header.
|
* will infer the appropriate method from the `Content-Type` response header.
|
||||||
@@ -36,135 +33,118 @@ export interface Config<T extends ClientOptions = ClientOptions>
|
|||||||
*
|
*
|
||||||
* @default 'auto'
|
* @default 'auto'
|
||||||
*/
|
*/
|
||||||
parseAs?:
|
parseAs?: "arrayBuffer" | "auto" | "blob" | "formData" | "json" | "stream" | "text"
|
||||||
| 'arrayBuffer'
|
|
||||||
| 'auto'
|
|
||||||
| 'blob'
|
|
||||||
| 'formData'
|
|
||||||
| 'json'
|
|
||||||
| 'stream'
|
|
||||||
| 'text';
|
|
||||||
/**
|
/**
|
||||||
* Should we return only data or multiple fields (data, error, response, etc.)?
|
* Should we return only data or multiple fields (data, error, response, etc.)?
|
||||||
*
|
*
|
||||||
* @default 'fields'
|
* @default 'fields'
|
||||||
*/
|
*/
|
||||||
responseStyle?: ResponseStyle;
|
responseStyle?: ResponseStyle
|
||||||
/**
|
/**
|
||||||
* Throw an error instead of returning it in the response?
|
* Throw an error instead of returning it in the response?
|
||||||
*
|
*
|
||||||
* @default false
|
* @default false
|
||||||
*/
|
*/
|
||||||
throwOnError?: T['throwOnError'];
|
throwOnError?: T["throwOnError"]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RequestOptions<
|
export interface RequestOptions<
|
||||||
TResponseStyle extends ResponseStyle = 'fields',
|
TResponseStyle extends ResponseStyle = "fields",
|
||||||
ThrowOnError extends boolean = boolean,
|
ThrowOnError extends boolean = boolean,
|
||||||
Url extends string = string,
|
Url extends string = string,
|
||||||
> extends Config<{
|
> extends Config<{
|
||||||
responseStyle: TResponseStyle;
|
responseStyle: TResponseStyle
|
||||||
throwOnError: ThrowOnError;
|
throwOnError: ThrowOnError
|
||||||
}> {
|
}> {
|
||||||
/**
|
/**
|
||||||
* Any body that you want to add to your request.
|
* Any body that you want to add to your request.
|
||||||
*
|
*
|
||||||
* {@link https://developer.mozilla.org/docs/Web/API/fetch#body}
|
* {@link https://developer.mozilla.org/docs/Web/API/fetch#body}
|
||||||
*/
|
*/
|
||||||
body?: unknown;
|
body?: unknown
|
||||||
path?: Record<string, unknown>;
|
path?: Record<string, unknown>
|
||||||
query?: Record<string, unknown>;
|
query?: Record<string, unknown>
|
||||||
/**
|
/**
|
||||||
* Security mechanism(s) to use for the request.
|
* Security mechanism(s) to use for the request.
|
||||||
*/
|
*/
|
||||||
security?: ReadonlyArray<Auth>;
|
security?: ReadonlyArray<Auth>
|
||||||
url: Url;
|
url: Url
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RequestResult<
|
export type RequestResult<
|
||||||
TData = unknown,
|
TData = unknown,
|
||||||
TError = unknown,
|
TError = unknown,
|
||||||
ThrowOnError extends boolean = boolean,
|
ThrowOnError extends boolean = boolean,
|
||||||
TResponseStyle extends ResponseStyle = 'fields',
|
TResponseStyle extends ResponseStyle = "fields",
|
||||||
> = ThrowOnError extends true
|
> = ThrowOnError extends true
|
||||||
? Promise<
|
? Promise<
|
||||||
TResponseStyle extends 'data'
|
TResponseStyle extends "data"
|
||||||
? TData extends Record<string, unknown>
|
? TData extends Record<string, unknown>
|
||||||
? TData[keyof TData]
|
? TData[keyof TData]
|
||||||
: TData
|
: TData
|
||||||
: {
|
: {
|
||||||
data: TData extends Record<string, unknown>
|
data: TData extends Record<string, unknown> ? TData[keyof TData] : TData
|
||||||
? TData[keyof TData]
|
request: Request
|
||||||
: TData;
|
response: Response
|
||||||
request: Request;
|
|
||||||
response: Response;
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
: Promise<
|
: Promise<
|
||||||
TResponseStyle extends 'data'
|
TResponseStyle extends "data"
|
||||||
?
|
? (TData extends Record<string, unknown> ? TData[keyof TData] : TData) | undefined
|
||||||
| (TData extends Record<string, unknown>
|
|
||||||
? TData[keyof TData]
|
|
||||||
: TData)
|
|
||||||
| undefined
|
|
||||||
: (
|
: (
|
||||||
| {
|
| {
|
||||||
data: TData extends Record<string, unknown>
|
data: TData extends Record<string, unknown> ? TData[keyof TData] : TData
|
||||||
? TData[keyof TData]
|
error: undefined
|
||||||
: TData;
|
|
||||||
error: undefined;
|
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
data: undefined;
|
data: undefined
|
||||||
error: TError extends Record<string, unknown>
|
error: TError extends Record<string, unknown> ? TError[keyof TError] : TError
|
||||||
? TError[keyof TError]
|
|
||||||
: TError;
|
|
||||||
}
|
}
|
||||||
) & {
|
) & {
|
||||||
request: Request;
|
request: Request
|
||||||
response: Response;
|
response: Response
|
||||||
}
|
}
|
||||||
>;
|
>
|
||||||
|
|
||||||
export interface ClientOptions {
|
export interface ClientOptions {
|
||||||
baseUrl?: string;
|
baseUrl?: string
|
||||||
responseStyle?: ResponseStyle;
|
responseStyle?: ResponseStyle
|
||||||
throwOnError?: boolean;
|
throwOnError?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type MethodFn = <
|
type MethodFn = <
|
||||||
TData = unknown,
|
TData = unknown,
|
||||||
TError = unknown,
|
TError = unknown,
|
||||||
ThrowOnError extends boolean = false,
|
ThrowOnError extends boolean = false,
|
||||||
TResponseStyle extends ResponseStyle = 'fields',
|
TResponseStyle extends ResponseStyle = "fields",
|
||||||
>(
|
>(
|
||||||
options: Omit<RequestOptions<TResponseStyle, ThrowOnError>, 'method'>,
|
options: Omit<RequestOptions<TResponseStyle, ThrowOnError>, "method">,
|
||||||
) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>;
|
) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>
|
||||||
|
|
||||||
type RequestFn = <
|
type RequestFn = <
|
||||||
TData = unknown,
|
TData = unknown,
|
||||||
TError = unknown,
|
TError = unknown,
|
||||||
ThrowOnError extends boolean = false,
|
ThrowOnError extends boolean = false,
|
||||||
TResponseStyle extends ResponseStyle = 'fields',
|
TResponseStyle extends ResponseStyle = "fields",
|
||||||
>(
|
>(
|
||||||
options: Omit<RequestOptions<TResponseStyle, ThrowOnError>, 'method'> &
|
options: Omit<RequestOptions<TResponseStyle, ThrowOnError>, "method"> &
|
||||||
Pick<Required<RequestOptions<TResponseStyle, ThrowOnError>>, 'method'>,
|
Pick<Required<RequestOptions<TResponseStyle, ThrowOnError>>, "method">,
|
||||||
) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>;
|
) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>
|
||||||
|
|
||||||
type BuildUrlFn = <
|
type BuildUrlFn = <
|
||||||
TData extends {
|
TData extends {
|
||||||
body?: unknown;
|
body?: unknown
|
||||||
path?: Record<string, unknown>;
|
path?: Record<string, unknown>
|
||||||
query?: Record<string, unknown>;
|
query?: Record<string, unknown>
|
||||||
url: string;
|
url: string
|
||||||
},
|
},
|
||||||
>(
|
>(
|
||||||
options: Pick<TData, 'url'> & Options<TData>,
|
options: Pick<TData, "url"> & Options<TData>,
|
||||||
) => string;
|
) => string
|
||||||
|
|
||||||
export type Client = CoreClient<RequestFn, Config, MethodFn, BuildUrlFn> & {
|
export type Client = CoreClient<RequestFn, Config, MethodFn, BuildUrlFn> & {
|
||||||
interceptors: Middleware<Request, Response, unknown, RequestOptions>;
|
interceptors: Middleware<Request, Response, unknown, RequestOptions>
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `createClientConfig()` function will be called on client initialization
|
* The `createClientConfig()` function will be called on client initialization
|
||||||
@@ -176,47 +156,36 @@ export type Client = CoreClient<RequestFn, Config, MethodFn, BuildUrlFn> & {
|
|||||||
*/
|
*/
|
||||||
export type CreateClientConfig<T extends ClientOptions = ClientOptions> = (
|
export type CreateClientConfig<T extends ClientOptions = ClientOptions> = (
|
||||||
override?: Config<ClientOptions & T>,
|
override?: Config<ClientOptions & T>,
|
||||||
) => Config<Required<ClientOptions> & T>;
|
) => Config<Required<ClientOptions> & T>
|
||||||
|
|
||||||
export interface TDataShape {
|
export interface TDataShape {
|
||||||
body?: unknown;
|
body?: unknown
|
||||||
headers?: unknown;
|
headers?: unknown
|
||||||
path?: unknown;
|
path?: unknown
|
||||||
query?: unknown;
|
query?: unknown
|
||||||
url: string;
|
url: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type OmitKeys<T, K> = Pick<T, Exclude<keyof T, K>>;
|
type OmitKeys<T, K> = Pick<T, Exclude<keyof T, K>>
|
||||||
|
|
||||||
export type Options<
|
export type Options<
|
||||||
TData extends TDataShape = TDataShape,
|
TData extends TDataShape = TDataShape,
|
||||||
ThrowOnError extends boolean = boolean,
|
ThrowOnError extends boolean = boolean,
|
||||||
TResponseStyle extends ResponseStyle = 'fields',
|
TResponseStyle extends ResponseStyle = "fields",
|
||||||
> = OmitKeys<
|
> = OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, "body" | "path" | "query" | "url"> & Omit<TData, "url">
|
||||||
RequestOptions<TResponseStyle, ThrowOnError>,
|
|
||||||
'body' | 'path' | 'query' | 'url'
|
|
||||||
> &
|
|
||||||
Omit<TData, 'url'>;
|
|
||||||
|
|
||||||
export type OptionsLegacyParser<
|
export type OptionsLegacyParser<
|
||||||
TData = unknown,
|
TData = unknown,
|
||||||
ThrowOnError extends boolean = boolean,
|
ThrowOnError extends boolean = boolean,
|
||||||
TResponseStyle extends ResponseStyle = 'fields',
|
TResponseStyle extends ResponseStyle = "fields",
|
||||||
> = TData extends { body?: any }
|
> = TData extends { body?: any }
|
||||||
? TData extends { headers?: any }
|
? TData extends { headers?: any }
|
||||||
? OmitKeys<
|
? OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, "body" | "headers" | "url"> & TData
|
||||||
RequestOptions<TResponseStyle, ThrowOnError>,
|
: OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, "body" | "url"> &
|
||||||
'body' | 'headers' | 'url'
|
|
||||||
> &
|
|
||||||
TData
|
|
||||||
: OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, 'body' | 'url'> &
|
|
||||||
TData &
|
TData &
|
||||||
Pick<RequestOptions<TResponseStyle, ThrowOnError>, 'headers'>
|
Pick<RequestOptions<TResponseStyle, ThrowOnError>, "headers">
|
||||||
: TData extends { headers?: any }
|
: TData extends { headers?: any }
|
||||||
? OmitKeys<
|
? OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, "headers" | "url"> &
|
||||||
RequestOptions<TResponseStyle, ThrowOnError>,
|
|
||||||
'headers' | 'url'
|
|
||||||
> &
|
|
||||||
TData &
|
TData &
|
||||||
Pick<RequestOptions<TResponseStyle, ThrowOnError>, 'body'>
|
Pick<RequestOptions<TResponseStyle, ThrowOnError>, "body">
|
||||||
: OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, 'url'> & TData;
|
: OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, "url"> & TData
|
||||||
|
|||||||
@@ -1,64 +1,54 @@
|
|||||||
import { getAuthToken } from '../core/auth';
|
import { getAuthToken } from "../core/auth"
|
||||||
import type {
|
import type { QuerySerializer, QuerySerializerOptions } from "../core/bodySerializer"
|
||||||
QuerySerializer,
|
import { jsonBodySerializer } from "../core/bodySerializer"
|
||||||
QuerySerializerOptions,
|
import { serializeArrayParam, serializeObjectParam, serializePrimitiveParam } from "../core/pathSerializer"
|
||||||
} from '../core/bodySerializer';
|
import type { Client, ClientOptions, Config, RequestOptions } from "./types"
|
||||||
import { jsonBodySerializer } from '../core/bodySerializer';
|
|
||||||
import {
|
|
||||||
serializeArrayParam,
|
|
||||||
serializeObjectParam,
|
|
||||||
serializePrimitiveParam,
|
|
||||||
} from '../core/pathSerializer';
|
|
||||||
import type { Client, ClientOptions, Config, RequestOptions } from './types';
|
|
||||||
|
|
||||||
interface PathSerializer {
|
interface PathSerializer {
|
||||||
path: Record<string, unknown>;
|
path: Record<string, unknown>
|
||||||
url: string;
|
url: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const PATH_PARAM_RE = /\{[^{}]+\}/g;
|
const PATH_PARAM_RE = /\{[^{}]+\}/g
|
||||||
|
|
||||||
type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited';
|
type ArrayStyle = "form" | "spaceDelimited" | "pipeDelimited"
|
||||||
type MatrixStyle = 'label' | 'matrix' | 'simple';
|
type MatrixStyle = "label" | "matrix" | "simple"
|
||||||
type ArraySeparatorStyle = ArrayStyle | MatrixStyle;
|
type ArraySeparatorStyle = ArrayStyle | MatrixStyle
|
||||||
|
|
||||||
const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => {
|
const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => {
|
||||||
let url = _url;
|
let url = _url
|
||||||
const matches = _url.match(PATH_PARAM_RE);
|
const matches = _url.match(PATH_PARAM_RE)
|
||||||
if (matches) {
|
if (matches) {
|
||||||
for (const match of matches) {
|
for (const match of matches) {
|
||||||
let explode = false;
|
let explode = false
|
||||||
let name = match.substring(1, match.length - 1);
|
let name = match.substring(1, match.length - 1)
|
||||||
let style: ArraySeparatorStyle = 'simple';
|
let style: ArraySeparatorStyle = "simple"
|
||||||
|
|
||||||
if (name.endsWith('*')) {
|
if (name.endsWith("*")) {
|
||||||
explode = true;
|
explode = true
|
||||||
name = name.substring(0, name.length - 1);
|
name = name.substring(0, name.length - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name.startsWith('.')) {
|
if (name.startsWith(".")) {
|
||||||
name = name.substring(1);
|
name = name.substring(1)
|
||||||
style = 'label';
|
style = "label"
|
||||||
} else if (name.startsWith(';')) {
|
} else if (name.startsWith(";")) {
|
||||||
name = name.substring(1);
|
name = name.substring(1)
|
||||||
style = 'matrix';
|
style = "matrix"
|
||||||
}
|
}
|
||||||
|
|
||||||
const value = path[name];
|
const value = path[name]
|
||||||
|
|
||||||
if (value === undefined || value === null) {
|
if (value === undefined || value === null) {
|
||||||
continue;
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
url = url.replace(
|
url = url.replace(match, serializeArrayParam({ explode, name, style, value }))
|
||||||
match,
|
continue
|
||||||
serializeArrayParam({ explode, name, style, value }),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof value === 'object') {
|
if (typeof value === "object") {
|
||||||
url = url.replace(
|
url = url.replace(
|
||||||
match,
|
match,
|
||||||
serializeObjectParam({
|
serializeObjectParam({
|
||||||
@@ -68,43 +58,37 @@ const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => {
|
|||||||
value: value as Record<string, unknown>,
|
value: value as Record<string, unknown>,
|
||||||
valueOnly: true,
|
valueOnly: true,
|
||||||
}),
|
}),
|
||||||
);
|
)
|
||||||
continue;
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (style === 'matrix') {
|
if (style === "matrix") {
|
||||||
url = url.replace(
|
url = url.replace(
|
||||||
match,
|
match,
|
||||||
`;${serializePrimitiveParam({
|
`;${serializePrimitiveParam({
|
||||||
name,
|
name,
|
||||||
value: value as string,
|
value: value as string,
|
||||||
})}`,
|
})}`,
|
||||||
);
|
)
|
||||||
continue;
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const replaceValue = encodeURIComponent(
|
const replaceValue = encodeURIComponent(style === "label" ? `.${value as string}` : (value as string))
|
||||||
style === 'label' ? `.${value as string}` : (value as string),
|
url = url.replace(match, replaceValue)
|
||||||
);
|
|
||||||
url = url.replace(match, replaceValue);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return url;
|
return url
|
||||||
};
|
}
|
||||||
|
|
||||||
export const createQuerySerializer = <T = unknown>({
|
export const createQuerySerializer = <T = unknown>({ allowReserved, array, object }: QuerySerializerOptions = {}) => {
|
||||||
allowReserved,
|
|
||||||
array,
|
|
||||||
object,
|
|
||||||
}: QuerySerializerOptions = {}) => {
|
|
||||||
const querySerializer = (queryParams: T) => {
|
const querySerializer = (queryParams: T) => {
|
||||||
const search: string[] = [];
|
const search: string[] = []
|
||||||
if (queryParams && typeof queryParams === 'object') {
|
if (queryParams && typeof queryParams === "object") {
|
||||||
for (const name in queryParams) {
|
for (const name in queryParams) {
|
||||||
const value = queryParams[name];
|
const value = queryParams[name]
|
||||||
|
|
||||||
if (value === undefined || value === null) {
|
if (value === undefined || value === null) {
|
||||||
continue;
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
@@ -112,129 +96,120 @@ export const createQuerySerializer = <T = unknown>({
|
|||||||
allowReserved,
|
allowReserved,
|
||||||
explode: true,
|
explode: true,
|
||||||
name,
|
name,
|
||||||
style: 'form',
|
style: "form",
|
||||||
value,
|
value,
|
||||||
...array,
|
...array,
|
||||||
});
|
})
|
||||||
if (serializedArray) search.push(serializedArray);
|
if (serializedArray) search.push(serializedArray)
|
||||||
} else if (typeof value === 'object') {
|
} else if (typeof value === "object") {
|
||||||
const serializedObject = serializeObjectParam({
|
const serializedObject = serializeObjectParam({
|
||||||
allowReserved,
|
allowReserved,
|
||||||
explode: true,
|
explode: true,
|
||||||
name,
|
name,
|
||||||
style: 'deepObject',
|
style: "deepObject",
|
||||||
value: value as Record<string, unknown>,
|
value: value as Record<string, unknown>,
|
||||||
...object,
|
...object,
|
||||||
});
|
})
|
||||||
if (serializedObject) search.push(serializedObject);
|
if (serializedObject) search.push(serializedObject)
|
||||||
} else {
|
} else {
|
||||||
const serializedPrimitive = serializePrimitiveParam({
|
const serializedPrimitive = serializePrimitiveParam({
|
||||||
allowReserved,
|
allowReserved,
|
||||||
name,
|
name,
|
||||||
value: value as string,
|
value: value as string,
|
||||||
});
|
})
|
||||||
if (serializedPrimitive) search.push(serializedPrimitive);
|
if (serializedPrimitive) search.push(serializedPrimitive)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return search.join('&');
|
return search.join("&")
|
||||||
};
|
}
|
||||||
return querySerializer;
|
return querySerializer
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Infers parseAs value from provided Content-Type header.
|
* Infers parseAs value from provided Content-Type header.
|
||||||
*/
|
*/
|
||||||
export const getParseAs = (
|
export const getParseAs = (contentType: string | null): Exclude<Config["parseAs"], "auto"> => {
|
||||||
contentType: string | null,
|
|
||||||
): Exclude<Config['parseAs'], 'auto'> => {
|
|
||||||
if (!contentType) {
|
if (!contentType) {
|
||||||
// If no Content-Type header is provided, the best we can do is return the raw response body,
|
// If no Content-Type header is provided, the best we can do is return the raw response body,
|
||||||
// which is effectively the same as the 'stream' option.
|
// which is effectively the same as the 'stream' option.
|
||||||
return 'stream';
|
return "stream"
|
||||||
}
|
}
|
||||||
|
|
||||||
const cleanContent = contentType.split(';')[0]?.trim();
|
const cleanContent = contentType.split(";")[0]?.trim()
|
||||||
|
|
||||||
if (!cleanContent) {
|
if (!cleanContent) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (cleanContent.startsWith("application/json") || cleanContent.endsWith("+json")) {
|
||||||
cleanContent.startsWith('application/json') ||
|
return "json"
|
||||||
cleanContent.endsWith('+json')
|
|
||||||
) {
|
|
||||||
return 'json';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cleanContent === 'multipart/form-data') {
|
if (cleanContent === "multipart/form-data") {
|
||||||
return 'formData';
|
return "formData"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (["application/", "audio/", "image/", "video/"].some((type) => cleanContent.startsWith(type))) {
|
||||||
['application/', 'audio/', 'image/', 'video/'].some((type) =>
|
return "blob"
|
||||||
cleanContent.startsWith(type),
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return 'blob';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cleanContent.startsWith('text/')) {
|
if (cleanContent.startsWith("text/")) {
|
||||||
return 'text';
|
return "text"
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return
|
||||||
};
|
}
|
||||||
|
|
||||||
export const setAuthParams = async ({
|
export const setAuthParams = async ({
|
||||||
security,
|
security,
|
||||||
...options
|
...options
|
||||||
}: Pick<Required<RequestOptions>, 'security'> &
|
}: Pick<Required<RequestOptions>, "security"> &
|
||||||
Pick<RequestOptions, 'auth' | 'query'> & {
|
Pick<RequestOptions, "auth" | "query"> & {
|
||||||
headers: Headers;
|
headers: Headers
|
||||||
}) => {
|
}) => {
|
||||||
for (const auth of security) {
|
for (const auth of security) {
|
||||||
const token = await getAuthToken(auth, options.auth);
|
const token = await getAuthToken(auth, options.auth)
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
continue;
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const name = auth.name ?? 'Authorization';
|
const name = auth.name ?? "Authorization"
|
||||||
|
|
||||||
switch (auth.in) {
|
switch (auth.in) {
|
||||||
case 'query':
|
case "query":
|
||||||
if (!options.query) {
|
if (!options.query) {
|
||||||
options.query = {};
|
options.query = {}
|
||||||
}
|
}
|
||||||
options.query[name] = token;
|
options.query[name] = token
|
||||||
break;
|
break
|
||||||
case 'cookie':
|
case "cookie":
|
||||||
options.headers.append('Cookie', `${name}=${token}`);
|
options.headers.append("Cookie", `${name}=${token}`)
|
||||||
break;
|
break
|
||||||
case 'header':
|
case "header":
|
||||||
default:
|
default:
|
||||||
options.headers.set(name, token);
|
options.headers.set(name, token)
|
||||||
break;
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export const buildUrl: Client['buildUrl'] = (options) => {
|
export const buildUrl: Client["buildUrl"] = (options) => {
|
||||||
const url = getUrl({
|
const url = getUrl({
|
||||||
baseUrl: options.baseUrl as string,
|
baseUrl: options.baseUrl as string,
|
||||||
path: options.path,
|
path: options.path,
|
||||||
query: options.query,
|
query: options.query,
|
||||||
querySerializer:
|
querySerializer:
|
||||||
typeof options.querySerializer === 'function'
|
typeof options.querySerializer === "function"
|
||||||
? options.querySerializer
|
? options.querySerializer
|
||||||
: createQuerySerializer(options.querySerializer),
|
: createQuerySerializer(options.querySerializer),
|
||||||
url: options.url,
|
url: options.url,
|
||||||
});
|
})
|
||||||
return url;
|
return url
|
||||||
};
|
}
|
||||||
|
|
||||||
export const getUrl = ({
|
export const getUrl = ({
|
||||||
baseUrl,
|
baseUrl,
|
||||||
@@ -243,144 +218,125 @@ export const getUrl = ({
|
|||||||
querySerializer,
|
querySerializer,
|
||||||
url: _url,
|
url: _url,
|
||||||
}: {
|
}: {
|
||||||
baseUrl?: string;
|
baseUrl?: string
|
||||||
path?: Record<string, unknown>;
|
path?: Record<string, unknown>
|
||||||
query?: Record<string, unknown>;
|
query?: Record<string, unknown>
|
||||||
querySerializer: QuerySerializer;
|
querySerializer: QuerySerializer
|
||||||
url: string;
|
url: string
|
||||||
}) => {
|
}) => {
|
||||||
const pathUrl = _url.startsWith('/') ? _url : `/${_url}`;
|
const pathUrl = _url.startsWith("/") ? _url : `/${_url}`
|
||||||
let url = (baseUrl ?? '') + pathUrl;
|
let url = (baseUrl ?? "") + pathUrl
|
||||||
if (path) {
|
if (path) {
|
||||||
url = defaultPathSerializer({ path, url });
|
url = defaultPathSerializer({ path, url })
|
||||||
}
|
}
|
||||||
let search = query ? querySerializer(query) : '';
|
let search = query ? querySerializer(query) : ""
|
||||||
if (search.startsWith('?')) {
|
if (search.startsWith("?")) {
|
||||||
search = search.substring(1);
|
search = search.substring(1)
|
||||||
}
|
}
|
||||||
if (search) {
|
if (search) {
|
||||||
url += `?${search}`;
|
url += `?${search}`
|
||||||
}
|
}
|
||||||
return url;
|
return url
|
||||||
};
|
}
|
||||||
|
|
||||||
export const mergeConfigs = (a: Config, b: Config): Config => {
|
export const mergeConfigs = (a: Config, b: Config): Config => {
|
||||||
const config = { ...a, ...b };
|
const config = { ...a, ...b }
|
||||||
if (config.baseUrl?.endsWith('/')) {
|
if (config.baseUrl?.endsWith("/")) {
|
||||||
config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1);
|
config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1)
|
||||||
}
|
}
|
||||||
config.headers = mergeHeaders(a.headers, b.headers);
|
config.headers = mergeHeaders(a.headers, b.headers)
|
||||||
return config;
|
return config
|
||||||
};
|
}
|
||||||
|
|
||||||
export const mergeHeaders = (
|
export const mergeHeaders = (...headers: Array<Required<Config>["headers"] | undefined>): Headers => {
|
||||||
...headers: Array<Required<Config>['headers'] | undefined>
|
const mergedHeaders = new Headers()
|
||||||
): Headers => {
|
|
||||||
const mergedHeaders = new Headers();
|
|
||||||
for (const header of headers) {
|
for (const header of headers) {
|
||||||
if (!header || typeof header !== 'object') {
|
if (!header || typeof header !== "object") {
|
||||||
continue;
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const iterator =
|
const iterator = header instanceof Headers ? header.entries() : Object.entries(header)
|
||||||
header instanceof Headers ? header.entries() : Object.entries(header);
|
|
||||||
|
|
||||||
for (const [key, value] of iterator) {
|
for (const [key, value] of iterator) {
|
||||||
if (value === null) {
|
if (value === null) {
|
||||||
mergedHeaders.delete(key);
|
mergedHeaders.delete(key)
|
||||||
} else if (Array.isArray(value)) {
|
} else if (Array.isArray(value)) {
|
||||||
for (const v of value) {
|
for (const v of value) {
|
||||||
mergedHeaders.append(key, v as string);
|
mergedHeaders.append(key, v as string)
|
||||||
}
|
}
|
||||||
} else if (value !== undefined) {
|
} else if (value !== undefined) {
|
||||||
// assume object headers are meant to be JSON stringified, i.e. their
|
// assume object headers are meant to be JSON stringified, i.e. their
|
||||||
// content value in OpenAPI specification is 'application/json'
|
// content value in OpenAPI specification is 'application/json'
|
||||||
mergedHeaders.set(
|
mergedHeaders.set(key, typeof value === "object" ? JSON.stringify(value) : (value as string))
|
||||||
key,
|
|
||||||
typeof value === 'object' ? JSON.stringify(value) : (value as string),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return mergedHeaders;
|
return mergedHeaders
|
||||||
};
|
}
|
||||||
|
|
||||||
type ErrInterceptor<Err, Res, Req, Options> = (
|
type ErrInterceptor<Err, Res, Req, Options> = (
|
||||||
error: Err,
|
error: Err,
|
||||||
response: Res,
|
response: Res,
|
||||||
request: Req,
|
request: Req,
|
||||||
options: Options,
|
options: Options,
|
||||||
) => Err | Promise<Err>;
|
) => Err | Promise<Err>
|
||||||
|
|
||||||
type ReqInterceptor<Req, Options> = (
|
type ReqInterceptor<Req, Options> = (request: Req, options: Options) => Req | Promise<Req>
|
||||||
request: Req,
|
|
||||||
options: Options,
|
|
||||||
) => Req | Promise<Req>;
|
|
||||||
|
|
||||||
type ResInterceptor<Res, Req, Options> = (
|
type ResInterceptor<Res, Req, Options> = (response: Res, request: Req, options: Options) => Res | Promise<Res>
|
||||||
response: Res,
|
|
||||||
request: Req,
|
|
||||||
options: Options,
|
|
||||||
) => Res | Promise<Res>;
|
|
||||||
|
|
||||||
class Interceptors<Interceptor> {
|
class Interceptors<Interceptor> {
|
||||||
_fns: (Interceptor | null)[];
|
_fns: (Interceptor | null)[]
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this._fns = [];
|
this._fns = []
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this._fns = [];
|
this._fns = []
|
||||||
}
|
}
|
||||||
|
|
||||||
getInterceptorIndex(id: number | Interceptor): number {
|
getInterceptorIndex(id: number | Interceptor): number {
|
||||||
if (typeof id === 'number') {
|
if (typeof id === "number") {
|
||||||
return this._fns[id] ? id : -1;
|
return this._fns[id] ? id : -1
|
||||||
} else {
|
} else {
|
||||||
return this._fns.indexOf(id);
|
return this._fns.indexOf(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exists(id: number | Interceptor) {
|
exists(id: number | Interceptor) {
|
||||||
const index = this.getInterceptorIndex(id);
|
const index = this.getInterceptorIndex(id)
|
||||||
return !!this._fns[index];
|
return !!this._fns[index]
|
||||||
}
|
}
|
||||||
|
|
||||||
eject(id: number | Interceptor) {
|
eject(id: number | Interceptor) {
|
||||||
const index = this.getInterceptorIndex(id);
|
const index = this.getInterceptorIndex(id)
|
||||||
if (this._fns[index]) {
|
if (this._fns[index]) {
|
||||||
this._fns[index] = null;
|
this._fns[index] = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
update(id: number | Interceptor, fn: Interceptor) {
|
update(id: number | Interceptor, fn: Interceptor) {
|
||||||
const index = this.getInterceptorIndex(id);
|
const index = this.getInterceptorIndex(id)
|
||||||
if (this._fns[index]) {
|
if (this._fns[index]) {
|
||||||
this._fns[index] = fn;
|
this._fns[index] = fn
|
||||||
return id;
|
return id
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use(fn: Interceptor) {
|
use(fn: Interceptor) {
|
||||||
this._fns = [...this._fns, fn];
|
this._fns = [...this._fns, fn]
|
||||||
return this._fns.length - 1;
|
return this._fns.length - 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// `createInterceptors()` response, meant for external use as it does not
|
// `createInterceptors()` response, meant for external use as it does not
|
||||||
// expose internals
|
// expose internals
|
||||||
export interface Middleware<Req, Res, Err, Options> {
|
export interface Middleware<Req, Res, Err, Options> {
|
||||||
error: Pick<
|
error: Pick<Interceptors<ErrInterceptor<Err, Res, Req, Options>>, "eject" | "use">
|
||||||
Interceptors<ErrInterceptor<Err, Res, Req, Options>>,
|
request: Pick<Interceptors<ReqInterceptor<Req, Options>>, "eject" | "use">
|
||||||
'eject' | 'use'
|
response: Pick<Interceptors<ResInterceptor<Res, Req, Options>>, "eject" | "use">
|
||||||
>;
|
|
||||||
request: Pick<Interceptors<ReqInterceptor<Req, Options>>, 'eject' | 'use'>;
|
|
||||||
response: Pick<
|
|
||||||
Interceptors<ResInterceptor<Res, Req, Options>>,
|
|
||||||
'eject' | 'use'
|
|
||||||
>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// do not add `Middleware` as return type so we can use _fns internally
|
// do not add `Middleware` as return type so we can use _fns internally
|
||||||
@@ -388,30 +344,30 @@ export const createInterceptors = <Req, Res, Err, Options>() => ({
|
|||||||
error: new Interceptors<ErrInterceptor<Err, Res, Req, Options>>(),
|
error: new Interceptors<ErrInterceptor<Err, Res, Req, Options>>(),
|
||||||
request: new Interceptors<ReqInterceptor<Req, Options>>(),
|
request: new Interceptors<ReqInterceptor<Req, Options>>(),
|
||||||
response: new Interceptors<ResInterceptor<Res, Req, Options>>(),
|
response: new Interceptors<ResInterceptor<Res, Req, Options>>(),
|
||||||
});
|
})
|
||||||
|
|
||||||
const defaultQuerySerializer = createQuerySerializer({
|
const defaultQuerySerializer = createQuerySerializer({
|
||||||
allowReserved: false,
|
allowReserved: false,
|
||||||
array: {
|
array: {
|
||||||
explode: true,
|
explode: true,
|
||||||
style: 'form',
|
style: "form",
|
||||||
},
|
},
|
||||||
object: {
|
object: {
|
||||||
explode: true,
|
explode: true,
|
||||||
style: 'deepObject',
|
style: "deepObject",
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
const defaultHeaders = {
|
const defaultHeaders = {
|
||||||
'Content-Type': 'application/json',
|
"Content-Type": "application/json",
|
||||||
};
|
}
|
||||||
|
|
||||||
export const createConfig = <T extends ClientOptions = ClientOptions>(
|
export const createConfig = <T extends ClientOptions = ClientOptions>(
|
||||||
override: Config<Omit<ClientOptions, keyof T> & T> = {},
|
override: Config<Omit<ClientOptions, keyof T> & T> = {},
|
||||||
): Config<Omit<ClientOptions, keyof T> & T> => ({
|
): Config<Omit<ClientOptions, keyof T> & T> => ({
|
||||||
...jsonBodySerializer,
|
...jsonBodySerializer,
|
||||||
headers: defaultHeaders,
|
headers: defaultHeaders,
|
||||||
parseAs: 'auto',
|
parseAs: "auto",
|
||||||
querySerializer: defaultQuerySerializer,
|
querySerializer: defaultQuerySerializer,
|
||||||
...override,
|
...override,
|
||||||
});
|
})
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export type AuthToken = string | undefined;
|
export type AuthToken = string | undefined
|
||||||
|
|
||||||
export interface Auth {
|
export interface Auth {
|
||||||
/**
|
/**
|
||||||
@@ -6,35 +6,34 @@ export interface Auth {
|
|||||||
*
|
*
|
||||||
* @default 'header'
|
* @default 'header'
|
||||||
*/
|
*/
|
||||||
in?: 'header' | 'query' | 'cookie';
|
in?: "header" | "query" | "cookie"
|
||||||
/**
|
/**
|
||||||
* Header or query parameter name.
|
* Header or query parameter name.
|
||||||
*
|
*
|
||||||
* @default 'Authorization'
|
* @default 'Authorization'
|
||||||
*/
|
*/
|
||||||
name?: string;
|
name?: string
|
||||||
scheme?: 'basic' | 'bearer';
|
scheme?: "basic" | "bearer"
|
||||||
type: 'apiKey' | 'http';
|
type: "apiKey" | "http"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getAuthToken = async (
|
export const getAuthToken = async (
|
||||||
auth: Auth,
|
auth: Auth,
|
||||||
callback: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken,
|
callback: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken,
|
||||||
): Promise<string | undefined> => {
|
): Promise<string | undefined> => {
|
||||||
const token =
|
const token = typeof callback === "function" ? await callback(auth) : callback
|
||||||
typeof callback === 'function' ? await callback(auth) : callback;
|
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auth.scheme === 'bearer') {
|
if (auth.scheme === "bearer") {
|
||||||
return `Bearer ${token}`;
|
return `Bearer ${token}`
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auth.scheme === 'basic') {
|
if (auth.scheme === "basic") {
|
||||||
return `Basic ${btoa(token)}`;
|
return `Basic ${btoa(token)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
return token;
|
return token
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -1,88 +1,70 @@
|
|||||||
import type {
|
import type { ArrayStyle, ObjectStyle, SerializerOptions } from "./pathSerializer"
|
||||||
ArrayStyle,
|
|
||||||
ObjectStyle,
|
|
||||||
SerializerOptions,
|
|
||||||
} from './pathSerializer';
|
|
||||||
|
|
||||||
export type QuerySerializer = (query: Record<string, unknown>) => string;
|
export type QuerySerializer = (query: Record<string, unknown>) => string
|
||||||
|
|
||||||
export type BodySerializer = (body: any) => any;
|
export type BodySerializer = (body: any) => any
|
||||||
|
|
||||||
export interface QuerySerializerOptions {
|
export interface QuerySerializerOptions {
|
||||||
allowReserved?: boolean;
|
allowReserved?: boolean
|
||||||
array?: SerializerOptions<ArrayStyle>;
|
array?: SerializerOptions<ArrayStyle>
|
||||||
object?: SerializerOptions<ObjectStyle>;
|
object?: SerializerOptions<ObjectStyle>
|
||||||
}
|
}
|
||||||
|
|
||||||
const serializeFormDataPair = (
|
const serializeFormDataPair = (data: FormData, key: string, value: unknown): void => {
|
||||||
data: FormData,
|
if (typeof value === "string" || value instanceof Blob) {
|
||||||
key: string,
|
data.append(key, value)
|
||||||
value: unknown,
|
|
||||||
): void => {
|
|
||||||
if (typeof value === 'string' || value instanceof Blob) {
|
|
||||||
data.append(key, value);
|
|
||||||
} else {
|
} else {
|
||||||
data.append(key, JSON.stringify(value));
|
data.append(key, JSON.stringify(value))
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const serializeUrlSearchParamsPair = (
|
const serializeUrlSearchParamsPair = (data: URLSearchParams, key: string, value: unknown): void => {
|
||||||
data: URLSearchParams,
|
if (typeof value === "string") {
|
||||||
key: string,
|
data.append(key, value)
|
||||||
value: unknown,
|
|
||||||
): void => {
|
|
||||||
if (typeof value === 'string') {
|
|
||||||
data.append(key, value);
|
|
||||||
} else {
|
} else {
|
||||||
data.append(key, JSON.stringify(value));
|
data.append(key, JSON.stringify(value))
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export const formDataBodySerializer = {
|
export const formDataBodySerializer = {
|
||||||
bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>(
|
bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>(body: T): FormData => {
|
||||||
body: T,
|
const data = new FormData()
|
||||||
): FormData => {
|
|
||||||
const data = new FormData();
|
|
||||||
|
|
||||||
Object.entries(body).forEach(([key, value]) => {
|
Object.entries(body).forEach(([key, value]) => {
|
||||||
if (value === undefined || value === null) {
|
if (value === undefined || value === null) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
value.forEach((v) => serializeFormDataPair(data, key, v));
|
value.forEach((v) => serializeFormDataPair(data, key, v))
|
||||||
} else {
|
} else {
|
||||||
serializeFormDataPair(data, key, value);
|
serializeFormDataPair(data, key, value)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
return data;
|
return data
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
|
||||||
export const jsonBodySerializer = {
|
export const jsonBodySerializer = {
|
||||||
bodySerializer: <T>(body: T): string =>
|
bodySerializer: <T>(body: T): string =>
|
||||||
JSON.stringify(body, (_key, value) =>
|
JSON.stringify(body, (_key, value) => (typeof value === "bigint" ? value.toString() : value)),
|
||||||
typeof value === 'bigint' ? value.toString() : value,
|
}
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const urlSearchParamsBodySerializer = {
|
export const urlSearchParamsBodySerializer = {
|
||||||
bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>(
|
bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>(body: T): string => {
|
||||||
body: T,
|
const data = new URLSearchParams()
|
||||||
): string => {
|
|
||||||
const data = new URLSearchParams();
|
|
||||||
|
|
||||||
Object.entries(body).forEach(([key, value]) => {
|
Object.entries(body).forEach(([key, value]) => {
|
||||||
if (value === undefined || value === null) {
|
if (value === undefined || value === null) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
value.forEach((v) => serializeUrlSearchParamsPair(data, key, v));
|
value.forEach((v) => serializeUrlSearchParamsPair(data, key, v))
|
||||||
} else {
|
} else {
|
||||||
serializeUrlSearchParamsPair(data, key, value);
|
serializeUrlSearchParamsPair(data, key, value)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
return data.toString();
|
return data.toString()
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -1,142 +1,133 @@
|
|||||||
type Slot = 'body' | 'headers' | 'path' | 'query';
|
type Slot = "body" | "headers" | "path" | "query"
|
||||||
|
|
||||||
export type Field =
|
export type Field =
|
||||||
| {
|
| {
|
||||||
in: Exclude<Slot, 'body'>;
|
in: Exclude<Slot, "body">
|
||||||
/**
|
/**
|
||||||
* Field name. This is the name we want the user to see and use.
|
* Field name. This is the name we want the user to see and use.
|
||||||
*/
|
*/
|
||||||
key: string;
|
key: string
|
||||||
/**
|
/**
|
||||||
* Field mapped name. This is the name we want to use in the request.
|
* Field mapped name. This is the name we want to use in the request.
|
||||||
* If omitted, we use the same value as `key`.
|
* If omitted, we use the same value as `key`.
|
||||||
*/
|
*/
|
||||||
map?: string;
|
map?: string
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
in: Extract<Slot, 'body'>;
|
in: Extract<Slot, "body">
|
||||||
/**
|
/**
|
||||||
* Key isn't required for bodies.
|
* Key isn't required for bodies.
|
||||||
*/
|
*/
|
||||||
key?: string;
|
key?: string
|
||||||
map?: string;
|
map?: string
|
||||||
};
|
}
|
||||||
|
|
||||||
export interface Fields {
|
export interface Fields {
|
||||||
allowExtra?: Partial<Record<Slot, boolean>>;
|
allowExtra?: Partial<Record<Slot, boolean>>
|
||||||
args?: ReadonlyArray<Field>;
|
args?: ReadonlyArray<Field>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FieldsConfig = ReadonlyArray<Field | Fields>;
|
export type FieldsConfig = ReadonlyArray<Field | Fields>
|
||||||
|
|
||||||
const extraPrefixesMap: Record<string, Slot> = {
|
const extraPrefixesMap: Record<string, Slot> = {
|
||||||
$body_: 'body',
|
$body_: "body",
|
||||||
$headers_: 'headers',
|
$headers_: "headers",
|
||||||
$path_: 'path',
|
$path_: "path",
|
||||||
$query_: 'query',
|
$query_: "query",
|
||||||
};
|
}
|
||||||
const extraPrefixes = Object.entries(extraPrefixesMap);
|
const extraPrefixes = Object.entries(extraPrefixesMap)
|
||||||
|
|
||||||
type KeyMap = Map<
|
type KeyMap = Map<
|
||||||
string,
|
string,
|
||||||
{
|
{
|
||||||
in: Slot;
|
in: Slot
|
||||||
map?: string;
|
map?: string
|
||||||
}
|
}
|
||||||
>;
|
>
|
||||||
|
|
||||||
const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => {
|
const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => {
|
||||||
if (!map) {
|
if (!map) {
|
||||||
map = new Map();
|
map = new Map()
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const config of fields) {
|
for (const config of fields) {
|
||||||
if ('in' in config) {
|
if ("in" in config) {
|
||||||
if (config.key) {
|
if (config.key) {
|
||||||
map.set(config.key, {
|
map.set(config.key, {
|
||||||
in: config.in,
|
in: config.in,
|
||||||
map: config.map,
|
map: config.map,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
} else if (config.args) {
|
} else if (config.args) {
|
||||||
buildKeyMap(config.args, map);
|
buildKeyMap(config.args, map)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return map;
|
return map
|
||||||
};
|
}
|
||||||
|
|
||||||
interface Params {
|
interface Params {
|
||||||
body: unknown;
|
body: unknown
|
||||||
headers: Record<string, unknown>;
|
headers: Record<string, unknown>
|
||||||
path: Record<string, unknown>;
|
path: Record<string, unknown>
|
||||||
query: Record<string, unknown>;
|
query: Record<string, unknown>
|
||||||
}
|
}
|
||||||
|
|
||||||
const stripEmptySlots = (params: Params) => {
|
const stripEmptySlots = (params: Params) => {
|
||||||
for (const [slot, value] of Object.entries(params)) {
|
for (const [slot, value] of Object.entries(params)) {
|
||||||
if (value && typeof value === 'object' && !Object.keys(value).length) {
|
if (value && typeof value === "object" && !Object.keys(value).length) {
|
||||||
delete params[slot as Slot];
|
delete params[slot as Slot]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export const buildClientParams = (
|
export const buildClientParams = (args: ReadonlyArray<unknown>, fields: FieldsConfig) => {
|
||||||
args: ReadonlyArray<unknown>,
|
|
||||||
fields: FieldsConfig,
|
|
||||||
) => {
|
|
||||||
const params: Params = {
|
const params: Params = {
|
||||||
body: {},
|
body: {},
|
||||||
headers: {},
|
headers: {},
|
||||||
path: {},
|
path: {},
|
||||||
query: {},
|
query: {},
|
||||||
};
|
}
|
||||||
|
|
||||||
const map = buildKeyMap(fields);
|
const map = buildKeyMap(fields)
|
||||||
|
|
||||||
let config: FieldsConfig[number] | undefined;
|
let config: FieldsConfig[number] | undefined
|
||||||
|
|
||||||
for (const [index, arg] of args.entries()) {
|
for (const [index, arg] of args.entries()) {
|
||||||
if (fields[index]) {
|
if (fields[index]) {
|
||||||
config = fields[index];
|
config = fields[index]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!config) {
|
if (!config) {
|
||||||
continue;
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('in' in config) {
|
if ("in" in config) {
|
||||||
if (config.key) {
|
if (config.key) {
|
||||||
const field = map.get(config.key)!;
|
const field = map.get(config.key)!
|
||||||
const name = field.map || config.key;
|
const name = field.map || config.key
|
||||||
(params[field.in] as Record<string, unknown>)[name] = arg;
|
;(params[field.in] as Record<string, unknown>)[name] = arg
|
||||||
} else {
|
} else {
|
||||||
params.body = arg;
|
params.body = arg
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (const [key, value] of Object.entries(arg ?? {})) {
|
for (const [key, value] of Object.entries(arg ?? {})) {
|
||||||
const field = map.get(key);
|
const field = map.get(key)
|
||||||
|
|
||||||
if (field) {
|
if (field) {
|
||||||
const name = field.map || key;
|
const name = field.map || key
|
||||||
(params[field.in] as Record<string, unknown>)[name] = value;
|
;(params[field.in] as Record<string, unknown>)[name] = value
|
||||||
} else {
|
} else {
|
||||||
const extra = extraPrefixes.find(([prefix]) =>
|
const extra = extraPrefixes.find(([prefix]) => key.startsWith(prefix))
|
||||||
key.startsWith(prefix),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (extra) {
|
if (extra) {
|
||||||
const [prefix, slot] = extra;
|
const [prefix, slot] = extra
|
||||||
(params[slot] as Record<string, unknown>)[
|
;(params[slot] as Record<string, unknown>)[key.slice(prefix.length)] = value
|
||||||
key.slice(prefix.length)
|
|
||||||
] = value;
|
|
||||||
} else {
|
} else {
|
||||||
for (const [slot, allowed] of Object.entries(
|
for (const [slot, allowed] of Object.entries(config.allowExtra ?? {})) {
|
||||||
config.allowExtra ?? {},
|
|
||||||
)) {
|
|
||||||
if (allowed) {
|
if (allowed) {
|
||||||
(params[slot as Slot] as Record<string, unknown>)[key] = value;
|
;(params[slot as Slot] as Record<string, unknown>)[key] = value
|
||||||
break;
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,7 +136,7 @@ export const buildClientParams = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stripEmptySlots(params);
|
stripEmptySlots(params)
|
||||||
|
|
||||||
return params;
|
return params
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -1,68 +1,66 @@
|
|||||||
interface SerializeOptions<T>
|
interface SerializeOptions<T> extends SerializePrimitiveOptions, SerializerOptions<T> {}
|
||||||
extends SerializePrimitiveOptions,
|
|
||||||
SerializerOptions<T> {}
|
|
||||||
|
|
||||||
interface SerializePrimitiveOptions {
|
interface SerializePrimitiveOptions {
|
||||||
allowReserved?: boolean;
|
allowReserved?: boolean
|
||||||
name: string;
|
name: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SerializerOptions<T> {
|
export interface SerializerOptions<T> {
|
||||||
/**
|
/**
|
||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
explode: boolean;
|
explode: boolean
|
||||||
style: T;
|
style: T
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited';
|
export type ArrayStyle = "form" | "spaceDelimited" | "pipeDelimited"
|
||||||
export type ArraySeparatorStyle = ArrayStyle | MatrixStyle;
|
export type ArraySeparatorStyle = ArrayStyle | MatrixStyle
|
||||||
type MatrixStyle = 'label' | 'matrix' | 'simple';
|
type MatrixStyle = "label" | "matrix" | "simple"
|
||||||
export type ObjectStyle = 'form' | 'deepObject';
|
export type ObjectStyle = "form" | "deepObject"
|
||||||
type ObjectSeparatorStyle = ObjectStyle | MatrixStyle;
|
type ObjectSeparatorStyle = ObjectStyle | MatrixStyle
|
||||||
|
|
||||||
interface SerializePrimitiveParam extends SerializePrimitiveOptions {
|
interface SerializePrimitiveParam extends SerializePrimitiveOptions {
|
||||||
value: string;
|
value: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const separatorArrayExplode = (style: ArraySeparatorStyle) => {
|
export const separatorArrayExplode = (style: ArraySeparatorStyle) => {
|
||||||
switch (style) {
|
switch (style) {
|
||||||
case 'label':
|
case "label":
|
||||||
return '.';
|
return "."
|
||||||
case 'matrix':
|
case "matrix":
|
||||||
return ';';
|
return ";"
|
||||||
case 'simple':
|
case "simple":
|
||||||
return ',';
|
return ","
|
||||||
default:
|
default:
|
||||||
return '&';
|
return "&"
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => {
|
export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => {
|
||||||
switch (style) {
|
switch (style) {
|
||||||
case 'form':
|
case "form":
|
||||||
return ',';
|
return ","
|
||||||
case 'pipeDelimited':
|
case "pipeDelimited":
|
||||||
return '|';
|
return "|"
|
||||||
case 'spaceDelimited':
|
case "spaceDelimited":
|
||||||
return '%20';
|
return "%20"
|
||||||
default:
|
default:
|
||||||
return ',';
|
return ","
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export const separatorObjectExplode = (style: ObjectSeparatorStyle) => {
|
export const separatorObjectExplode = (style: ObjectSeparatorStyle) => {
|
||||||
switch (style) {
|
switch (style) {
|
||||||
case 'label':
|
case "label":
|
||||||
return '.';
|
return "."
|
||||||
case 'matrix':
|
case "matrix":
|
||||||
return ';';
|
return ";"
|
||||||
case 'simple':
|
case "simple":
|
||||||
return ',';
|
return ","
|
||||||
default:
|
default:
|
||||||
return '&';
|
return "&"
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export const serializeArrayParam = ({
|
export const serializeArrayParam = ({
|
||||||
allowReserved,
|
allowReserved,
|
||||||
@@ -71,60 +69,54 @@ export const serializeArrayParam = ({
|
|||||||
style,
|
style,
|
||||||
value,
|
value,
|
||||||
}: SerializeOptions<ArraySeparatorStyle> & {
|
}: SerializeOptions<ArraySeparatorStyle> & {
|
||||||
value: unknown[];
|
value: unknown[]
|
||||||
}) => {
|
}) => {
|
||||||
if (!explode) {
|
if (!explode) {
|
||||||
const joinedValues = (
|
const joinedValues = (allowReserved ? value : value.map((v) => encodeURIComponent(v as string))).join(
|
||||||
allowReserved ? value : value.map((v) => encodeURIComponent(v as string))
|
separatorArrayNoExplode(style),
|
||||||
).join(separatorArrayNoExplode(style));
|
)
|
||||||
switch (style) {
|
switch (style) {
|
||||||
case 'label':
|
case "label":
|
||||||
return `.${joinedValues}`;
|
return `.${joinedValues}`
|
||||||
case 'matrix':
|
case "matrix":
|
||||||
return `;${name}=${joinedValues}`;
|
return `;${name}=${joinedValues}`
|
||||||
case 'simple':
|
case "simple":
|
||||||
return joinedValues;
|
return joinedValues
|
||||||
default:
|
default:
|
||||||
return `${name}=${joinedValues}`;
|
return `${name}=${joinedValues}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const separator = separatorArrayExplode(style);
|
const separator = separatorArrayExplode(style)
|
||||||
const joinedValues = value
|
const joinedValues = value
|
||||||
.map((v) => {
|
.map((v) => {
|
||||||
if (style === 'label' || style === 'simple') {
|
if (style === "label" || style === "simple") {
|
||||||
return allowReserved ? v : encodeURIComponent(v as string);
|
return allowReserved ? v : encodeURIComponent(v as string)
|
||||||
}
|
}
|
||||||
|
|
||||||
return serializePrimitiveParam({
|
return serializePrimitiveParam({
|
||||||
allowReserved,
|
allowReserved,
|
||||||
name,
|
name,
|
||||||
value: v as string,
|
value: v as string,
|
||||||
});
|
})
|
||||||
})
|
})
|
||||||
.join(separator);
|
.join(separator)
|
||||||
return style === 'label' || style === 'matrix'
|
return style === "label" || style === "matrix" ? separator + joinedValues : joinedValues
|
||||||
? separator + joinedValues
|
}
|
||||||
: joinedValues;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const serializePrimitiveParam = ({
|
export const serializePrimitiveParam = ({ allowReserved, name, value }: SerializePrimitiveParam) => {
|
||||||
allowReserved,
|
|
||||||
name,
|
|
||||||
value,
|
|
||||||
}: SerializePrimitiveParam) => {
|
|
||||||
if (value === undefined || value === null) {
|
if (value === undefined || value === null) {
|
||||||
return '';
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof value === 'object') {
|
if (typeof value === "object") {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.',
|
"Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.",
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${name}=${allowReserved ? value : encodeURIComponent(value)}`;
|
return `${name}=${allowReserved ? value : encodeURIComponent(value)}`
|
||||||
};
|
}
|
||||||
|
|
||||||
export const serializeObjectParam = ({
|
export const serializeObjectParam = ({
|
||||||
allowReserved,
|
allowReserved,
|
||||||
@@ -134,46 +126,40 @@ export const serializeObjectParam = ({
|
|||||||
value,
|
value,
|
||||||
valueOnly,
|
valueOnly,
|
||||||
}: SerializeOptions<ObjectSeparatorStyle> & {
|
}: SerializeOptions<ObjectSeparatorStyle> & {
|
||||||
value: Record<string, unknown> | Date;
|
value: Record<string, unknown> | Date
|
||||||
valueOnly?: boolean;
|
valueOnly?: boolean
|
||||||
}) => {
|
}) => {
|
||||||
if (value instanceof Date) {
|
if (value instanceof Date) {
|
||||||
return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`;
|
return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`
|
||||||
}
|
}
|
||||||
|
|
||||||
if (style !== 'deepObject' && !explode) {
|
if (style !== "deepObject" && !explode) {
|
||||||
let values: string[] = [];
|
let values: string[] = []
|
||||||
Object.entries(value).forEach(([key, v]) => {
|
Object.entries(value).forEach(([key, v]) => {
|
||||||
values = [
|
values = [...values, key, allowReserved ? (v as string) : encodeURIComponent(v as string)]
|
||||||
...values,
|
})
|
||||||
key,
|
const joinedValues = values.join(",")
|
||||||
allowReserved ? (v as string) : encodeURIComponent(v as string),
|
|
||||||
];
|
|
||||||
});
|
|
||||||
const joinedValues = values.join(',');
|
|
||||||
switch (style) {
|
switch (style) {
|
||||||
case 'form':
|
case "form":
|
||||||
return `${name}=${joinedValues}`;
|
return `${name}=${joinedValues}`
|
||||||
case 'label':
|
case "label":
|
||||||
return `.${joinedValues}`;
|
return `.${joinedValues}`
|
||||||
case 'matrix':
|
case "matrix":
|
||||||
return `;${name}=${joinedValues}`;
|
return `;${name}=${joinedValues}`
|
||||||
default:
|
default:
|
||||||
return joinedValues;
|
return joinedValues
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const separator = separatorObjectExplode(style);
|
const separator = separatorObjectExplode(style)
|
||||||
const joinedValues = Object.entries(value)
|
const joinedValues = Object.entries(value)
|
||||||
.map(([key, v]) =>
|
.map(([key, v]) =>
|
||||||
serializePrimitiveParam({
|
serializePrimitiveParam({
|
||||||
allowReserved,
|
allowReserved,
|
||||||
name: style === 'deepObject' ? `${name}[${key}]` : key,
|
name: style === "deepObject" ? `${name}[${key}]` : key,
|
||||||
value: v as string,
|
value: v as string,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.join(separator);
|
.join(separator)
|
||||||
return style === 'label' || style === 'matrix'
|
return style === "label" || style === "matrix" ? separator + joinedValues : joinedValues
|
||||||
? separator + joinedValues
|
}
|
||||||
: joinedValues;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,32 +1,23 @@
|
|||||||
import type { Auth, AuthToken } from './auth';
|
import type { Auth, AuthToken } from "./auth"
|
||||||
import type {
|
import type { BodySerializer, QuerySerializer, QuerySerializerOptions } from "./bodySerializer"
|
||||||
BodySerializer,
|
|
||||||
QuerySerializer,
|
|
||||||
QuerySerializerOptions,
|
|
||||||
} from './bodySerializer';
|
|
||||||
|
|
||||||
export interface Client<
|
export interface Client<RequestFn = never, Config = unknown, MethodFn = never, BuildUrlFn = never> {
|
||||||
RequestFn = never,
|
|
||||||
Config = unknown,
|
|
||||||
MethodFn = never,
|
|
||||||
BuildUrlFn = never,
|
|
||||||
> {
|
|
||||||
/**
|
/**
|
||||||
* Returns the final request URL.
|
* Returns the final request URL.
|
||||||
*/
|
*/
|
||||||
buildUrl: BuildUrlFn;
|
buildUrl: BuildUrlFn
|
||||||
connect: MethodFn;
|
connect: MethodFn
|
||||||
delete: MethodFn;
|
delete: MethodFn
|
||||||
get: MethodFn;
|
get: MethodFn
|
||||||
getConfig: () => Config;
|
getConfig: () => Config
|
||||||
head: MethodFn;
|
head: MethodFn
|
||||||
options: MethodFn;
|
options: MethodFn
|
||||||
patch: MethodFn;
|
patch: MethodFn
|
||||||
post: MethodFn;
|
post: MethodFn
|
||||||
put: MethodFn;
|
put: MethodFn
|
||||||
request: RequestFn;
|
request: RequestFn
|
||||||
setConfig: (config: Config) => Config;
|
setConfig: (config: Config) => Config
|
||||||
trace: MethodFn;
|
trace: MethodFn
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Config {
|
export interface Config {
|
||||||
@@ -34,12 +25,12 @@ export interface Config {
|
|||||||
* Auth token or a function returning auth token. The resolved value will be
|
* Auth token or a function returning auth token. The resolved value will be
|
||||||
* added to the request payload as defined by its `security` array.
|
* added to the request payload as defined by its `security` array.
|
||||||
*/
|
*/
|
||||||
auth?: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken;
|
auth?: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken
|
||||||
/**
|
/**
|
||||||
* A function for serializing request body parameter. By default,
|
* A function for serializing request body parameter. By default,
|
||||||
* {@link JSON.stringify()} will be used.
|
* {@link JSON.stringify()} will be used.
|
||||||
*/
|
*/
|
||||||
bodySerializer?: BodySerializer | null;
|
bodySerializer?: BodySerializer | null
|
||||||
/**
|
/**
|
||||||
* An object containing any HTTP headers that you want to pre-populate your
|
* An object containing any HTTP headers that you want to pre-populate your
|
||||||
* `Headers` object with.
|
* `Headers` object with.
|
||||||
@@ -47,32 +38,14 @@ export interface Config {
|
|||||||
* {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more}
|
* {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more}
|
||||||
*/
|
*/
|
||||||
headers?:
|
headers?:
|
||||||
| RequestInit['headers']
|
| RequestInit["headers"]
|
||||||
| Record<
|
| Record<string, string | number | boolean | (string | number | boolean)[] | null | undefined | unknown>
|
||||||
string,
|
|
||||||
| string
|
|
||||||
| number
|
|
||||||
| boolean
|
|
||||||
| (string | number | boolean)[]
|
|
||||||
| null
|
|
||||||
| undefined
|
|
||||||
| unknown
|
|
||||||
>;
|
|
||||||
/**
|
/**
|
||||||
* The request method.
|
* The request method.
|
||||||
*
|
*
|
||||||
* {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more}
|
* {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more}
|
||||||
*/
|
*/
|
||||||
method?:
|
method?: "CONNECT" | "DELETE" | "GET" | "HEAD" | "OPTIONS" | "PATCH" | "POST" | "PUT" | "TRACE"
|
||||||
| 'CONNECT'
|
|
||||||
| 'DELETE'
|
|
||||||
| 'GET'
|
|
||||||
| 'HEAD'
|
|
||||||
| 'OPTIONS'
|
|
||||||
| 'PATCH'
|
|
||||||
| 'POST'
|
|
||||||
| 'PUT'
|
|
||||||
| 'TRACE';
|
|
||||||
/**
|
/**
|
||||||
* A function for serializing request query parameters. By default, arrays
|
* A function for serializing request query parameters. By default, arrays
|
||||||
* will be exploded in form style, objects will be exploded in deepObject
|
* will be exploded in form style, objects will be exploded in deepObject
|
||||||
@@ -83,24 +56,24 @@ export interface Config {
|
|||||||
*
|
*
|
||||||
* {@link https://swagger.io/docs/specification/serialization/#query View examples}
|
* {@link https://swagger.io/docs/specification/serialization/#query View examples}
|
||||||
*/
|
*/
|
||||||
querySerializer?: QuerySerializer | QuerySerializerOptions;
|
querySerializer?: QuerySerializer | QuerySerializerOptions
|
||||||
/**
|
/**
|
||||||
* A function validating request data. This is useful if you want to ensure
|
* A function validating request data. This is useful if you want to ensure
|
||||||
* the request conforms to the desired shape, so it can be safely sent to
|
* the request conforms to the desired shape, so it can be safely sent to
|
||||||
* the server.
|
* the server.
|
||||||
*/
|
*/
|
||||||
requestValidator?: (data: unknown) => Promise<unknown>;
|
requestValidator?: (data: unknown) => Promise<unknown>
|
||||||
/**
|
/**
|
||||||
* A function transforming response data before it's returned. This is useful
|
* A function transforming response data before it's returned. This is useful
|
||||||
* for post-processing data, e.g. converting ISO strings into Date objects.
|
* for post-processing data, e.g. converting ISO strings into Date objects.
|
||||||
*/
|
*/
|
||||||
responseTransformer?: (data: unknown) => Promise<unknown>;
|
responseTransformer?: (data: unknown) => Promise<unknown>
|
||||||
/**
|
/**
|
||||||
* A function validating response data. This is useful if you want to ensure
|
* A function validating response data. This is useful if you want to ensure
|
||||||
* the response conforms to the desired shape, so it can be safely passed to
|
* the response conforms to the desired shape, so it can be safely passed to
|
||||||
* the transformers and returned to the user.
|
* the transformers and returned to the user.
|
||||||
*/
|
*/
|
||||||
responseValidator?: (data: unknown) => Promise<unknown>;
|
responseValidator?: (data: unknown) => Promise<unknown>
|
||||||
}
|
}
|
||||||
|
|
||||||
type IsExactlyNeverOrNeverUndefined<T> = [T] extends [never]
|
type IsExactlyNeverOrNeverUndefined<T> = [T] extends [never]
|
||||||
@@ -109,10 +82,8 @@ type IsExactlyNeverOrNeverUndefined<T> = [T] extends [never]
|
|||||||
? [undefined] extends [T]
|
? [undefined] extends [T]
|
||||||
? false
|
? false
|
||||||
: true
|
: true
|
||||||
: false;
|
: false
|
||||||
|
|
||||||
export type OmitNever<T extends Record<string, unknown>> = {
|
export type OmitNever<T extends Record<string, unknown>> = {
|
||||||
[K in keyof T as IsExactlyNeverOrNeverUndefined<T[K]> extends true
|
[K in keyof T as IsExactlyNeverOrNeverUndefined<T[K]> extends true ? never : K]: T[K]
|
||||||
? never
|
}
|
||||||
: K]: T[K];
|
|
||||||
};
|
|
||||||
|
|||||||
+445
-368
@@ -1,426 +1,503 @@
|
|||||||
// This file is auto-generated by @hey-api/openapi-ts
|
// This file is auto-generated by @hey-api/openapi-ts
|
||||||
|
|
||||||
import type { Options as ClientOptions, TDataShape, Client } from './client';
|
import type { Options as ClientOptions, TDataShape, Client } from "./client"
|
||||||
import type { EventSubscribeData, EventSubscribeResponses, AppGetData, AppGetResponses, AppInitData, AppInitResponses, ConfigGetData, ConfigGetResponses, SessionListData, SessionListResponses, SessionCreateData, SessionCreateResponses, SessionCreateErrors, SessionDeleteData, SessionDeleteResponses, SessionInitData, SessionInitResponses, SessionAbortData, SessionAbortResponses, SessionUnshareData, SessionUnshareResponses, SessionShareData, SessionShareResponses, SessionSummarizeData, SessionSummarizeResponses, SessionMessagesData, SessionMessagesResponses, SessionChatData, SessionChatResponses, SessionMessageData, SessionMessageResponses, SessionRevertData, SessionRevertResponses, SessionUnrevertData, SessionUnrevertResponses, PostSessionByIdPermissionsByPermissionIdData, PostSessionByIdPermissionsByPermissionIdResponses, ConfigProvidersData, ConfigProvidersResponses, FindTextData, FindTextResponses, FindFilesData, FindFilesResponses, FindSymbolsData, FindSymbolsResponses, FileReadData, FileReadResponses, FileStatusData, FileStatusResponses, AppLogData, AppLogResponses, AppModesData, AppModesResponses, TuiAppendPromptData, TuiAppendPromptResponses, TuiOpenHelpData, TuiOpenHelpResponses, TuiOpenSessionsData, TuiOpenSessionsResponses, TuiOpenThemesData, TuiOpenThemesResponses, TuiOpenModelsData, TuiOpenModelsResponses, TuiSubmitPromptData, TuiSubmitPromptResponses, TuiClearPromptData, TuiClearPromptResponses, TuiExecuteCommandData, TuiExecuteCommandResponses } from './types.gen';
|
import type {
|
||||||
import { client as _heyApiClient } from './client.gen';
|
EventSubscribeData,
|
||||||
|
EventSubscribeResponses,
|
||||||
|
AppGetData,
|
||||||
|
AppGetResponses,
|
||||||
|
AppInitData,
|
||||||
|
AppInitResponses,
|
||||||
|
ConfigGetData,
|
||||||
|
ConfigGetResponses,
|
||||||
|
SessionListData,
|
||||||
|
SessionListResponses,
|
||||||
|
SessionCreateData,
|
||||||
|
SessionCreateResponses,
|
||||||
|
SessionCreateErrors,
|
||||||
|
SessionDeleteData,
|
||||||
|
SessionDeleteResponses,
|
||||||
|
SessionInitData,
|
||||||
|
SessionInitResponses,
|
||||||
|
SessionAbortData,
|
||||||
|
SessionAbortResponses,
|
||||||
|
SessionUnshareData,
|
||||||
|
SessionUnshareResponses,
|
||||||
|
SessionShareData,
|
||||||
|
SessionShareResponses,
|
||||||
|
SessionSummarizeData,
|
||||||
|
SessionSummarizeResponses,
|
||||||
|
SessionMessagesData,
|
||||||
|
SessionMessagesResponses,
|
||||||
|
SessionChatData,
|
||||||
|
SessionChatResponses,
|
||||||
|
SessionMessageData,
|
||||||
|
SessionMessageResponses,
|
||||||
|
SessionRevertData,
|
||||||
|
SessionRevertResponses,
|
||||||
|
SessionUnrevertData,
|
||||||
|
SessionUnrevertResponses,
|
||||||
|
PostSessionByIdPermissionsByPermissionIdData,
|
||||||
|
PostSessionByIdPermissionsByPermissionIdResponses,
|
||||||
|
ConfigProvidersData,
|
||||||
|
ConfigProvidersResponses,
|
||||||
|
FindTextData,
|
||||||
|
FindTextResponses,
|
||||||
|
FindFilesData,
|
||||||
|
FindFilesResponses,
|
||||||
|
FindSymbolsData,
|
||||||
|
FindSymbolsResponses,
|
||||||
|
FileReadData,
|
||||||
|
FileReadResponses,
|
||||||
|
FileStatusData,
|
||||||
|
FileStatusResponses,
|
||||||
|
AppLogData,
|
||||||
|
AppLogResponses,
|
||||||
|
AppModesData,
|
||||||
|
AppModesResponses,
|
||||||
|
TuiAppendPromptData,
|
||||||
|
TuiAppendPromptResponses,
|
||||||
|
TuiOpenHelpData,
|
||||||
|
TuiOpenHelpResponses,
|
||||||
|
TuiOpenSessionsData,
|
||||||
|
TuiOpenSessionsResponses,
|
||||||
|
TuiOpenThemesData,
|
||||||
|
TuiOpenThemesResponses,
|
||||||
|
TuiOpenModelsData,
|
||||||
|
TuiOpenModelsResponses,
|
||||||
|
TuiSubmitPromptData,
|
||||||
|
TuiSubmitPromptResponses,
|
||||||
|
TuiClearPromptData,
|
||||||
|
TuiClearPromptResponses,
|
||||||
|
TuiExecuteCommandData,
|
||||||
|
TuiExecuteCommandResponses,
|
||||||
|
} from "./types.gen"
|
||||||
|
import { client as _heyApiClient } from "./client.gen"
|
||||||
|
|
||||||
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<TData, ThrowOnError> & {
|
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<
|
||||||
/**
|
TData,
|
||||||
* You can provide a client instance returned by `createClient()` instead of
|
ThrowOnError
|
||||||
* individual options. This might be also useful if you want to implement a
|
> & {
|
||||||
* custom client.
|
/**
|
||||||
*/
|
* You can provide a client instance returned by `createClient()` instead of
|
||||||
client?: Client;
|
* individual options. This might be also useful if you want to implement a
|
||||||
/**
|
* custom client.
|
||||||
* You can pass arbitrary values through the `meta` object. This can be
|
*/
|
||||||
* used to access values that aren't defined as part of the SDK function.
|
client?: Client
|
||||||
*/
|
/**
|
||||||
meta?: Record<string, unknown>;
|
* You can pass arbitrary values through the `meta` object. This can be
|
||||||
};
|
* used to access values that aren't defined as part of the SDK function.
|
||||||
|
*/
|
||||||
|
meta?: Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
class _HeyApiClient {
|
class _HeyApiClient {
|
||||||
protected _client: Client = _heyApiClient;
|
protected _client: Client = _heyApiClient
|
||||||
|
|
||||||
constructor(args?: {
|
constructor(args?: { client?: Client }) {
|
||||||
client?: Client;
|
if (args?.client) {
|
||||||
}) {
|
this._client = args.client
|
||||||
if (args?.client) {
|
|
||||||
this._client = args.client;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Event extends _HeyApiClient {
|
class Event extends _HeyApiClient {
|
||||||
/**
|
/**
|
||||||
* Get events
|
* Get events
|
||||||
*/
|
*/
|
||||||
public subscribe<ThrowOnError extends boolean = false>(options?: Options<EventSubscribeData, ThrowOnError>) {
|
public subscribe<ThrowOnError extends boolean = false>(options?: Options<EventSubscribeData, ThrowOnError>) {
|
||||||
return (options?.client ?? this._client).get<EventSubscribeResponses, unknown, ThrowOnError>({
|
return (options?.client ?? this._client).get<EventSubscribeResponses, unknown, ThrowOnError>({
|
||||||
url: '/event',
|
url: "/event",
|
||||||
...options
|
...options,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class App extends _HeyApiClient {
|
class App extends _HeyApiClient {
|
||||||
/**
|
/**
|
||||||
* Get app info
|
* Get app info
|
||||||
*/
|
*/
|
||||||
public get<ThrowOnError extends boolean = false>(options?: Options<AppGetData, ThrowOnError>) {
|
public get<ThrowOnError extends boolean = false>(options?: Options<AppGetData, ThrowOnError>) {
|
||||||
return (options?.client ?? this._client).get<AppGetResponses, unknown, ThrowOnError>({
|
return (options?.client ?? this._client).get<AppGetResponses, unknown, ThrowOnError>({
|
||||||
url: '/app',
|
url: "/app",
|
||||||
...options
|
...options,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the app
|
* Initialize the app
|
||||||
*/
|
*/
|
||||||
public init<ThrowOnError extends boolean = false>(options?: Options<AppInitData, ThrowOnError>) {
|
public init<ThrowOnError extends boolean = false>(options?: Options<AppInitData, ThrowOnError>) {
|
||||||
return (options?.client ?? this._client).post<AppInitResponses, unknown, ThrowOnError>({
|
return (options?.client ?? this._client).post<AppInitResponses, unknown, ThrowOnError>({
|
||||||
url: '/app/init',
|
url: "/app/init",
|
||||||
...options
|
...options,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write a log entry to the server logs
|
* Write a log entry to the server logs
|
||||||
*/
|
*/
|
||||||
public log<ThrowOnError extends boolean = false>(options?: Options<AppLogData, ThrowOnError>) {
|
public log<ThrowOnError extends boolean = false>(options?: Options<AppLogData, ThrowOnError>) {
|
||||||
return (options?.client ?? this._client).post<AppLogResponses, unknown, ThrowOnError>({
|
return (options?.client ?? this._client).post<AppLogResponses, unknown, ThrowOnError>({
|
||||||
url: '/log',
|
url: "/log",
|
||||||
...options,
|
...options,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
"Content-Type": "application/json",
|
||||||
...options?.headers
|
...options?.headers,
|
||||||
}
|
},
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List all modes
|
* List all modes
|
||||||
*/
|
*/
|
||||||
public modes<ThrowOnError extends boolean = false>(options?: Options<AppModesData, ThrowOnError>) {
|
public modes<ThrowOnError extends boolean = false>(options?: Options<AppModesData, ThrowOnError>) {
|
||||||
return (options?.client ?? this._client).get<AppModesResponses, unknown, ThrowOnError>({
|
return (options?.client ?? this._client).get<AppModesResponses, unknown, ThrowOnError>({
|
||||||
url: '/mode',
|
url: "/mode",
|
||||||
...options
|
...options,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Config extends _HeyApiClient {
|
class Config extends _HeyApiClient {
|
||||||
/**
|
/**
|
||||||
* Get config info
|
* Get config info
|
||||||
*/
|
*/
|
||||||
public get<ThrowOnError extends boolean = false>(options?: Options<ConfigGetData, ThrowOnError>) {
|
public get<ThrowOnError extends boolean = false>(options?: Options<ConfigGetData, ThrowOnError>) {
|
||||||
return (options?.client ?? this._client).get<ConfigGetResponses, unknown, ThrowOnError>({
|
return (options?.client ?? this._client).get<ConfigGetResponses, unknown, ThrowOnError>({
|
||||||
url: '/config',
|
url: "/config",
|
||||||
...options
|
...options,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List all providers
|
* List all providers
|
||||||
*/
|
*/
|
||||||
public providers<ThrowOnError extends boolean = false>(options?: Options<ConfigProvidersData, ThrowOnError>) {
|
public providers<ThrowOnError extends boolean = false>(options?: Options<ConfigProvidersData, ThrowOnError>) {
|
||||||
return (options?.client ?? this._client).get<ConfigProvidersResponses, unknown, ThrowOnError>({
|
return (options?.client ?? this._client).get<ConfigProvidersResponses, unknown, ThrowOnError>({
|
||||||
url: '/config/providers',
|
url: "/config/providers",
|
||||||
...options
|
...options,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Session extends _HeyApiClient {
|
class Session extends _HeyApiClient {
|
||||||
/**
|
/**
|
||||||
* List all sessions
|
* List all sessions
|
||||||
*/
|
*/
|
||||||
public list<ThrowOnError extends boolean = false>(options?: Options<SessionListData, ThrowOnError>) {
|
public list<ThrowOnError extends boolean = false>(options?: Options<SessionListData, ThrowOnError>) {
|
||||||
return (options?.client ?? this._client).get<SessionListResponses, unknown, ThrowOnError>({
|
return (options?.client ?? this._client).get<SessionListResponses, unknown, ThrowOnError>({
|
||||||
url: '/session',
|
url: "/session",
|
||||||
...options
|
...options,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new session
|
* Create a new session
|
||||||
*/
|
*/
|
||||||
public create<ThrowOnError extends boolean = false>(options?: Options<SessionCreateData, ThrowOnError>) {
|
public create<ThrowOnError extends boolean = false>(options?: Options<SessionCreateData, ThrowOnError>) {
|
||||||
return (options?.client ?? this._client).post<SessionCreateResponses, SessionCreateErrors, ThrowOnError>({
|
return (options?.client ?? this._client).post<SessionCreateResponses, SessionCreateErrors, ThrowOnError>({
|
||||||
url: '/session',
|
url: "/session",
|
||||||
...options
|
...options,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a session and all its data
|
* Delete a session and all its data
|
||||||
*/
|
*/
|
||||||
public delete<ThrowOnError extends boolean = false>(options: Options<SessionDeleteData, ThrowOnError>) {
|
public delete<ThrowOnError extends boolean = false>(options: Options<SessionDeleteData, ThrowOnError>) {
|
||||||
return (options.client ?? this._client).delete<SessionDeleteResponses, unknown, ThrowOnError>({
|
return (options.client ?? this._client).delete<SessionDeleteResponses, unknown, ThrowOnError>({
|
||||||
url: '/session/{id}',
|
url: "/session/{id}",
|
||||||
...options
|
...options,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Analyze the app and create an AGENTS.md file
|
* Analyze the app and create an AGENTS.md file
|
||||||
*/
|
*/
|
||||||
public init<ThrowOnError extends boolean = false>(options: Options<SessionInitData, ThrowOnError>) {
|
public init<ThrowOnError extends boolean = false>(options: Options<SessionInitData, ThrowOnError>) {
|
||||||
return (options.client ?? this._client).post<SessionInitResponses, unknown, ThrowOnError>({
|
return (options.client ?? this._client).post<SessionInitResponses, unknown, ThrowOnError>({
|
||||||
url: '/session/{id}/init',
|
url: "/session/{id}/init",
|
||||||
...options,
|
...options,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
"Content-Type": "application/json",
|
||||||
...options.headers
|
...options.headers,
|
||||||
}
|
},
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abort a session
|
* Abort a session
|
||||||
*/
|
*/
|
||||||
public abort<ThrowOnError extends boolean = false>(options: Options<SessionAbortData, ThrowOnError>) {
|
public abort<ThrowOnError extends boolean = false>(options: Options<SessionAbortData, ThrowOnError>) {
|
||||||
return (options.client ?? this._client).post<SessionAbortResponses, unknown, ThrowOnError>({
|
return (options.client ?? this._client).post<SessionAbortResponses, unknown, ThrowOnError>({
|
||||||
url: '/session/{id}/abort',
|
url: "/session/{id}/abort",
|
||||||
...options
|
...options,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unshare the session
|
* Unshare the session
|
||||||
*/
|
*/
|
||||||
public unshare<ThrowOnError extends boolean = false>(options: Options<SessionUnshareData, ThrowOnError>) {
|
public unshare<ThrowOnError extends boolean = false>(options: Options<SessionUnshareData, ThrowOnError>) {
|
||||||
return (options.client ?? this._client).delete<SessionUnshareResponses, unknown, ThrowOnError>({
|
return (options.client ?? this._client).delete<SessionUnshareResponses, unknown, ThrowOnError>({
|
||||||
url: '/session/{id}/share',
|
url: "/session/{id}/share",
|
||||||
...options
|
...options,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Share a session
|
* Share a session
|
||||||
*/
|
*/
|
||||||
public share<ThrowOnError extends boolean = false>(options: Options<SessionShareData, ThrowOnError>) {
|
public share<ThrowOnError extends boolean = false>(options: Options<SessionShareData, ThrowOnError>) {
|
||||||
return (options.client ?? this._client).post<SessionShareResponses, unknown, ThrowOnError>({
|
return (options.client ?? this._client).post<SessionShareResponses, unknown, ThrowOnError>({
|
||||||
url: '/session/{id}/share',
|
url: "/session/{id}/share",
|
||||||
...options
|
...options,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Summarize the session
|
* Summarize the session
|
||||||
*/
|
*/
|
||||||
public summarize<ThrowOnError extends boolean = false>(options: Options<SessionSummarizeData, ThrowOnError>) {
|
public summarize<ThrowOnError extends boolean = false>(options: Options<SessionSummarizeData, ThrowOnError>) {
|
||||||
return (options.client ?? this._client).post<SessionSummarizeResponses, unknown, ThrowOnError>({
|
return (options.client ?? this._client).post<SessionSummarizeResponses, unknown, ThrowOnError>({
|
||||||
url: '/session/{id}/summarize',
|
url: "/session/{id}/summarize",
|
||||||
...options,
|
...options,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
"Content-Type": "application/json",
|
||||||
...options.headers
|
...options.headers,
|
||||||
}
|
},
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List messages for a session
|
* List messages for a session
|
||||||
*/
|
*/
|
||||||
public messages<ThrowOnError extends boolean = false>(options: Options<SessionMessagesData, ThrowOnError>) {
|
public messages<ThrowOnError extends boolean = false>(options: Options<SessionMessagesData, ThrowOnError>) {
|
||||||
return (options.client ?? this._client).get<SessionMessagesResponses, unknown, ThrowOnError>({
|
return (options.client ?? this._client).get<SessionMessagesResponses, unknown, ThrowOnError>({
|
||||||
url: '/session/{id}/message',
|
url: "/session/{id}/message",
|
||||||
...options
|
...options,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create and send a new message to a session
|
* Create and send a new message to a session
|
||||||
*/
|
*/
|
||||||
public chat<ThrowOnError extends boolean = false>(options: Options<SessionChatData, ThrowOnError>) {
|
public chat<ThrowOnError extends boolean = false>(options: Options<SessionChatData, ThrowOnError>) {
|
||||||
return (options.client ?? this._client).post<SessionChatResponses, unknown, ThrowOnError>({
|
return (options.client ?? this._client).post<SessionChatResponses, unknown, ThrowOnError>({
|
||||||
url: '/session/{id}/message',
|
url: "/session/{id}/message",
|
||||||
...options,
|
...options,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
"Content-Type": "application/json",
|
||||||
...options.headers
|
...options.headers,
|
||||||
}
|
},
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a message from a session
|
* Get a message from a session
|
||||||
*/
|
*/
|
||||||
public message<ThrowOnError extends boolean = false>(options: Options<SessionMessageData, ThrowOnError>) {
|
public message<ThrowOnError extends boolean = false>(options: Options<SessionMessageData, ThrowOnError>) {
|
||||||
return (options.client ?? this._client).get<SessionMessageResponses, unknown, ThrowOnError>({
|
return (options.client ?? this._client).get<SessionMessageResponses, unknown, ThrowOnError>({
|
||||||
url: '/session/{id}/message/{messageID}',
|
url: "/session/{id}/message/{messageID}",
|
||||||
...options
|
...options,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Revert a message
|
* Revert a message
|
||||||
*/
|
*/
|
||||||
public revert<ThrowOnError extends boolean = false>(options: Options<SessionRevertData, ThrowOnError>) {
|
public revert<ThrowOnError extends boolean = false>(options: Options<SessionRevertData, ThrowOnError>) {
|
||||||
return (options.client ?? this._client).post<SessionRevertResponses, unknown, ThrowOnError>({
|
return (options.client ?? this._client).post<SessionRevertResponses, unknown, ThrowOnError>({
|
||||||
url: '/session/{id}/revert',
|
url: "/session/{id}/revert",
|
||||||
...options,
|
...options,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
"Content-Type": "application/json",
|
||||||
...options.headers
|
...options.headers,
|
||||||
}
|
},
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restore all reverted messages
|
* Restore all reverted messages
|
||||||
*/
|
*/
|
||||||
public unrevert<ThrowOnError extends boolean = false>(options: Options<SessionUnrevertData, ThrowOnError>) {
|
public unrevert<ThrowOnError extends boolean = false>(options: Options<SessionUnrevertData, ThrowOnError>) {
|
||||||
return (options.client ?? this._client).post<SessionUnrevertResponses, unknown, ThrowOnError>({
|
return (options.client ?? this._client).post<SessionUnrevertResponses, unknown, ThrowOnError>({
|
||||||
url: '/session/{id}/unrevert',
|
url: "/session/{id}/unrevert",
|
||||||
...options
|
...options,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Find extends _HeyApiClient {
|
class Find extends _HeyApiClient {
|
||||||
/**
|
/**
|
||||||
* Find text in files
|
* Find text in files
|
||||||
*/
|
*/
|
||||||
public text<ThrowOnError extends boolean = false>(options: Options<FindTextData, ThrowOnError>) {
|
public text<ThrowOnError extends boolean = false>(options: Options<FindTextData, ThrowOnError>) {
|
||||||
return (options.client ?? this._client).get<FindTextResponses, unknown, ThrowOnError>({
|
return (options.client ?? this._client).get<FindTextResponses, unknown, ThrowOnError>({
|
||||||
url: '/find',
|
url: "/find",
|
||||||
...options
|
...options,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find files
|
* Find files
|
||||||
*/
|
*/
|
||||||
public files<ThrowOnError extends boolean = false>(options: Options<FindFilesData, ThrowOnError>) {
|
public files<ThrowOnError extends boolean = false>(options: Options<FindFilesData, ThrowOnError>) {
|
||||||
return (options.client ?? this._client).get<FindFilesResponses, unknown, ThrowOnError>({
|
return (options.client ?? this._client).get<FindFilesResponses, unknown, ThrowOnError>({
|
||||||
url: '/find/file',
|
url: "/find/file",
|
||||||
...options
|
...options,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find workspace symbols
|
* Find workspace symbols
|
||||||
*/
|
*/
|
||||||
public symbols<ThrowOnError extends boolean = false>(options: Options<FindSymbolsData, ThrowOnError>) {
|
public symbols<ThrowOnError extends boolean = false>(options: Options<FindSymbolsData, ThrowOnError>) {
|
||||||
return (options.client ?? this._client).get<FindSymbolsResponses, unknown, ThrowOnError>({
|
return (options.client ?? this._client).get<FindSymbolsResponses, unknown, ThrowOnError>({
|
||||||
url: '/find/symbol',
|
url: "/find/symbol",
|
||||||
...options
|
...options,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class File extends _HeyApiClient {
|
class File extends _HeyApiClient {
|
||||||
/**
|
/**
|
||||||
* Read a file
|
* Read a file
|
||||||
*/
|
*/
|
||||||
public read<ThrowOnError extends boolean = false>(options: Options<FileReadData, ThrowOnError>) {
|
public read<ThrowOnError extends boolean = false>(options: Options<FileReadData, ThrowOnError>) {
|
||||||
return (options.client ?? this._client).get<FileReadResponses, unknown, ThrowOnError>({
|
return (options.client ?? this._client).get<FileReadResponses, unknown, ThrowOnError>({
|
||||||
url: '/file',
|
url: "/file",
|
||||||
...options
|
...options,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get file status
|
* Get file status
|
||||||
*/
|
*/
|
||||||
public status<ThrowOnError extends boolean = false>(options?: Options<FileStatusData, ThrowOnError>) {
|
public status<ThrowOnError extends boolean = false>(options?: Options<FileStatusData, ThrowOnError>) {
|
||||||
return (options?.client ?? this._client).get<FileStatusResponses, unknown, ThrowOnError>({
|
return (options?.client ?? this._client).get<FileStatusResponses, unknown, ThrowOnError>({
|
||||||
url: '/file/status',
|
url: "/file/status",
|
||||||
...options
|
...options,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Tui extends _HeyApiClient {
|
class Tui extends _HeyApiClient {
|
||||||
/**
|
/**
|
||||||
* Append prompt to the TUI
|
* Append prompt to the TUI
|
||||||
*/
|
*/
|
||||||
public appendPrompt<ThrowOnError extends boolean = false>(options?: Options<TuiAppendPromptData, ThrowOnError>) {
|
public appendPrompt<ThrowOnError extends boolean = false>(options?: Options<TuiAppendPromptData, ThrowOnError>) {
|
||||||
return (options?.client ?? this._client).post<TuiAppendPromptResponses, unknown, ThrowOnError>({
|
return (options?.client ?? this._client).post<TuiAppendPromptResponses, unknown, ThrowOnError>({
|
||||||
url: '/tui/append-prompt',
|
url: "/tui/append-prompt",
|
||||||
...options,
|
...options,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
"Content-Type": "application/json",
|
||||||
...options?.headers
|
...options?.headers,
|
||||||
}
|
},
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open the help dialog
|
* Open the help dialog
|
||||||
*/
|
*/
|
||||||
public openHelp<ThrowOnError extends boolean = false>(options?: Options<TuiOpenHelpData, ThrowOnError>) {
|
public openHelp<ThrowOnError extends boolean = false>(options?: Options<TuiOpenHelpData, ThrowOnError>) {
|
||||||
return (options?.client ?? this._client).post<TuiOpenHelpResponses, unknown, ThrowOnError>({
|
return (options?.client ?? this._client).post<TuiOpenHelpResponses, unknown, ThrowOnError>({
|
||||||
url: '/tui/open-help',
|
url: "/tui/open-help",
|
||||||
...options
|
...options,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open the session dialog
|
* Open the session dialog
|
||||||
*/
|
*/
|
||||||
public openSessions<ThrowOnError extends boolean = false>(options?: Options<TuiOpenSessionsData, ThrowOnError>) {
|
public openSessions<ThrowOnError extends boolean = false>(options?: Options<TuiOpenSessionsData, ThrowOnError>) {
|
||||||
return (options?.client ?? this._client).post<TuiOpenSessionsResponses, unknown, ThrowOnError>({
|
return (options?.client ?? this._client).post<TuiOpenSessionsResponses, unknown, ThrowOnError>({
|
||||||
url: '/tui/open-sessions',
|
url: "/tui/open-sessions",
|
||||||
...options
|
...options,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open the theme dialog
|
* Open the theme dialog
|
||||||
*/
|
*/
|
||||||
public openThemes<ThrowOnError extends boolean = false>(options?: Options<TuiOpenThemesData, ThrowOnError>) {
|
public openThemes<ThrowOnError extends boolean = false>(options?: Options<TuiOpenThemesData, ThrowOnError>) {
|
||||||
return (options?.client ?? this._client).post<TuiOpenThemesResponses, unknown, ThrowOnError>({
|
return (options?.client ?? this._client).post<TuiOpenThemesResponses, unknown, ThrowOnError>({
|
||||||
url: '/tui/open-themes',
|
url: "/tui/open-themes",
|
||||||
...options
|
...options,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open the model dialog
|
* Open the model dialog
|
||||||
*/
|
*/
|
||||||
public openModels<ThrowOnError extends boolean = false>(options?: Options<TuiOpenModelsData, ThrowOnError>) {
|
public openModels<ThrowOnError extends boolean = false>(options?: Options<TuiOpenModelsData, ThrowOnError>) {
|
||||||
return (options?.client ?? this._client).post<TuiOpenModelsResponses, unknown, ThrowOnError>({
|
return (options?.client ?? this._client).post<TuiOpenModelsResponses, unknown, ThrowOnError>({
|
||||||
url: '/tui/open-models',
|
url: "/tui/open-models",
|
||||||
...options
|
...options,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Submit the prompt
|
* Submit the prompt
|
||||||
*/
|
*/
|
||||||
public submitPrompt<ThrowOnError extends boolean = false>(options?: Options<TuiSubmitPromptData, ThrowOnError>) {
|
public submitPrompt<ThrowOnError extends boolean = false>(options?: Options<TuiSubmitPromptData, ThrowOnError>) {
|
||||||
return (options?.client ?? this._client).post<TuiSubmitPromptResponses, unknown, ThrowOnError>({
|
return (options?.client ?? this._client).post<TuiSubmitPromptResponses, unknown, ThrowOnError>({
|
||||||
url: '/tui/submit-prompt',
|
url: "/tui/submit-prompt",
|
||||||
...options
|
...options,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear the prompt
|
* Clear the prompt
|
||||||
*/
|
*/
|
||||||
public clearPrompt<ThrowOnError extends boolean = false>(options?: Options<TuiClearPromptData, ThrowOnError>) {
|
public clearPrompt<ThrowOnError extends boolean = false>(options?: Options<TuiClearPromptData, ThrowOnError>) {
|
||||||
return (options?.client ?? this._client).post<TuiClearPromptResponses, unknown, ThrowOnError>({
|
return (options?.client ?? this._client).post<TuiClearPromptResponses, unknown, ThrowOnError>({
|
||||||
url: '/tui/clear-prompt',
|
url: "/tui/clear-prompt",
|
||||||
...options
|
...options,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute a TUI command (e.g. switch_mode)
|
* Execute a TUI command (e.g. switch_mode)
|
||||||
*/
|
*/
|
||||||
public executeCommand<ThrowOnError extends boolean = false>(options?: Options<TuiExecuteCommandData, ThrowOnError>) {
|
public executeCommand<ThrowOnError extends boolean = false>(options?: Options<TuiExecuteCommandData, ThrowOnError>) {
|
||||||
return (options?.client ?? this._client).post<TuiExecuteCommandResponses, unknown, ThrowOnError>({
|
return (options?.client ?? this._client).post<TuiExecuteCommandResponses, unknown, ThrowOnError>({
|
||||||
url: '/tui/execute-command',
|
url: "/tui/execute-command",
|
||||||
...options,
|
...options,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
"Content-Type": "application/json",
|
||||||
...options?.headers
|
...options?.headers,
|
||||||
}
|
},
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class OpencodeClient extends _HeyApiClient {
|
export class OpencodeClient extends _HeyApiClient {
|
||||||
/**
|
/**
|
||||||
* Respond to a permission request
|
* Respond to a permission request
|
||||||
*/
|
*/
|
||||||
public postSessionByIdPermissionsByPermissionId<ThrowOnError extends boolean = false>(options: Options<PostSessionByIdPermissionsByPermissionIdData, ThrowOnError>) {
|
public postSessionByIdPermissionsByPermissionId<ThrowOnError extends boolean = false>(
|
||||||
return (options.client ?? this._client).post<PostSessionByIdPermissionsByPermissionIdResponses, unknown, ThrowOnError>({
|
options: Options<PostSessionByIdPermissionsByPermissionIdData, ThrowOnError>,
|
||||||
url: '/session/{id}/permissions/{permissionID}',
|
) {
|
||||||
...options,
|
return (options.client ?? this._client).post<
|
||||||
headers: {
|
PostSessionByIdPermissionsByPermissionIdResponses,
|
||||||
'Content-Type': 'application/json',
|
unknown,
|
||||||
...options.headers
|
ThrowOnError
|
||||||
}
|
>({
|
||||||
});
|
url: "/session/{id}/permissions/{permissionID}",
|
||||||
}
|
...options,
|
||||||
event = new Event({ client: this._client });
|
headers: {
|
||||||
app = new App({ client: this._client });
|
"Content-Type": "application/json",
|
||||||
config = new Config({ client: this._client });
|
...options.headers,
|
||||||
session = new Session({ client: this._client });
|
},
|
||||||
find = new Find({ client: this._client });
|
})
|
||||||
file = new File({ client: this._client });
|
}
|
||||||
tui = new Tui({ client: this._client });
|
event = new Event({ client: this._client })
|
||||||
|
app = new App({ client: this._client })
|
||||||
|
config = new Config({ client: this._client })
|
||||||
|
session = new Session({ client: this._client })
|
||||||
|
find = new Find({ client: this._client })
|
||||||
|
file = new File({ client: this._client })
|
||||||
|
tui = new Tui({ client: this._client })
|
||||||
}
|
}
|
||||||
+1413
-1335
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
|||||||
import { createClient } from "./gen/client/client"
|
import { createClient } from "./gen/client/client"
|
||||||
import { type Config } from "./gen/client/types"
|
import { type Config } from "./gen/client/types"
|
||||||
import { OpencodeClient } from "./gen/sdk.gen"
|
import { OpencodeClient } from "./gen/sdk.gen"
|
||||||
|
export * from "./gen/types.gen"
|
||||||
|
|
||||||
export function createOpencodeClient(config?: Config) {
|
export function createOpencodeClient(config?: Config) {
|
||||||
const client = createClient(config)
|
const client = createClient(config)
|
||||||
|
|||||||
Vendored
+9
@@ -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 {}
|
||||||
@@ -5,7 +5,15 @@
|
|||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"module": "preserve",
|
"module": "preserve",
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"moduleResolution": "bundler"
|
"moduleResolution": "bundler",
|
||||||
|
"lib": [
|
||||||
|
"es2022",
|
||||||
|
"dom",
|
||||||
|
"dom.iterable"
|
||||||
|
],
|
||||||
|
"customConditions": [
|
||||||
|
"development"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src"
|
"src"
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
|
||||||
|
|
||||||
export {
|
|
||||||
Permissions,
|
|
||||||
type Permission,
|
|
||||||
type PermissionRespondResponse,
|
|
||||||
type PermissionRespondParams,
|
|
||||||
} from './permissions';
|
|
||||||
export {
|
|
||||||
SessionResource,
|
|
||||||
type AssistantMessage,
|
|
||||||
type FilePart,
|
|
||||||
type FilePartInput,
|
|
||||||
type FilePartSource,
|
|
||||||
type FilePartSourceText,
|
|
||||||
type FileSource,
|
|
||||||
type Message,
|
|
||||||
type Part,
|
|
||||||
type Session,
|
|
||||||
type SnapshotPart,
|
|
||||||
type StepFinishPart,
|
|
||||||
type StepStartPart,
|
|
||||||
type SymbolSource,
|
|
||||||
type TextPart,
|
|
||||||
type TextPartInput,
|
|
||||||
type ToolPart,
|
|
||||||
type ToolStateCompleted,
|
|
||||||
type ToolStateError,
|
|
||||||
type ToolStatePending,
|
|
||||||
type ToolStateRunning,
|
|
||||||
type UserMessage,
|
|
||||||
type SessionListResponse,
|
|
||||||
type SessionDeleteResponse,
|
|
||||||
type SessionAbortResponse,
|
|
||||||
type SessionInitResponse,
|
|
||||||
type SessionMessageResponse,
|
|
||||||
type SessionMessagesResponse,
|
|
||||||
type SessionSummarizeResponse,
|
|
||||||
type SessionChatParams,
|
|
||||||
type SessionInitParams,
|
|
||||||
type SessionMessageParams,
|
|
||||||
type SessionRevertParams,
|
|
||||||
type SessionSummarizeParams,
|
|
||||||
} from './session';
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
|
||||||
|
|
||||||
import { APIResource } from '../../core/resource';
|
|
||||||
import { APIPromise } from '../../core/api-promise';
|
|
||||||
import { RequestOptions } from '../../internal/request-options';
|
|
||||||
import { path } from '../../internal/utils/path';
|
|
||||||
|
|
||||||
export class Permissions extends APIResource {
|
|
||||||
/**
|
|
||||||
* Respond to a permission request
|
|
||||||
*/
|
|
||||||
respond(
|
|
||||||
permissionID: string,
|
|
||||||
params: PermissionRespondParams,
|
|
||||||
options?: RequestOptions,
|
|
||||||
): APIPromise<PermissionRespondResponse> {
|
|
||||||
const { id, ...body } = params;
|
|
||||||
return this._client.post(path`/session/${id}/permissions/${permissionID}`, { body, ...options });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Permission {
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
messageID: string;
|
|
||||||
|
|
||||||
metadata: { [key: string]: unknown };
|
|
||||||
|
|
||||||
sessionID: string;
|
|
||||||
|
|
||||||
time: Permission.Time;
|
|
||||||
|
|
||||||
title: string;
|
|
||||||
|
|
||||||
toolCallID?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace Permission {
|
|
||||||
export interface Time {
|
|
||||||
created: number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type PermissionRespondResponse = boolean;
|
|
||||||
|
|
||||||
export interface PermissionRespondParams {
|
|
||||||
/**
|
|
||||||
* Path param:
|
|
||||||
*/
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Body param:
|
|
||||||
*/
|
|
||||||
response: 'once' | 'always' | 'reject';
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare namespace Permissions {
|
|
||||||
export {
|
|
||||||
type Permission as Permission,
|
|
||||||
type PermissionRespondResponse as PermissionRespondResponse,
|
|
||||||
type PermissionRespondParams as PermissionRespondParams,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,645 +0,0 @@
|
|||||||
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
|
||||||
|
|
||||||
import { APIResource } from '../../core/resource';
|
|
||||||
import * as SessionAPI from './session';
|
|
||||||
import * as Shared from '../shared';
|
|
||||||
import * as PermissionsAPI from './permissions';
|
|
||||||
import { Permission, PermissionRespondParams, PermissionRespondResponse, Permissions } from './permissions';
|
|
||||||
import { APIPromise } from '../../core/api-promise';
|
|
||||||
import { RequestOptions } from '../../internal/request-options';
|
|
||||||
import { path } from '../../internal/utils/path';
|
|
||||||
|
|
||||||
export class SessionResource extends APIResource {
|
|
||||||
permissions: PermissionsAPI.Permissions = new PermissionsAPI.Permissions(this._client);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new session
|
|
||||||
*/
|
|
||||||
create(options?: RequestOptions): APIPromise<Session> {
|
|
||||||
return this._client.post('/session', options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List all sessions
|
|
||||||
*/
|
|
||||||
list(options?: RequestOptions): APIPromise<SessionListResponse> {
|
|
||||||
return this._client.get('/session', options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a session and all its data
|
|
||||||
*/
|
|
||||||
delete(id: string, options?: RequestOptions): APIPromise<SessionDeleteResponse> {
|
|
||||||
return this._client.delete(path`/session/${id}`, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abort a session
|
|
||||||
*/
|
|
||||||
abort(id: string, options?: RequestOptions): APIPromise<SessionAbortResponse> {
|
|
||||||
return this._client.post(path`/session/${id}/abort`, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create and send a new message to a session
|
|
||||||
*/
|
|
||||||
chat(id: string, body: SessionChatParams, options?: RequestOptions): APIPromise<AssistantMessage> {
|
|
||||||
return this._client.post(path`/session/${id}/message`, { body, ...options });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Analyze the app and create an AGENTS.md file
|
|
||||||
*/
|
|
||||||
init(id: string, body: SessionInitParams, options?: RequestOptions): APIPromise<SessionInitResponse> {
|
|
||||||
return this._client.post(path`/session/${id}/init`, { body, ...options });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a message from a session
|
|
||||||
*/
|
|
||||||
message(
|
|
||||||
messageID: string,
|
|
||||||
params: SessionMessageParams,
|
|
||||||
options?: RequestOptions,
|
|
||||||
): APIPromise<SessionMessageResponse> {
|
|
||||||
const { id } = params;
|
|
||||||
return this._client.get(path`/session/${id}/message/${messageID}`, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List messages for a session
|
|
||||||
*/
|
|
||||||
messages(id: string, options?: RequestOptions): APIPromise<SessionMessagesResponse> {
|
|
||||||
return this._client.get(path`/session/${id}/message`, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Revert a message
|
|
||||||
*/
|
|
||||||
revert(id: string, body: SessionRevertParams, options?: RequestOptions): APIPromise<Session> {
|
|
||||||
return this._client.post(path`/session/${id}/revert`, { body, ...options });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Share a session
|
|
||||||
*/
|
|
||||||
share(id: string, options?: RequestOptions): APIPromise<Session> {
|
|
||||||
return this._client.post(path`/session/${id}/share`, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Summarize the session
|
|
||||||
*/
|
|
||||||
summarize(
|
|
||||||
id: string,
|
|
||||||
body: SessionSummarizeParams,
|
|
||||||
options?: RequestOptions,
|
|
||||||
): APIPromise<SessionSummarizeResponse> {
|
|
||||||
return this._client.post(path`/session/${id}/summarize`, { body, ...options });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Restore all reverted messages
|
|
||||||
*/
|
|
||||||
unrevert(id: string, options?: RequestOptions): APIPromise<Session> {
|
|
||||||
return this._client.post(path`/session/${id}/unrevert`, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unshare the session
|
|
||||||
*/
|
|
||||||
unshare(id: string, options?: RequestOptions): APIPromise<Session> {
|
|
||||||
return this._client.delete(path`/session/${id}/share`, options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AssistantMessage {
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
cost: number;
|
|
||||||
|
|
||||||
mode: string;
|
|
||||||
|
|
||||||
modelID: string;
|
|
||||||
|
|
||||||
path: AssistantMessage.Path;
|
|
||||||
|
|
||||||
providerID: string;
|
|
||||||
|
|
||||||
role: 'assistant';
|
|
||||||
|
|
||||||
sessionID: string;
|
|
||||||
|
|
||||||
system: Array<string>;
|
|
||||||
|
|
||||||
time: AssistantMessage.Time;
|
|
||||||
|
|
||||||
tokens: AssistantMessage.Tokens;
|
|
||||||
|
|
||||||
error?:
|
|
||||||
| Shared.ProviderAuthError
|
|
||||||
| Shared.UnknownError
|
|
||||||
| AssistantMessage.MessageOutputLengthError
|
|
||||||
| Shared.MessageAbortedError;
|
|
||||||
|
|
||||||
summary?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace AssistantMessage {
|
|
||||||
export interface Path {
|
|
||||||
cwd: string;
|
|
||||||
|
|
||||||
root: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Time {
|
|
||||||
created: number;
|
|
||||||
|
|
||||||
completed?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Tokens {
|
|
||||||
cache: Tokens.Cache;
|
|
||||||
|
|
||||||
input: number;
|
|
||||||
|
|
||||||
output: number;
|
|
||||||
|
|
||||||
reasoning: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace Tokens {
|
|
||||||
export interface Cache {
|
|
||||||
read: number;
|
|
||||||
|
|
||||||
write: number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MessageOutputLengthError {
|
|
||||||
data: unknown;
|
|
||||||
|
|
||||||
name: 'MessageOutputLengthError';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FilePart {
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
messageID: string;
|
|
||||||
|
|
||||||
mime: string;
|
|
||||||
|
|
||||||
sessionID: string;
|
|
||||||
|
|
||||||
type: 'file';
|
|
||||||
|
|
||||||
url: string;
|
|
||||||
|
|
||||||
filename?: string;
|
|
||||||
|
|
||||||
source?: FilePartSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FilePartInput {
|
|
||||||
mime: string;
|
|
||||||
|
|
||||||
type: 'file';
|
|
||||||
|
|
||||||
url: string;
|
|
||||||
|
|
||||||
id?: string;
|
|
||||||
|
|
||||||
filename?: string;
|
|
||||||
|
|
||||||
source?: FilePartSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type FilePartSource = FileSource | SymbolSource;
|
|
||||||
|
|
||||||
export interface FilePartSourceText {
|
|
||||||
end: number;
|
|
||||||
|
|
||||||
start: number;
|
|
||||||
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FileSource {
|
|
||||||
path: string;
|
|
||||||
|
|
||||||
text: FilePartSourceText;
|
|
||||||
|
|
||||||
type: 'file';
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Message = UserMessage | AssistantMessage;
|
|
||||||
|
|
||||||
export type Part =
|
|
||||||
| TextPart
|
|
||||||
| FilePart
|
|
||||||
| ToolPart
|
|
||||||
| StepStartPart
|
|
||||||
| StepFinishPart
|
|
||||||
| SnapshotPart
|
|
||||||
| Part.PatchPart;
|
|
||||||
|
|
||||||
export namespace Part {
|
|
||||||
export interface PatchPart {
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
files: Array<string>;
|
|
||||||
|
|
||||||
hash: string;
|
|
||||||
|
|
||||||
messageID: string;
|
|
||||||
|
|
||||||
sessionID: string;
|
|
||||||
|
|
||||||
type: 'patch';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Session {
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
time: Session.Time;
|
|
||||||
|
|
||||||
title: string;
|
|
||||||
|
|
||||||
version: string;
|
|
||||||
|
|
||||||
parentID?: string;
|
|
||||||
|
|
||||||
revert?: Session.Revert;
|
|
||||||
|
|
||||||
share?: Session.Share;
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace Session {
|
|
||||||
export interface Time {
|
|
||||||
created: number;
|
|
||||||
|
|
||||||
updated: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Revert {
|
|
||||||
messageID: string;
|
|
||||||
|
|
||||||
diff?: string;
|
|
||||||
|
|
||||||
partID?: string;
|
|
||||||
|
|
||||||
snapshot?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Share {
|
|
||||||
url: string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SnapshotPart {
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
messageID: string;
|
|
||||||
|
|
||||||
sessionID: string;
|
|
||||||
|
|
||||||
snapshot: string;
|
|
||||||
|
|
||||||
type: 'snapshot';
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StepFinishPart {
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
cost: number;
|
|
||||||
|
|
||||||
messageID: string;
|
|
||||||
|
|
||||||
sessionID: string;
|
|
||||||
|
|
||||||
tokens: StepFinishPart.Tokens;
|
|
||||||
|
|
||||||
type: 'step-finish';
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace StepFinishPart {
|
|
||||||
export interface Tokens {
|
|
||||||
cache: Tokens.Cache;
|
|
||||||
|
|
||||||
input: number;
|
|
||||||
|
|
||||||
output: number;
|
|
||||||
|
|
||||||
reasoning: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace Tokens {
|
|
||||||
export interface Cache {
|
|
||||||
read: number;
|
|
||||||
|
|
||||||
write: number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StepStartPart {
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
messageID: string;
|
|
||||||
|
|
||||||
sessionID: string;
|
|
||||||
|
|
||||||
type: 'step-start';
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SymbolSource {
|
|
||||||
kind: number;
|
|
||||||
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
path: string;
|
|
||||||
|
|
||||||
range: SymbolSource.Range;
|
|
||||||
|
|
||||||
text: FilePartSourceText;
|
|
||||||
|
|
||||||
type: 'symbol';
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace SymbolSource {
|
|
||||||
export interface Range {
|
|
||||||
end: Range.End;
|
|
||||||
|
|
||||||
start: Range.Start;
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace Range {
|
|
||||||
export interface End {
|
|
||||||
character: number;
|
|
||||||
|
|
||||||
line: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Start {
|
|
||||||
character: number;
|
|
||||||
|
|
||||||
line: number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TextPart {
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
messageID: string;
|
|
||||||
|
|
||||||
sessionID: string;
|
|
||||||
|
|
||||||
text: string;
|
|
||||||
|
|
||||||
type: 'text';
|
|
||||||
|
|
||||||
synthetic?: boolean;
|
|
||||||
|
|
||||||
time?: TextPart.Time;
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace TextPart {
|
|
||||||
export interface Time {
|
|
||||||
start: number;
|
|
||||||
|
|
||||||
end?: number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TextPartInput {
|
|
||||||
text: string;
|
|
||||||
|
|
||||||
type: 'text';
|
|
||||||
|
|
||||||
id?: string;
|
|
||||||
|
|
||||||
synthetic?: boolean;
|
|
||||||
|
|
||||||
time?: TextPartInput.Time;
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace TextPartInput {
|
|
||||||
export interface Time {
|
|
||||||
start: number;
|
|
||||||
|
|
||||||
end?: number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ToolPart {
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
callID: string;
|
|
||||||
|
|
||||||
messageID: string;
|
|
||||||
|
|
||||||
sessionID: string;
|
|
||||||
|
|
||||||
state: ToolStatePending | ToolStateRunning | ToolStateCompleted | ToolStateError;
|
|
||||||
|
|
||||||
tool: string;
|
|
||||||
|
|
||||||
type: 'tool';
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ToolStateCompleted {
|
|
||||||
input: { [key: string]: unknown };
|
|
||||||
|
|
||||||
metadata: { [key: string]: unknown };
|
|
||||||
|
|
||||||
output: string;
|
|
||||||
|
|
||||||
status: 'completed';
|
|
||||||
|
|
||||||
time: ToolStateCompleted.Time;
|
|
||||||
|
|
||||||
title: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace ToolStateCompleted {
|
|
||||||
export interface Time {
|
|
||||||
end: number;
|
|
||||||
|
|
||||||
start: number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ToolStateError {
|
|
||||||
error: string;
|
|
||||||
|
|
||||||
input: { [key: string]: unknown };
|
|
||||||
|
|
||||||
status: 'error';
|
|
||||||
|
|
||||||
time: ToolStateError.Time;
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace ToolStateError {
|
|
||||||
export interface Time {
|
|
||||||
end: number;
|
|
||||||
|
|
||||||
start: number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ToolStatePending {
|
|
||||||
status: 'pending';
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ToolStateRunning {
|
|
||||||
status: 'running';
|
|
||||||
|
|
||||||
time: ToolStateRunning.Time;
|
|
||||||
|
|
||||||
input?: unknown;
|
|
||||||
|
|
||||||
metadata?: { [key: string]: unknown };
|
|
||||||
|
|
||||||
title?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace ToolStateRunning {
|
|
||||||
export interface Time {
|
|
||||||
start: number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UserMessage {
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
role: 'user';
|
|
||||||
|
|
||||||
sessionID: string;
|
|
||||||
|
|
||||||
time: UserMessage.Time;
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace UserMessage {
|
|
||||||
export interface Time {
|
|
||||||
created: number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SessionListResponse = Array<Session>;
|
|
||||||
|
|
||||||
export type SessionDeleteResponse = boolean;
|
|
||||||
|
|
||||||
export type SessionAbortResponse = boolean;
|
|
||||||
|
|
||||||
export type SessionInitResponse = boolean;
|
|
||||||
|
|
||||||
export interface SessionMessageResponse {
|
|
||||||
info: Message;
|
|
||||||
|
|
||||||
parts: Array<Part>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SessionMessagesResponse = Array<SessionMessagesResponse.SessionMessagesResponseItem>;
|
|
||||||
|
|
||||||
export namespace SessionMessagesResponse {
|
|
||||||
export interface SessionMessagesResponseItem {
|
|
||||||
info: SessionAPI.Message;
|
|
||||||
|
|
||||||
parts: Array<SessionAPI.Part>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SessionSummarizeResponse = boolean;
|
|
||||||
|
|
||||||
export interface SessionChatParams {
|
|
||||||
modelID: string;
|
|
||||||
|
|
||||||
parts: Array<TextPartInput | FilePartInput>;
|
|
||||||
|
|
||||||
providerID: string;
|
|
||||||
|
|
||||||
messageID?: string;
|
|
||||||
|
|
||||||
mode?: string;
|
|
||||||
|
|
||||||
system?: string;
|
|
||||||
|
|
||||||
tools?: { [key: string]: boolean };
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SessionInitParams {
|
|
||||||
messageID: string;
|
|
||||||
|
|
||||||
modelID: string;
|
|
||||||
|
|
||||||
providerID: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SessionMessageParams {
|
|
||||||
/**
|
|
||||||
* Session ID
|
|
||||||
*/
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SessionRevertParams {
|
|
||||||
messageID: string;
|
|
||||||
|
|
||||||
partID?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SessionSummarizeParams {
|
|
||||||
modelID: string;
|
|
||||||
|
|
||||||
providerID: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
SessionResource.Permissions = Permissions;
|
|
||||||
|
|
||||||
export declare namespace SessionResource {
|
|
||||||
export {
|
|
||||||
type AssistantMessage as AssistantMessage,
|
|
||||||
type FilePart as FilePart,
|
|
||||||
type FilePartInput as FilePartInput,
|
|
||||||
type FilePartSource as FilePartSource,
|
|
||||||
type FilePartSourceText as FilePartSourceText,
|
|
||||||
type FileSource as FileSource,
|
|
||||||
type Message as Message,
|
|
||||||
type Part as Part,
|
|
||||||
type Session as Session,
|
|
||||||
type SnapshotPart as SnapshotPart,
|
|
||||||
type StepFinishPart as StepFinishPart,
|
|
||||||
type StepStartPart as StepStartPart,
|
|
||||||
type SymbolSource as SymbolSource,
|
|
||||||
type TextPart as TextPart,
|
|
||||||
type TextPartInput as TextPartInput,
|
|
||||||
type ToolPart as ToolPart,
|
|
||||||
type ToolStateCompleted as ToolStateCompleted,
|
|
||||||
type ToolStateError as ToolStateError,
|
|
||||||
type ToolStatePending as ToolStatePending,
|
|
||||||
type ToolStateRunning as ToolStateRunning,
|
|
||||||
type UserMessage as UserMessage,
|
|
||||||
type SessionListResponse as SessionListResponse,
|
|
||||||
type SessionDeleteResponse as SessionDeleteResponse,
|
|
||||||
type SessionAbortResponse as SessionAbortResponse,
|
|
||||||
type SessionInitResponse as SessionInitResponse,
|
|
||||||
type SessionMessageResponse as SessionMessageResponse,
|
|
||||||
type SessionMessagesResponse as SessionMessagesResponse,
|
|
||||||
type SessionSummarizeResponse as SessionSummarizeResponse,
|
|
||||||
type SessionChatParams as SessionChatParams,
|
|
||||||
type SessionInitParams as SessionInitParams,
|
|
||||||
type SessionMessageParams as SessionMessageParams,
|
|
||||||
type SessionRevertParams as SessionRevertParams,
|
|
||||||
type SessionSummarizeParams as SessionSummarizeParams,
|
|
||||||
};
|
|
||||||
|
|
||||||
export {
|
|
||||||
Permissions as Permissions,
|
|
||||||
type Permission as Permission,
|
|
||||||
type PermissionRespondResponse as PermissionRespondResponse,
|
|
||||||
type PermissionRespondParams as PermissionRespondParams,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
|
||||||
|
|
||||||
import Opencode from '@opencode-ai/sdk';
|
|
||||||
|
|
||||||
const client = new Opencode({ baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010' });
|
|
||||||
|
|
||||||
describe('resource permissions', () => {
|
|
||||||
// skipped: tests are disabled for the time being
|
|
||||||
test.skip('respond: only required params', async () => {
|
|
||||||
const responsePromise = client.session.permissions.respond('permissionID', {
|
|
||||||
id: 'id',
|
|
||||||
response: 'once',
|
|
||||||
});
|
|
||||||
const rawResponse = await responsePromise.asResponse();
|
|
||||||
expect(rawResponse).toBeInstanceOf(Response);
|
|
||||||
const response = await responsePromise;
|
|
||||||
expect(response).not.toBeInstanceOf(Response);
|
|
||||||
const dataAndResponse = await responsePromise.withResponse();
|
|
||||||
expect(dataAndResponse.data).toBe(response);
|
|
||||||
expect(dataAndResponse.response).toBe(rawResponse);
|
|
||||||
});
|
|
||||||
|
|
||||||
// skipped: tests are disabled for the time being
|
|
||||||
test.skip('respond: required and optional params', async () => {
|
|
||||||
const response = await client.session.permissions.respond('permissionID', { id: 'id', response: 'once' });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -270,6 +270,50 @@ func (a *App) SwitchModeReverse() (*App, tea.Cmd) {
|
|||||||
return a.cycleMode(false)
|
return a.cycleMode(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// findModelByFullID finds a model by its full ID in the format "provider/model"
|
||||||
|
func findModelByFullID(providers []opencode.Provider, fullModelID string) (*opencode.Provider, *opencode.Model) {
|
||||||
|
modelParts := strings.SplitN(fullModelID, "/", 2)
|
||||||
|
if len(modelParts) < 2 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
providerID := modelParts[0]
|
||||||
|
modelID := modelParts[1]
|
||||||
|
|
||||||
|
return findModelByProviderAndModelID(providers, providerID, modelID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// findModelByProviderAndModelID finds a model by provider ID and model ID
|
||||||
|
func findModelByProviderAndModelID(providers []opencode.Provider, providerID, modelID string) (*opencode.Provider, *opencode.Model) {
|
||||||
|
for _, provider := range providers {
|
||||||
|
if provider.ID != providerID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, model := range provider.Models {
|
||||||
|
if model.ID == modelID {
|
||||||
|
return &provider, &model
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provider found but model not found
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provider not found
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// findProviderByID finds a provider by its ID
|
||||||
|
func findProviderByID(providers []opencode.Provider, providerID string) *opencode.Provider {
|
||||||
|
for _, provider := range providers {
|
||||||
|
if provider.ID == providerID {
|
||||||
|
return &provider
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) InitializeProvider() tea.Cmd {
|
func (a *App) InitializeProvider() tea.Cmd {
|
||||||
providersResponse, err := a.Client.App.Providers(context.Background())
|
providersResponse, err := a.Client.App.Providers(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -278,29 +322,6 @@ func (a *App) InitializeProvider() tea.Cmd {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
providers := providersResponse.Providers
|
providers := providersResponse.Providers
|
||||||
var defaultProvider *opencode.Provider
|
|
||||||
var defaultModel *opencode.Model
|
|
||||||
|
|
||||||
var anthropic *opencode.Provider
|
|
||||||
for _, provider := range providers {
|
|
||||||
if provider.ID == "anthropic" {
|
|
||||||
anthropic = &provider
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// default to anthropic if available
|
|
||||||
if anthropic != nil {
|
|
||||||
defaultProvider = anthropic
|
|
||||||
defaultModel = getDefaultModel(providersResponse, *anthropic)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, provider := range providers {
|
|
||||||
if defaultProvider == nil || defaultModel == nil {
|
|
||||||
defaultProvider = &provider
|
|
||||||
defaultModel = getDefaultModel(providersResponse, provider)
|
|
||||||
}
|
|
||||||
providers = append(providers, provider)
|
|
||||||
}
|
|
||||||
if len(providers) == 0 {
|
if len(providers) == 0 {
|
||||||
slog.Error("No providers configured")
|
slog.Error("No providers configured")
|
||||||
return nil
|
return nil
|
||||||
@@ -314,50 +335,86 @@ func (a *App) InitializeProvider() tea.Cmd {
|
|||||||
a.State.Model = model.ModelID
|
a.State.Model = model.ModelID
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentProvider *opencode.Provider
|
var selectedProvider *opencode.Provider
|
||||||
var currentModel *opencode.Model
|
var selectedModel *opencode.Model
|
||||||
for _, provider := range providers {
|
|
||||||
if provider.ID == a.State.Provider {
|
|
||||||
currentProvider = &provider
|
|
||||||
|
|
||||||
for _, model := range provider.Models {
|
// Priority 1: Command line --model flag (InitialModel)
|
||||||
if model.ID == a.State.Model {
|
|
||||||
currentModel = &model
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if currentProvider == nil || currentModel == nil {
|
|
||||||
currentProvider = defaultProvider
|
|
||||||
currentModel = defaultModel
|
|
||||||
}
|
|
||||||
|
|
||||||
var initialProvider *opencode.Provider
|
|
||||||
var initialModel *opencode.Model
|
|
||||||
if a.InitialModel != nil && *a.InitialModel != "" {
|
if a.InitialModel != nil && *a.InitialModel != "" {
|
||||||
splits := strings.Split(*a.InitialModel, "/")
|
if provider, model := findModelByFullID(providers, *a.InitialModel); provider != nil && model != nil {
|
||||||
for _, provider := range providers {
|
selectedProvider = provider
|
||||||
if provider.ID == splits[0] {
|
selectedModel = model
|
||||||
initialProvider = &provider
|
slog.Debug("Selected model from command line", "provider", provider.ID, "model", model.ID)
|
||||||
for _, model := range provider.Models {
|
} else {
|
||||||
modelID := strings.Join(splits[1:], "/")
|
slog.Debug("Command line model not found", "model", *a.InitialModel)
|
||||||
if model.ID == modelID {
|
}
|
||||||
initialModel = &model
|
}
|
||||||
}
|
|
||||||
}
|
// Priority 2: Config file model setting
|
||||||
|
if selectedProvider == nil && a.Config.Model != "" {
|
||||||
|
if provider, model := findModelByFullID(providers, a.Config.Model); provider != nil && model != nil {
|
||||||
|
selectedProvider = provider
|
||||||
|
selectedModel = model
|
||||||
|
slog.Debug("Selected model from config", "provider", provider.ID, "model", model.ID)
|
||||||
|
} else {
|
||||||
|
slog.Debug("Config model not found", "model", a.Config.Model)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Priority 3: Recent model usage (most recently used model)
|
||||||
|
if selectedProvider == nil && len(a.State.RecentlyUsedModels) > 0 {
|
||||||
|
recentUsage := a.State.RecentlyUsedModels[0] // Most recent is first
|
||||||
|
if provider, model := findModelByProviderAndModelID(providers, recentUsage.ProviderID, recentUsage.ModelID); provider != nil && model != nil {
|
||||||
|
selectedProvider = provider
|
||||||
|
selectedModel = model
|
||||||
|
slog.Debug("Selected model from recent usage", "provider", provider.ID, "model", model.ID)
|
||||||
|
} else {
|
||||||
|
slog.Debug("Recent model not found", "provider", recentUsage.ProviderID, "model", recentUsage.ModelID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Priority 4: State-based model (backwards compatibility)
|
||||||
|
if selectedProvider == nil && a.State.Provider != "" && a.State.Model != "" {
|
||||||
|
if provider, model := findModelByProviderAndModelID(providers, a.State.Provider, a.State.Model); provider != nil && model != nil {
|
||||||
|
selectedProvider = provider
|
||||||
|
selectedModel = model
|
||||||
|
slog.Debug("Selected model from state", "provider", provider.ID, "model", model.ID)
|
||||||
|
} else {
|
||||||
|
slog.Debug("State model not found", "provider", a.State.Provider, "model", a.State.Model)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Priority 5: Internal priority fallback (Anthropic preferred, then first available)
|
||||||
|
if selectedProvider == nil {
|
||||||
|
// Try Anthropic first as internal priority
|
||||||
|
if provider := findProviderByID(providers, "anthropic"); provider != nil {
|
||||||
|
if model := getDefaultModel(providersResponse, *provider); model != nil {
|
||||||
|
selectedProvider = provider
|
||||||
|
selectedModel = model
|
||||||
|
slog.Debug("Selected model from internal priority (Anthropic)", "provider", provider.ID, "model", model.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If Anthropic not available, use first available provider
|
||||||
|
if selectedProvider == nil && len(providers) > 0 {
|
||||||
|
provider := &providers[0]
|
||||||
|
if model := getDefaultModel(providersResponse, *provider); model != nil {
|
||||||
|
selectedProvider = provider
|
||||||
|
selectedModel = model
|
||||||
|
slog.Debug("Selected model from fallback (first available)", "provider", provider.ID, "model", model.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if initialProvider != nil && initialModel != nil {
|
// Final safety check
|
||||||
currentProvider = initialProvider
|
if selectedProvider == nil || selectedModel == nil {
|
||||||
currentModel = initialModel
|
slog.Error("Failed to select any model")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var cmds []tea.Cmd
|
var cmds []tea.Cmd
|
||||||
cmds = append(cmds, util.CmdHandler(ModelSelectedMsg{
|
cmds = append(cmds, util.CmdHandler(ModelSelectedMsg{
|
||||||
Provider: *currentProvider,
|
Provider: *selectedProvider,
|
||||||
Model: *currentModel,
|
Model: *selectedModel,
|
||||||
}))
|
}))
|
||||||
if a.InitialPrompt != nil && *a.InitialPrompt != "" {
|
if a.InitialPrompt != nil && *a.InitialPrompt != "" {
|
||||||
cmds = append(cmds, util.CmdHandler(SendPrompt{Text: *a.InitialPrompt}))
|
cmds = append(cmds, util.CmdHandler(SendPrompt{Text: *a.InitialPrompt}))
|
||||||
|
|||||||
@@ -0,0 +1,228 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/sst/opencode-sdk-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestFindModelByFullID tests the findModelByFullID function
|
||||||
|
func TestFindModelByFullID(t *testing.T) {
|
||||||
|
// Create test providers with models
|
||||||
|
providers := []opencode.Provider{
|
||||||
|
{
|
||||||
|
ID: "anthropic",
|
||||||
|
Models: map[string]opencode.Model{
|
||||||
|
"claude-3-opus-20240229": {ID: "claude-3-opus-20240229"},
|
||||||
|
"claude-3-sonnet-20240229": {ID: "claude-3-sonnet-20240229"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "openai",
|
||||||
|
Models: map[string]opencode.Model{
|
||||||
|
"gpt-4": {ID: "gpt-4"},
|
||||||
|
"gpt-3.5-turbo": {ID: "gpt-3.5-turbo"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fullModelID string
|
||||||
|
expectedFound bool
|
||||||
|
expectedProviderID string
|
||||||
|
expectedModelID string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid full model ID",
|
||||||
|
fullModelID: "anthropic/claude-3-opus-20240229",
|
||||||
|
expectedFound: true,
|
||||||
|
expectedProviderID: "anthropic",
|
||||||
|
expectedModelID: "claude-3-opus-20240229",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid full model ID with slash in model name",
|
||||||
|
fullModelID: "openai/gpt-3.5-turbo",
|
||||||
|
expectedFound: true,
|
||||||
|
expectedProviderID: "openai",
|
||||||
|
expectedModelID: "gpt-3.5-turbo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid format - missing slash",
|
||||||
|
fullModelID: "anthropic",
|
||||||
|
expectedFound: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid format - empty string",
|
||||||
|
fullModelID: "",
|
||||||
|
expectedFound: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "provider not found",
|
||||||
|
fullModelID: "nonexistent/model",
|
||||||
|
expectedFound: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "model not found",
|
||||||
|
fullModelID: "anthropic/nonexistent-model",
|
||||||
|
expectedFound: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
provider, model := findModelByFullID(providers, tt.fullModelID)
|
||||||
|
|
||||||
|
if tt.expectedFound {
|
||||||
|
if provider == nil || model == nil {
|
||||||
|
t.Errorf("Expected to find provider/model, but got nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if provider.ID != tt.expectedProviderID {
|
||||||
|
t.Errorf("Expected provider ID %s, got %s", tt.expectedProviderID, provider.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if model.ID != tt.expectedModelID {
|
||||||
|
t.Errorf("Expected model ID %s, got %s", tt.expectedModelID, model.ID)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if provider != nil || model != nil {
|
||||||
|
t.Errorf("Expected not to find provider/model, but got provider: %v, model: %v", provider, model)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFindModelByProviderAndModelID tests the findModelByProviderAndModelID function
|
||||||
|
func TestFindModelByProviderAndModelID(t *testing.T) {
|
||||||
|
// Create test providers with models
|
||||||
|
providers := []opencode.Provider{
|
||||||
|
{
|
||||||
|
ID: "anthropic",
|
||||||
|
Models: map[string]opencode.Model{
|
||||||
|
"claude-3-opus-20240229": {ID: "claude-3-opus-20240229"},
|
||||||
|
"claude-3-sonnet-20240229": {ID: "claude-3-sonnet-20240229"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "openai",
|
||||||
|
Models: map[string]opencode.Model{
|
||||||
|
"gpt-4": {ID: "gpt-4"},
|
||||||
|
"gpt-3.5-turbo": {ID: "gpt-3.5-turbo"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
providerID string
|
||||||
|
modelID string
|
||||||
|
expectedFound bool
|
||||||
|
expectedProviderID string
|
||||||
|
expectedModelID string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid provider and model",
|
||||||
|
providerID: "anthropic",
|
||||||
|
modelID: "claude-3-opus-20240229",
|
||||||
|
expectedFound: true,
|
||||||
|
expectedProviderID: "anthropic",
|
||||||
|
expectedModelID: "claude-3-opus-20240229",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "provider not found",
|
||||||
|
providerID: "nonexistent",
|
||||||
|
modelID: "claude-3-opus-20240229",
|
||||||
|
expectedFound: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "model not found",
|
||||||
|
providerID: "anthropic",
|
||||||
|
modelID: "nonexistent-model",
|
||||||
|
expectedFound: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "both provider and model not found",
|
||||||
|
providerID: "nonexistent",
|
||||||
|
modelID: "nonexistent-model",
|
||||||
|
expectedFound: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
provider, model := findModelByProviderAndModelID(providers, tt.providerID, tt.modelID)
|
||||||
|
|
||||||
|
if tt.expectedFound {
|
||||||
|
if provider == nil || model == nil {
|
||||||
|
t.Errorf("Expected to find provider/model, but got nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if provider.ID != tt.expectedProviderID {
|
||||||
|
t.Errorf("Expected provider ID %s, got %s", tt.expectedProviderID, provider.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if model.ID != tt.expectedModelID {
|
||||||
|
t.Errorf("Expected model ID %s, got %s", tt.expectedModelID, model.ID)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if provider != nil || model != nil {
|
||||||
|
t.Errorf("Expected not to find provider/model, but got provider: %v, model: %v", provider, model)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFindProviderByID tests the findProviderByID function
|
||||||
|
func TestFindProviderByID(t *testing.T) {
|
||||||
|
// Create test providers
|
||||||
|
providers := []opencode.Provider{
|
||||||
|
{ID: "anthropic"},
|
||||||
|
{ID: "openai"},
|
||||||
|
{ID: "google"},
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
providerID string
|
||||||
|
expectedFound bool
|
||||||
|
expectedProviderID string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "provider found",
|
||||||
|
providerID: "anthropic",
|
||||||
|
expectedFound: true,
|
||||||
|
expectedProviderID: "anthropic",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "provider not found",
|
||||||
|
providerID: "nonexistent",
|
||||||
|
expectedFound: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
provider := findProviderByID(providers, tt.providerID)
|
||||||
|
|
||||||
|
if tt.expectedFound {
|
||||||
|
if provider == nil {
|
||||||
|
t.Errorf("Expected to find provider, but got nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if provider.ID != tt.expectedProviderID {
|
||||||
|
t.Errorf("Expected provider ID %s, got %s", tt.expectedProviderID, provider.ID)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if provider != nil {
|
||||||
|
t.Errorf("Expected not to find provider, but got %v", provider)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -120,5 +120,13 @@ func LoadState(filePath string) (*State, error) {
|
|||||||
}
|
}
|
||||||
return nil, fmt.Errorf("failed to decode TOML from file %s: %w", filePath, err)
|
return nil, fmt.Errorf("failed to decode TOML from file %s: %w", filePath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restore attachment sources types that were deserialized as map[string]any
|
||||||
|
for _, prompt := range state.MessageHistory {
|
||||||
|
for _, att := range prompt.Attachments {
|
||||||
|
att.RestoreSourceType()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &state, nil
|
return &state, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,3 +75,80 @@ func (a *Attachment) GetSymbolSource() (*SymbolSource, bool) {
|
|||||||
ss, ok := a.Source.(*SymbolSource)
|
ss, ok := a.Source.(*SymbolSource)
|
||||||
return ss, ok
|
return ss, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FromMap creates a TextSource from a map[string]any
|
||||||
|
func (ts *TextSource) FromMap(sourceMap map[string]any) {
|
||||||
|
if value, ok := sourceMap["value"].(string); ok {
|
||||||
|
ts.Value = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromMap creates a FileSource from a map[string]any
|
||||||
|
func (fs *FileSource) FromMap(sourceMap map[string]any) {
|
||||||
|
if path, ok := sourceMap["path"].(string); ok {
|
||||||
|
fs.Path = path
|
||||||
|
}
|
||||||
|
if mime, ok := sourceMap["mime"].(string); ok {
|
||||||
|
fs.Mime = mime
|
||||||
|
}
|
||||||
|
if data, ok := sourceMap["data"].([]byte); ok {
|
||||||
|
fs.Data = data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromMap creates a SymbolSource from a map[string]any
|
||||||
|
func (ss *SymbolSource) FromMap(sourceMap map[string]any) {
|
||||||
|
if path, ok := sourceMap["path"].(string); ok {
|
||||||
|
ss.Path = path
|
||||||
|
}
|
||||||
|
if name, ok := sourceMap["name"].(string); ok {
|
||||||
|
ss.Name = name
|
||||||
|
}
|
||||||
|
if kind, ok := sourceMap["kind"].(int); ok {
|
||||||
|
ss.Kind = kind
|
||||||
|
}
|
||||||
|
if rangeMap, ok := sourceMap["range"].(map[string]any); ok {
|
||||||
|
ss.Range = SymbolRange{}
|
||||||
|
if startMap, ok := rangeMap["start"].(map[string]any); ok {
|
||||||
|
if line, ok := startMap["line"].(int); ok {
|
||||||
|
ss.Range.Start.Line = line
|
||||||
|
}
|
||||||
|
if char, ok := startMap["char"].(int); ok {
|
||||||
|
ss.Range.Start.Char = char
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if endMap, ok := rangeMap["end"].(map[string]any); ok {
|
||||||
|
if line, ok := endMap["line"].(int); ok {
|
||||||
|
ss.Range.End.Line = line
|
||||||
|
}
|
||||||
|
if char, ok := endMap["char"].(int); ok {
|
||||||
|
ss.Range.End.Char = char
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RestoreSourceType converts a map[string]any source back to the proper type
|
||||||
|
func (a *Attachment) RestoreSourceType() {
|
||||||
|
if a.Source == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if Source is a map[string]any
|
||||||
|
if sourceMap, ok := a.Source.(map[string]any); ok {
|
||||||
|
switch a.Type {
|
||||||
|
case "text":
|
||||||
|
ts := &TextSource{}
|
||||||
|
ts.FromMap(sourceMap)
|
||||||
|
a.Source = ts
|
||||||
|
case "file":
|
||||||
|
fs := &FileSource{}
|
||||||
|
fs.FromMap(sourceMap)
|
||||||
|
a.Source = fs
|
||||||
|
case "symbol":
|
||||||
|
ss := &SymbolSource{}
|
||||||
|
ss.FromMap(sourceMap)
|
||||||
|
a.Source = ss
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package commands
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"log/slog"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -155,6 +156,9 @@ func (k Command) Matches(msg tea.KeyPressMsg, leader bool) bool {
|
|||||||
func parseBindings(bindings ...string) []Keybinding {
|
func parseBindings(bindings ...string) []Keybinding {
|
||||||
var parsedBindings []Keybinding
|
var parsedBindings []Keybinding
|
||||||
for _, binding := range bindings {
|
for _, binding := range bindings {
|
||||||
|
if binding == "none" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
for p := range strings.SplitSeq(binding, ",") {
|
for p := range strings.SplitSeq(binding, ",") {
|
||||||
requireLeader := strings.HasPrefix(p, "<leader>")
|
requireLeader := strings.HasPrefix(p, "<leader>")
|
||||||
keybinding := strings.ReplaceAll(p, "<leader>", "")
|
keybinding := strings.ReplaceAll(p, "<leader>", "")
|
||||||
@@ -219,7 +223,6 @@ func LoadFromConfig(config *opencode.Config) CommandRegistry {
|
|||||||
{
|
{
|
||||||
Name: SessionUnshareCommand,
|
Name: SessionUnshareCommand,
|
||||||
Description: "unshare session",
|
Description: "unshare session",
|
||||||
Keybindings: parseBindings("<leader>u"),
|
|
||||||
Trigger: []string{"unshare"},
|
Trigger: []string{"unshare"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -375,15 +378,14 @@ func LoadFromConfig(config *opencode.Config) CommandRegistry {
|
|||||||
// Remove share/unshare commands if sharing is disabled
|
// Remove share/unshare commands if sharing is disabled
|
||||||
if config.Share == opencode.ConfigShareDisabled &&
|
if config.Share == opencode.ConfigShareDisabled &&
|
||||||
(command.Name == SessionShareCommand || command.Name == SessionUnshareCommand) {
|
(command.Name == SessionShareCommand || command.Name == SessionUnshareCommand) {
|
||||||
|
slog.Info("Removing share/unshare commands")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if keybind, ok := keybinds[string(command.Name)]; ok && keybind != "" {
|
if keybind, ok := keybinds[string(command.Name)]; ok && keybind != "" {
|
||||||
if keybind == "none" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
command.Keybindings = parseBindings(keybind)
|
command.Keybindings = parseBindings(keybind)
|
||||||
}
|
}
|
||||||
registry[command.Name] = command
|
registry[command.Name] = command
|
||||||
}
|
}
|
||||||
|
slog.Info("Loaded commands", "commands", registry)
|
||||||
return registry
|
return registry
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -217,22 +217,42 @@ func renderText(
|
|||||||
base := styles.NewStyle().Foreground(t.Text()).Background(backgroundColor)
|
base := styles.NewStyle().Foreground(t.Text()).Background(backgroundColor)
|
||||||
text = ansi.WordwrapWc(text, width-6, " -")
|
text = ansi.WordwrapWc(text, width-6, " -")
|
||||||
|
|
||||||
// Build list of attachment filenames for highlighting
|
var result strings.Builder
|
||||||
|
lastEnd := int64(0)
|
||||||
|
|
||||||
|
// Apply highlighting to filenames and base style to rest of text
|
||||||
|
textLen := int64(len(text))
|
||||||
for _, filePart := range fileParts {
|
for _, filePart := range fileParts {
|
||||||
atFilename := "@" + filePart.Filename
|
highlight := base.Foreground(t.Secondary())
|
||||||
// Find and highlight complete @filename references
|
start, end := filePart.Source.Text.Start, filePart.Source.Text.End
|
||||||
highlightStyle := base.Foreground(t.Secondary())
|
|
||||||
text = strings.ReplaceAll(text, atFilename, highlightStyle.Render(atFilename))
|
if end > textLen {
|
||||||
|
end = textLen
|
||||||
|
}
|
||||||
|
if start > textLen {
|
||||||
|
start = textLen
|
||||||
|
}
|
||||||
|
|
||||||
|
if start > lastEnd {
|
||||||
|
result.WriteString(base.Render(text[lastEnd:start]))
|
||||||
|
}
|
||||||
|
if start < end {
|
||||||
|
result.WriteString(highlight.Render(text[start:end]))
|
||||||
|
}
|
||||||
|
|
||||||
|
lastEnd = end
|
||||||
}
|
}
|
||||||
|
|
||||||
content = base.Width(width - 6).Render(text)
|
if lastEnd < textLen {
|
||||||
|
result.WriteString(base.Render(text[lastEnd:]))
|
||||||
|
}
|
||||||
|
content = base.Width(width - 6).Render(result.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
timestamp := ts.
|
timestamp := ts.
|
||||||
Local().
|
Local().
|
||||||
Format("02 Jan 2006 03:04 PM")
|
Format("02 Jan 2006 03:04 PM")
|
||||||
if time.Now().Format("02 Jan 2006") == timestamp[:11] {
|
if time.Now().Format("02 Jan 2006") == timestamp[:11] {
|
||||||
// don't show the date if it's today
|
|
||||||
timestamp = timestamp[12:]
|
timestamp = timestamp[12:]
|
||||||
}
|
}
|
||||||
info := fmt.Sprintf("%s (%s)", author, timestamp)
|
info := fmt.Sprintf("%s (%s)", author, timestamp)
|
||||||
@@ -438,6 +458,10 @@ func renderToolDetails(
|
|||||||
if stdout != nil {
|
if stdout != nil {
|
||||||
body += ansi.Strip(fmt.Sprintf("%s", stdout))
|
body += ansi.Strip(fmt.Sprintf("%s", stdout))
|
||||||
}
|
}
|
||||||
|
stderr := metadata["stderr"]
|
||||||
|
if stderr != nil {
|
||||||
|
body += ansi.Strip(fmt.Sprintf("%s", stderr))
|
||||||
|
}
|
||||||
body += "```"
|
body += "```"
|
||||||
body = util.ToMarkdown(body, width, backgroundColor)
|
body = util.ToMarkdown(body, width, backgroundColor)
|
||||||
case "webfetch":
|
case "webfetch":
|
||||||
@@ -539,6 +563,8 @@ func renderToolName(name string) string {
|
|||||||
switch name {
|
switch name {
|
||||||
case "webfetch":
|
case "webfetch":
|
||||||
return "Fetch"
|
return "Fetch"
|
||||||
|
case "invalid":
|
||||||
|
return "Invalid"
|
||||||
default:
|
default:
|
||||||
normalizedName := name
|
normalizedName := name
|
||||||
if after, ok := strings.CutPrefix(name, "opencode_"); ok {
|
if after, ok := strings.CutPrefix(name, "opencode_"); ok {
|
||||||
@@ -641,6 +667,10 @@ func renderToolTitle(
|
|||||||
title = getTodoTitle(toolCall)
|
title = getTodoTitle(toolCall)
|
||||||
case "todoread":
|
case "todoread":
|
||||||
return "Plan"
|
return "Plan"
|
||||||
|
case "invalid":
|
||||||
|
if actualTool, ok := toolArgsMap["tool"].(string); ok {
|
||||||
|
title = renderToolName(actualTool)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
toolName := renderToolName(toolCall.Tool)
|
toolName := renderToolName(toolCall.Tool)
|
||||||
title = fmt.Sprintf("%s %s", toolName, toolArgs)
|
title = fmt.Sprintf("%s %s", toolName, toolArgs)
|
||||||
|
|||||||
@@ -303,7 +303,9 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
|||||||
for _, part := range remainingParts {
|
for _, part := range remainingParts {
|
||||||
switch part := part.(type) {
|
switch part := part.(type) {
|
||||||
case opencode.FilePart:
|
case opencode.FilePart:
|
||||||
fileParts = append(fileParts, part)
|
if part.Source.Text.Start >= 0 && part.Source.Text.End >= part.Source.Text.Start {
|
||||||
|
fileParts = append(fileParts, part)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flexItems := []layout.FlexItem{}
|
flexItems := []layout.FlexItem{}
|
||||||
|
|||||||
@@ -0,0 +1,219 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://opencode.ai/theme.json",
|
||||||
|
"defs": {
|
||||||
|
"vesperBg": "#101010",
|
||||||
|
"vesperFg": "#FFF",
|
||||||
|
"vesperComment": "#8b8b8b94",
|
||||||
|
"vesperKeyword": "#A0A0A0",
|
||||||
|
"vesperFunction": "#FFC799",
|
||||||
|
"vesperString": "#99FFE4",
|
||||||
|
"vesperNumber": "#FFC799",
|
||||||
|
"vesperError": "#FF8080",
|
||||||
|
"vesperWarning": "#FFC799",
|
||||||
|
"vesperSuccess": "#99FFE4",
|
||||||
|
"vesperMuted": "#A0A0A0"
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"primary": {
|
||||||
|
"dark": "#FFC799",
|
||||||
|
"light": "#FFC799"
|
||||||
|
},
|
||||||
|
"secondary": {
|
||||||
|
"dark": "#99FFE4",
|
||||||
|
"light": "#99FFE4"
|
||||||
|
},
|
||||||
|
"accent": {
|
||||||
|
"dark": "#FFC799",
|
||||||
|
"light": "#FFC799"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"dark": "vesperError",
|
||||||
|
"light": "vesperError"
|
||||||
|
},
|
||||||
|
"warning": {
|
||||||
|
"dark": "vesperWarning",
|
||||||
|
"light": "vesperWarning"
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"dark": "vesperSuccess",
|
||||||
|
"light": "vesperSuccess"
|
||||||
|
},
|
||||||
|
"info": {
|
||||||
|
"dark": "#FFC799",
|
||||||
|
"light": "#FFC799"
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"dark": "vesperFg",
|
||||||
|
"light": "vesperBg"
|
||||||
|
},
|
||||||
|
"textMuted": {
|
||||||
|
"dark": "vesperMuted",
|
||||||
|
"light": "vesperMuted"
|
||||||
|
},
|
||||||
|
"background": {
|
||||||
|
"dark": "vesperBg",
|
||||||
|
"light": "#FFF"
|
||||||
|
},
|
||||||
|
"backgroundPanel": {
|
||||||
|
"dark": "vesperBg",
|
||||||
|
"light": "#F0F0F0"
|
||||||
|
},
|
||||||
|
"backgroundElement": {
|
||||||
|
"dark": "vesperBg",
|
||||||
|
"light": "#E0E0E0"
|
||||||
|
},
|
||||||
|
"border": {
|
||||||
|
"dark": "#282828",
|
||||||
|
"light": "#D0D0D0"
|
||||||
|
},
|
||||||
|
"borderActive": {
|
||||||
|
"dark": "#FFC799",
|
||||||
|
"light": "#FFC799"
|
||||||
|
},
|
||||||
|
"borderSubtle": {
|
||||||
|
"dark": "#1C1C1C",
|
||||||
|
"light": "#E8E8E8"
|
||||||
|
},
|
||||||
|
"diffAdded": {
|
||||||
|
"dark": "vesperSuccess",
|
||||||
|
"light": "vesperSuccess"
|
||||||
|
},
|
||||||
|
"diffRemoved": {
|
||||||
|
"dark": "vesperError",
|
||||||
|
"light": "vesperError"
|
||||||
|
},
|
||||||
|
"diffContext": {
|
||||||
|
"dark": "vesperMuted",
|
||||||
|
"light": "vesperMuted"
|
||||||
|
},
|
||||||
|
"diffHunkHeader": {
|
||||||
|
"dark": "vesperMuted",
|
||||||
|
"light": "vesperMuted"
|
||||||
|
},
|
||||||
|
"diffHighlightAdded": {
|
||||||
|
"dark": "vesperSuccess",
|
||||||
|
"light": "vesperSuccess"
|
||||||
|
},
|
||||||
|
"diffHighlightRemoved": {
|
||||||
|
"dark": "vesperError",
|
||||||
|
"light": "vesperError"
|
||||||
|
},
|
||||||
|
"diffAddedBg": {
|
||||||
|
"dark": "#0d2818",
|
||||||
|
"light": "#e8f5e8"
|
||||||
|
},
|
||||||
|
"diffRemovedBg": {
|
||||||
|
"dark": "#281a1a",
|
||||||
|
"light": "#f5e8e8"
|
||||||
|
},
|
||||||
|
"diffContextBg": {
|
||||||
|
"dark": "vesperBg",
|
||||||
|
"light": "#F8F8F8"
|
||||||
|
},
|
||||||
|
"diffLineNumber": {
|
||||||
|
"dark": "#505050",
|
||||||
|
"light": "#808080"
|
||||||
|
},
|
||||||
|
"diffAddedLineNumberBg": {
|
||||||
|
"dark": "#0d2818",
|
||||||
|
"light": "#e8f5e8"
|
||||||
|
},
|
||||||
|
"diffRemovedLineNumberBg": {
|
||||||
|
"dark": "#281a1a",
|
||||||
|
"light": "#f5e8e8"
|
||||||
|
},
|
||||||
|
"markdownText": {
|
||||||
|
"dark": "vesperFg",
|
||||||
|
"light": "vesperBg"
|
||||||
|
},
|
||||||
|
"markdownHeading": {
|
||||||
|
"dark": "#FFC799",
|
||||||
|
"light": "#FFC799"
|
||||||
|
},
|
||||||
|
"markdownLink": {
|
||||||
|
"dark": "#FFC799",
|
||||||
|
"light": "#FFC799"
|
||||||
|
},
|
||||||
|
"markdownLinkText": {
|
||||||
|
"dark": "vesperMuted",
|
||||||
|
"light": "vesperMuted"
|
||||||
|
},
|
||||||
|
"markdownCode": {
|
||||||
|
"dark": "vesperMuted",
|
||||||
|
"light": "vesperMuted"
|
||||||
|
},
|
||||||
|
"markdownBlockQuote": {
|
||||||
|
"dark": "vesperFg",
|
||||||
|
"light": "vesperBg"
|
||||||
|
},
|
||||||
|
"markdownEmph": {
|
||||||
|
"dark": "vesperFg",
|
||||||
|
"light": "vesperBg"
|
||||||
|
},
|
||||||
|
"markdownStrong": {
|
||||||
|
"dark": "vesperFg",
|
||||||
|
"light": "vesperBg"
|
||||||
|
},
|
||||||
|
"markdownHorizontalRule": {
|
||||||
|
"dark": "#65737E",
|
||||||
|
"light": "#65737E"
|
||||||
|
},
|
||||||
|
"markdownListItem": {
|
||||||
|
"dark": "vesperFg",
|
||||||
|
"light": "vesperBg"
|
||||||
|
},
|
||||||
|
"markdownListEnumeration": {
|
||||||
|
"dark": "vesperFg",
|
||||||
|
"light": "vesperBg"
|
||||||
|
},
|
||||||
|
"markdownImage": {
|
||||||
|
"dark": "#FFC799",
|
||||||
|
"light": "#FFC799"
|
||||||
|
},
|
||||||
|
"markdownImageText": {
|
||||||
|
"dark": "vesperMuted",
|
||||||
|
"light": "vesperMuted"
|
||||||
|
},
|
||||||
|
"markdownCodeBlock": {
|
||||||
|
"dark": "vesperFg",
|
||||||
|
"light": "vesperBg"
|
||||||
|
},
|
||||||
|
"syntaxComment": {
|
||||||
|
"dark": "vesperComment",
|
||||||
|
"light": "vesperComment"
|
||||||
|
},
|
||||||
|
"syntaxKeyword": {
|
||||||
|
"dark": "vesperKeyword",
|
||||||
|
"light": "vesperKeyword"
|
||||||
|
},
|
||||||
|
"syntaxFunction": {
|
||||||
|
"dark": "vesperFunction",
|
||||||
|
"light": "vesperFunction"
|
||||||
|
},
|
||||||
|
"syntaxVariable": {
|
||||||
|
"dark": "vesperFg",
|
||||||
|
"light": "vesperBg"
|
||||||
|
},
|
||||||
|
"syntaxString": {
|
||||||
|
"dark": "vesperString",
|
||||||
|
"light": "vesperString"
|
||||||
|
},
|
||||||
|
"syntaxNumber": {
|
||||||
|
"dark": "vesperNumber",
|
||||||
|
"light": "vesperNumber"
|
||||||
|
},
|
||||||
|
"syntaxType": {
|
||||||
|
"dark": "vesperFunction",
|
||||||
|
"light": "vesperFunction"
|
||||||
|
},
|
||||||
|
"syntaxOperator": {
|
||||||
|
"dark": "vesperKeyword",
|
||||||
|
"light": "vesperKeyword"
|
||||||
|
},
|
||||||
|
"syntaxPunctuation": {
|
||||||
|
"dark": "vesperFg",
|
||||||
|
"light": "vesperBg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
|
||||||
// README at: https://github.com/devcontainers/templates/tree/main/src/debian
|
|
||||||
{
|
|
||||||
"name": "Development",
|
|
||||||
"image": "mcr.microsoft.com/devcontainers/go:1.23-bookworm",
|
|
||||||
"postCreateCommand": "go mod tidy"
|
|
||||||
}
|
|
||||||
-49
@@ -1,49 +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-go' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
|
|
||||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version-file: ./go.mod
|
|
||||||
|
|
||||||
- name: Run lints
|
|
||||||
run: ./scripts/lint
|
|
||||||
test:
|
|
||||||
timeout-minutes: 10
|
|
||||||
name: test
|
|
||||||
runs-on: ${{ github.repository == 'stainless-sdks/opencode-go' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
|
|
||||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version-file: ./go.mod
|
|
||||||
|
|
||||||
- name: Bootstrap
|
|
||||||
run: ./scripts/bootstrap
|
|
||||||
|
|
||||||
- name: Run tests
|
|
||||||
run: ./scripts/test
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
.prism.log
|
|
||||||
codegen.log
|
|
||||||
Brewfile.lock.json
|
|
||||||
.idea/
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
".": "0.1.0-alpha.8"
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
configured_endpoints: 28
|
|
||||||
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-90f0ff2a2f214a34b74f49a5909e95c31f617bd9bb881da24ab3fe664424c79d.yml
|
|
||||||
openapi_spec_hash: 5ef69219c1869f78455b0c5374f638f8
|
|
||||||
config_hash: 7707d73ebbd7ad7042ab70466b39348d
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user