mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-03 08:46:15 -04:00
Compare commits
92 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c2f487906a | |||
| ae78ec7a0c | |||
| e8c03f13dd | |||
| f85d30c484 | |||
| 1bac46612c | |||
| 9ab3462821 | |||
| 3b36822696 | |||
| 5b731479d5 | |||
| a50bef6913 | |||
| ed397c5057 | |||
| c9187a9f3a | |||
| 2c67b26b5d | |||
| 170b94a99e | |||
| cd58f10e3c | |||
| ea85fdf3cd | |||
| edda26ab33 | |||
| ea4e1913c0 | |||
| 5eebc8ab51 | |||
| 21c52fd5cb | |||
| 5e8634afaf | |||
| d4bac5cdbd | |||
| 263b266476 | |||
| 06830327e7 | |||
| 4b204fee58 | |||
| 99d3a0bb24 | |||
| 0930f6ac55 | |||
| 24515162fa | |||
| 53aa899e45 | |||
| 7e763e1c06 | |||
| b0f2cc0c22 | |||
| f90aa62784 | |||
| 852191f6cb | |||
| c5e9dc081c | |||
| 49c8889228 | |||
| f739e1a958 | |||
| 841f1907bb | |||
| 9255c507d6 | |||
| 2711047166 | |||
| 908048baef | |||
| a9fbe07408 | |||
| 0ae213ee0e | |||
| ca031278ca | |||
| ae6e47bb42 | |||
| 42a5fcead4 | |||
| 8ad83f71a9 | |||
| fa95c09cdc | |||
| 0b132c032a | |||
| 44d7103a42 | |||
| 8f45a0e227 | |||
| 6581741318 | |||
| 80d68d01f4 | |||
| fa9db3c167 | |||
| 5a727c0794 | |||
| 71cd84dbbb | |||
| e1b7e25f4d | |||
| 98b6bb218b | |||
| 5592ce8eaf | |||
| 510fe8a72a | |||
| 04a1ab3893 | |||
| e74b4d098b | |||
| 50e4b3e6a7 | |||
| 6ebd828aa5 | |||
| 022c979d28 | |||
| 4172e3ad28 | |||
| 90d1698aed | |||
| b0c38ce56b | |||
| 9b37d0e191 | |||
| ea794a4bf6 | |||
| 52f9b37576 | |||
| a0d2e53bde | |||
| 851e900982 | |||
| 3aa6eeb426 | |||
| b6ee8e92f9 | |||
| 44211e1526 | |||
| 12f84f198f | |||
| e6db1cf29d | |||
| f07f04d969 | |||
| 33d613a470 | |||
| 0bbd7ea17b | |||
| 87f3166437 | |||
| 7665bd9439 | |||
| 30e10127f2 | |||
| 5e66fc2318 | |||
| c1c99c7e0f | |||
| 04e3e83db3 | |||
| 4273714a62 | |||
| a21e237706 | |||
| aa9105649d | |||
| 53be288040 | |||
| 13dbf912ca | |||
| 69966c73f8 | |||
| a00de2df08 |
@@ -7,8 +7,10 @@ on:
|
||||
jobs:
|
||||
opencode:
|
||||
if: |
|
||||
contains(github.event.comment.body, '/oc') ||
|
||||
contains(github.event.comment.body, '/opencode')
|
||||
contains(github.event.comment.body, ' /oc') ||
|
||||
startsWith(github.event.comment.body, '/oc') ||
|
||||
contains(github.event.comment.body, ' /opencode') ||
|
||||
startsWith(github.event.comment.body, '/opencode')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -24,4 +26,4 @@ jobs:
|
||||
env:
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
with:
|
||||
model: anthropic/claude-sonnet-4-20250514
|
||||
model: anthropic/claude-sonnet-4-20250514
|
||||
|
||||
@@ -39,6 +39,15 @@ jobs:
|
||||
with:
|
||||
bun-version: 1.2.19
|
||||
|
||||
- name: Cache ~/.bun
|
||||
id: cache-bun
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.bun
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
|
||||
- name: Install makepkg
|
||||
run: |
|
||||
sudo apt-get update
|
||||
@@ -53,9 +62,11 @@ jobs:
|
||||
git config --global user.email "opencode@sst.dev"
|
||||
git config --global user.name "opencode"
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Publish
|
||||
run: |
|
||||
bun install
|
||||
OPENCODE_VERSION=${{ inputs.version }} ./script/publish.ts
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
|
||||
|
||||
@@ -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
|
||||
.vscode
|
||||
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,35 +1,40 @@
|
||||
# Download Stats
|
||||
|
||||
| Date | GitHub Downloads | npm Downloads | Total |
|
||||
| ---------- | ---------------- | ---------------- | ----------------- |
|
||||
| 2025-06-29 | 18,789 (+0) | 39,420 (+0) | 58,209 (+0) |
|
||||
| 2025-06-30 | 20,127 (+1,338) | 41,059 (+1,639) | 61,186 (+2,977) |
|
||||
| 2025-07-01 | 22,108 (+1,981) | 43,745 (+2,686) | 65,853 (+4,667) |
|
||||
| 2025-07-02 | 24,814 (+2,706) | 46,168 (+2,423) | 70,982 (+5,129) |
|
||||
| 2025-07-03 | 27,834 (+3,020) | 49,955 (+3,787) | 77,789 (+6,807) |
|
||||
| 2025-07-04 | 30,608 (+2,774) | 54,758 (+4,803) | 85,366 (+7,577) |
|
||||
| 2025-07-05 | 32,524 (+1,916) | 58,371 (+3,613) | 90,895 (+5,529) |
|
||||
| 2025-07-06 | 33,766 (+1,242) | 59,694 (+1,323) | 93,460 (+2,565) |
|
||||
| 2025-07-08 | 38,052 (+4,286) | 64,468 (+4,774) | 102,520 (+9,060) |
|
||||
| 2025-07-10 | 43,796 (+5,744) | 71,402 (+6,934) | 115,198 (+12,678) |
|
||||
| 2025-07-11 | 46,982 (+3,186) | 77,462 (+6,060) | 124,444 (+9,246) |
|
||||
| 2025-07-12 | 49,302 (+2,320) | 82,177 (+4,715) | 131,479 (+7,035) |
|
||||
| 2025-07-13 | 50,803 (+1,501) | 86,394 (+4,217) | 137,197 (+5,718) |
|
||||
| 2025-07-14 | 53,283 (+2,480) | 87,860 (+1,466) | 141,143 (+3,946) |
|
||||
| 2025-07-15 | 57,590 (+4,307) | 91,036 (+3,176) | 148,626 (+7,483) |
|
||||
| 2025-07-16 | 62,313 (+4,723) | 95,258 (+4,222) | 157,571 (+8,945) |
|
||||
| 2025-07-17 | 66,684 (+4,371) | 100,048 (+4,790) | 166,732 (+9,161) |
|
||||
| 2025-07-18 | 70,379 (+3,695) | 102,587 (+2,539) | 172,966 (+6,234) |
|
||||
| 2025-07-19 | 73,497 (+3,117) | 105,904 (+3,317) | 179,401 (+6,434) |
|
||||
| 2025-07-20 | 76,453 (+2,956) | 109,044 (+3,140) | 185,497 (+6,096) |
|
||||
| 2025-07-21 | 80,197 (+3,744) | 113,537 (+4,493) | 193,734 (+8,237) |
|
||||
| 2025-07-22 | 84,251 (+4,054) | 118,073 (+4,536) | 202,324 (+8,590) |
|
||||
| 2025-07-23 | 88,589 (+4,338) | 121,436 (+3,363) | 210,025 (+7,701) |
|
||||
| 2025-07-24 | 92,469 (+3,880) | 124,091 (+2,655) | 216,560 (+6,535) |
|
||||
| 2025-07-25 | 96,417 (+3,948) | 126,985 (+2,894) | 223,402 (+6,842) |
|
||||
| 2025-07-26 | 100,646 (+4,229) | 131,411 (+4,426) | 232,057 (+8,655) |
|
||||
| 2025-07-27 | 102,644 (+1,998) | 134,736 (+3,325) | 237,380 (+5,323) |
|
||||
| 2025-07-28 | 105,446 (+2,802) | 136,016 (+1,280) | 241,462 (+4,082) |
|
||||
| 2025-07-29 | 108,998 (+3,552) | 137,542 (+1,526) | 246,540 (+5,078) |
|
||||
| 2025-07-30 | 113,544 (+4,546) | 140,317 (+2,775) | 253,861 (+7,321) |
|
||||
| 2025-07-31 | 118,339 (+4,795) | 143,344 (+3,027) | 261,683 (+7,822) |
|
||||
| Date | GitHub Downloads | npm Downloads | Total |
|
||||
| ---------- | ---------------- | ---------------- | ---------------- |
|
||||
| 2025-06-29 | 18,789 (+0) | 39,420 (+0) | 58,209 (+0) |
|
||||
| 2025-06-30 | 20,127 (+1,338) | 41,059 (+1,639) | 61,186 (+2,977) |
|
||||
| 2025-07-01 | 22,108 (+1,981) | 43,745 (+2,686) | 65,853 (+4,667) |
|
||||
| 2025-07-02 | 24,814 (+2,706) | 46,168 (+2,423) | 70,982 (+5,129) |
|
||||
| 2025-07-03 | 27,834 (+3,020) | 49,955 (+3,787) | 77,789 (+6,807) |
|
||||
| 2025-07-04 | 30,608 (+2,774) | 54,758 (+4,803) | 85,366 (+7,577) |
|
||||
| 2025-07-05 | 32,524 (+1,916) | 58,371 (+3,613) | 90,895 (+5,529) |
|
||||
| 2025-07-06 | 33,766 (+1,242) | 59,694 (+1,323) | 93,460 (+2,565) |
|
||||
| 2025-07-08 | 38,052 (+4,286) | 64,468 (+4,774) | 102,520 (+9,060) |
|
||||
| 2025-07-09 | 40,924 (+2,872) | 67,935 (+3,467) | 108,859 (+6,339) |
|
||||
| 2025-07-10 | 43,796 (+2,872) | 71,402 (+3,467) | 115,198 (+6,339) |
|
||||
| 2025-07-11 | 46,982 (+3,186) | 77,462 (+6,060) | 124,444 (+9,246) |
|
||||
| 2025-07-12 | 49,302 (+2,320) | 82,177 (+4,715) | 131,479 (+7,035) |
|
||||
| 2025-07-13 | 50,803 (+1,501) | 86,394 (+4,217) | 137,197 (+5,718) |
|
||||
| 2025-07-14 | 53,283 (+2,480) | 87,860 (+1,466) | 141,143 (+3,946) |
|
||||
| 2025-07-15 | 57,590 (+4,307) | 91,036 (+3,176) | 148,626 (+7,483) |
|
||||
| 2025-07-16 | 62,313 (+4,723) | 95,258 (+4,222) | 157,571 (+8,945) |
|
||||
| 2025-07-17 | 66,684 (+4,371) | 100,048 (+4,790) | 166,732 (+9,161) |
|
||||
| 2025-07-18 | 70,379 (+3,695) | 102,587 (+2,539) | 172,966 (+6,234) |
|
||||
| 2025-07-19 | 73,497 (+3,117) | 105,904 (+3,317) | 179,401 (+6,434) |
|
||||
| 2025-07-20 | 76,453 (+2,956) | 109,044 (+3,140) | 185,497 (+6,096) |
|
||||
| 2025-07-21 | 80,197 (+3,744) | 113,537 (+4,493) | 193,734 (+8,237) |
|
||||
| 2025-07-22 | 84,251 (+4,054) | 118,073 (+4,536) | 202,324 (+8,590) |
|
||||
| 2025-07-23 | 88,589 (+4,338) | 121,436 (+3,363) | 210,025 (+7,701) |
|
||||
| 2025-07-24 | 92,469 (+3,880) | 124,091 (+2,655) | 216,560 (+6,535) |
|
||||
| 2025-07-25 | 96,417 (+3,948) | 126,985 (+2,894) | 223,402 (+6,842) |
|
||||
| 2025-07-26 | 100,646 (+4,229) | 131,411 (+4,426) | 232,057 (+8,655) |
|
||||
| 2025-07-27 | 102,644 (+1,998) | 134,736 (+3,325) | 237,380 (+5,323) |
|
||||
| 2025-07-28 | 105,446 (+2,802) | 136,016 (+1,280) | 241,462 (+4,082) |
|
||||
| 2025-07-29 | 108,998 (+3,552) | 137,542 (+1,526) | 246,540 (+5,078) |
|
||||
| 2025-07-30 | 113,544 (+4,546) | 140,317 (+2,775) | 253,861 (+7,321) |
|
||||
| 2025-07-31 | 118,339 (+4,795) | 143,344 (+3,027) | 261,683 (+7,822) |
|
||||
| 2025-08-01 | 123,539 (+5,200) | 146,680 (+3,336) | 270,219 (+8,536) |
|
||||
| 2025-08-02 | 127,864 (+4,325) | 149,236 (+2,556) | 277,100 (+6,881) |
|
||||
| 2025-08-03 | 131,397 (+3,533) | 150,451 (+1,215) | 281,848 (+4,748) |
|
||||
| 2025-08-04 | 136,266 (+4,869) | 153,260 (+2,809) | 289,526 (+7,678) |
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode/function",
|
||||
"version": "0.0.1",
|
||||
"version": "0.3.126",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "22.0.0",
|
||||
@@ -25,19 +25,21 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"version": "0.0.0",
|
||||
"version": "0.3.126",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": "1.11.1",
|
||||
"@actions/github": "6.0.1",
|
||||
"@clack/prompts": "0.11.0",
|
||||
"@clack/prompts": "1.0.0-alpha.1",
|
||||
"@hono/zod-validator": "0.4.2",
|
||||
"@modelcontextprotocol/sdk": "1.15.1",
|
||||
"@octokit/graphql": "9.0.1",
|
||||
"@octokit/rest": "22.0.0",
|
||||
"@openauthjs/openauth": "0.4.3",
|
||||
"@opencode-ai/plugin": "workspace:*",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@standard-schema/spec": "1.0.0",
|
||||
"@zip.js/zip.js": "2.7.62",
|
||||
"ai": "catalog:",
|
||||
@@ -48,6 +50,7 @@
|
||||
"hono-openapi": "0.4.8",
|
||||
"isomorphic-git": "1.32.1",
|
||||
"jsonc-parser": "3.3.1",
|
||||
"minimatch": "10.0.3",
|
||||
"open": "10.1.2",
|
||||
"remeda": "catalog:",
|
||||
"tree-sitter": "0.22.4",
|
||||
@@ -73,9 +76,19 @@
|
||||
"zod-to-json-schema": "3.24.5",
|
||||
},
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "0.3.126",
|
||||
"devDependencies": {
|
||||
"@hey-api/openapi-ts": "0.80.1",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@tsconfig/node22": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
},
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "0.0.0",
|
||||
"version": "0.3.126",
|
||||
"devDependencies": {
|
||||
"@hey-api/openapi-ts": "0.80.1",
|
||||
"@tsconfig/node22": "catalog:",
|
||||
@@ -84,7 +97,7 @@
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode/web",
|
||||
"version": "0.0.1",
|
||||
"version": "0.3.126",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "^12.5.4",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
@@ -119,10 +132,13 @@
|
||||
"sharp",
|
||||
"esbuild",
|
||||
],
|
||||
"patchedDependencies": {
|
||||
"marked-shiki@1.2.0": "patches/marked-shiki@1.2.0.patch",
|
||||
},
|
||||
"catalog": {
|
||||
"@tsconfig/node22": "22.0.2",
|
||||
"@types/node": "22.13.9",
|
||||
"ai": "5.0.0-beta.33",
|
||||
"ai": "5.0.0-beta.34",
|
||||
"hono": "4.7.10",
|
||||
"remeda": "2.26.0",
|
||||
"typescript": "5.8.2",
|
||||
@@ -143,11 +159,11 @@
|
||||
|
||||
"@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/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/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/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": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
|
||||
|
||||
@@ -221,9 +237,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=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
@@ -355,6 +371,10 @@
|
||||
|
||||
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.33.5", "", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="],
|
||||
|
||||
"@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="],
|
||||
|
||||
"@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="],
|
||||
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.12", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg=="],
|
||||
|
||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||
@@ -411,6 +431,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=="],
|
||||
|
||||
"@opencode-ai/plugin": ["@opencode-ai/plugin@workspace:packages/plugin"],
|
||||
|
||||
"@opencode-ai/sdk": ["@opencode-ai/sdk@workspace:packages/sdk/js"],
|
||||
|
||||
"@opencode/function": ["@opencode/function@workspace:packages/function"],
|
||||
@@ -589,7 +611,7 @@
|
||||
|
||||
"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=="],
|
||||
|
||||
@@ -1271,6 +1293,8 @@
|
||||
|
||||
"miniflare": ["miniflare@4.20250730.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "^7.10.0", "workerd": "1.20250730.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-avGXBStHQSqcJr8ra1mJ3/OQvnLZ49B1uAILQapAha1DHNZZvXWLIgUVre/WGY6ZOlNGFPh5CJ+dXLm4yuV3Jw=="],
|
||||
|
||||
"minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="],
|
||||
|
||||
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
|
||||
|
||||
"minimisted": ["minimisted@2.0.1", "", { "dependencies": { "minimist": "^1.2.5" } }, "sha512-1oPjfuLQa2caorJUM8HV8lGgWCc0qqAO1MNv/k05G4qslmsndV/5WdNZrqCiyqiz3wohia2Ij2B7w2Dr7/IyrA=="],
|
||||
|
||||
@@ -1,24 +1,5 @@
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"provider": {
|
||||
"openrouter": {
|
||||
"models": {
|
||||
"moonshotai/kimi-k2": {
|
||||
"options": {
|
||||
"provider": {
|
||||
"order": ["baseten"],
|
||||
"allow_fallbacks": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"huggingface": {
|
||||
"models": {
|
||||
"Qwen/Qwen3-235B-A22B-Instruct-2507:fireworks-ai": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mcp": {
|
||||
"context7": {
|
||||
"type": "remote",
|
||||
@@ -28,9 +9,5 @@
|
||||
"type": "local",
|
||||
"command": ["opencode", "x", "@h1deya/mcp-server-weather"]
|
||||
}
|
||||
},
|
||||
"permission": {
|
||||
"edit": "ask",
|
||||
"bash": "ask"
|
||||
}
|
||||
}
|
||||
|
||||
+5
-3
@@ -5,7 +5,7 @@
|
||||
"type": "module",
|
||||
"packageManager": "bun@1.2.14",
|
||||
"scripts": {
|
||||
"dev": "bun run packages/opencode/src/index.ts",
|
||||
"dev": "bun run --conditions=development packages/opencode/src/index.ts",
|
||||
"typecheck": "bun run --filter='*' typecheck",
|
||||
"stainless": "./scripts/stainless",
|
||||
"postinstall": "./script/hooks"
|
||||
@@ -18,7 +18,7 @@
|
||||
"catalog": {
|
||||
"@types/node": "22.13.9",
|
||||
"@tsconfig/node22": "22.0.2",
|
||||
"ai": "5.0.0-beta.33",
|
||||
"ai": "5.0.0-beta.34",
|
||||
"hono": "4.7.10",
|
||||
"typescript": "5.8.2",
|
||||
"zod": "3.25.49",
|
||||
@@ -43,5 +43,7 @@
|
||||
"protobufjs",
|
||||
"sharp"
|
||||
],
|
||||
"patchedDependencies": {}
|
||||
"patchedDependencies": {
|
||||
"marked-shiki@1.2.0": "patches/marked-shiki@1.2.0.patch"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/function",
|
||||
"version": "0.0.1",
|
||||
"version": "0.3.126",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "0.0.0",
|
||||
"version": "0.3.126",
|
||||
"name": "opencode",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit",
|
||||
"dev": "bun run ./src/index.ts"
|
||||
"dev": "bun run --conditions=development ./src/index.ts"
|
||||
},
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode"
|
||||
@@ -30,12 +30,14 @@
|
||||
"dependencies": {
|
||||
"@actions/core": "1.11.1",
|
||||
"@actions/github": "6.0.1",
|
||||
"@clack/prompts": "0.11.0",
|
||||
"@clack/prompts": "1.0.0-alpha.1",
|
||||
"@hono/zod-validator": "0.4.2",
|
||||
"@modelcontextprotocol/sdk": "1.15.1",
|
||||
"@octokit/graphql": "9.0.1",
|
||||
"@octokit/rest": "22.0.0",
|
||||
"@openauthjs/openauth": "0.4.3",
|
||||
"@opencode-ai/plugin": "workspace:*",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@standard-schema/spec": "1.0.0",
|
||||
"@zip.js/zip.js": "2.7.62",
|
||||
"ai": "catalog:",
|
||||
@@ -46,11 +48,12 @@
|
||||
"hono-openapi": "0.4.8",
|
||||
"isomorphic-git": "1.32.1",
|
||||
"jsonc-parser": "3.3.1",
|
||||
"minimatch": "10.0.3",
|
||||
"open": "10.1.2",
|
||||
"remeda": "catalog:",
|
||||
"turndown": "7.2.0",
|
||||
"tree-sitter": "0.22.4",
|
||||
"tree-sitter-bash": "0.23.3",
|
||||
"turndown": "7.2.0",
|
||||
"vscode-jsonrpc": "8.2.1",
|
||||
"xdg-basedir": "5.1.0",
|
||||
"yargs": "18.0.0",
|
||||
|
||||
@@ -104,6 +104,7 @@ if (!snapshot) {
|
||||
.filter((x: string) => {
|
||||
const lower = x.toLowerCase()
|
||||
return (
|
||||
!lower.includes("release:") &&
|
||||
!lower.includes("ignore:") &&
|
||||
!lower.includes("chore:") &&
|
||||
!lower.includes("ci:") &&
|
||||
|
||||
@@ -2,6 +2,7 @@ import { App } from "../app/app"
|
||||
import { ConfigHooks } from "../config/hooks"
|
||||
import { Format } from "../format"
|
||||
import { LSP } from "../lsp"
|
||||
import { Plugin } from "../plugin"
|
||||
import { Share } from "../share/share"
|
||||
import { Snapshot } from "../snapshot"
|
||||
|
||||
@@ -9,6 +10,7 @@ export async function bootstrap<T>(input: App.Input, cb: (app: App.Info) => Prom
|
||||
return App.provide(input, async (app) => {
|
||||
Share.init()
|
||||
Format.init()
|
||||
Plugin.init()
|
||||
ConfigHooks.init()
|
||||
LSP.init()
|
||||
Snapshot.init()
|
||||
|
||||
@@ -39,7 +39,7 @@ const AgentCreateCommand = cmd({
|
||||
const query = await prompts.text({
|
||||
message: "Description",
|
||||
placeholder: "What should this agent do?",
|
||||
validate: (x) => (x.length > 0 ? undefined : "Required"),
|
||||
validate: (x) => x && (x.length > 0 ? undefined : "Required"),
|
||||
})
|
||||
if (prompts.isCancel(query)) throw new UI.CancelledError()
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ export const AuthLoginCommand = cmd({
|
||||
type: "string",
|
||||
}),
|
||||
async handler(args) {
|
||||
UI.empty()
|
||||
prompts.intro("Add credential")
|
||||
if (args.url) {
|
||||
const wellknown = await fetch(`${args.url}/.well-known/opencode`).then((x) => x.json())
|
||||
@@ -99,7 +100,7 @@ export const AuthLoginCommand = cmd({
|
||||
prompts.outro("Done")
|
||||
return
|
||||
}
|
||||
UI.empty()
|
||||
await ModelsDev.refresh().catch(() => {})
|
||||
const providers = await ModelsDev.get()
|
||||
const priority: Record<string, number> = {
|
||||
anthropic: 0,
|
||||
@@ -109,7 +110,7 @@ export const AuthLoginCommand = cmd({
|
||||
openrouter: 4,
|
||||
vercel: 5,
|
||||
}
|
||||
let provider = await prompts.select({
|
||||
let provider = await prompts.autocomplete({
|
||||
message: "Select provider",
|
||||
maxItems: 8,
|
||||
options: [
|
||||
@@ -138,7 +139,7 @@ export const AuthLoginCommand = cmd({
|
||||
if (provider === "other") {
|
||||
provider = await prompts.text({
|
||||
message: "Enter provider id",
|
||||
validate: (x) => (x.match(/^[0-9a-z-]+$/) ? undefined : "a-z, 0-9 and hyphens only"),
|
||||
validate: (x) => x && (x.match(/^[0-9a-z-]+$/) ? undefined : "a-z, 0-9 and hyphens only"),
|
||||
})
|
||||
if (prompts.isCancel(provider)) throw new UI.CancelledError()
|
||||
provider = provider.replace(/^@ai-sdk\//, "")
|
||||
@@ -192,7 +193,7 @@ export const AuthLoginCommand = cmd({
|
||||
|
||||
const code = await prompts.text({
|
||||
message: "Paste the authorization code here: ",
|
||||
validate: (x) => (x.length > 0 ? undefined : "Required"),
|
||||
validate: (x) => x && (x.length > 0 ? undefined : "Required"),
|
||||
})
|
||||
if (prompts.isCancel(code)) throw new UI.CancelledError()
|
||||
|
||||
@@ -228,7 +229,7 @@ export const AuthLoginCommand = cmd({
|
||||
|
||||
const code = await prompts.text({
|
||||
message: "Paste the authorization code here: ",
|
||||
validate: (x) => (x.length > 0 ? undefined : "Required"),
|
||||
validate: (x) => x && (x.length > 0 ? undefined : "Required"),
|
||||
})
|
||||
if (prompts.isCancel(code)) throw new UI.CancelledError()
|
||||
|
||||
@@ -301,7 +302,7 @@ export const AuthLoginCommand = cmd({
|
||||
|
||||
const key = await prompts.password({
|
||||
message: "Enter your API key",
|
||||
validate: (x) => (x.length > 0 ? undefined : "Required"),
|
||||
validate: (x) => x && (x.length > 0 ? undefined : "Required"),
|
||||
})
|
||||
if (prompts.isCancel(key)) throw new UI.CancelledError()
|
||||
await Auth.set(provider, {
|
||||
|
||||
@@ -318,8 +318,10 @@ on:
|
||||
jobs:
|
||||
opencode:
|
||||
if: |
|
||||
contains(github.event.comment.body, '/oc') ||
|
||||
contains(github.event.comment.body, '/opencode')
|
||||
contains(github.event.comment.body, ' /oc') ||
|
||||
startsWith(github.event.comment.body, '/oc') ||
|
||||
contains(github.event.comment.body, ' /opencode') ||
|
||||
startsWith(github.event.comment.body, '/opencode')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -784,7 +786,7 @@ export const GithubRunCommand = cmd({
|
||||
console.log("Pushing to new branch...")
|
||||
await $`git add .`
|
||||
await $`git commit -m "${summary}
|
||||
|
||||
|
||||
Co-authored-by: ${actor} <${actor}@users.noreply.github.com>"`
|
||||
await $`git push -u origin ${branch}`
|
||||
}
|
||||
@@ -793,7 +795,7 @@ Co-authored-by: ${actor} <${actor}@users.noreply.github.com>"`
|
||||
console.log("Pushing to local branch...")
|
||||
await $`git add .`
|
||||
await $`git commit -m "${summary}
|
||||
|
||||
|
||||
Co-authored-by: ${actor} <${actor}@users.noreply.github.com>"`
|
||||
await $`git push`
|
||||
}
|
||||
@@ -805,7 +807,7 @@ Co-authored-by: ${actor} <${actor}@users.noreply.github.com>"`
|
||||
|
||||
await $`git add .`
|
||||
await $`git commit -m "${summary}
|
||||
|
||||
|
||||
Co-authored-by: ${actor} <${actor}@users.noreply.github.com>"`
|
||||
await $`git push fork HEAD:${remoteBranch}`
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ export const McpAddCommand = cmd({
|
||||
|
||||
const name = await prompts.text({
|
||||
message: "Enter MCP server name",
|
||||
validate: (x) => (x.length > 0 ? undefined : "Required"),
|
||||
validate: (x) => x && (x.length > 0 ? undefined : "Required"),
|
||||
})
|
||||
if (prompts.isCancel(name)) throw new UI.CancelledError()
|
||||
|
||||
@@ -44,7 +44,7 @@ export const McpAddCommand = cmd({
|
||||
const command = await prompts.text({
|
||||
message: "Enter command to run",
|
||||
placeholder: "e.g., opencode x @modelcontextprotocol/server-filesystem",
|
||||
validate: (x) => (x.length > 0 ? undefined : "Required"),
|
||||
validate: (x) => x && (x.length > 0 ? undefined : "Required"),
|
||||
})
|
||||
if (prompts.isCancel(command)) throw new UI.CancelledError()
|
||||
|
||||
@@ -58,6 +58,7 @@ export const McpAddCommand = cmd({
|
||||
message: "Enter MCP server URL",
|
||||
placeholder: "e.g., https://example.com/mcp",
|
||||
validate: (x) => {
|
||||
if (!x) return "Required"
|
||||
if (x.length === 0) return "Required"
|
||||
const isValid = URL.canParse(x)
|
||||
return isValid ? undefined : "Invalid URL"
|
||||
|
||||
@@ -32,7 +32,7 @@ export const UpgradeCommand = {
|
||||
return
|
||||
}
|
||||
prompts.log.info("Using method: " + method)
|
||||
const target = args.target ?? (await Installation.latest())
|
||||
const target = args.target ? args.target.replace(/^v/, "") : await Installation.latest()
|
||||
|
||||
if (Installation.VERSION === target) {
|
||||
prompts.log.warn(`opencode upgrade skipped: ${target} is already installed`)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Log } from "../util/log"
|
||||
import path from "path"
|
||||
import os from "os"
|
||||
import { z } from "zod"
|
||||
import { App } from "../app/app"
|
||||
import { Filesystem } from "../util/filesystem"
|
||||
@@ -23,13 +24,13 @@ export namespace Config {
|
||||
for (const file of ["opencode.jsonc", "opencode.json"]) {
|
||||
const found = await Filesystem.findUp(file, app.path.cwd, app.path.root)
|
||||
for (const resolved of found.toReversed()) {
|
||||
result = mergeDeep(result, await load(resolved))
|
||||
result = mergeDeep(result, await loadFile(resolved))
|
||||
}
|
||||
}
|
||||
|
||||
// Override with custom config if provided
|
||||
if (Flag.OPENCODE_CONFIG) {
|
||||
result = mergeDeep(result, await load(Flag.OPENCODE_CONFIG))
|
||||
result = mergeDeep(result, await loadFile(Flag.OPENCODE_CONFIG))
|
||||
log.debug("loaded custom config", { path: Flag.OPENCODE_CONFIG })
|
||||
}
|
||||
|
||||
@@ -37,7 +38,7 @@ export namespace Config {
|
||||
if (value.type === "wellknown") {
|
||||
process.env[value.key] = value.token
|
||||
const wellknown = await fetch(`${key}/.well-known/opencode`).then((x) => x.json())
|
||||
result = mergeDeep(result, await loadRaw(JSON.stringify(wellknown.config ?? {}), process.cwd()))
|
||||
result = mergeDeep(result, await load(JSON.stringify(wellknown.config ?? {}), process.cwd()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +93,14 @@ export namespace Config {
|
||||
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
|
||||
if (result.autoshare === true && !result.share) {
|
||||
result.share = "auto"
|
||||
@@ -144,6 +153,7 @@ export namespace Config {
|
||||
.object({
|
||||
model: z.string().optional(),
|
||||
temperature: z.number().optional(),
|
||||
top_p: z.number().optional(),
|
||||
prompt: z.string().optional(),
|
||||
tools: z.record(z.string(), z.boolean()).optional(),
|
||||
disable: z.boolean().optional(),
|
||||
@@ -222,6 +232,7 @@ export namespace Config {
|
||||
$schema: z.string().optional().describe("JSON schema reference for configuration validation"),
|
||||
theme: z.string().optional().describe("Theme name to use for the interface"),
|
||||
keybinds: Keybinds.optional().describe("Custom keybind configurations"),
|
||||
plugin: z.string().array().optional(),
|
||||
share: z
|
||||
.enum(["manual", "auto", "disabled"])
|
||||
.optional()
|
||||
@@ -278,6 +289,34 @@ export namespace Config {
|
||||
.optional()
|
||||
.describe("Custom provider configurations and model overrides"),
|
||||
mcp: z.record(z.string(), Mcp).optional().describe("MCP (Model Context Protocol) server configurations"),
|
||||
formatter: z
|
||||
.record(
|
||||
z.string(),
|
||||
z.object({
|
||||
disabled: z.boolean().optional(),
|
||||
command: z.array(z.string()).optional(),
|
||||
environment: z.record(z.string(), z.string()).optional(),
|
||||
extensions: z.array(z.string()).optional(),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
lsp: z
|
||||
.record(
|
||||
z.string(),
|
||||
z.union([
|
||||
z.object({
|
||||
disabled: z.literal(true),
|
||||
}),
|
||||
z.object({
|
||||
command: z.array(z.string()),
|
||||
extensions: z.array(z.string()).optional(),
|
||||
disabled: z.boolean().optional(),
|
||||
env: z.record(z.string(), z.string()).optional(),
|
||||
initialization: z.record(z.string(), z.any()).optional(),
|
||||
}),
|
||||
]),
|
||||
)
|
||||
.optional(),
|
||||
instructions: z.array(z.string()).optional().describe("Additional instruction files or patterns to include"),
|
||||
layout: Layout.optional().describe("@deprecated Always uses stretch layout."),
|
||||
permission: z
|
||||
@@ -323,9 +362,9 @@ export namespace Config {
|
||||
export const global = lazy(async () => {
|
||||
let result: Info = pipe(
|
||||
{},
|
||||
mergeDeep(await load(path.join(Global.Path.config, "config.json"))),
|
||||
mergeDeep(await load(path.join(Global.Path.config, "opencode.json"))),
|
||||
mergeDeep(await load(path.join(Global.Path.config, "opencode.jsonc"))),
|
||||
mergeDeep(await loadFile(path.join(Global.Path.config, "config.json"))),
|
||||
mergeDeep(await loadFile(path.join(Global.Path.config, "opencode.json"))),
|
||||
mergeDeep(await loadFile(path.join(Global.Path.config, "opencode.jsonc"))),
|
||||
)
|
||||
|
||||
await import(path.join(Global.Path.config, "config"), {
|
||||
@@ -346,46 +385,65 @@ export namespace Config {
|
||||
return result
|
||||
})
|
||||
|
||||
async function load(configPath: string): Promise<Info> {
|
||||
let text = await Bun.file(configPath)
|
||||
async function loadFile(filepath: string): Promise<Info> {
|
||||
log.info("loading", { path: filepath })
|
||||
let text = await Bun.file(filepath)
|
||||
.text()
|
||||
.catch((err) => {
|
||||
if (err.code === "ENOENT") return
|
||||
throw new JsonError({ path: configPath }, { cause: err })
|
||||
throw new JsonError({ path: filepath }, { cause: err })
|
||||
})
|
||||
if (!text) return {}
|
||||
return loadRaw(text, configPath)
|
||||
return load(text, filepath)
|
||||
}
|
||||
|
||||
async function loadRaw(text: string, configPath: string) {
|
||||
async function load(text: string, filepath: string) {
|
||||
text = text.replace(/\{env:([^}]+)\}/g, (_, varName) => {
|
||||
return process.env[varName] || ""
|
||||
})
|
||||
|
||||
const fileMatches = text.match(/"?\{file:([^}]+)\}"?/g)
|
||||
const fileMatches = text.match(/\{file:[^}]+\}/g)
|
||||
if (fileMatches) {
|
||||
const configDir = path.dirname(configPath)
|
||||
const configDir = path.dirname(filepath)
|
||||
const lines = text.split("\n")
|
||||
|
||||
for (const match of fileMatches) {
|
||||
const filePath = match.replace(/^"?\{file:/, "").replace(/\}"?$/, "")
|
||||
const lineIndex = lines.findIndex((line) => line.includes(match))
|
||||
if (lineIndex !== -1 && lines[lineIndex].trim().startsWith("//")) {
|
||||
continue // Skip if line is commented
|
||||
}
|
||||
let filePath = match.replace(/^\{file:/, "").replace(/\}$/, "")
|
||||
if (filePath.startsWith("~/")) {
|
||||
filePath = path.join(os.homedir(), filePath.slice(2))
|
||||
}
|
||||
const resolvedPath = path.isAbsolute(filePath) ? filePath : path.resolve(configDir, filePath)
|
||||
const fileContent = await Bun.file(resolvedPath).text()
|
||||
text = text.replace(match, JSON.stringify(fileContent))
|
||||
const fileContent = (await Bun.file(resolvedPath).text()).trim()
|
||||
// escape newlines/quotes, strip outer quotes
|
||||
text = text.replace(match, JSON.stringify(fileContent).slice(1, -1))
|
||||
}
|
||||
}
|
||||
|
||||
const errors: JsoncParseError[] = []
|
||||
const data = parseJsonc(text, errors, { allowTrailingComma: true })
|
||||
if (errors.length) {
|
||||
const lines = text.split("\n")
|
||||
const errorDetails = errors
|
||||
.map((e) => {
|
||||
const beforeOffset = text.substring(0, e.offset).split("\n")
|
||||
const line = beforeOffset.length
|
||||
const column = beforeOffset[beforeOffset.length - 1].length + 1
|
||||
const problemLine = lines[line - 1]
|
||||
|
||||
const error = `${printParseErrorCode(e.error)} at line ${line}, column ${column}`
|
||||
if (!problemLine) return error
|
||||
|
||||
return `${error}\n Line ${line}: ${problemLine}\n${"".padStart(column + 9)}^`
|
||||
})
|
||||
.join("\n")
|
||||
|
||||
throw new JsonError({
|
||||
path: configPath,
|
||||
message: errors
|
||||
.map((e) => {
|
||||
const lines = text.substring(0, e.offset).split("\n")
|
||||
const line = lines.length
|
||||
const column = lines[lines.length - 1].length + 1
|
||||
return `${printParseErrorCode(e.error)} at line ${line}, column ${column}`
|
||||
})
|
||||
.join("; "),
|
||||
path: filepath,
|
||||
message: `\n--- JSONC Input ---\n${text}\n--- Errors ---\n${errorDetails}\n--- End ---`,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -393,11 +451,21 @@ export namespace Config {
|
||||
if (parsed.success) {
|
||||
if (!parsed.data.$schema) {
|
||||
parsed.data.$schema = "https://opencode.ai/config.json"
|
||||
await Bun.write(configPath, JSON.stringify(parsed.data, null, 2))
|
||||
await Bun.write(filepath, JSON.stringify(parsed.data, null, 2))
|
||||
}
|
||||
return parsed.data
|
||||
const data = parsed.data
|
||||
if (data.plugin) {
|
||||
for (let i = 0; i < data.plugin?.length; i++) {
|
||||
const plugin = data.plugin[i]
|
||||
try {
|
||||
data.plugin[i] = import.meta.resolve(plugin, filepath)
|
||||
} catch (err) {}
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
throw new InvalidError({ path: configPath, issues: parsed.error.issues })
|
||||
|
||||
throw new InvalidError({ path: filepath, issues: parsed.error.issues })
|
||||
}
|
||||
export const JsonError = NamedError.create(
|
||||
"ConfigJsonError",
|
||||
|
||||
@@ -129,7 +129,9 @@ export const clang: Info = {
|
||||
command: ["clang-format", "-i", "$FILE"],
|
||||
extensions: [".c", ".cc", ".cpp", ".cxx", ".c++", ".h", ".hh", ".hpp", ".hxx", ".h++", ".ino", ".C", ".H"],
|
||||
async enabled() {
|
||||
return Bun.which("clang-format") !== null
|
||||
const app = App.info()
|
||||
const items = await Filesystem.findUp(".clang-format", app.path.cwd, app.path.root)
|
||||
return items.length > 0
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -5,20 +5,40 @@ import { Log } from "../util/log"
|
||||
import path from "path"
|
||||
|
||||
import * as Formatter from "./formatter"
|
||||
import { Config } from "../config/config"
|
||||
import { mergeDeep } from "remeda"
|
||||
|
||||
export namespace Format {
|
||||
const log = Log.create({ service: "format" })
|
||||
|
||||
const state = App.state("format", () => {
|
||||
const state = App.state("format", async () => {
|
||||
const enabled: Record<string, boolean> = {}
|
||||
const cfg = await Config.get()
|
||||
|
||||
const formatters = { ...Formatter } as Record<string, Formatter.Info>
|
||||
for (const [name, item] of Object.entries(cfg.formatter ?? {})) {
|
||||
if (item.disabled) {
|
||||
delete formatters[name]
|
||||
continue
|
||||
}
|
||||
const result: Formatter.Info = mergeDeep(formatters[name] ?? {}, {
|
||||
command: [],
|
||||
extensions: [],
|
||||
...item,
|
||||
})
|
||||
result.enabled = async () => true
|
||||
result.name = name
|
||||
formatters[name] = result
|
||||
}
|
||||
|
||||
return {
|
||||
enabled,
|
||||
formatters,
|
||||
}
|
||||
})
|
||||
|
||||
async function isEnabled(item: Formatter.Info) {
|
||||
const s = state()
|
||||
const s = await state()
|
||||
let status = s.enabled[item.name]
|
||||
if (status === undefined) {
|
||||
status = await item.enabled()
|
||||
@@ -28,8 +48,10 @@ export namespace Format {
|
||||
}
|
||||
|
||||
async function getFormatter(ext: string) {
|
||||
const formatters = await state().then((x) => x.formatters)
|
||||
const result = []
|
||||
for (const item of Object.values(Formatter)) {
|
||||
for (const item of Object.values(formatters)) {
|
||||
log.info("checking", { name: item.name, ext })
|
||||
if (!item.extensions.includes(ext)) continue
|
||||
if (!(await isEnabled(item))) continue
|
||||
result.push(item)
|
||||
|
||||
@@ -27,7 +27,7 @@ await Promise.all([
|
||||
fs.mkdir(Global.Path.log, { recursive: true }),
|
||||
])
|
||||
|
||||
const CACHE_VERSION = "3"
|
||||
const CACHE_VERSION = "4"
|
||||
|
||||
const version = await Bun.file(path.join(Global.Path.cache, "version"))
|
||||
.text()
|
||||
|
||||
@@ -83,6 +83,7 @@ const cli = yargs(hideBin(process.argv))
|
||||
if (msg.startsWith("Unknown argument") || msg.startsWith("Not enough non-option arguments")) {
|
||||
cli.showHelp("log")
|
||||
}
|
||||
process.exit(1)
|
||||
})
|
||||
.strict()
|
||||
|
||||
|
||||
@@ -136,6 +136,7 @@ export namespace Installation {
|
||||
}
|
||||
|
||||
export const VERSION = typeof OPENCODE_VERSION === "string" ? OPENCODE_VERSION : "dev"
|
||||
export const USER_AGENT = `opencode/${VERSION}`
|
||||
|
||||
export async function latest() {
|
||||
return fetch("https://api.github.com/repos/sst/opencode/releases/latest")
|
||||
|
||||
@@ -4,6 +4,8 @@ import { LSPClient } from "./client"
|
||||
import path from "path"
|
||||
import { LSPServer } from "./server"
|
||||
import { z } from "zod"
|
||||
import { Config } from "../config/config"
|
||||
import { spawn } from "child_process"
|
||||
|
||||
export namespace LSP {
|
||||
const log = Log.create({ service: "lsp" })
|
||||
@@ -55,8 +57,34 @@ export namespace LSP {
|
||||
"lsp",
|
||||
async () => {
|
||||
const clients: LSPClient.Info[] = []
|
||||
const servers: Record<string, LSPServer.Info> = LSPServer
|
||||
const cfg = await Config.get()
|
||||
for (const [name, item] of Object.entries(cfg.lsp ?? {})) {
|
||||
const existing = servers[name]
|
||||
if (item.disabled) {
|
||||
delete servers[name]
|
||||
continue
|
||||
}
|
||||
servers[name] = {
|
||||
...existing,
|
||||
extensions: item.extensions ?? existing.extensions,
|
||||
spawn: async (_app, root) => {
|
||||
return {
|
||||
process: spawn(item.command[0], item.command.slice(1), {
|
||||
cwd: root,
|
||||
env: {
|
||||
...process.env,
|
||||
...item.env,
|
||||
},
|
||||
}),
|
||||
initialization: item.initialization,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
return {
|
||||
broken: new Set<string>(),
|
||||
servers,
|
||||
clients,
|
||||
}
|
||||
},
|
||||
@@ -76,7 +104,7 @@ export namespace LSP {
|
||||
const extension = path.parse(file).ext
|
||||
const result: LSPClient.Info[] = []
|
||||
for (const server of Object.values(LSPServer)) {
|
||||
if (!server.extensions.includes(extension)) continue
|
||||
if (server.extensions.length && !server.extensions.includes(extension)) continue
|
||||
const root = await server.root(file, App.info())
|
||||
if (!root) continue
|
||||
if (s.broken.has(root + server.id)) continue
|
||||
|
||||
@@ -115,7 +115,8 @@ export namespace MCP {
|
||||
const result: Record<string, Tool> = {}
|
||||
for (const [clientName, client] of Object.entries(await clients())) {
|
||||
for (const [toolName, tool] of Object.entries(await client.tools())) {
|
||||
result[clientName + "_" + toolName] = tool
|
||||
const sanitizedClientName = clientName.replace(/\s+/g, "_")
|
||||
result[sanitizedClientName + "_" + toolName] = tool
|
||||
}
|
||||
}
|
||||
return result
|
||||
|
||||
@@ -3,6 +3,7 @@ import { z } from "zod"
|
||||
import { Bus } from "../bus"
|
||||
import { Log } from "../util/log"
|
||||
import { Identifier } from "../id/id"
|
||||
import { Plugin } from "../plugin"
|
||||
|
||||
export namespace Permission {
|
||||
const log = Log.create({ service: "permission" })
|
||||
@@ -67,7 +68,7 @@ export namespace Permission {
|
||||
},
|
||||
)
|
||||
|
||||
export function ask(input: {
|
||||
export async function ask(input: {
|
||||
type: Info["type"]
|
||||
title: Info["title"]
|
||||
pattern?: Info["pattern"]
|
||||
@@ -95,6 +96,18 @@ export namespace Permission {
|
||||
created: Date.now(),
|
||||
},
|
||||
}
|
||||
|
||||
switch (
|
||||
await Plugin.trigger("permission.ask", info, {
|
||||
status: "ask",
|
||||
}).then((x) => x.status)
|
||||
) {
|
||||
case "deny":
|
||||
throw new RejectedError(info.sessionID, info.id, info.callID)
|
||||
case "allow":
|
||||
return
|
||||
}
|
||||
|
||||
pending[input.sessionID] = pending[input.sessionID] || {}
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
pending[input.sessionID][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 { z } from "zod"
|
||||
import { data } from "./models-macro" with { type: "macro" }
|
||||
import { Installation } from "../installation"
|
||||
|
||||
export namespace ModelsDev {
|
||||
const log = Log.create({ service: "models.dev" })
|
||||
@@ -50,21 +51,30 @@ export namespace ModelsDev {
|
||||
export type Provider = z.infer<typeof Provider>
|
||||
|
||||
export async function get() {
|
||||
refresh()
|
||||
const file = Bun.file(filepath)
|
||||
const result = await file.json().catch(() => {})
|
||||
if (result) {
|
||||
refresh()
|
||||
return result as Record<string, Provider>
|
||||
}
|
||||
refresh()
|
||||
if (result) return result as Record<string, Provider>
|
||||
const json = await data()
|
||||
return JSON.parse(json) as Record<string, Provider>
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
export async function refresh() {
|
||||
const file = Bun.file(filepath)
|
||||
log.info("refreshing")
|
||||
const result = await fetch("https://models.dev/api.json").catch(() => {})
|
||||
if (result && result.ok) await Bun.write(file, result)
|
||||
log.info("refreshing", {
|
||||
file,
|
||||
})
|
||||
const result = await fetch("https://models.dev/api.json", {
|
||||
headers: {
|
||||
"User-Agent": Installation.USER_AGENT,
|
||||
},
|
||||
}).catch((e) => {
|
||||
log.error("Failed to fetch models.dev", {
|
||||
error: e,
|
||||
})
|
||||
})
|
||||
if (result && result.ok) await Bun.write(file, await result.text())
|
||||
}
|
||||
}
|
||||
|
||||
setInterval(() => ModelsDev.refresh(), 60 * 1000 * 60).unref()
|
||||
|
||||
@@ -97,7 +97,7 @@ export namespace Provider {
|
||||
Array.isArray(msg.content) && msg.content.some((part: any) => part.type === "image_url"),
|
||||
)
|
||||
}
|
||||
} catch { }
|
||||
} catch {}
|
||||
const headers: Record<string, string> = {
|
||||
...init.headers,
|
||||
...copilot.HEADERS,
|
||||
@@ -283,26 +283,26 @@ export namespace Provider {
|
||||
cost:
|
||||
!model.cost && !existing?.cost
|
||||
? {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cache_read: 0,
|
||||
cache_write: 0,
|
||||
}
|
||||
input: 0,
|
||||
output: 0,
|
||||
cache_read: 0,
|
||||
cache_write: 0,
|
||||
}
|
||||
: {
|
||||
cache_read: 0,
|
||||
cache_write: 0,
|
||||
...existing?.cost,
|
||||
...model.cost,
|
||||
},
|
||||
cache_read: 0,
|
||||
cache_write: 0,
|
||||
...existing?.cost,
|
||||
...model.cost,
|
||||
},
|
||||
options: {
|
||||
...existing?.options,
|
||||
...model.options,
|
||||
},
|
||||
limit: model.limit ??
|
||||
existing?.limit ?? {
|
||||
context: 0,
|
||||
output: 0,
|
||||
},
|
||||
context: 0,
|
||||
output: 0,
|
||||
},
|
||||
}
|
||||
parsed.models[modelID] = parsedModel
|
||||
}
|
||||
@@ -386,6 +386,10 @@ export namespace Provider {
|
||||
})
|
||||
}
|
||||
|
||||
export async function getProvider(providerID: string) {
|
||||
return state().then((s) => s.providers[providerID])
|
||||
}
|
||||
|
||||
export async function getModel(providerID: string, modelID: string) {
|
||||
const key = `${providerID}/${modelID}`
|
||||
const s = await state()
|
||||
|
||||
@@ -2,47 +2,73 @@ import type { ModelMessage } from "ai"
|
||||
import { unique } from "remeda"
|
||||
|
||||
export namespace ProviderTransform {
|
||||
export function message(msgs: ModelMessage[], providerID: string, modelID: string) {
|
||||
if (providerID === "anthropic" || modelID.includes("anthropic") || modelID.includes("claude")) {
|
||||
const system = msgs.filter((msg) => msg.role === "system").slice(0, 2)
|
||||
const final = msgs.filter((msg) => msg.role !== "system").slice(-2)
|
||||
function normalizeToolCallIds(msgs: ModelMessage[]): ModelMessage[] {
|
||||
return msgs.map((msg) => {
|
||||
if ((msg.role === "assistant" || msg.role === "tool") && Array.isArray(msg.content)) {
|
||||
msg.content = msg.content.map((part) => {
|
||||
if ((part.type === "tool-call" || part.type === "tool-result") && "toolCallId" in part) {
|
||||
return {
|
||||
...part,
|
||||
toolCallId: part.toolCallId.replace(/[^a-zA-Z0-9_-]/g, "_"),
|
||||
}
|
||||
}
|
||||
return part
|
||||
})
|
||||
}
|
||||
return msg
|
||||
})
|
||||
}
|
||||
|
||||
const providerOptions = {
|
||||
anthropic: {
|
||||
cacheControl: { type: "ephemeral" },
|
||||
},
|
||||
openrouter: {
|
||||
cache_control: { type: "ephemeral" },
|
||||
},
|
||||
bedrock: {
|
||||
cachePoint: { type: "ephemeral" },
|
||||
},
|
||||
openaiCompatible: {
|
||||
cache_control: { type: "ephemeral" },
|
||||
},
|
||||
function applyCaching(msgs: ModelMessage[], providerID: string): ModelMessage[] {
|
||||
const system = msgs.filter((msg) => msg.role === "system").slice(0, 2)
|
||||
const final = msgs.filter((msg) => msg.role !== "system").slice(-2)
|
||||
|
||||
const providerOptions = {
|
||||
anthropic: {
|
||||
cacheControl: { type: "ephemeral" },
|
||||
},
|
||||
openrouter: {
|
||||
cache_control: { type: "ephemeral" },
|
||||
},
|
||||
bedrock: {
|
||||
cachePoint: { type: "ephemeral" },
|
||||
},
|
||||
openaiCompatible: {
|
||||
cache_control: { type: "ephemeral" },
|
||||
},
|
||||
}
|
||||
|
||||
for (const msg of unique([...system, ...final])) {
|
||||
const shouldUseContentOptions = providerID !== "anthropic" && Array.isArray(msg.content) && msg.content.length > 0
|
||||
|
||||
if (shouldUseContentOptions) {
|
||||
const lastContent = msg.content[msg.content.length - 1]
|
||||
if (lastContent && typeof lastContent === "object") {
|
||||
lastContent.providerOptions = {
|
||||
...lastContent.providerOptions,
|
||||
...providerOptions,
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
for (const msg of unique([...system, ...final])) {
|
||||
const shouldUseContentOptions =
|
||||
providerID !== "anthropic" && Array.isArray(msg.content) && msg.content.length > 0
|
||||
|
||||
if (shouldUseContentOptions) {
|
||||
const lastContent = msg.content[msg.content.length - 1]
|
||||
if (lastContent && typeof lastContent === "object") {
|
||||
lastContent.providerOptions = {
|
||||
...lastContent.providerOptions,
|
||||
...providerOptions,
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
msg.providerOptions = {
|
||||
...msg.providerOptions,
|
||||
...providerOptions,
|
||||
}
|
||||
msg.providerOptions = {
|
||||
...msg.providerOptions,
|
||||
...providerOptions,
|
||||
}
|
||||
}
|
||||
|
||||
return msgs
|
||||
}
|
||||
|
||||
export function message(msgs: ModelMessage[], providerID: string, modelID: string) {
|
||||
if (modelID.includes("claude")) {
|
||||
msgs = normalizeToolCallIds(msgs)
|
||||
}
|
||||
if (providerID === "anthropic" || modelID.includes("anthropic") || modelID.includes("claude")) {
|
||||
msgs = applyCaching(msgs, providerID)
|
||||
}
|
||||
|
||||
return msgs
|
||||
}
|
||||
|
||||
@@ -50,4 +76,9 @@ export namespace ProviderTransform {
|
||||
if (modelID.toLowerCase().includes("qwen")) return 0.55
|
||||
return 0
|
||||
}
|
||||
|
||||
export function topP(_providerID: string, modelID: string) {
|
||||
if (modelID.toLowerCase().includes("qwen")) return 1
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import { MessageV2 } from "../session/message-v2"
|
||||
import { Mode } from "../session/mode"
|
||||
import { callTui, TuiRoute } from "./tui"
|
||||
import { Permission } from "../permission"
|
||||
import { lazy } from "../util/lazy"
|
||||
|
||||
const ERRORS = {
|
||||
400: {
|
||||
@@ -48,7 +49,7 @@ export namespace Server {
|
||||
Connected: Bus.event("server.connected", z.object({})),
|
||||
}
|
||||
|
||||
function app() {
|
||||
export const app = lazy(() => {
|
||||
const app = new Hono()
|
||||
|
||||
const result = app
|
||||
@@ -1022,7 +1023,7 @@ export namespace Server {
|
||||
.route("/tui/control", TuiRoute)
|
||||
|
||||
return result
|
||||
}
|
||||
})
|
||||
|
||||
export async function openapi() {
|
||||
const a = app()
|
||||
|
||||
@@ -41,6 +41,7 @@ import { LSP } from "../lsp"
|
||||
import { ReadTool } from "../tool/read"
|
||||
import { mergeDeep, pipe, splitWhen } from "remeda"
|
||||
import { ToolRegistry } from "../tool/registry"
|
||||
import { Plugin } from "../plugin"
|
||||
|
||||
export namespace Session {
|
||||
const log = Log.create({ service: "session" })
|
||||
@@ -571,7 +572,14 @@ export namespace Session {
|
||||
text: PROMPT_PLAN,
|
||||
synthetic: true,
|
||||
})
|
||||
|
||||
await Plugin.trigger(
|
||||
"chat.message",
|
||||
{},
|
||||
{
|
||||
message: userMsg,
|
||||
parts: userParts,
|
||||
},
|
||||
)
|
||||
await updateMessage(userMsg)
|
||||
for (const part of userParts) {
|
||||
await updatePart(part)
|
||||
@@ -716,10 +724,20 @@ export namespace Session {
|
||||
description: item.description,
|
||||
inputSchema: item.parameters as ZodSchema,
|
||||
async execute(args, options) {
|
||||
await processor.track(options.toolCallId)
|
||||
await Plugin.trigger(
|
||||
"tool.execute.before",
|
||||
{
|
||||
tool: item.id,
|
||||
sessionID: input.sessionID,
|
||||
callID: options.toolCallId,
|
||||
},
|
||||
{
|
||||
args,
|
||||
},
|
||||
)
|
||||
const result = await item.execute(args, {
|
||||
sessionID: input.sessionID,
|
||||
abort: abort.signal,
|
||||
abort: options.abortSignal!,
|
||||
messageID: assistantMsg.id,
|
||||
callID: options.toolCallId,
|
||||
metadata: async (val) => {
|
||||
@@ -740,6 +758,15 @@ export namespace Session {
|
||||
}
|
||||
},
|
||||
})
|
||||
await Plugin.trigger(
|
||||
"tool.execute.after",
|
||||
{
|
||||
tool: item.id,
|
||||
sessionID: input.sessionID,
|
||||
callID: options.toolCallId,
|
||||
},
|
||||
result,
|
||||
)
|
||||
return result
|
||||
},
|
||||
toModelOutput(result) {
|
||||
@@ -752,11 +779,10 @@ export namespace Session {
|
||||
}
|
||||
|
||||
for (const [key, item] of Object.entries(await MCP.tools())) {
|
||||
if (mode.tools[key] === false) continue
|
||||
if (enabledTools[key] === false) continue
|
||||
const execute = item.execute
|
||||
if (!execute) continue
|
||||
item.execute = async (args, opts) => {
|
||||
await processor.track(opts.toolCallId)
|
||||
const result = await execute(args, opts)
|
||||
const output = result.content
|
||||
.filter((x: any) => x.type === "text")
|
||||
@@ -776,6 +802,21 @@ export namespace Session {
|
||||
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({
|
||||
onError(e) {
|
||||
log.error("streamText error", {
|
||||
@@ -835,6 +876,8 @@ export namespace Session {
|
||||
providerOptions: {
|
||||
[input.providerID]: model.info.options,
|
||||
},
|
||||
temperature: params.temperature,
|
||||
topP: params.topP,
|
||||
messages: [
|
||||
...system.map(
|
||||
(x): ModelMessage => ({
|
||||
@@ -844,9 +887,6 @@ export namespace Session {
|
||||
),
|
||||
...MessageV2.toModelMessage(msgs),
|
||||
],
|
||||
temperature: model.info.temperature
|
||||
? (mode.temperature ?? ProviderTransform.temperature(input.providerID, input.modelID))
|
||||
: undefined,
|
||||
tools: model.info.tool_call === false ? undefined : tools,
|
||||
model: wrapLanguageModel({
|
||||
model: model.language,
|
||||
@@ -878,15 +918,11 @@ export namespace Session {
|
||||
}
|
||||
|
||||
function createProcessor(assistantMsg: MessageV2.Assistant, model: ModelsDev.Model) {
|
||||
const toolCalls: Record<string, MessageV2.ToolPart> = {}
|
||||
const snapshots: Record<string, string> = {}
|
||||
const toolcalls: Record<string, MessageV2.ToolPart> = {}
|
||||
let snapshot: string | undefined
|
||||
return {
|
||||
async track(toolCallID: string) {
|
||||
const hash = await Snapshot.track()
|
||||
if (hash) snapshots[toolCallID] = hash
|
||||
},
|
||||
partFromToolCall(toolCallID: string) {
|
||||
return toolCalls[toolCallID]
|
||||
return toolcalls[toolCallID]
|
||||
},
|
||||
async process(stream: StreamTextResult<Record<string, AITool>, never>) {
|
||||
try {
|
||||
@@ -902,7 +938,7 @@ export namespace Session {
|
||||
|
||||
case "tool-input-start":
|
||||
const part = await updatePart({
|
||||
id: toolCalls[value.id]?.id ?? Identifier.ascending("part"),
|
||||
id: toolcalls[value.id]?.id ?? Identifier.ascending("part"),
|
||||
messageID: assistantMsg.id,
|
||||
sessionID: assistantMsg.sessionID,
|
||||
type: "tool",
|
||||
@@ -912,7 +948,7 @@ export namespace Session {
|
||||
status: "pending",
|
||||
},
|
||||
})
|
||||
toolCalls[value.id] = part as MessageV2.ToolPart
|
||||
toolcalls[value.id] = part as MessageV2.ToolPart
|
||||
break
|
||||
|
||||
case "tool-input-delta":
|
||||
@@ -922,7 +958,7 @@ export namespace Session {
|
||||
break
|
||||
|
||||
case "tool-call": {
|
||||
const match = toolCalls[value.toolCallId]
|
||||
const match = toolcalls[value.toolCallId]
|
||||
if (match) {
|
||||
const part = await updatePart({
|
||||
...match,
|
||||
@@ -934,12 +970,12 @@ export namespace Session {
|
||||
},
|
||||
},
|
||||
})
|
||||
toolCalls[value.toolCallId] = part as MessageV2.ToolPart
|
||||
toolcalls[value.toolCallId] = part as MessageV2.ToolPart
|
||||
}
|
||||
break
|
||||
}
|
||||
case "tool-result": {
|
||||
const match = toolCalls[value.toolCallId]
|
||||
const match = toolcalls[value.toolCallId]
|
||||
if (match && match.state.status === "running") {
|
||||
await updatePart({
|
||||
...match,
|
||||
@@ -955,27 +991,13 @@ export namespace Session {
|
||||
},
|
||||
},
|
||||
})
|
||||
delete toolCalls[value.toolCallId]
|
||||
const snapshot = snapshots[value.toolCallId]
|
||||
if (snapshot) {
|
||||
const patch = await Snapshot.patch(snapshot)
|
||||
if (patch.files.length) {
|
||||
await updatePart({
|
||||
id: Identifier.ascending("part"),
|
||||
messageID: assistantMsg.id,
|
||||
sessionID: assistantMsg.sessionID,
|
||||
type: "patch",
|
||||
hash: patch.hash,
|
||||
files: patch.files,
|
||||
})
|
||||
}
|
||||
}
|
||||
delete toolcalls[value.toolCallId]
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case "tool-error": {
|
||||
const match = toolCalls[value.toolCallId]
|
||||
const match = toolcalls[value.toolCallId]
|
||||
if (match && match.state.status === "running") {
|
||||
await updatePart({
|
||||
...match,
|
||||
@@ -989,19 +1011,7 @@ export namespace Session {
|
||||
},
|
||||
},
|
||||
})
|
||||
delete toolCalls[value.toolCallId]
|
||||
const snapshot = snapshots[value.toolCallId]
|
||||
if (snapshot) {
|
||||
const patch = await Snapshot.patch(snapshot)
|
||||
await updatePart({
|
||||
id: Identifier.ascending("part"),
|
||||
messageID: assistantMsg.id,
|
||||
sessionID: assistantMsg.sessionID,
|
||||
type: "patch",
|
||||
hash: patch.hash,
|
||||
files: patch.files,
|
||||
})
|
||||
}
|
||||
delete toolcalls[value.toolCallId]
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -1016,6 +1026,7 @@ export namespace Session {
|
||||
sessionID: assistantMsg.sessionID,
|
||||
type: "step-start",
|
||||
})
|
||||
snapshot = await Snapshot.track()
|
||||
break
|
||||
|
||||
case "finish-step":
|
||||
@@ -1031,6 +1042,20 @@ export namespace Session {
|
||||
cost: usage.cost,
|
||||
})
|
||||
await updateMessage(assistantMsg)
|
||||
if (snapshot) {
|
||||
const patch = await Snapshot.patch(snapshot)
|
||||
if (patch.files.length) {
|
||||
await updatePart({
|
||||
id: Identifier.ascending("part"),
|
||||
messageID: assistantMsg.id,
|
||||
sessionID: assistantMsg.sessionID,
|
||||
type: "patch",
|
||||
hash: patch.hash,
|
||||
files: patch.files,
|
||||
})
|
||||
}
|
||||
snapshot = undefined
|
||||
}
|
||||
break
|
||||
|
||||
case "text-start":
|
||||
@@ -1115,7 +1140,7 @@ export namespace Session {
|
||||
}
|
||||
const p = await getParts(assistantMsg.sessionID, assistantMsg.id)
|
||||
for (const part of p) {
|
||||
if (part.type === "tool" && part.state.status !== "completed") {
|
||||
if (part.type === "tool" && part.state.status !== "completed" && part.state.status !== "error") {
|
||||
updatePart({
|
||||
...part,
|
||||
state: {
|
||||
|
||||
@@ -8,6 +8,7 @@ export namespace Mode {
|
||||
.object({
|
||||
name: z.string(),
|
||||
temperature: z.number().optional(),
|
||||
topP: z.number().optional(),
|
||||
model: z
|
||||
.object({
|
||||
modelID: z.string(),
|
||||
@@ -51,7 +52,8 @@ export namespace Mode {
|
||||
item.name = key
|
||||
if (value.model) item.model = Provider.parseModel(value.model)
|
||||
if (value.prompt) item.prompt = value.prompt
|
||||
if (value.temperature) item.temperature = value.temperature
|
||||
if (value.temperature != undefined) item.temperature = value.temperature
|
||||
if (value.top_p != undefined) item.topP = value.top_p
|
||||
if (value.tools)
|
||||
item.tools = {
|
||||
...value.tools,
|
||||
|
||||
@@ -60,33 +60,28 @@ export namespace SystemPrompt {
|
||||
export async function custom() {
|
||||
const { cwd, root } = App.info().path
|
||||
const config = await Config.get()
|
||||
const found = []
|
||||
const paths = new Set<string>()
|
||||
|
||||
for (const item of CUSTOM_FILES) {
|
||||
const matches = await Filesystem.findUp(item, cwd, root)
|
||||
found.push(...matches.map((x) => Bun.file(x).text()))
|
||||
matches.forEach((path) => paths.add(path))
|
||||
}
|
||||
found.push(
|
||||
Bun.file(path.join(Global.Path.config, "AGENTS.md"))
|
||||
.text()
|
||||
.catch(() => ""),
|
||||
)
|
||||
found.push(
|
||||
Bun.file(path.join(os.homedir(), ".claude", "CLAUDE.md"))
|
||||
.text()
|
||||
.catch(() => ""),
|
||||
)
|
||||
|
||||
paths.add(path.join(Global.Path.config, "AGENTS.md"))
|
||||
paths.add(path.join(os.homedir(), ".claude", "CLAUDE.md"))
|
||||
|
||||
if (config.instructions) {
|
||||
for (const instruction of config.instructions) {
|
||||
try {
|
||||
const matches = await Filesystem.globUp(instruction, cwd, root)
|
||||
found.push(...matches.map((x) => Bun.file(x).text()))
|
||||
} catch {
|
||||
continue // Skip invalid glob patterns
|
||||
}
|
||||
const matches = await Filesystem.globUp(instruction, cwd, root).catch(() => [])
|
||||
matches.forEach((path) => paths.add(path))
|
||||
}
|
||||
}
|
||||
|
||||
const found = Array.from(paths).map((p) =>
|
||||
Bun.file(p)
|
||||
.text()
|
||||
.catch(() => ""),
|
||||
)
|
||||
return Promise.all(found).then((result) => result.filter(Boolean))
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ export namespace Snapshot {
|
||||
log.info("initialized")
|
||||
}
|
||||
await $`git --git-dir ${git} add .`.quiet().cwd(app.path.cwd).nothrow()
|
||||
const hash = await $`git --git-dir ${git} write-tree`.quiet().cwd(app.path.cwd).text()
|
||||
const hash = await $`git --git-dir ${git} write-tree`.quiet().cwd(app.path.cwd).nothrow().text()
|
||||
return hash.trim()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
import { z } from "zod"
|
||||
import { exec } from "child_process"
|
||||
import { text } from "stream/consumers"
|
||||
import { Tool } from "./tool"
|
||||
import DESCRIPTION from "./bash.txt"
|
||||
import { App } from "../app/app"
|
||||
import { Permission } from "../permission"
|
||||
import { Config } from "../config/config"
|
||||
import { Filesystem } from "../util/filesystem"
|
||||
import path from "path"
|
||||
import { lazy } from "../util/lazy"
|
||||
import { Log } from "../util/log"
|
||||
import { Wildcard } from "../util/wildcard"
|
||||
import { $ } from "bun"
|
||||
|
||||
const MAX_OUTPUT_LENGTH = 30000
|
||||
const DEFAULT_TIMEOUT = 1 * 60 * 1000
|
||||
const MAX_TIMEOUT = 10 * 60 * 1000
|
||||
|
||||
const log = Log.create({ service: "bash-tool" })
|
||||
|
||||
const parser = lazy(async () => {
|
||||
const { default: Parser } = await import("tree-sitter")
|
||||
const Bash = await import("tree-sitter-bash")
|
||||
@@ -70,9 +76,14 @@ export const BashTool = Tool.define("bash", {
|
||||
// not an exhaustive list, but covers most common cases
|
||||
if (["cd", "rm", "cp", "mv", "mkdir", "touch", "chmod", "chown"].includes(command[0])) {
|
||||
for (const arg of command.slice(1)) {
|
||||
if (arg.startsWith("-")) continue
|
||||
const resolved = path.resolve(app.path.cwd, arg)
|
||||
if (!Filesystem.contains(app.path.cwd, resolved)) {
|
||||
if (arg.startsWith("-") || (command[0] === "chmod" && arg.startsWith("+"))) continue
|
||||
const resolved = await $`realpath ${arg}`
|
||||
.quiet()
|
||||
.nothrow()
|
||||
.text()
|
||||
.then((x) => x.trim())
|
||||
log.info("resolved path", { arg, resolved })
|
||||
if (resolved && !Filesystem.contains(app.path.cwd, resolved)) {
|
||||
throw new Error(
|
||||
`This command references paths outside of ${app.path.cwd} so it is not allowed to be executed.`,
|
||||
)
|
||||
@@ -84,9 +95,9 @@ export const BashTool = Tool.define("bash", {
|
||||
if (!needsAsk && command[0] !== "cd") {
|
||||
const ask = (() => {
|
||||
for (const [pattern, value] of Object.entries(permissions)) {
|
||||
if (new Bun.Glob(pattern).match(node.text)) {
|
||||
return value
|
||||
}
|
||||
const match = Wildcard.match(node.text, pattern)
|
||||
log.info("checking", { text: node.text.trim(), pattern, match })
|
||||
if (match) return value
|
||||
}
|
||||
return "ask"
|
||||
})()
|
||||
@@ -107,18 +118,24 @@ export const BashTool = Tool.define("bash", {
|
||||
})
|
||||
}
|
||||
|
||||
const process = Bun.spawn({
|
||||
cmd: ["bash", "-c", params.command],
|
||||
const process = exec(params.command, {
|
||||
cwd: app.path.cwd,
|
||||
maxBuffer: MAX_OUTPUT_LENGTH,
|
||||
signal: ctx.abort,
|
||||
timeout: timeout,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
maxBuffer: MAX_OUTPUT_LENGTH,
|
||||
timeout,
|
||||
})
|
||||
await process.exited
|
||||
const stdout = await new Response(process.stdout).text()
|
||||
const stderr = await new Response(process.stderr).text()
|
||||
|
||||
const stdoutPromise = text(process.stdout!)
|
||||
const stderrPromise = text(process.stderr!)
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
process.on("close", () => {
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
|
||||
const stdout = await stdoutPromise
|
||||
const stderr = await stderrPromise
|
||||
|
||||
return {
|
||||
title: params.command,
|
||||
|
||||
@@ -96,6 +96,7 @@ export const EditTool = Tool.define("edit", {
|
||||
file: filePath,
|
||||
})
|
||||
contentNew = await file.text()
|
||||
diff = trimDiff(createTwoFilesPatch(filePath, filePath, contentOld, contentNew))
|
||||
})()
|
||||
|
||||
FileTime.read(ctx.sessionID, filePath)
|
||||
|
||||
@@ -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", () => {
|
||||
test("basic", async () => {
|
||||
await App.provide({ cwd: projectRoot }, async () => {
|
||||
await bash.execute(
|
||||
const result = await bash.execute(
|
||||
{
|
||||
command: "cd foo/bar && ls",
|
||||
description: "List files in foo/bar",
|
||||
command: "echo 'test'",
|
||||
description: "Echo test message",
|
||||
},
|
||||
ctx,
|
||||
)
|
||||
expect(result.metadata.exit).toBe(0)
|
||||
expect(result.metadata.stdout).toContain("test")
|
||||
})
|
||||
})
|
||||
|
||||
test("cd ../ should fail", async () => {
|
||||
test("cd ../ should fail outside of project root", async () => {
|
||||
await App.provide({ cwd: projectRoot }, async () => {
|
||||
expect(
|
||||
await expect(
|
||||
bash.execute(
|
||||
{
|
||||
command: "cd ../",
|
||||
@@ -39,7 +41,7 @@ describe("tool.bash", () => {
|
||||
},
|
||||
ctx,
|
||||
),
|
||||
).rejects.toThrow()
|
||||
).rejects.toThrow("This command references paths outside of")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@tsconfig/bun/tsconfig.json",
|
||||
"compilerOptions": {}
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"ESNext",
|
||||
"DOM",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"customConditions": [
|
||||
"development"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
dist
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "0.3.126",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"devDependencies": {
|
||||
"typescript": "catalog:",
|
||||
"@hey-api/openapi-ts": "0.80.1",
|
||||
"@tsconfig/node22": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*"
|
||||
}
|
||||
}
|
||||
@@ -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 { $ } from "bun"
|
||||
|
||||
export type PluginInput = {
|
||||
client: ReturnType<typeof createOpencodeClient>
|
||||
app: App
|
||||
$: $
|
||||
}
|
||||
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,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
|
||||
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-2ebd9d5478864042a2e01b4995f42acbc39069fa7fcccd1c2e567366ee6c243d.yml
|
||||
openapi_spec_hash: 2a34451b288ea30af1cb61332c417c2a
|
||||
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-52fd0b61e84fdc1cdd31ec12e1600510e9dd2f9d4fb20c2315b4975cb763ee98.yml
|
||||
openapi_spec_hash: e851b8d5a2412f5fc9be82ab88ebdfde
|
||||
config_hash: 11a6f0803eb407367c3f677d3e524c37
|
||||
|
||||
@@ -151,6 +151,7 @@ type Mode struct {
|
||||
Model ModeModel `json:"model"`
|
||||
Prompt string `json:"prompt"`
|
||||
Temperature float64 `json:"temperature"`
|
||||
TopP float64 `json:"topP"`
|
||||
JSON modeJSON `json:"-"`
|
||||
}
|
||||
|
||||
@@ -161,6 +162,7 @@ type modeJSON struct {
|
||||
Model apijson.Field
|
||||
Prompt apijson.Field
|
||||
Temperature apijson.Field
|
||||
TopP apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
+170
-3
@@ -51,14 +51,16 @@ type Config struct {
|
||||
// Automatically update to the latest version
|
||||
Autoupdate bool `json:"autoupdate"`
|
||||
// Disable providers that are loaded automatically
|
||||
DisabledProviders []string `json:"disabled_providers"`
|
||||
Experimental ConfigExperimental `json:"experimental"`
|
||||
DisabledProviders []string `json:"disabled_providers"`
|
||||
Experimental ConfigExperimental `json:"experimental"`
|
||||
Formatter map[string]ConfigFormatter `json:"formatter"`
|
||||
// Additional instruction files or patterns to include
|
||||
Instructions []string `json:"instructions"`
|
||||
// Custom keybind configurations
|
||||
Keybinds KeybindsConfig `json:"keybinds"`
|
||||
// @deprecated Always uses stretch layout.
|
||||
Layout ConfigLayout `json:"layout"`
|
||||
Layout ConfigLayout `json:"layout"`
|
||||
Lsp map[string]ConfigLsp `json:"lsp"`
|
||||
// MCP (Model Context Protocol) server configurations
|
||||
Mcp map[string]ConfigMcp `json:"mcp"`
|
||||
// Modes configuration, see https://opencode.ai/docs/modes
|
||||
@@ -66,6 +68,7 @@ type Config struct {
|
||||
// Model to use in the format of provider/model, eg anthropic/claude-2
|
||||
Model string `json:"model"`
|
||||
Permission ConfigPermission `json:"permission"`
|
||||
Plugin []string `json:"plugin"`
|
||||
// Custom provider configurations and model overrides
|
||||
Provider map[string]ConfigProvider `json:"provider"`
|
||||
// Control sharing behavior:'manual' allows manual sharing via commands, 'auto'
|
||||
@@ -89,13 +92,16 @@ type configJSON struct {
|
||||
Autoupdate apijson.Field
|
||||
DisabledProviders apijson.Field
|
||||
Experimental apijson.Field
|
||||
Formatter apijson.Field
|
||||
Instructions apijson.Field
|
||||
Keybinds apijson.Field
|
||||
Layout apijson.Field
|
||||
Lsp apijson.Field
|
||||
Mcp apijson.Field
|
||||
Mode apijson.Field
|
||||
Model apijson.Field
|
||||
Permission apijson.Field
|
||||
Plugin apijson.Field
|
||||
Provider apijson.Field
|
||||
Share apijson.Field
|
||||
SmallModel apijson.Field
|
||||
@@ -247,6 +253,32 @@ func (r configExperimentalHookSessionCompletedJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type ConfigFormatter struct {
|
||||
Command []string `json:"command"`
|
||||
Disabled bool `json:"disabled"`
|
||||
Environment map[string]string `json:"environment"`
|
||||
Extensions []string `json:"extensions"`
|
||||
JSON configFormatterJSON `json:"-"`
|
||||
}
|
||||
|
||||
// configFormatterJSON contains the JSON metadata for the struct [ConfigFormatter]
|
||||
type configFormatterJSON struct {
|
||||
Command apijson.Field
|
||||
Disabled apijson.Field
|
||||
Environment apijson.Field
|
||||
Extensions apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *ConfigFormatter) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r configFormatterJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
// @deprecated Always uses stretch layout.
|
||||
type ConfigLayout string
|
||||
|
||||
@@ -263,6 +295,139 @@ func (r ConfigLayout) IsKnown() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type ConfigLsp struct {
|
||||
// This field can have the runtime type of [[]string].
|
||||
Command interface{} `json:"command"`
|
||||
Disabled bool `json:"disabled"`
|
||||
// This field can have the runtime type of [map[string]string].
|
||||
Env interface{} `json:"env"`
|
||||
// This field can have the runtime type of [[]string].
|
||||
Extensions interface{} `json:"extensions"`
|
||||
// This field can have the runtime type of [map[string]interface{}].
|
||||
Initialization interface{} `json:"initialization"`
|
||||
JSON configLspJSON `json:"-"`
|
||||
union ConfigLspUnion
|
||||
}
|
||||
|
||||
// configLspJSON contains the JSON metadata for the struct [ConfigLsp]
|
||||
type configLspJSON struct {
|
||||
Command apijson.Field
|
||||
Disabled apijson.Field
|
||||
Env apijson.Field
|
||||
Extensions apijson.Field
|
||||
Initialization apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r configLspJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
func (r *ConfigLsp) UnmarshalJSON(data []byte) (err error) {
|
||||
*r = ConfigLsp{}
|
||||
err = apijson.UnmarshalRoot(data, &r.union)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return apijson.Port(r.union, &r)
|
||||
}
|
||||
|
||||
// AsUnion returns a [ConfigLspUnion] interface which you can cast to the specific
|
||||
// types for more type safety.
|
||||
//
|
||||
// Possible runtime types of the union are [ConfigLspDisabled], [ConfigLspObject].
|
||||
func (r ConfigLsp) AsUnion() ConfigLspUnion {
|
||||
return r.union
|
||||
}
|
||||
|
||||
// Union satisfied by [ConfigLspDisabled] or [ConfigLspObject].
|
||||
type ConfigLspUnion interface {
|
||||
implementsConfigLsp()
|
||||
}
|
||||
|
||||
func init() {
|
||||
apijson.RegisterUnion(
|
||||
reflect.TypeOf((*ConfigLspUnion)(nil)).Elem(),
|
||||
"",
|
||||
apijson.UnionVariant{
|
||||
TypeFilter: gjson.JSON,
|
||||
Type: reflect.TypeOf(ConfigLspDisabled{}),
|
||||
},
|
||||
apijson.UnionVariant{
|
||||
TypeFilter: gjson.JSON,
|
||||
Type: reflect.TypeOf(ConfigLspObject{}),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
type ConfigLspDisabled struct {
|
||||
Disabled ConfigLspDisabledDisabled `json:"disabled,required"`
|
||||
JSON configLspDisabledJSON `json:"-"`
|
||||
}
|
||||
|
||||
// configLspDisabledJSON contains the JSON metadata for the struct
|
||||
// [ConfigLspDisabled]
|
||||
type configLspDisabledJSON struct {
|
||||
Disabled apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *ConfigLspDisabled) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r configLspDisabledJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
func (r ConfigLspDisabled) implementsConfigLsp() {}
|
||||
|
||||
type ConfigLspDisabledDisabled bool
|
||||
|
||||
const (
|
||||
ConfigLspDisabledDisabledTrue ConfigLspDisabledDisabled = true
|
||||
)
|
||||
|
||||
func (r ConfigLspDisabledDisabled) IsKnown() bool {
|
||||
switch r {
|
||||
case ConfigLspDisabledDisabledTrue:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type ConfigLspObject struct {
|
||||
Command []string `json:"command,required"`
|
||||
Disabled bool `json:"disabled"`
|
||||
Env map[string]string `json:"env"`
|
||||
Extensions []string `json:"extensions"`
|
||||
Initialization map[string]interface{} `json:"initialization"`
|
||||
JSON configLspObjectJSON `json:"-"`
|
||||
}
|
||||
|
||||
// configLspObjectJSON contains the JSON metadata for the struct [ConfigLspObject]
|
||||
type configLspObjectJSON struct {
|
||||
Command apijson.Field
|
||||
Disabled apijson.Field
|
||||
Env apijson.Field
|
||||
Extensions apijson.Field
|
||||
Initialization apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *ConfigLspObject) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r configLspObjectJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
func (r ConfigLspObject) implementsConfigLsp() {}
|
||||
|
||||
type ConfigMcp struct {
|
||||
// Type of MCP server connection
|
||||
Type ConfigMcpType `json:"type,required"`
|
||||
@@ -864,6 +1029,7 @@ type ModeConfig struct {
|
||||
Prompt string `json:"prompt"`
|
||||
Temperature float64 `json:"temperature"`
|
||||
Tools map[string]bool `json:"tools"`
|
||||
TopP float64 `json:"top_p"`
|
||||
JSON modeConfigJSON `json:"-"`
|
||||
}
|
||||
|
||||
@@ -874,6 +1040,7 @@ type modeConfigJSON struct {
|
||||
Prompt apijson.Field
|
||||
Temperature apijson.Field
|
||||
Tools apijson.Field
|
||||
TopP apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
+123
-121
@@ -54,13 +54,13 @@ type EventListResponse struct {
|
||||
// [EventListResponseEventMessageRemovedProperties],
|
||||
// [EventListResponseEventMessagePartUpdatedProperties],
|
||||
// [EventListResponseEventMessagePartRemovedProperties],
|
||||
// [EventListResponseEventStorageWriteProperties], [Permission],
|
||||
// [EventListResponseEventStorageWriteProperties],
|
||||
// [EventListResponseEventFileEditedProperties], [interface{}], [Permission],
|
||||
// [EventListResponseEventPermissionRepliedProperties],
|
||||
// [EventListResponseEventFileEditedProperties],
|
||||
// [EventListResponseEventSessionUpdatedProperties],
|
||||
// [EventListResponseEventSessionDeletedProperties],
|
||||
// [EventListResponseEventSessionIdleProperties],
|
||||
// [EventListResponseEventSessionErrorProperties], [interface{}],
|
||||
// [EventListResponseEventSessionErrorProperties],
|
||||
// [EventListResponseEventFileWatcherUpdatedProperties],
|
||||
// [EventListResponseEventIdeInstalledProperties].
|
||||
Properties interface{} `json:"properties,required"`
|
||||
@@ -100,11 +100,12 @@ func (r *EventListResponse) UnmarshalJSON(data []byte) (err error) {
|
||||
// [EventListResponseEventMessageUpdated], [EventListResponseEventMessageRemoved],
|
||||
// [EventListResponseEventMessagePartUpdated],
|
||||
// [EventListResponseEventMessagePartRemoved],
|
||||
// [EventListResponseEventStorageWrite], [EventListResponseEventPermissionUpdated],
|
||||
// [EventListResponseEventPermissionReplied], [EventListResponseEventFileEdited],
|
||||
// [EventListResponseEventStorageWrite], [EventListResponseEventFileEdited],
|
||||
// [EventListResponseEventServerConnected],
|
||||
// [EventListResponseEventPermissionUpdated],
|
||||
// [EventListResponseEventPermissionReplied],
|
||||
// [EventListResponseEventSessionUpdated], [EventListResponseEventSessionDeleted],
|
||||
// [EventListResponseEventSessionIdle], [EventListResponseEventSessionError],
|
||||
// [EventListResponseEventServerConnected],
|
||||
// [EventListResponseEventFileWatcherUpdated],
|
||||
// [EventListResponseEventIdeInstalled].
|
||||
func (r EventListResponse) AsUnion() EventListResponseUnion {
|
||||
@@ -116,11 +117,12 @@ func (r EventListResponse) AsUnion() EventListResponseUnion {
|
||||
// [EventListResponseEventMessageUpdated], [EventListResponseEventMessageRemoved],
|
||||
// [EventListResponseEventMessagePartUpdated],
|
||||
// [EventListResponseEventMessagePartRemoved],
|
||||
// [EventListResponseEventStorageWrite], [EventListResponseEventPermissionUpdated],
|
||||
// [EventListResponseEventPermissionReplied], [EventListResponseEventFileEdited],
|
||||
// [EventListResponseEventStorageWrite], [EventListResponseEventFileEdited],
|
||||
// [EventListResponseEventServerConnected],
|
||||
// [EventListResponseEventPermissionUpdated],
|
||||
// [EventListResponseEventPermissionReplied],
|
||||
// [EventListResponseEventSessionUpdated], [EventListResponseEventSessionDeleted],
|
||||
// [EventListResponseEventSessionIdle], [EventListResponseEventSessionError],
|
||||
// [EventListResponseEventServerConnected],
|
||||
// [EventListResponseEventFileWatcherUpdated] or
|
||||
// [EventListResponseEventIdeInstalled].
|
||||
type EventListResponseUnion interface {
|
||||
@@ -166,6 +168,16 @@ func init() {
|
||||
Type: reflect.TypeOf(EventListResponseEventStorageWrite{}),
|
||||
DiscriminatorValue: "storage.write",
|
||||
},
|
||||
apijson.UnionVariant{
|
||||
TypeFilter: gjson.JSON,
|
||||
Type: reflect.TypeOf(EventListResponseEventFileEdited{}),
|
||||
DiscriminatorValue: "file.edited",
|
||||
},
|
||||
apijson.UnionVariant{
|
||||
TypeFilter: gjson.JSON,
|
||||
Type: reflect.TypeOf(EventListResponseEventServerConnected{}),
|
||||
DiscriminatorValue: "server.connected",
|
||||
},
|
||||
apijson.UnionVariant{
|
||||
TypeFilter: gjson.JSON,
|
||||
Type: reflect.TypeOf(EventListResponseEventPermissionUpdated{}),
|
||||
@@ -176,11 +188,6 @@ func init() {
|
||||
Type: reflect.TypeOf(EventListResponseEventPermissionReplied{}),
|
||||
DiscriminatorValue: "permission.replied",
|
||||
},
|
||||
apijson.UnionVariant{
|
||||
TypeFilter: gjson.JSON,
|
||||
Type: reflect.TypeOf(EventListResponseEventFileEdited{}),
|
||||
DiscriminatorValue: "file.edited",
|
||||
},
|
||||
apijson.UnionVariant{
|
||||
TypeFilter: gjson.JSON,
|
||||
Type: reflect.TypeOf(EventListResponseEventSessionUpdated{}),
|
||||
@@ -201,11 +208,6 @@ func init() {
|
||||
Type: reflect.TypeOf(EventListResponseEventSessionError{}),
|
||||
DiscriminatorValue: "session.error",
|
||||
},
|
||||
apijson.UnionVariant{
|
||||
TypeFilter: gjson.JSON,
|
||||
Type: reflect.TypeOf(EventListResponseEventServerConnected{}),
|
||||
DiscriminatorValue: "server.connected",
|
||||
},
|
||||
apijson.UnionVariant{
|
||||
TypeFilter: gjson.JSON,
|
||||
Type: reflect.TypeOf(EventListResponseEventFileWatcherUpdated{}),
|
||||
@@ -649,6 +651,105 @@ func (r EventListResponseEventStorageWriteType) IsKnown() bool {
|
||||
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 {
|
||||
Properties Permission `json:"properties,required"`
|
||||
Type EventListResponseEventPermissionUpdatedType `json:"type,required"`
|
||||
@@ -752,66 +853,6 @@ func (r EventListResponseEventPermissionRepliedType) IsKnown() bool {
|
||||
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 {
|
||||
Properties EventListResponseEventSessionUpdatedProperties `json:"properties,required"`
|
||||
Type EventListResponseEventSessionUpdatedType `json:"type,required"`
|
||||
@@ -1188,45 +1229,6 @@ func (r EventListResponseEventSessionErrorType) IsKnown() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type EventListResponseEventServerConnected struct {
|
||||
Properties interface{} `json:"properties,required"`
|
||||
Type EventListResponseEventServerConnectedType `json:"type,required"`
|
||||
JSON eventListResponseEventServerConnectedJSON `json:"-"`
|
||||
}
|
||||
|
||||
// eventListResponseEventServerConnectedJSON contains the JSON metadata for the
|
||||
// struct [EventListResponseEventServerConnected]
|
||||
type eventListResponseEventServerConnectedJSON struct {
|
||||
Properties apijson.Field
|
||||
Type apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *EventListResponseEventServerConnected) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r eventListResponseEventServerConnectedJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
func (r EventListResponseEventServerConnected) implementsEventListResponse() {}
|
||||
|
||||
type EventListResponseEventServerConnectedType string
|
||||
|
||||
const (
|
||||
EventListResponseEventServerConnectedTypeServerConnected EventListResponseEventServerConnectedType = "server.connected"
|
||||
)
|
||||
|
||||
func (r EventListResponseEventServerConnectedType) IsKnown() bool {
|
||||
switch r {
|
||||
case EventListResponseEventServerConnectedTypeServerConnected:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type EventListResponseEventFileWatcherUpdated struct {
|
||||
Properties EventListResponseEventFileWatcherUpdatedProperties `json:"properties,required"`
|
||||
Type EventListResponseEventFileWatcherUpdatedType `json:"type,required"`
|
||||
@@ -1374,21 +1376,21 @@ const (
|
||||
EventListResponseTypeMessagePartUpdated EventListResponseType = "message.part.updated"
|
||||
EventListResponseTypeMessagePartRemoved EventListResponseType = "message.part.removed"
|
||||
EventListResponseTypeStorageWrite EventListResponseType = "storage.write"
|
||||
EventListResponseTypeFileEdited EventListResponseType = "file.edited"
|
||||
EventListResponseTypeServerConnected EventListResponseType = "server.connected"
|
||||
EventListResponseTypePermissionUpdated EventListResponseType = "permission.updated"
|
||||
EventListResponseTypePermissionReplied EventListResponseType = "permission.replied"
|
||||
EventListResponseTypeFileEdited EventListResponseType = "file.edited"
|
||||
EventListResponseTypeSessionUpdated EventListResponseType = "session.updated"
|
||||
EventListResponseTypeSessionDeleted EventListResponseType = "session.deleted"
|
||||
EventListResponseTypeSessionIdle EventListResponseType = "session.idle"
|
||||
EventListResponseTypeSessionError EventListResponseType = "session.error"
|
||||
EventListResponseTypeServerConnected EventListResponseType = "server.connected"
|
||||
EventListResponseTypeFileWatcherUpdated EventListResponseType = "file.watcher.updated"
|
||||
EventListResponseTypeIdeInstalled EventListResponseType = "ide.installed"
|
||||
)
|
||||
|
||||
func (r EventListResponseType) IsKnown() bool {
|
||||
switch r {
|
||||
case EventListResponseTypeInstallationUpdated, EventListResponseTypeLspClientDiagnostics, EventListResponseTypeMessageUpdated, EventListResponseTypeMessageRemoved, EventListResponseTypeMessagePartUpdated, EventListResponseTypeMessagePartRemoved, EventListResponseTypeStorageWrite, EventListResponseTypePermissionUpdated, 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 false
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "0.0.0-202507312003",
|
||||
"version": "0.3.126",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"exports": {
|
||||
".": "./dist/index.js"
|
||||
".": {
|
||||
"development": "./src/index.ts",
|
||||
"import": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -4,16 +4,11 @@ const dir = new URL("..", import.meta.url).pathname
|
||||
process.chdir(dir)
|
||||
|
||||
import { $ } from "bun"
|
||||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
|
||||
console.log("=== Generating JS SDK ===")
|
||||
console.log()
|
||||
|
||||
import { createClient } from "@hey-api/openapi-ts"
|
||||
|
||||
await fs.rm(path.join(dir, "src/gen"), { recursive: true, force: true })
|
||||
await $`bun run ../../opencode/src/index.ts generate > openapi.json`
|
||||
await $`bun dev generate > ${dir}/openapi.json`.cwd(path.resolve(dir, "../../opencode"))
|
||||
|
||||
await createClient({
|
||||
input: "./openapi.json",
|
||||
@@ -36,6 +31,4 @@ await createClient({
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
await $`rm -rf dist`
|
||||
await $`bun tsc`
|
||||
await $`bun prettier --write src/gen`
|
||||
|
||||
@@ -5,20 +5,15 @@ process.chdir(dir)
|
||||
|
||||
import { $ } from "bun"
|
||||
|
||||
const version = process.env["OPENCODE_VERSION"]
|
||||
if (!version) {
|
||||
throw new Error("OPENCODE_VERSION is required")
|
||||
}
|
||||
|
||||
await import("./generate")
|
||||
await $`rm -rf dist`
|
||||
await $`bun tsc`
|
||||
|
||||
const snapshot = process.env["OPENCODE_SNAPSHOT"] === "true"
|
||||
|
||||
await $`bun pm version --allow-same-version --no-git-tag-version ${version}`
|
||||
if (snapshot) {
|
||||
await $`bun publish --tag snapshot`
|
||||
}
|
||||
if (!snapshot) {
|
||||
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
|
||||
|
||||
import type { ClientOptions } from './types.gen';
|
||||
import { type Config, type ClientOptions as DefaultClientOptions, createClient, createConfig } from './client';
|
||||
import type { ClientOptions } from "./types.gen"
|
||||
import { type Config, type ClientOptions as DefaultClientOptions, createClient, createConfig } from "./client"
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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>({
|
||||
baseUrl: 'http://localhost:4096'
|
||||
}));
|
||||
export const client = createClient(
|
||||
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 {
|
||||
buildUrl,
|
||||
createConfig,
|
||||
@@ -7,189 +7,179 @@ import {
|
||||
mergeConfigs,
|
||||
mergeHeaders,
|
||||
setAuthParams,
|
||||
} from './utils';
|
||||
} from "./utils"
|
||||
|
||||
type ReqInit = Omit<RequestInit, 'body' | 'headers'> & {
|
||||
body?: any;
|
||||
headers: ReturnType<typeof mergeHeaders>;
|
||||
};
|
||||
type ReqInit = Omit<RequestInit, "body" | "headers"> & {
|
||||
body?: any
|
||||
headers: ReturnType<typeof mergeHeaders>
|
||||
}
|
||||
|
||||
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 => {
|
||||
_config = mergeConfigs(_config, config);
|
||||
return getConfig();
|
||||
};
|
||||
_config = mergeConfigs(_config, config)
|
||||
return getConfig()
|
||||
}
|
||||
|
||||
const interceptors = createInterceptors<
|
||||
Request,
|
||||
Response,
|
||||
unknown,
|
||||
RequestOptions
|
||||
>();
|
||||
const interceptors = createInterceptors<Request, Response, unknown, RequestOptions>()
|
||||
|
||||
const request: Client['request'] = async (options) => {
|
||||
const request: Client["request"] = async (options) => {
|
||||
const opts = {
|
||||
..._config,
|
||||
...options,
|
||||
fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,
|
||||
headers: mergeHeaders(_config.headers, options.headers),
|
||||
};
|
||||
}
|
||||
|
||||
if (opts.security) {
|
||||
await setAuthParams({
|
||||
...opts,
|
||||
security: opts.security,
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
if (opts.requestValidator) {
|
||||
await opts.requestValidator(opts);
|
||||
await opts.requestValidator(opts)
|
||||
}
|
||||
|
||||
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
|
||||
if (opts.body === undefined || opts.body === '') {
|
||||
opts.headers.delete('Content-Type');
|
||||
if (opts.body === undefined || opts.body === "") {
|
||||
opts.headers.delete("Content-Type")
|
||||
}
|
||||
|
||||
const url = buildUrl(opts);
|
||||
const url = buildUrl(opts)
|
||||
const requestInit: ReqInit = {
|
||||
redirect: 'follow',
|
||||
redirect: "follow",
|
||||
...opts,
|
||||
};
|
||||
}
|
||||
|
||||
let request = new Request(url, requestInit);
|
||||
let request = new Request(url, requestInit)
|
||||
|
||||
for (const fn of interceptors.request._fns) {
|
||||
if (fn) {
|
||||
request = await fn(request, opts);
|
||||
request = await fn(request, opts)
|
||||
}
|
||||
}
|
||||
|
||||
// fetch must be assigned here, otherwise it would throw the error:
|
||||
// TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
|
||||
const _fetch = opts.fetch!;
|
||||
let response = await _fetch(request);
|
||||
const _fetch = opts.fetch!
|
||||
let response = await _fetch(request)
|
||||
|
||||
for (const fn of interceptors.response._fns) {
|
||||
if (fn) {
|
||||
response = await fn(response, request, opts);
|
||||
response = await fn(response, request, opts)
|
||||
}
|
||||
}
|
||||
|
||||
const result = {
|
||||
request,
|
||||
response,
|
||||
};
|
||||
}
|
||||
|
||||
if (response.ok) {
|
||||
if (
|
||||
response.status === 204 ||
|
||||
response.headers.get('Content-Length') === '0'
|
||||
) {
|
||||
return opts.responseStyle === 'data'
|
||||
if (response.status === 204 || response.headers.get("Content-Length") === "0") {
|
||||
return opts.responseStyle === "data"
|
||||
? {}
|
||||
: {
|
||||
data: {},
|
||||
...result,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const parseAs =
|
||||
(opts.parseAs === 'auto'
|
||||
? getParseAs(response.headers.get('Content-Type'))
|
||||
: opts.parseAs) ?? 'json';
|
||||
(opts.parseAs === "auto" ? getParseAs(response.headers.get("Content-Type")) : opts.parseAs) ?? "json"
|
||||
|
||||
let data: any;
|
||||
let data: any
|
||||
switch (parseAs) {
|
||||
case 'arrayBuffer':
|
||||
case 'blob':
|
||||
case 'formData':
|
||||
case 'json':
|
||||
case 'text':
|
||||
data = await response[parseAs]();
|
||||
break;
|
||||
case 'stream':
|
||||
return opts.responseStyle === 'data'
|
||||
case "arrayBuffer":
|
||||
case "blob":
|
||||
case "formData":
|
||||
case "json":
|
||||
case "text":
|
||||
data = await response[parseAs]()
|
||||
break
|
||||
case "stream":
|
||||
return opts.responseStyle === "data"
|
||||
? response.body
|
||||
: {
|
||||
data: response.body,
|
||||
...result,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (parseAs === 'json') {
|
||||
if (parseAs === "json") {
|
||||
if (opts.responseValidator) {
|
||||
await opts.responseValidator(data);
|
||||
await opts.responseValidator(data)
|
||||
}
|
||||
|
||||
if (opts.responseTransformer) {
|
||||
data = await opts.responseTransformer(data);
|
||||
data = await opts.responseTransformer(data)
|
||||
}
|
||||
}
|
||||
|
||||
return opts.responseStyle === 'data'
|
||||
return opts.responseStyle === "data"
|
||||
? data
|
||||
: {
|
||||
data,
|
||||
...result,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const textError = await response.text();
|
||||
let jsonError: unknown;
|
||||
const textError = await response.text()
|
||||
let jsonError: unknown
|
||||
|
||||
try {
|
||||
jsonError = JSON.parse(textError);
|
||||
jsonError = JSON.parse(textError)
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
|
||||
const error = jsonError ?? textError;
|
||||
let finalError = error;
|
||||
const error = jsonError ?? textError
|
||||
let finalError = error
|
||||
|
||||
for (const fn of interceptors.error._fns) {
|
||||
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) {
|
||||
throw finalError;
|
||||
throw finalError
|
||||
}
|
||||
|
||||
// TODO: we probably want to return error and improve types
|
||||
return opts.responseStyle === 'data'
|
||||
return opts.responseStyle === "data"
|
||||
? undefined
|
||||
: {
|
||||
error: finalError,
|
||||
...result,
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
buildUrl,
|
||||
connect: (options) => request({ ...options, method: 'CONNECT' }),
|
||||
delete: (options) => request({ ...options, method: 'DELETE' }),
|
||||
get: (options) => request({ ...options, method: 'GET' }),
|
||||
connect: (options) => request({ ...options, method: "CONNECT" }),
|
||||
delete: (options) => request({ ...options, method: "DELETE" }),
|
||||
get: (options) => request({ ...options, method: "GET" }),
|
||||
getConfig,
|
||||
head: (options) => request({ ...options, method: 'HEAD' }),
|
||||
head: (options) => request({ ...options, method: "HEAD" }),
|
||||
interceptors,
|
||||
options: (options) => request({ ...options, method: 'OPTIONS' }),
|
||||
patch: (options) => request({ ...options, method: 'PATCH' }),
|
||||
post: (options) => request({ ...options, method: 'POST' }),
|
||||
put: (options) => request({ ...options, method: 'PUT' }),
|
||||
options: (options) => request({ ...options, method: "OPTIONS" }),
|
||||
patch: (options) => request({ ...options, method: "PATCH" }),
|
||||
post: (options) => request({ ...options, method: "POST" }),
|
||||
put: (options) => request({ ...options, method: "PUT" }),
|
||||
request,
|
||||
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 { QuerySerializerOptions } from '../core/bodySerializer';
|
||||
export {
|
||||
formDataBodySerializer,
|
||||
jsonBodySerializer,
|
||||
urlSearchParamsBodySerializer,
|
||||
} from '../core/bodySerializer';
|
||||
export { buildClientParams } from '../core/params';
|
||||
export { createClient } from './client';
|
||||
export type { Auth } from "../core/auth"
|
||||
export type { QuerySerializerOptions } from "../core/bodySerializer"
|
||||
export { formDataBodySerializer, jsonBodySerializer, urlSearchParamsBodySerializer } from "../core/bodySerializer"
|
||||
export { buildClientParams } from "../core/params"
|
||||
export { createClient } from "./client"
|
||||
export type {
|
||||
Client,
|
||||
ClientOptions,
|
||||
@@ -18,5 +14,5 @@ export type {
|
||||
RequestResult,
|
||||
ResponseStyle,
|
||||
TDataShape,
|
||||
} from './types';
|
||||
export { createConfig, mergeHeaders } from './utils';
|
||||
} from "./types"
|
||||
export { createConfig, mergeHeaders } from "./utils"
|
||||
|
||||
@@ -1,33 +1,30 @@
|
||||
import type { Auth } from '../core/auth';
|
||||
import type {
|
||||
Client as CoreClient,
|
||||
Config as CoreConfig,
|
||||
} from '../core/types';
|
||||
import type { Middleware } from './utils';
|
||||
import type { Auth } from "../core/auth"
|
||||
import type { Client as CoreClient, 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>
|
||||
extends Omit<RequestInit, 'body' | 'headers' | 'method'>,
|
||||
extends Omit<RequestInit, "body" | "headers" | "method">,
|
||||
CoreConfig {
|
||||
/**
|
||||
* 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 instance.
|
||||
*
|
||||
* @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`
|
||||
* options won't have any effect.
|
||||
*
|
||||
* 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`
|
||||
* will infer the appropriate method from the `Content-Type` response header.
|
||||
@@ -36,135 +33,118 @@ export interface Config<T extends ClientOptions = ClientOptions>
|
||||
*
|
||||
* @default 'auto'
|
||||
*/
|
||||
parseAs?:
|
||||
| 'arrayBuffer'
|
||||
| 'auto'
|
||||
| 'blob'
|
||||
| 'formData'
|
||||
| 'json'
|
||||
| 'stream'
|
||||
| 'text';
|
||||
parseAs?: "arrayBuffer" | "auto" | "blob" | "formData" | "json" | "stream" | "text"
|
||||
/**
|
||||
* Should we return only data or multiple fields (data, error, response, etc.)?
|
||||
*
|
||||
* @default 'fields'
|
||||
*/
|
||||
responseStyle?: ResponseStyle;
|
||||
responseStyle?: ResponseStyle
|
||||
/**
|
||||
* Throw an error instead of returning it in the response?
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
throwOnError?: T['throwOnError'];
|
||||
throwOnError?: T["throwOnError"]
|
||||
}
|
||||
|
||||
export interface RequestOptions<
|
||||
TResponseStyle extends ResponseStyle = 'fields',
|
||||
TResponseStyle extends ResponseStyle = "fields",
|
||||
ThrowOnError extends boolean = boolean,
|
||||
Url extends string = string,
|
||||
> extends Config<{
|
||||
responseStyle: TResponseStyle;
|
||||
throwOnError: ThrowOnError;
|
||||
responseStyle: TResponseStyle
|
||||
throwOnError: ThrowOnError
|
||||
}> {
|
||||
/**
|
||||
* Any body that you want to add to your request.
|
||||
*
|
||||
* {@link https://developer.mozilla.org/docs/Web/API/fetch#body}
|
||||
*/
|
||||
body?: unknown;
|
||||
path?: Record<string, unknown>;
|
||||
query?: Record<string, unknown>;
|
||||
body?: unknown
|
||||
path?: Record<string, unknown>
|
||||
query?: Record<string, unknown>
|
||||
/**
|
||||
* Security mechanism(s) to use for the request.
|
||||
*/
|
||||
security?: ReadonlyArray<Auth>;
|
||||
url: Url;
|
||||
security?: ReadonlyArray<Auth>
|
||||
url: Url
|
||||
}
|
||||
|
||||
export type RequestResult<
|
||||
TData = unknown,
|
||||
TError = unknown,
|
||||
ThrowOnError extends boolean = boolean,
|
||||
TResponseStyle extends ResponseStyle = 'fields',
|
||||
TResponseStyle extends ResponseStyle = "fields",
|
||||
> = ThrowOnError extends true
|
||||
? Promise<
|
||||
TResponseStyle extends 'data'
|
||||
TResponseStyle extends "data"
|
||||
? TData extends Record<string, unknown>
|
||||
? TData[keyof TData]
|
||||
: TData
|
||||
: {
|
||||
data: TData extends Record<string, unknown>
|
||||
? TData[keyof TData]
|
||||
: TData;
|
||||
request: Request;
|
||||
response: Response;
|
||||
data: TData extends Record<string, unknown> ? TData[keyof TData] : TData
|
||||
request: Request
|
||||
response: Response
|
||||
}
|
||||
>
|
||||
: Promise<
|
||||
TResponseStyle extends 'data'
|
||||
?
|
||||
| (TData extends Record<string, unknown>
|
||||
? TData[keyof TData]
|
||||
: TData)
|
||||
| undefined
|
||||
TResponseStyle extends "data"
|
||||
? (TData extends Record<string, unknown> ? TData[keyof TData] : TData) | undefined
|
||||
: (
|
||||
| {
|
||||
data: TData extends Record<string, unknown>
|
||||
? TData[keyof TData]
|
||||
: TData;
|
||||
error: undefined;
|
||||
data: TData extends Record<string, unknown> ? TData[keyof TData] : TData
|
||||
error: undefined
|
||||
}
|
||||
| {
|
||||
data: undefined;
|
||||
error: TError extends Record<string, unknown>
|
||||
? TError[keyof TError]
|
||||
: TError;
|
||||
data: undefined
|
||||
error: TError extends Record<string, unknown> ? TError[keyof TError] : TError
|
||||
}
|
||||
) & {
|
||||
request: Request;
|
||||
response: Response;
|
||||
request: Request
|
||||
response: Response
|
||||
}
|
||||
>;
|
||||
>
|
||||
|
||||
export interface ClientOptions {
|
||||
baseUrl?: string;
|
||||
responseStyle?: ResponseStyle;
|
||||
throwOnError?: boolean;
|
||||
baseUrl?: string
|
||||
responseStyle?: ResponseStyle
|
||||
throwOnError?: boolean
|
||||
}
|
||||
|
||||
type MethodFn = <
|
||||
TData = unknown,
|
||||
TError = unknown,
|
||||
ThrowOnError extends boolean = false,
|
||||
TResponseStyle extends ResponseStyle = 'fields',
|
||||
TResponseStyle extends ResponseStyle = "fields",
|
||||
>(
|
||||
options: Omit<RequestOptions<TResponseStyle, ThrowOnError>, 'method'>,
|
||||
) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>;
|
||||
options: Omit<RequestOptions<TResponseStyle, ThrowOnError>, "method">,
|
||||
) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>
|
||||
|
||||
type RequestFn = <
|
||||
TData = unknown,
|
||||
TError = unknown,
|
||||
ThrowOnError extends boolean = false,
|
||||
TResponseStyle extends ResponseStyle = 'fields',
|
||||
TResponseStyle extends ResponseStyle = "fields",
|
||||
>(
|
||||
options: Omit<RequestOptions<TResponseStyle, ThrowOnError>, 'method'> &
|
||||
Pick<Required<RequestOptions<TResponseStyle, ThrowOnError>>, 'method'>,
|
||||
) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>;
|
||||
options: Omit<RequestOptions<TResponseStyle, ThrowOnError>, "method"> &
|
||||
Pick<Required<RequestOptions<TResponseStyle, ThrowOnError>>, "method">,
|
||||
) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>
|
||||
|
||||
type BuildUrlFn = <
|
||||
TData extends {
|
||||
body?: unknown;
|
||||
path?: Record<string, unknown>;
|
||||
query?: Record<string, unknown>;
|
||||
url: string;
|
||||
body?: unknown
|
||||
path?: Record<string, unknown>
|
||||
query?: Record<string, unknown>
|
||||
url: string
|
||||
},
|
||||
>(
|
||||
options: Pick<TData, 'url'> & Options<TData>,
|
||||
) => string;
|
||||
options: Pick<TData, "url"> & Options<TData>,
|
||||
) => string
|
||||
|
||||
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
|
||||
@@ -176,47 +156,36 @@ export type Client = CoreClient<RequestFn, Config, MethodFn, BuildUrlFn> & {
|
||||
*/
|
||||
export type CreateClientConfig<T extends ClientOptions = ClientOptions> = (
|
||||
override?: Config<ClientOptions & T>,
|
||||
) => Config<Required<ClientOptions> & T>;
|
||||
) => Config<Required<ClientOptions> & T>
|
||||
|
||||
export interface TDataShape {
|
||||
body?: unknown;
|
||||
headers?: unknown;
|
||||
path?: unknown;
|
||||
query?: unknown;
|
||||
url: string;
|
||||
body?: unknown
|
||||
headers?: unknown
|
||||
path?: unknown
|
||||
query?: unknown
|
||||
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<
|
||||
TData extends TDataShape = TDataShape,
|
||||
ThrowOnError extends boolean = boolean,
|
||||
TResponseStyle extends ResponseStyle = 'fields',
|
||||
> = OmitKeys<
|
||||
RequestOptions<TResponseStyle, ThrowOnError>,
|
||||
'body' | 'path' | 'query' | 'url'
|
||||
> &
|
||||
Omit<TData, 'url'>;
|
||||
TResponseStyle extends ResponseStyle = "fields",
|
||||
> = OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, "body" | "path" | "query" | "url"> & Omit<TData, "url">
|
||||
|
||||
export type OptionsLegacyParser<
|
||||
TData = unknown,
|
||||
ThrowOnError extends boolean = boolean,
|
||||
TResponseStyle extends ResponseStyle = 'fields',
|
||||
TResponseStyle extends ResponseStyle = "fields",
|
||||
> = TData extends { body?: any }
|
||||
? TData extends { headers?: any }
|
||||
? OmitKeys<
|
||||
RequestOptions<TResponseStyle, ThrowOnError>,
|
||||
'body' | 'headers' | 'url'
|
||||
> &
|
||||
TData
|
||||
: OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, 'body' | 'url'> &
|
||||
? OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, "body" | "headers" | "url"> & TData
|
||||
: OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, "body" | "url"> &
|
||||
TData &
|
||||
Pick<RequestOptions<TResponseStyle, ThrowOnError>, 'headers'>
|
||||
Pick<RequestOptions<TResponseStyle, ThrowOnError>, "headers">
|
||||
: TData extends { headers?: any }
|
||||
? OmitKeys<
|
||||
RequestOptions<TResponseStyle, ThrowOnError>,
|
||||
'headers' | 'url'
|
||||
> &
|
||||
? OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, "headers" | "url"> &
|
||||
TData &
|
||||
Pick<RequestOptions<TResponseStyle, ThrowOnError>, 'body'>
|
||||
: OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, 'url'> & TData;
|
||||
Pick<RequestOptions<TResponseStyle, ThrowOnError>, "body">
|
||||
: OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, "url"> & TData
|
||||
|
||||
@@ -1,64 +1,54 @@
|
||||
import { getAuthToken } from '../core/auth';
|
||||
import type {
|
||||
QuerySerializer,
|
||||
QuerySerializerOptions,
|
||||
} from '../core/bodySerializer';
|
||||
import { jsonBodySerializer } from '../core/bodySerializer';
|
||||
import {
|
||||
serializeArrayParam,
|
||||
serializeObjectParam,
|
||||
serializePrimitiveParam,
|
||||
} from '../core/pathSerializer';
|
||||
import type { Client, ClientOptions, Config, RequestOptions } from './types';
|
||||
import { getAuthToken } from "../core/auth"
|
||||
import type { QuerySerializer, QuerySerializerOptions } from "../core/bodySerializer"
|
||||
import { jsonBodySerializer } from "../core/bodySerializer"
|
||||
import { serializeArrayParam, serializeObjectParam, serializePrimitiveParam } from "../core/pathSerializer"
|
||||
import type { Client, ClientOptions, Config, RequestOptions } from "./types"
|
||||
|
||||
interface PathSerializer {
|
||||
path: Record<string, unknown>;
|
||||
url: string;
|
||||
path: Record<string, unknown>
|
||||
url: string
|
||||
}
|
||||
|
||||
const PATH_PARAM_RE = /\{[^{}]+\}/g;
|
||||
const PATH_PARAM_RE = /\{[^{}]+\}/g
|
||||
|
||||
type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited';
|
||||
type MatrixStyle = 'label' | 'matrix' | 'simple';
|
||||
type ArraySeparatorStyle = ArrayStyle | MatrixStyle;
|
||||
type ArrayStyle = "form" | "spaceDelimited" | "pipeDelimited"
|
||||
type MatrixStyle = "label" | "matrix" | "simple"
|
||||
type ArraySeparatorStyle = ArrayStyle | MatrixStyle
|
||||
|
||||
const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => {
|
||||
let url = _url;
|
||||
const matches = _url.match(PATH_PARAM_RE);
|
||||
let url = _url
|
||||
const matches = _url.match(PATH_PARAM_RE)
|
||||
if (matches) {
|
||||
for (const match of matches) {
|
||||
let explode = false;
|
||||
let name = match.substring(1, match.length - 1);
|
||||
let style: ArraySeparatorStyle = 'simple';
|
||||
let explode = false
|
||||
let name = match.substring(1, match.length - 1)
|
||||
let style: ArraySeparatorStyle = "simple"
|
||||
|
||||
if (name.endsWith('*')) {
|
||||
explode = true;
|
||||
name = name.substring(0, name.length - 1);
|
||||
if (name.endsWith("*")) {
|
||||
explode = true
|
||||
name = name.substring(0, name.length - 1)
|
||||
}
|
||||
|
||||
if (name.startsWith('.')) {
|
||||
name = name.substring(1);
|
||||
style = 'label';
|
||||
} else if (name.startsWith(';')) {
|
||||
name = name.substring(1);
|
||||
style = 'matrix';
|
||||
if (name.startsWith(".")) {
|
||||
name = name.substring(1)
|
||||
style = "label"
|
||||
} else if (name.startsWith(";")) {
|
||||
name = name.substring(1)
|
||||
style = "matrix"
|
||||
}
|
||||
|
||||
const value = path[name];
|
||||
const value = path[name]
|
||||
|
||||
if (value === undefined || value === null) {
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
url = url.replace(
|
||||
match,
|
||||
serializeArrayParam({ explode, name, style, value }),
|
||||
);
|
||||
continue;
|
||||
url = url.replace(match, serializeArrayParam({ explode, name, style, value }))
|
||||
continue
|
||||
}
|
||||
|
||||
if (typeof value === 'object') {
|
||||
if (typeof value === "object") {
|
||||
url = url.replace(
|
||||
match,
|
||||
serializeObjectParam({
|
||||
@@ -68,43 +58,37 @@ const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => {
|
||||
value: value as Record<string, unknown>,
|
||||
valueOnly: true,
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
if (style === 'matrix') {
|
||||
if (style === "matrix") {
|
||||
url = url.replace(
|
||||
match,
|
||||
`;${serializePrimitiveParam({
|
||||
name,
|
||||
value: value as string,
|
||||
})}`,
|
||||
);
|
||||
continue;
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
const replaceValue = encodeURIComponent(
|
||||
style === 'label' ? `.${value as string}` : (value as string),
|
||||
);
|
||||
url = url.replace(match, replaceValue);
|
||||
const replaceValue = encodeURIComponent(style === "label" ? `.${value as string}` : (value as string))
|
||||
url = url.replace(match, replaceValue)
|
||||
}
|
||||
}
|
||||
return url;
|
||||
};
|
||||
return url
|
||||
}
|
||||
|
||||
export const createQuerySerializer = <T = unknown>({
|
||||
allowReserved,
|
||||
array,
|
||||
object,
|
||||
}: QuerySerializerOptions = {}) => {
|
||||
export const createQuerySerializer = <T = unknown>({ allowReserved, array, object }: QuerySerializerOptions = {}) => {
|
||||
const querySerializer = (queryParams: T) => {
|
||||
const search: string[] = [];
|
||||
if (queryParams && typeof queryParams === 'object') {
|
||||
const search: string[] = []
|
||||
if (queryParams && typeof queryParams === "object") {
|
||||
for (const name in queryParams) {
|
||||
const value = queryParams[name];
|
||||
const value = queryParams[name]
|
||||
|
||||
if (value === undefined || value === null) {
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
@@ -112,129 +96,120 @@ export const createQuerySerializer = <T = unknown>({
|
||||
allowReserved,
|
||||
explode: true,
|
||||
name,
|
||||
style: 'form',
|
||||
style: "form",
|
||||
value,
|
||||
...array,
|
||||
});
|
||||
if (serializedArray) search.push(serializedArray);
|
||||
} else if (typeof value === 'object') {
|
||||
})
|
||||
if (serializedArray) search.push(serializedArray)
|
||||
} else if (typeof value === "object") {
|
||||
const serializedObject = serializeObjectParam({
|
||||
allowReserved,
|
||||
explode: true,
|
||||
name,
|
||||
style: 'deepObject',
|
||||
style: "deepObject",
|
||||
value: value as Record<string, unknown>,
|
||||
...object,
|
||||
});
|
||||
if (serializedObject) search.push(serializedObject);
|
||||
})
|
||||
if (serializedObject) search.push(serializedObject)
|
||||
} else {
|
||||
const serializedPrimitive = serializePrimitiveParam({
|
||||
allowReserved,
|
||||
name,
|
||||
value: value as string,
|
||||
});
|
||||
if (serializedPrimitive) search.push(serializedPrimitive);
|
||||
})
|
||||
if (serializedPrimitive) search.push(serializedPrimitive)
|
||||
}
|
||||
}
|
||||
}
|
||||
return search.join('&');
|
||||
};
|
||||
return querySerializer;
|
||||
};
|
||||
return search.join("&")
|
||||
}
|
||||
return querySerializer
|
||||
}
|
||||
|
||||
/**
|
||||
* Infers parseAs value from provided Content-Type header.
|
||||
*/
|
||||
export const getParseAs = (
|
||||
contentType: string | null,
|
||||
): Exclude<Config['parseAs'], 'auto'> => {
|
||||
export const getParseAs = (contentType: string | null): Exclude<Config["parseAs"], "auto"> => {
|
||||
if (!contentType) {
|
||||
// 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.
|
||||
return 'stream';
|
||||
return "stream"
|
||||
}
|
||||
|
||||
const cleanContent = contentType.split(';')[0]?.trim();
|
||||
const cleanContent = contentType.split(";")[0]?.trim()
|
||||
|
||||
if (!cleanContent) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
cleanContent.startsWith('application/json') ||
|
||||
cleanContent.endsWith('+json')
|
||||
) {
|
||||
return 'json';
|
||||
if (cleanContent.startsWith("application/json") || cleanContent.endsWith("+json")) {
|
||||
return "json"
|
||||
}
|
||||
|
||||
if (cleanContent === 'multipart/form-data') {
|
||||
return 'formData';
|
||||
if (cleanContent === "multipart/form-data") {
|
||||
return "formData"
|
||||
}
|
||||
|
||||
if (
|
||||
['application/', 'audio/', 'image/', 'video/'].some((type) =>
|
||||
cleanContent.startsWith(type),
|
||||
)
|
||||
) {
|
||||
return 'blob';
|
||||
if (["application/", "audio/", "image/", "video/"].some((type) => cleanContent.startsWith(type))) {
|
||||
return "blob"
|
||||
}
|
||||
|
||||
if (cleanContent.startsWith('text/')) {
|
||||
return 'text';
|
||||
if (cleanContent.startsWith("text/")) {
|
||||
return "text"
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
return
|
||||
}
|
||||
|
||||
export const setAuthParams = async ({
|
||||
security,
|
||||
...options
|
||||
}: Pick<Required<RequestOptions>, 'security'> &
|
||||
Pick<RequestOptions, 'auth' | 'query'> & {
|
||||
headers: Headers;
|
||||
}: Pick<Required<RequestOptions>, "security"> &
|
||||
Pick<RequestOptions, "auth" | "query"> & {
|
||||
headers: Headers
|
||||
}) => {
|
||||
for (const auth of security) {
|
||||
const token = await getAuthToken(auth, options.auth);
|
||||
const token = await getAuthToken(auth, options.auth)
|
||||
|
||||
if (!token) {
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
|
||||
const name = auth.name ?? 'Authorization';
|
||||
const name = auth.name ?? "Authorization"
|
||||
|
||||
switch (auth.in) {
|
||||
case 'query':
|
||||
case "query":
|
||||
if (!options.query) {
|
||||
options.query = {};
|
||||
options.query = {}
|
||||
}
|
||||
options.query[name] = token;
|
||||
break;
|
||||
case 'cookie':
|
||||
options.headers.append('Cookie', `${name}=${token}`);
|
||||
break;
|
||||
case 'header':
|
||||
options.query[name] = token
|
||||
break
|
||||
case "cookie":
|
||||
options.headers.append("Cookie", `${name}=${token}`)
|
||||
break
|
||||
case "header":
|
||||
default:
|
||||
options.headers.set(name, token);
|
||||
break;
|
||||
options.headers.set(name, token)
|
||||
break
|
||||
}
|
||||
|
||||
return;
|
||||
return
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const buildUrl: Client['buildUrl'] = (options) => {
|
||||
export const buildUrl: Client["buildUrl"] = (options) => {
|
||||
const url = getUrl({
|
||||
baseUrl: options.baseUrl as string,
|
||||
path: options.path,
|
||||
query: options.query,
|
||||
querySerializer:
|
||||
typeof options.querySerializer === 'function'
|
||||
typeof options.querySerializer === "function"
|
||||
? options.querySerializer
|
||||
: createQuerySerializer(options.querySerializer),
|
||||
url: options.url,
|
||||
});
|
||||
return url;
|
||||
};
|
||||
})
|
||||
return url
|
||||
}
|
||||
|
||||
export const getUrl = ({
|
||||
baseUrl,
|
||||
@@ -243,144 +218,125 @@ export const getUrl = ({
|
||||
querySerializer,
|
||||
url: _url,
|
||||
}: {
|
||||
baseUrl?: string;
|
||||
path?: Record<string, unknown>;
|
||||
query?: Record<string, unknown>;
|
||||
querySerializer: QuerySerializer;
|
||||
url: string;
|
||||
baseUrl?: string
|
||||
path?: Record<string, unknown>
|
||||
query?: Record<string, unknown>
|
||||
querySerializer: QuerySerializer
|
||||
url: string
|
||||
}) => {
|
||||
const pathUrl = _url.startsWith('/') ? _url : `/${_url}`;
|
||||
let url = (baseUrl ?? '') + pathUrl;
|
||||
const pathUrl = _url.startsWith("/") ? _url : `/${_url}`
|
||||
let url = (baseUrl ?? "") + pathUrl
|
||||
if (path) {
|
||||
url = defaultPathSerializer({ path, url });
|
||||
url = defaultPathSerializer({ path, url })
|
||||
}
|
||||
let search = query ? querySerializer(query) : '';
|
||||
if (search.startsWith('?')) {
|
||||
search = search.substring(1);
|
||||
let search = query ? querySerializer(query) : ""
|
||||
if (search.startsWith("?")) {
|
||||
search = search.substring(1)
|
||||
}
|
||||
if (search) {
|
||||
url += `?${search}`;
|
||||
url += `?${search}`
|
||||
}
|
||||
return url;
|
||||
};
|
||||
return url
|
||||
}
|
||||
|
||||
export const mergeConfigs = (a: Config, b: Config): Config => {
|
||||
const config = { ...a, ...b };
|
||||
if (config.baseUrl?.endsWith('/')) {
|
||||
config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1);
|
||||
const config = { ...a, ...b }
|
||||
if (config.baseUrl?.endsWith("/")) {
|
||||
config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1)
|
||||
}
|
||||
config.headers = mergeHeaders(a.headers, b.headers);
|
||||
return config;
|
||||
};
|
||||
config.headers = mergeHeaders(a.headers, b.headers)
|
||||
return config
|
||||
}
|
||||
|
||||
export const mergeHeaders = (
|
||||
...headers: Array<Required<Config>['headers'] | undefined>
|
||||
): Headers => {
|
||||
const mergedHeaders = new Headers();
|
||||
export const mergeHeaders = (...headers: Array<Required<Config>["headers"] | undefined>): Headers => {
|
||||
const mergedHeaders = new Headers()
|
||||
for (const header of headers) {
|
||||
if (!header || typeof header !== 'object') {
|
||||
continue;
|
||||
if (!header || typeof header !== "object") {
|
||||
continue
|
||||
}
|
||||
|
||||
const iterator =
|
||||
header instanceof Headers ? header.entries() : Object.entries(header);
|
||||
const iterator = header instanceof Headers ? header.entries() : Object.entries(header)
|
||||
|
||||
for (const [key, value] of iterator) {
|
||||
if (value === null) {
|
||||
mergedHeaders.delete(key);
|
||||
mergedHeaders.delete(key)
|
||||
} else if (Array.isArray(value)) {
|
||||
for (const v of value) {
|
||||
mergedHeaders.append(key, v as string);
|
||||
mergedHeaders.append(key, v as string)
|
||||
}
|
||||
} else if (value !== undefined) {
|
||||
// assume object headers are meant to be JSON stringified, i.e. their
|
||||
// content value in OpenAPI specification is 'application/json'
|
||||
mergedHeaders.set(
|
||||
key,
|
||||
typeof value === 'object' ? JSON.stringify(value) : (value as string),
|
||||
);
|
||||
mergedHeaders.set(key, typeof value === "object" ? JSON.stringify(value) : (value as string))
|
||||
}
|
||||
}
|
||||
}
|
||||
return mergedHeaders;
|
||||
};
|
||||
return mergedHeaders
|
||||
}
|
||||
|
||||
type ErrInterceptor<Err, Res, Req, Options> = (
|
||||
error: Err,
|
||||
response: Res,
|
||||
request: Req,
|
||||
options: Options,
|
||||
) => Err | Promise<Err>;
|
||||
) => Err | Promise<Err>
|
||||
|
||||
type ReqInterceptor<Req, Options> = (
|
||||
request: Req,
|
||||
options: Options,
|
||||
) => Req | Promise<Req>;
|
||||
type ReqInterceptor<Req, Options> = (request: Req, options: Options) => Req | Promise<Req>
|
||||
|
||||
type ResInterceptor<Res, Req, Options> = (
|
||||
response: Res,
|
||||
request: Req,
|
||||
options: Options,
|
||||
) => Res | Promise<Res>;
|
||||
type ResInterceptor<Res, Req, Options> = (response: Res, request: Req, options: Options) => Res | Promise<Res>
|
||||
|
||||
class Interceptors<Interceptor> {
|
||||
_fns: (Interceptor | null)[];
|
||||
_fns: (Interceptor | null)[]
|
||||
|
||||
constructor() {
|
||||
this._fns = [];
|
||||
this._fns = []
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._fns = [];
|
||||
this._fns = []
|
||||
}
|
||||
|
||||
getInterceptorIndex(id: number | Interceptor): number {
|
||||
if (typeof id === 'number') {
|
||||
return this._fns[id] ? id : -1;
|
||||
if (typeof id === "number") {
|
||||
return this._fns[id] ? id : -1
|
||||
} else {
|
||||
return this._fns.indexOf(id);
|
||||
return this._fns.indexOf(id)
|
||||
}
|
||||
}
|
||||
exists(id: number | Interceptor) {
|
||||
const index = this.getInterceptorIndex(id);
|
||||
return !!this._fns[index];
|
||||
const index = this.getInterceptorIndex(id)
|
||||
return !!this._fns[index]
|
||||
}
|
||||
|
||||
eject(id: number | Interceptor) {
|
||||
const index = this.getInterceptorIndex(id);
|
||||
const index = this.getInterceptorIndex(id)
|
||||
if (this._fns[index]) {
|
||||
this._fns[index] = null;
|
||||
this._fns[index] = null
|
||||
}
|
||||
}
|
||||
|
||||
update(id: number | Interceptor, fn: Interceptor) {
|
||||
const index = this.getInterceptorIndex(id);
|
||||
const index = this.getInterceptorIndex(id)
|
||||
if (this._fns[index]) {
|
||||
this._fns[index] = fn;
|
||||
return id;
|
||||
this._fns[index] = fn
|
||||
return id
|
||||
} else {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
use(fn: Interceptor) {
|
||||
this._fns = [...this._fns, fn];
|
||||
return this._fns.length - 1;
|
||||
this._fns = [...this._fns, fn]
|
||||
return this._fns.length - 1
|
||||
}
|
||||
}
|
||||
|
||||
// `createInterceptors()` response, meant for external use as it does not
|
||||
// expose internals
|
||||
export interface Middleware<Req, Res, Err, Options> {
|
||||
error: Pick<
|
||||
Interceptors<ErrInterceptor<Err, Res, Req, Options>>,
|
||||
'eject' | 'use'
|
||||
>;
|
||||
request: Pick<Interceptors<ReqInterceptor<Req, Options>>, 'eject' | 'use'>;
|
||||
response: Pick<
|
||||
Interceptors<ResInterceptor<Res, Req, Options>>,
|
||||
'eject' | 'use'
|
||||
>;
|
||||
error: Pick<Interceptors<ErrInterceptor<Err, 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
|
||||
@@ -388,30 +344,30 @@ export const createInterceptors = <Req, Res, Err, Options>() => ({
|
||||
error: new Interceptors<ErrInterceptor<Err, Res, Req, Options>>(),
|
||||
request: new Interceptors<ReqInterceptor<Req, Options>>(),
|
||||
response: new Interceptors<ResInterceptor<Res, Req, Options>>(),
|
||||
});
|
||||
})
|
||||
|
||||
const defaultQuerySerializer = createQuerySerializer({
|
||||
allowReserved: false,
|
||||
array: {
|
||||
explode: true,
|
||||
style: 'form',
|
||||
style: "form",
|
||||
},
|
||||
object: {
|
||||
explode: true,
|
||||
style: 'deepObject',
|
||||
style: "deepObject",
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
const defaultHeaders = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
export const createConfig = <T extends ClientOptions = ClientOptions>(
|
||||
override: Config<Omit<ClientOptions, keyof T> & T> = {},
|
||||
): Config<Omit<ClientOptions, keyof T> & T> => ({
|
||||
...jsonBodySerializer,
|
||||
headers: defaultHeaders,
|
||||
parseAs: 'auto',
|
||||
parseAs: "auto",
|
||||
querySerializer: defaultQuerySerializer,
|
||||
...override,
|
||||
});
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export type AuthToken = string | undefined;
|
||||
export type AuthToken = string | undefined
|
||||
|
||||
export interface Auth {
|
||||
/**
|
||||
@@ -6,35 +6,34 @@ export interface Auth {
|
||||
*
|
||||
* @default 'header'
|
||||
*/
|
||||
in?: 'header' | 'query' | 'cookie';
|
||||
in?: "header" | "query" | "cookie"
|
||||
/**
|
||||
* Header or query parameter name.
|
||||
*
|
||||
* @default 'Authorization'
|
||||
*/
|
||||
name?: string;
|
||||
scheme?: 'basic' | 'bearer';
|
||||
type: 'apiKey' | 'http';
|
||||
name?: string
|
||||
scheme?: "basic" | "bearer"
|
||||
type: "apiKey" | "http"
|
||||
}
|
||||
|
||||
export const getAuthToken = async (
|
||||
auth: Auth,
|
||||
callback: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken,
|
||||
): Promise<string | undefined> => {
|
||||
const token =
|
||||
typeof callback === 'function' ? await callback(auth) : callback;
|
||||
const token = typeof callback === "function" ? await callback(auth) : callback
|
||||
|
||||
if (!token) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
if (auth.scheme === 'bearer') {
|
||||
return `Bearer ${token}`;
|
||||
if (auth.scheme === "bearer") {
|
||||
return `Bearer ${token}`
|
||||
}
|
||||
|
||||
if (auth.scheme === 'basic') {
|
||||
return `Basic ${btoa(token)}`;
|
||||
if (auth.scheme === "basic") {
|
||||
return `Basic ${btoa(token)}`
|
||||
}
|
||||
|
||||
return token;
|
||||
};
|
||||
return token
|
||||
}
|
||||
|
||||
@@ -1,88 +1,70 @@
|
||||
import type {
|
||||
ArrayStyle,
|
||||
ObjectStyle,
|
||||
SerializerOptions,
|
||||
} from './pathSerializer';
|
||||
import type { 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 {
|
||||
allowReserved?: boolean;
|
||||
array?: SerializerOptions<ArrayStyle>;
|
||||
object?: SerializerOptions<ObjectStyle>;
|
||||
allowReserved?: boolean
|
||||
array?: SerializerOptions<ArrayStyle>
|
||||
object?: SerializerOptions<ObjectStyle>
|
||||
}
|
||||
|
||||
const serializeFormDataPair = (
|
||||
data: FormData,
|
||||
key: string,
|
||||
value: unknown,
|
||||
): void => {
|
||||
if (typeof value === 'string' || value instanceof Blob) {
|
||||
data.append(key, value);
|
||||
const serializeFormDataPair = (data: FormData, key: string, value: unknown): void => {
|
||||
if (typeof value === "string" || value instanceof Blob) {
|
||||
data.append(key, value)
|
||||
} else {
|
||||
data.append(key, JSON.stringify(value));
|
||||
data.append(key, JSON.stringify(value))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const serializeUrlSearchParamsPair = (
|
||||
data: URLSearchParams,
|
||||
key: string,
|
||||
value: unknown,
|
||||
): void => {
|
||||
if (typeof value === 'string') {
|
||||
data.append(key, value);
|
||||
const serializeUrlSearchParamsPair = (data: URLSearchParams, key: string, value: unknown): void => {
|
||||
if (typeof value === "string") {
|
||||
data.append(key, value)
|
||||
} else {
|
||||
data.append(key, JSON.stringify(value));
|
||||
data.append(key, JSON.stringify(value))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const formDataBodySerializer = {
|
||||
bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>(
|
||||
body: T,
|
||||
): FormData => {
|
||||
const data = new FormData();
|
||||
bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>(body: T): FormData => {
|
||||
const data = new FormData()
|
||||
|
||||
Object.entries(body).forEach(([key, value]) => {
|
||||
if (value === undefined || value === null) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((v) => serializeFormDataPair(data, key, v));
|
||||
value.forEach((v) => serializeFormDataPair(data, key, v))
|
||||
} else {
|
||||
serializeFormDataPair(data, key, value);
|
||||
serializeFormDataPair(data, key, value)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
return data;
|
||||
return data
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const jsonBodySerializer = {
|
||||
bodySerializer: <T>(body: T): string =>
|
||||
JSON.stringify(body, (_key, value) =>
|
||||
typeof value === 'bigint' ? value.toString() : value,
|
||||
),
|
||||
};
|
||||
JSON.stringify(body, (_key, value) => (typeof value === "bigint" ? value.toString() : value)),
|
||||
}
|
||||
|
||||
export const urlSearchParamsBodySerializer = {
|
||||
bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>(
|
||||
body: T,
|
||||
): string => {
|
||||
const data = new URLSearchParams();
|
||||
bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>(body: T): string => {
|
||||
const data = new URLSearchParams()
|
||||
|
||||
Object.entries(body).forEach(([key, value]) => {
|
||||
if (value === undefined || value === null) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((v) => serializeUrlSearchParamsPair(data, key, v));
|
||||
value.forEach((v) => serializeUrlSearchParamsPair(data, key, v))
|
||||
} 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 =
|
||||
| {
|
||||
in: Exclude<Slot, 'body'>;
|
||||
in: Exclude<Slot, "body">
|
||||
/**
|
||||
* 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.
|
||||
* 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?: string;
|
||||
map?: string;
|
||||
};
|
||||
key?: string
|
||||
map?: string
|
||||
}
|
||||
|
||||
export interface Fields {
|
||||
allowExtra?: Partial<Record<Slot, boolean>>;
|
||||
args?: ReadonlyArray<Field>;
|
||||
allowExtra?: Partial<Record<Slot, boolean>>
|
||||
args?: ReadonlyArray<Field>
|
||||
}
|
||||
|
||||
export type FieldsConfig = ReadonlyArray<Field | Fields>;
|
||||
export type FieldsConfig = ReadonlyArray<Field | Fields>
|
||||
|
||||
const extraPrefixesMap: Record<string, Slot> = {
|
||||
$body_: 'body',
|
||||
$headers_: 'headers',
|
||||
$path_: 'path',
|
||||
$query_: 'query',
|
||||
};
|
||||
const extraPrefixes = Object.entries(extraPrefixesMap);
|
||||
$body_: "body",
|
||||
$headers_: "headers",
|
||||
$path_: "path",
|
||||
$query_: "query",
|
||||
}
|
||||
const extraPrefixes = Object.entries(extraPrefixesMap)
|
||||
|
||||
type KeyMap = Map<
|
||||
string,
|
||||
{
|
||||
in: Slot;
|
||||
map?: string;
|
||||
in: Slot
|
||||
map?: string
|
||||
}
|
||||
>;
|
||||
>
|
||||
|
||||
const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => {
|
||||
if (!map) {
|
||||
map = new Map();
|
||||
map = new Map()
|
||||
}
|
||||
|
||||
for (const config of fields) {
|
||||
if ('in' in config) {
|
||||
if ("in" in config) {
|
||||
if (config.key) {
|
||||
map.set(config.key, {
|
||||
in: config.in,
|
||||
map: config.map,
|
||||
});
|
||||
})
|
||||
}
|
||||
} else if (config.args) {
|
||||
buildKeyMap(config.args, map);
|
||||
buildKeyMap(config.args, map)
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
};
|
||||
return map
|
||||
}
|
||||
|
||||
interface Params {
|
||||
body: unknown;
|
||||
headers: Record<string, unknown>;
|
||||
path: Record<string, unknown>;
|
||||
query: Record<string, unknown>;
|
||||
body: unknown
|
||||
headers: Record<string, unknown>
|
||||
path: Record<string, unknown>
|
||||
query: Record<string, unknown>
|
||||
}
|
||||
|
||||
const stripEmptySlots = (params: Params) => {
|
||||
for (const [slot, value] of Object.entries(params)) {
|
||||
if (value && typeof value === 'object' && !Object.keys(value).length) {
|
||||
delete params[slot as Slot];
|
||||
if (value && typeof value === "object" && !Object.keys(value).length) {
|
||||
delete params[slot as Slot]
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const buildClientParams = (
|
||||
args: ReadonlyArray<unknown>,
|
||||
fields: FieldsConfig,
|
||||
) => {
|
||||
export const buildClientParams = (args: ReadonlyArray<unknown>, fields: FieldsConfig) => {
|
||||
const params: Params = {
|
||||
body: {},
|
||||
headers: {},
|
||||
path: {},
|
||||
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()) {
|
||||
if (fields[index]) {
|
||||
config = fields[index];
|
||||
config = fields[index]
|
||||
}
|
||||
|
||||
if (!config) {
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
|
||||
if ('in' in config) {
|
||||
if ("in" in config) {
|
||||
if (config.key) {
|
||||
const field = map.get(config.key)!;
|
||||
const name = field.map || config.key;
|
||||
(params[field.in] as Record<string, unknown>)[name] = arg;
|
||||
const field = map.get(config.key)!
|
||||
const name = field.map || config.key
|
||||
;(params[field.in] as Record<string, unknown>)[name] = arg
|
||||
} else {
|
||||
params.body = arg;
|
||||
params.body = arg
|
||||
}
|
||||
} else {
|
||||
for (const [key, value] of Object.entries(arg ?? {})) {
|
||||
const field = map.get(key);
|
||||
const field = map.get(key)
|
||||
|
||||
if (field) {
|
||||
const name = field.map || key;
|
||||
(params[field.in] as Record<string, unknown>)[name] = value;
|
||||
const name = field.map || key
|
||||
;(params[field.in] as Record<string, unknown>)[name] = value
|
||||
} else {
|
||||
const extra = extraPrefixes.find(([prefix]) =>
|
||||
key.startsWith(prefix),
|
||||
);
|
||||
const extra = extraPrefixes.find(([prefix]) => key.startsWith(prefix))
|
||||
|
||||
if (extra) {
|
||||
const [prefix, slot] = extra;
|
||||
(params[slot] as Record<string, unknown>)[
|
||||
key.slice(prefix.length)
|
||||
] = value;
|
||||
const [prefix, slot] = extra
|
||||
;(params[slot] as Record<string, unknown>)[key.slice(prefix.length)] = value
|
||||
} else {
|
||||
for (const [slot, allowed] of Object.entries(
|
||||
config.allowExtra ?? {},
|
||||
)) {
|
||||
for (const [slot, allowed] of Object.entries(config.allowExtra ?? {})) {
|
||||
if (allowed) {
|
||||
(params[slot as Slot] as Record<string, unknown>)[key] = value;
|
||||
break;
|
||||
;(params[slot as Slot] as Record<string, unknown>)[key] = value
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,7 +136,7 @@ export const buildClientParams = (
|
||||
}
|
||||
}
|
||||
|
||||
stripEmptySlots(params);
|
||||
stripEmptySlots(params)
|
||||
|
||||
return params;
|
||||
};
|
||||
return params
|
||||
}
|
||||
|
||||
@@ -1,68 +1,66 @@
|
||||
interface SerializeOptions<T>
|
||||
extends SerializePrimitiveOptions,
|
||||
SerializerOptions<T> {}
|
||||
interface SerializeOptions<T> extends SerializePrimitiveOptions, SerializerOptions<T> {}
|
||||
|
||||
interface SerializePrimitiveOptions {
|
||||
allowReserved?: boolean;
|
||||
name: string;
|
||||
allowReserved?: boolean
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface SerializerOptions<T> {
|
||||
/**
|
||||
* @default true
|
||||
*/
|
||||
explode: boolean;
|
||||
style: T;
|
||||
explode: boolean
|
||||
style: T
|
||||
}
|
||||
|
||||
export type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited';
|
||||
export type ArraySeparatorStyle = ArrayStyle | MatrixStyle;
|
||||
type MatrixStyle = 'label' | 'matrix' | 'simple';
|
||||
export type ObjectStyle = 'form' | 'deepObject';
|
||||
type ObjectSeparatorStyle = ObjectStyle | MatrixStyle;
|
||||
export type ArrayStyle = "form" | "spaceDelimited" | "pipeDelimited"
|
||||
export type ArraySeparatorStyle = ArrayStyle | MatrixStyle
|
||||
type MatrixStyle = "label" | "matrix" | "simple"
|
||||
export type ObjectStyle = "form" | "deepObject"
|
||||
type ObjectSeparatorStyle = ObjectStyle | MatrixStyle
|
||||
|
||||
interface SerializePrimitiveParam extends SerializePrimitiveOptions {
|
||||
value: string;
|
||||
value: string
|
||||
}
|
||||
|
||||
export const separatorArrayExplode = (style: ArraySeparatorStyle) => {
|
||||
switch (style) {
|
||||
case 'label':
|
||||
return '.';
|
||||
case 'matrix':
|
||||
return ';';
|
||||
case 'simple':
|
||||
return ',';
|
||||
case "label":
|
||||
return "."
|
||||
case "matrix":
|
||||
return ";"
|
||||
case "simple":
|
||||
return ","
|
||||
default:
|
||||
return '&';
|
||||
return "&"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => {
|
||||
switch (style) {
|
||||
case 'form':
|
||||
return ',';
|
||||
case 'pipeDelimited':
|
||||
return '|';
|
||||
case 'spaceDelimited':
|
||||
return '%20';
|
||||
case "form":
|
||||
return ","
|
||||
case "pipeDelimited":
|
||||
return "|"
|
||||
case "spaceDelimited":
|
||||
return "%20"
|
||||
default:
|
||||
return ',';
|
||||
return ","
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const separatorObjectExplode = (style: ObjectSeparatorStyle) => {
|
||||
switch (style) {
|
||||
case 'label':
|
||||
return '.';
|
||||
case 'matrix':
|
||||
return ';';
|
||||
case 'simple':
|
||||
return ',';
|
||||
case "label":
|
||||
return "."
|
||||
case "matrix":
|
||||
return ";"
|
||||
case "simple":
|
||||
return ","
|
||||
default:
|
||||
return '&';
|
||||
return "&"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const serializeArrayParam = ({
|
||||
allowReserved,
|
||||
@@ -71,60 +69,54 @@ export const serializeArrayParam = ({
|
||||
style,
|
||||
value,
|
||||
}: SerializeOptions<ArraySeparatorStyle> & {
|
||||
value: unknown[];
|
||||
value: unknown[]
|
||||
}) => {
|
||||
if (!explode) {
|
||||
const joinedValues = (
|
||||
allowReserved ? value : value.map((v) => encodeURIComponent(v as string))
|
||||
).join(separatorArrayNoExplode(style));
|
||||
const joinedValues = (allowReserved ? value : value.map((v) => encodeURIComponent(v as string))).join(
|
||||
separatorArrayNoExplode(style),
|
||||
)
|
||||
switch (style) {
|
||||
case 'label':
|
||||
return `.${joinedValues}`;
|
||||
case 'matrix':
|
||||
return `;${name}=${joinedValues}`;
|
||||
case 'simple':
|
||||
return joinedValues;
|
||||
case "label":
|
||||
return `.${joinedValues}`
|
||||
case "matrix":
|
||||
return `;${name}=${joinedValues}`
|
||||
case "simple":
|
||||
return joinedValues
|
||||
default:
|
||||
return `${name}=${joinedValues}`;
|
||||
return `${name}=${joinedValues}`
|
||||
}
|
||||
}
|
||||
|
||||
const separator = separatorArrayExplode(style);
|
||||
const separator = separatorArrayExplode(style)
|
||||
const joinedValues = value
|
||||
.map((v) => {
|
||||
if (style === 'label' || style === 'simple') {
|
||||
return allowReserved ? v : encodeURIComponent(v as string);
|
||||
if (style === "label" || style === "simple") {
|
||||
return allowReserved ? v : encodeURIComponent(v as string)
|
||||
}
|
||||
|
||||
return serializePrimitiveParam({
|
||||
allowReserved,
|
||||
name,
|
||||
value: v as string,
|
||||
});
|
||||
})
|
||||
})
|
||||
.join(separator);
|
||||
return style === 'label' || style === 'matrix'
|
||||
? separator + joinedValues
|
||||
: joinedValues;
|
||||
};
|
||||
.join(separator)
|
||||
return style === "label" || style === "matrix" ? separator + joinedValues : joinedValues
|
||||
}
|
||||
|
||||
export const serializePrimitiveParam = ({
|
||||
allowReserved,
|
||||
name,
|
||||
value,
|
||||
}: SerializePrimitiveParam) => {
|
||||
export const serializePrimitiveParam = ({ allowReserved, name, value }: SerializePrimitiveParam) => {
|
||||
if (value === undefined || value === null) {
|
||||
return '';
|
||||
return ""
|
||||
}
|
||||
|
||||
if (typeof value === 'object') {
|
||||
if (typeof value === "object") {
|
||||
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 = ({
|
||||
allowReserved,
|
||||
@@ -134,46 +126,40 @@ export const serializeObjectParam = ({
|
||||
value,
|
||||
valueOnly,
|
||||
}: SerializeOptions<ObjectSeparatorStyle> & {
|
||||
value: Record<string, unknown> | Date;
|
||||
valueOnly?: boolean;
|
||||
value: Record<string, unknown> | Date
|
||||
valueOnly?: boolean
|
||||
}) => {
|
||||
if (value instanceof Date) {
|
||||
return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`;
|
||||
return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`
|
||||
}
|
||||
|
||||
if (style !== 'deepObject' && !explode) {
|
||||
let values: string[] = [];
|
||||
if (style !== "deepObject" && !explode) {
|
||||
let values: string[] = []
|
||||
Object.entries(value).forEach(([key, v]) => {
|
||||
values = [
|
||||
...values,
|
||||
key,
|
||||
allowReserved ? (v as string) : encodeURIComponent(v as string),
|
||||
];
|
||||
});
|
||||
const joinedValues = values.join(',');
|
||||
values = [...values, key, allowReserved ? (v as string) : encodeURIComponent(v as string)]
|
||||
})
|
||||
const joinedValues = values.join(",")
|
||||
switch (style) {
|
||||
case 'form':
|
||||
return `${name}=${joinedValues}`;
|
||||
case 'label':
|
||||
return `.${joinedValues}`;
|
||||
case 'matrix':
|
||||
return `;${name}=${joinedValues}`;
|
||||
case "form":
|
||||
return `${name}=${joinedValues}`
|
||||
case "label":
|
||||
return `.${joinedValues}`
|
||||
case "matrix":
|
||||
return `;${name}=${joinedValues}`
|
||||
default:
|
||||
return joinedValues;
|
||||
return joinedValues
|
||||
}
|
||||
}
|
||||
|
||||
const separator = separatorObjectExplode(style);
|
||||
const separator = separatorObjectExplode(style)
|
||||
const joinedValues = Object.entries(value)
|
||||
.map(([key, v]) =>
|
||||
serializePrimitiveParam({
|
||||
allowReserved,
|
||||
name: style === 'deepObject' ? `${name}[${key}]` : key,
|
||||
name: style === "deepObject" ? `${name}[${key}]` : key,
|
||||
value: v as string,
|
||||
}),
|
||||
)
|
||||
.join(separator);
|
||||
return style === 'label' || style === 'matrix'
|
||||
? separator + joinedValues
|
||||
: joinedValues;
|
||||
};
|
||||
.join(separator)
|
||||
return style === "label" || style === "matrix" ? separator + joinedValues : joinedValues
|
||||
}
|
||||
|
||||
@@ -1,32 +1,23 @@
|
||||
import type { Auth, AuthToken } from './auth';
|
||||
import type {
|
||||
BodySerializer,
|
||||
QuerySerializer,
|
||||
QuerySerializerOptions,
|
||||
} from './bodySerializer';
|
||||
import type { Auth, AuthToken } from "./auth"
|
||||
import type { BodySerializer, QuerySerializer, QuerySerializerOptions } from "./bodySerializer"
|
||||
|
||||
export interface Client<
|
||||
RequestFn = never,
|
||||
Config = unknown,
|
||||
MethodFn = never,
|
||||
BuildUrlFn = never,
|
||||
> {
|
||||
export interface Client<RequestFn = never, Config = unknown, MethodFn = never, BuildUrlFn = never> {
|
||||
/**
|
||||
* Returns the final request URL.
|
||||
*/
|
||||
buildUrl: BuildUrlFn;
|
||||
connect: MethodFn;
|
||||
delete: MethodFn;
|
||||
get: MethodFn;
|
||||
getConfig: () => Config;
|
||||
head: MethodFn;
|
||||
options: MethodFn;
|
||||
patch: MethodFn;
|
||||
post: MethodFn;
|
||||
put: MethodFn;
|
||||
request: RequestFn;
|
||||
setConfig: (config: Config) => Config;
|
||||
trace: MethodFn;
|
||||
buildUrl: BuildUrlFn
|
||||
connect: MethodFn
|
||||
delete: MethodFn
|
||||
get: MethodFn
|
||||
getConfig: () => Config
|
||||
head: MethodFn
|
||||
options: MethodFn
|
||||
patch: MethodFn
|
||||
post: MethodFn
|
||||
put: MethodFn
|
||||
request: RequestFn
|
||||
setConfig: (config: Config) => Config
|
||||
trace: MethodFn
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
@@ -34,12 +25,12 @@ export interface Config {
|
||||
* Auth token or a function returning auth token. The resolved value will be
|
||||
* 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,
|
||||
* {@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
|
||||
* `Headers` object with.
|
||||
@@ -47,32 +38,14 @@ export interface Config {
|
||||
* {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more}
|
||||
*/
|
||||
headers?:
|
||||
| RequestInit['headers']
|
||||
| Record<
|
||||
string,
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| (string | number | boolean)[]
|
||||
| null
|
||||
| undefined
|
||||
| unknown
|
||||
>;
|
||||
| RequestInit["headers"]
|
||||
| Record<string, string | number | boolean | (string | number | boolean)[] | null | undefined | unknown>
|
||||
/**
|
||||
* The request method.
|
||||
*
|
||||
* {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more}
|
||||
*/
|
||||
method?:
|
||||
| 'CONNECT'
|
||||
| 'DELETE'
|
||||
| 'GET'
|
||||
| 'HEAD'
|
||||
| 'OPTIONS'
|
||||
| 'PATCH'
|
||||
| 'POST'
|
||||
| 'PUT'
|
||||
| 'TRACE';
|
||||
method?: "CONNECT" | "DELETE" | "GET" | "HEAD" | "OPTIONS" | "PATCH" | "POST" | "PUT" | "TRACE"
|
||||
/**
|
||||
* A function for serializing request query parameters. By default, arrays
|
||||
* 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}
|
||||
*/
|
||||
querySerializer?: QuerySerializer | QuerySerializerOptions;
|
||||
querySerializer?: QuerySerializer | QuerySerializerOptions
|
||||
/**
|
||||
* 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 server.
|
||||
*/
|
||||
requestValidator?: (data: unknown) => Promise<unknown>;
|
||||
requestValidator?: (data: unknown) => Promise<unknown>
|
||||
/**
|
||||
* A function transforming response data before it's returned. This is useful
|
||||
* 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
|
||||
* the response conforms to the desired shape, so it can be safely passed to
|
||||
* the transformers and returned to the user.
|
||||
*/
|
||||
responseValidator?: (data: unknown) => Promise<unknown>;
|
||||
responseValidator?: (data: unknown) => Promise<unknown>
|
||||
}
|
||||
|
||||
type IsExactlyNeverOrNeverUndefined<T> = [T] extends [never]
|
||||
@@ -109,10 +82,8 @@ type IsExactlyNeverOrNeverUndefined<T> = [T] extends [never]
|
||||
? [undefined] extends [T]
|
||||
? false
|
||||
: true
|
||||
: false;
|
||||
: false
|
||||
|
||||
export type OmitNever<T extends Record<string, unknown>> = {
|
||||
[K in keyof T as IsExactlyNeverOrNeverUndefined<T[K]> extends true
|
||||
? never
|
||||
: K]: T[K];
|
||||
};
|
||||
[K in keyof T as IsExactlyNeverOrNeverUndefined<T[K]> extends true ? never : K]: T[K]
|
||||
}
|
||||
|
||||
+473
-396
@@ -1,426 +1,503 @@
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
|
||||
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 { client as _heyApiClient } from './client.gen';
|
||||
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 { client as _heyApiClient } from "./client.gen"
|
||||
|
||||
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<TData, ThrowOnError> & {
|
||||
/**
|
||||
* You can provide a client instance returned by `createClient()` instead of
|
||||
* individual options. This might be also useful if you want to implement a
|
||||
* custom client.
|
||||
*/
|
||||
client?: 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.
|
||||
*/
|
||||
meta?: Record<string, unknown>;
|
||||
};
|
||||
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<
|
||||
TData,
|
||||
ThrowOnError
|
||||
> & {
|
||||
/**
|
||||
* You can provide a client instance returned by `createClient()` instead of
|
||||
* individual options. This might be also useful if you want to implement a
|
||||
* custom client.
|
||||
*/
|
||||
client?: 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.
|
||||
*/
|
||||
meta?: Record<string, unknown>
|
||||
}
|
||||
|
||||
class _HeyApiClient {
|
||||
protected _client: Client = _heyApiClient;
|
||||
|
||||
constructor(args?: {
|
||||
client?: Client;
|
||||
}) {
|
||||
if (args?.client) {
|
||||
this._client = args.client;
|
||||
}
|
||||
protected _client: Client = _heyApiClient
|
||||
|
||||
constructor(args?: { client?: Client }) {
|
||||
if (args?.client) {
|
||||
this._client = args.client
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Event extends _HeyApiClient {
|
||||
/**
|
||||
* Get events
|
||||
*/
|
||||
public subscribe<ThrowOnError extends boolean = false>(options?: Options<EventSubscribeData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).get<EventSubscribeResponses, unknown, ThrowOnError>({
|
||||
url: '/event',
|
||||
...options
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Get events
|
||||
*/
|
||||
public subscribe<ThrowOnError extends boolean = false>(options?: Options<EventSubscribeData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).get<EventSubscribeResponses, unknown, ThrowOnError>({
|
||||
url: "/event",
|
||||
...options,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class App extends _HeyApiClient {
|
||||
/**
|
||||
* Get app info
|
||||
*/
|
||||
public get<ThrowOnError extends boolean = false>(options?: Options<AppGetData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).get<AppGetResponses, unknown, ThrowOnError>({
|
||||
url: '/app',
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the app
|
||||
*/
|
||||
public init<ThrowOnError extends boolean = false>(options?: Options<AppInitData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).post<AppInitResponses, unknown, ThrowOnError>({
|
||||
url: '/app/init',
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a log entry to the server logs
|
||||
*/
|
||||
public log<ThrowOnError extends boolean = false>(options?: Options<AppLogData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).post<AppLogResponses, unknown, ThrowOnError>({
|
||||
url: '/log',
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options?.headers
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* List all modes
|
||||
*/
|
||||
public modes<ThrowOnError extends boolean = false>(options?: Options<AppModesData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).get<AppModesResponses, unknown, ThrowOnError>({
|
||||
url: '/mode',
|
||||
...options
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Get app info
|
||||
*/
|
||||
public get<ThrowOnError extends boolean = false>(options?: Options<AppGetData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).get<AppGetResponses, unknown, ThrowOnError>({
|
||||
url: "/app",
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the app
|
||||
*/
|
||||
public init<ThrowOnError extends boolean = false>(options?: Options<AppInitData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).post<AppInitResponses, unknown, ThrowOnError>({
|
||||
url: "/app/init",
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a log entry to the server logs
|
||||
*/
|
||||
public log<ThrowOnError extends boolean = false>(options?: Options<AppLogData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).post<AppLogResponses, unknown, ThrowOnError>({
|
||||
url: "/log",
|
||||
...options,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options?.headers,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* List all modes
|
||||
*/
|
||||
public modes<ThrowOnError extends boolean = false>(options?: Options<AppModesData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).get<AppModesResponses, unknown, ThrowOnError>({
|
||||
url: "/mode",
|
||||
...options,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class Config extends _HeyApiClient {
|
||||
/**
|
||||
* Get config info
|
||||
*/
|
||||
public get<ThrowOnError extends boolean = false>(options?: Options<ConfigGetData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).get<ConfigGetResponses, unknown, ThrowOnError>({
|
||||
url: '/config',
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* List all providers
|
||||
*/
|
||||
public providers<ThrowOnError extends boolean = false>(options?: Options<ConfigProvidersData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).get<ConfigProvidersResponses, unknown, ThrowOnError>({
|
||||
url: '/config/providers',
|
||||
...options
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Get config info
|
||||
*/
|
||||
public get<ThrowOnError extends boolean = false>(options?: Options<ConfigGetData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).get<ConfigGetResponses, unknown, ThrowOnError>({
|
||||
url: "/config",
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* List all providers
|
||||
*/
|
||||
public providers<ThrowOnError extends boolean = false>(options?: Options<ConfigProvidersData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).get<ConfigProvidersResponses, unknown, ThrowOnError>({
|
||||
url: "/config/providers",
|
||||
...options,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class Session extends _HeyApiClient {
|
||||
/**
|
||||
* List all sessions
|
||||
*/
|
||||
public list<ThrowOnError extends boolean = false>(options?: Options<SessionListData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).get<SessionListResponses, unknown, ThrowOnError>({
|
||||
url: '/session',
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new session
|
||||
*/
|
||||
public create<ThrowOnError extends boolean = false>(options?: Options<SessionCreateData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).post<SessionCreateResponses, SessionCreateErrors, ThrowOnError>({
|
||||
url: '/session',
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a session and all its data
|
||||
*/
|
||||
public delete<ThrowOnError extends boolean = false>(options: Options<SessionDeleteData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).delete<SessionDeleteResponses, unknown, ThrowOnError>({
|
||||
url: '/session/{id}',
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze the app and create an AGENTS.md file
|
||||
*/
|
||||
public init<ThrowOnError extends boolean = false>(options: Options<SessionInitData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).post<SessionInitResponses, unknown, ThrowOnError>({
|
||||
url: '/session/{id}/init',
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Abort a session
|
||||
*/
|
||||
public abort<ThrowOnError extends boolean = false>(options: Options<SessionAbortData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).post<SessionAbortResponses, unknown, ThrowOnError>({
|
||||
url: '/session/{id}/abort',
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unshare the session
|
||||
*/
|
||||
public unshare<ThrowOnError extends boolean = false>(options: Options<SessionUnshareData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).delete<SessionUnshareResponses, unknown, ThrowOnError>({
|
||||
url: '/session/{id}/share',
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Share a session
|
||||
*/
|
||||
public share<ThrowOnError extends boolean = false>(options: Options<SessionShareData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).post<SessionShareResponses, unknown, ThrowOnError>({
|
||||
url: '/session/{id}/share',
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Summarize the session
|
||||
*/
|
||||
public summarize<ThrowOnError extends boolean = false>(options: Options<SessionSummarizeData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).post<SessionSummarizeResponses, unknown, ThrowOnError>({
|
||||
url: '/session/{id}/summarize',
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* List messages for a session
|
||||
*/
|
||||
public messages<ThrowOnError extends boolean = false>(options: Options<SessionMessagesData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).get<SessionMessagesResponses, unknown, ThrowOnError>({
|
||||
url: '/session/{id}/message',
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and send a new message to a session
|
||||
*/
|
||||
public chat<ThrowOnError extends boolean = false>(options: Options<SessionChatData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).post<SessionChatResponses, unknown, ThrowOnError>({
|
||||
url: '/session/{id}/message',
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a message from a session
|
||||
*/
|
||||
public message<ThrowOnError extends boolean = false>(options: Options<SessionMessageData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).get<SessionMessageResponses, unknown, ThrowOnError>({
|
||||
url: '/session/{id}/message/{messageID}',
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert a message
|
||||
*/
|
||||
public revert<ThrowOnError extends boolean = false>(options: Options<SessionRevertData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).post<SessionRevertResponses, unknown, ThrowOnError>({
|
||||
url: '/session/{id}/revert',
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore all reverted messages
|
||||
*/
|
||||
public unrevert<ThrowOnError extends boolean = false>(options: Options<SessionUnrevertData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).post<SessionUnrevertResponses, unknown, ThrowOnError>({
|
||||
url: '/session/{id}/unrevert',
|
||||
...options
|
||||
});
|
||||
}
|
||||
/**
|
||||
* List all sessions
|
||||
*/
|
||||
public list<ThrowOnError extends boolean = false>(options?: Options<SessionListData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).get<SessionListResponses, unknown, ThrowOnError>({
|
||||
url: "/session",
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new session
|
||||
*/
|
||||
public create<ThrowOnError extends boolean = false>(options?: Options<SessionCreateData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).post<SessionCreateResponses, SessionCreateErrors, ThrowOnError>({
|
||||
url: "/session",
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a session and all its data
|
||||
*/
|
||||
public delete<ThrowOnError extends boolean = false>(options: Options<SessionDeleteData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).delete<SessionDeleteResponses, unknown, ThrowOnError>({
|
||||
url: "/session/{id}",
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze the app and create an AGENTS.md file
|
||||
*/
|
||||
public init<ThrowOnError extends boolean = false>(options: Options<SessionInitData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).post<SessionInitResponses, unknown, ThrowOnError>({
|
||||
url: "/session/{id}/init",
|
||||
...options,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Abort a session
|
||||
*/
|
||||
public abort<ThrowOnError extends boolean = false>(options: Options<SessionAbortData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).post<SessionAbortResponses, unknown, ThrowOnError>({
|
||||
url: "/session/{id}/abort",
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Unshare the session
|
||||
*/
|
||||
public unshare<ThrowOnError extends boolean = false>(options: Options<SessionUnshareData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).delete<SessionUnshareResponses, unknown, ThrowOnError>({
|
||||
url: "/session/{id}/share",
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Share a session
|
||||
*/
|
||||
public share<ThrowOnError extends boolean = false>(options: Options<SessionShareData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).post<SessionShareResponses, unknown, ThrowOnError>({
|
||||
url: "/session/{id}/share",
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Summarize the session
|
||||
*/
|
||||
public summarize<ThrowOnError extends boolean = false>(options: Options<SessionSummarizeData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).post<SessionSummarizeResponses, unknown, ThrowOnError>({
|
||||
url: "/session/{id}/summarize",
|
||||
...options,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* List messages for a session
|
||||
*/
|
||||
public messages<ThrowOnError extends boolean = false>(options: Options<SessionMessagesData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).get<SessionMessagesResponses, unknown, ThrowOnError>({
|
||||
url: "/session/{id}/message",
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and send a new message to a session
|
||||
*/
|
||||
public chat<ThrowOnError extends boolean = false>(options: Options<SessionChatData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).post<SessionChatResponses, unknown, ThrowOnError>({
|
||||
url: "/session/{id}/message",
|
||||
...options,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a message from a session
|
||||
*/
|
||||
public message<ThrowOnError extends boolean = false>(options: Options<SessionMessageData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).get<SessionMessageResponses, unknown, ThrowOnError>({
|
||||
url: "/session/{id}/message/{messageID}",
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert a message
|
||||
*/
|
||||
public revert<ThrowOnError extends boolean = false>(options: Options<SessionRevertData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).post<SessionRevertResponses, unknown, ThrowOnError>({
|
||||
url: "/session/{id}/revert",
|
||||
...options,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore all reverted messages
|
||||
*/
|
||||
public unrevert<ThrowOnError extends boolean = false>(options: Options<SessionUnrevertData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).post<SessionUnrevertResponses, unknown, ThrowOnError>({
|
||||
url: "/session/{id}/unrevert",
|
||||
...options,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class Find extends _HeyApiClient {
|
||||
/**
|
||||
* Find text in files
|
||||
*/
|
||||
public text<ThrowOnError extends boolean = false>(options: Options<FindTextData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).get<FindTextResponses, unknown, ThrowOnError>({
|
||||
url: '/find',
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Find files
|
||||
*/
|
||||
public files<ThrowOnError extends boolean = false>(options: Options<FindFilesData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).get<FindFilesResponses, unknown, ThrowOnError>({
|
||||
url: '/find/file',
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Find workspace symbols
|
||||
*/
|
||||
public symbols<ThrowOnError extends boolean = false>(options: Options<FindSymbolsData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).get<FindSymbolsResponses, unknown, ThrowOnError>({
|
||||
url: '/find/symbol',
|
||||
...options
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Find text in files
|
||||
*/
|
||||
public text<ThrowOnError extends boolean = false>(options: Options<FindTextData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).get<FindTextResponses, unknown, ThrowOnError>({
|
||||
url: "/find",
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Find files
|
||||
*/
|
||||
public files<ThrowOnError extends boolean = false>(options: Options<FindFilesData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).get<FindFilesResponses, unknown, ThrowOnError>({
|
||||
url: "/find/file",
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Find workspace symbols
|
||||
*/
|
||||
public symbols<ThrowOnError extends boolean = false>(options: Options<FindSymbolsData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).get<FindSymbolsResponses, unknown, ThrowOnError>({
|
||||
url: "/find/symbol",
|
||||
...options,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class File extends _HeyApiClient {
|
||||
/**
|
||||
* Read a file
|
||||
*/
|
||||
public read<ThrowOnError extends boolean = false>(options: Options<FileReadData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).get<FileReadResponses, unknown, ThrowOnError>({
|
||||
url: '/file',
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file status
|
||||
*/
|
||||
public status<ThrowOnError extends boolean = false>(options?: Options<FileStatusData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).get<FileStatusResponses, unknown, ThrowOnError>({
|
||||
url: '/file/status',
|
||||
...options
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Read a file
|
||||
*/
|
||||
public read<ThrowOnError extends boolean = false>(options: Options<FileReadData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).get<FileReadResponses, unknown, ThrowOnError>({
|
||||
url: "/file",
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file status
|
||||
*/
|
||||
public status<ThrowOnError extends boolean = false>(options?: Options<FileStatusData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).get<FileStatusResponses, unknown, ThrowOnError>({
|
||||
url: "/file/status",
|
||||
...options,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class Tui extends _HeyApiClient {
|
||||
/**
|
||||
* Append prompt to the TUI
|
||||
*/
|
||||
public appendPrompt<ThrowOnError extends boolean = false>(options?: Options<TuiAppendPromptData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).post<TuiAppendPromptResponses, unknown, ThrowOnError>({
|
||||
url: '/tui/append-prompt',
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options?.headers
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the help dialog
|
||||
*/
|
||||
public openHelp<ThrowOnError extends boolean = false>(options?: Options<TuiOpenHelpData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).post<TuiOpenHelpResponses, unknown, ThrowOnError>({
|
||||
url: '/tui/open-help',
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the session dialog
|
||||
*/
|
||||
public openSessions<ThrowOnError extends boolean = false>(options?: Options<TuiOpenSessionsData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).post<TuiOpenSessionsResponses, unknown, ThrowOnError>({
|
||||
url: '/tui/open-sessions',
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the theme dialog
|
||||
*/
|
||||
public openThemes<ThrowOnError extends boolean = false>(options?: Options<TuiOpenThemesData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).post<TuiOpenThemesResponses, unknown, ThrowOnError>({
|
||||
url: '/tui/open-themes',
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the model dialog
|
||||
*/
|
||||
public openModels<ThrowOnError extends boolean = false>(options?: Options<TuiOpenModelsData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).post<TuiOpenModelsResponses, unknown, ThrowOnError>({
|
||||
url: '/tui/open-models',
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit the prompt
|
||||
*/
|
||||
public submitPrompt<ThrowOnError extends boolean = false>(options?: Options<TuiSubmitPromptData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).post<TuiSubmitPromptResponses, unknown, ThrowOnError>({
|
||||
url: '/tui/submit-prompt',
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the prompt
|
||||
*/
|
||||
public clearPrompt<ThrowOnError extends boolean = false>(options?: Options<TuiClearPromptData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).post<TuiClearPromptResponses, unknown, ThrowOnError>({
|
||||
url: '/tui/clear-prompt',
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a TUI command (e.g. switch_mode)
|
||||
*/
|
||||
public executeCommand<ThrowOnError extends boolean = false>(options?: Options<TuiExecuteCommandData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).post<TuiExecuteCommandResponses, unknown, ThrowOnError>({
|
||||
url: '/tui/execute-command',
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options?.headers
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Append prompt to the TUI
|
||||
*/
|
||||
public appendPrompt<ThrowOnError extends boolean = false>(options?: Options<TuiAppendPromptData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).post<TuiAppendPromptResponses, unknown, ThrowOnError>({
|
||||
url: "/tui/append-prompt",
|
||||
...options,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options?.headers,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the help dialog
|
||||
*/
|
||||
public openHelp<ThrowOnError extends boolean = false>(options?: Options<TuiOpenHelpData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).post<TuiOpenHelpResponses, unknown, ThrowOnError>({
|
||||
url: "/tui/open-help",
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the session dialog
|
||||
*/
|
||||
public openSessions<ThrowOnError extends boolean = false>(options?: Options<TuiOpenSessionsData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).post<TuiOpenSessionsResponses, unknown, ThrowOnError>({
|
||||
url: "/tui/open-sessions",
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the theme dialog
|
||||
*/
|
||||
public openThemes<ThrowOnError extends boolean = false>(options?: Options<TuiOpenThemesData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).post<TuiOpenThemesResponses, unknown, ThrowOnError>({
|
||||
url: "/tui/open-themes",
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the model dialog
|
||||
*/
|
||||
public openModels<ThrowOnError extends boolean = false>(options?: Options<TuiOpenModelsData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).post<TuiOpenModelsResponses, unknown, ThrowOnError>({
|
||||
url: "/tui/open-models",
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit the prompt
|
||||
*/
|
||||
public submitPrompt<ThrowOnError extends boolean = false>(options?: Options<TuiSubmitPromptData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).post<TuiSubmitPromptResponses, unknown, ThrowOnError>({
|
||||
url: "/tui/submit-prompt",
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the prompt
|
||||
*/
|
||||
public clearPrompt<ThrowOnError extends boolean = false>(options?: Options<TuiClearPromptData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).post<TuiClearPromptResponses, unknown, ThrowOnError>({
|
||||
url: "/tui/clear-prompt",
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a TUI command (e.g. switch_mode)
|
||||
*/
|
||||
public executeCommand<ThrowOnError extends boolean = false>(options?: Options<TuiExecuteCommandData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).post<TuiExecuteCommandResponses, unknown, ThrowOnError>({
|
||||
url: "/tui/execute-command",
|
||||
...options,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options?.headers,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export class OpencodeClient extends _HeyApiClient {
|
||||
/**
|
||||
* Respond to a permission request
|
||||
*/
|
||||
public postSessionByIdPermissionsByPermissionId<ThrowOnError extends boolean = false>(options: Options<PostSessionByIdPermissionsByPermissionIdData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).post<PostSessionByIdPermissionsByPermissionIdResponses, unknown, ThrowOnError>({
|
||||
url: '/session/{id}/permissions/{permissionID}',
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers
|
||||
}
|
||||
});
|
||||
}
|
||||
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 });
|
||||
}
|
||||
/**
|
||||
* Respond to a permission request
|
||||
*/
|
||||
public postSessionByIdPermissionsByPermissionId<ThrowOnError extends boolean = false>(
|
||||
options: Options<PostSessionByIdPermissionsByPermissionIdData, ThrowOnError>,
|
||||
) {
|
||||
return (options.client ?? this._client).post<
|
||||
PostSessionByIdPermissionsByPermissionIdResponses,
|
||||
unknown,
|
||||
ThrowOnError
|
||||
>({
|
||||
url: "/session/{id}/permissions/{permissionID}",
|
||||
...options,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
},
|
||||
})
|
||||
}
|
||||
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
-1324
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
import { createClient } from "./gen/client/client"
|
||||
import { type Config } from "./gen/client/types"
|
||||
import { OpencodeClient } from "./gen/sdk.gen"
|
||||
export * from "./gen/types.gen"
|
||||
|
||||
export function createOpencodeClient(config?: Config) {
|
||||
const client = createClient(config)
|
||||
|
||||
@@ -5,7 +5,15 @@
|
||||
"outDir": "dist",
|
||||
"module": "preserve",
|
||||
"declaration": true,
|
||||
"moduleResolution": "bundler"
|
||||
"moduleResolution": "bundler",
|
||||
"lib": [
|
||||
"es2022",
|
||||
"dom",
|
||||
"dom.iterable"
|
||||
],
|
||||
"customConditions": [
|
||||
"development"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"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)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
providersResponse, err := a.Client.App.Providers(context.Background())
|
||||
if err != nil {
|
||||
@@ -278,29 +322,6 @@ func (a *App) InitializeProvider() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
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 {
|
||||
slog.Error("No providers configured")
|
||||
return nil
|
||||
@@ -314,50 +335,86 @@ func (a *App) InitializeProvider() tea.Cmd {
|
||||
a.State.Model = model.ModelID
|
||||
}
|
||||
|
||||
var currentProvider *opencode.Provider
|
||||
var currentModel *opencode.Model
|
||||
for _, provider := range providers {
|
||||
if provider.ID == a.State.Provider {
|
||||
currentProvider = &provider
|
||||
var selectedProvider *opencode.Provider
|
||||
var selectedModel *opencode.Model
|
||||
|
||||
for _, model := range provider.Models {
|
||||
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
|
||||
// Priority 1: Command line --model flag (InitialModel)
|
||||
if a.InitialModel != nil && *a.InitialModel != "" {
|
||||
splits := strings.Split(*a.InitialModel, "/")
|
||||
for _, provider := range providers {
|
||||
if provider.ID == splits[0] {
|
||||
initialProvider = &provider
|
||||
for _, model := range provider.Models {
|
||||
modelID := strings.Join(splits[1:], "/")
|
||||
if model.ID == modelID {
|
||||
initialModel = &model
|
||||
}
|
||||
}
|
||||
if provider, model := findModelByFullID(providers, *a.InitialModel); provider != nil && model != nil {
|
||||
selectedProvider = provider
|
||||
selectedModel = model
|
||||
slog.Debug("Selected model from command line", "provider", provider.ID, "model", model.ID)
|
||||
} else {
|
||||
slog.Debug("Command line model not found", "model", *a.InitialModel)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
currentProvider = initialProvider
|
||||
currentModel = initialModel
|
||||
// Final safety check
|
||||
if selectedProvider == nil || selectedModel == nil {
|
||||
slog.Error("Failed to select any model")
|
||||
return nil
|
||||
}
|
||||
|
||||
var cmds []tea.Cmd
|
||||
cmds = append(cmds, util.CmdHandler(ModelSelectedMsg{
|
||||
Provider: *currentProvider,
|
||||
Model: *currentModel,
|
||||
Provider: *selectedProvider,
|
||||
Model: *selectedModel,
|
||||
}))
|
||||
if a.InitialPrompt != nil && *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)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -75,3 +75,80 @@ func (a *Attachment) GetSymbolSource() (*SymbolSource, bool) {
|
||||
ss, ok := a.Source.(*SymbolSource)
|
||||
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 (
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
@@ -155,6 +156,9 @@ func (k Command) Matches(msg tea.KeyPressMsg, leader bool) bool {
|
||||
func parseBindings(bindings ...string) []Keybinding {
|
||||
var parsedBindings []Keybinding
|
||||
for _, binding := range bindings {
|
||||
if binding == "none" {
|
||||
continue
|
||||
}
|
||||
for p := range strings.SplitSeq(binding, ",") {
|
||||
requireLeader := strings.HasPrefix(p, "<leader>")
|
||||
keybinding := strings.ReplaceAll(p, "<leader>", "")
|
||||
@@ -219,7 +223,6 @@ func LoadFromConfig(config *opencode.Config) CommandRegistry {
|
||||
{
|
||||
Name: SessionUnshareCommand,
|
||||
Description: "unshare session",
|
||||
Keybindings: parseBindings("<leader>u"),
|
||||
Trigger: []string{"unshare"},
|
||||
},
|
||||
{
|
||||
@@ -375,15 +378,14 @@ func LoadFromConfig(config *opencode.Config) CommandRegistry {
|
||||
// Remove share/unshare commands if sharing is disabled
|
||||
if config.Share == opencode.ConfigShareDisabled &&
|
||||
(command.Name == SessionShareCommand || command.Name == SessionUnshareCommand) {
|
||||
slog.Info("Removing share/unshare commands")
|
||||
continue
|
||||
}
|
||||
if keybind, ok := keybinds[string(command.Name)]; ok && keybind != "" {
|
||||
if keybind == "none" {
|
||||
continue
|
||||
}
|
||||
command.Keybindings = parseBindings(keybind)
|
||||
}
|
||||
registry[command.Name] = command
|
||||
}
|
||||
slog.Info("Loaded commands", "commands", registry)
|
||||
return registry
|
||||
}
|
||||
|
||||
@@ -217,15 +217,27 @@ func renderText(
|
||||
base := styles.NewStyle().Foreground(t.Text()).Background(backgroundColor)
|
||||
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
|
||||
for _, filePart := range fileParts {
|
||||
atFilename := "@" + filePart.Filename
|
||||
// Find and highlight complete @filename references
|
||||
highlightStyle := base.Foreground(t.Secondary())
|
||||
text = strings.ReplaceAll(text, atFilename, highlightStyle.Render(atFilename))
|
||||
highlight := base.Foreground(t.Secondary())
|
||||
start, end := filePart.Source.Text.Start, filePart.Source.Text.End
|
||||
|
||||
if start > lastEnd {
|
||||
result.WriteString(base.Render(text[lastEnd:start]))
|
||||
}
|
||||
result.WriteString(highlight.Render(text[start:end]))
|
||||
|
||||
lastEnd = end
|
||||
}
|
||||
|
||||
content = base.Width(width - 6).Render(text)
|
||||
if lastEnd < int64(len(text)) {
|
||||
result.WriteString(base.Render(text[lastEnd:]))
|
||||
}
|
||||
|
||||
content = base.Width(width - 6).Render(result.String())
|
||||
}
|
||||
|
||||
timestamp := ts.
|
||||
@@ -438,6 +450,10 @@ func renderToolDetails(
|
||||
if stdout != nil {
|
||||
body += ansi.Strip(fmt.Sprintf("%s", stdout))
|
||||
}
|
||||
stderr := metadata["stderr"]
|
||||
if stderr != nil {
|
||||
body += ansi.Strip(fmt.Sprintf("%s", stderr))
|
||||
}
|
||||
body += "```"
|
||||
body = util.ToMarkdown(body, width, backgroundColor)
|
||||
case "webfetch":
|
||||
|
||||
@@ -69,12 +69,7 @@ export default defineConfig({
|
||||
|
||||
{
|
||||
label: "Usage",
|
||||
items: [
|
||||
"docs/cli",
|
||||
"docs/ide",
|
||||
"docs/share",
|
||||
"docs/github",
|
||||
]
|
||||
items: ["docs/cli", "docs/ide", "docs/share", "docs/github"],
|
||||
},
|
||||
|
||||
{
|
||||
@@ -86,9 +81,12 @@ export default defineConfig({
|
||||
"docs/models",
|
||||
"docs/themes",
|
||||
"docs/keybinds",
|
||||
"docs/permissions",
|
||||
"docs/mcp-servers",
|
||||
]
|
||||
}
|
||||
"docs/formatters",
|
||||
"docs/lsp",
|
||||
],
|
||||
},
|
||||
],
|
||||
components: {
|
||||
Hero: "./src/components/Hero.astro",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode/web",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"version": "0.3.126",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"dev:remote": "sst shell --stage=dev --target=Web astro dev",
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
# Disallow shared content pages
|
||||
Disallow: /s/
|
||||
@@ -230,6 +230,13 @@ You can also define agents using markdown files in `~/.config/opencode/agent/` o
|
||||
|
||||
You can disable providers that are loaded automatically through the `disabled_providers` option. This is useful when you want to prevent certain providers from being loaded even if their credentials are available.
|
||||
|
||||
The `disabled_providers` option accepts an array of provider IDs. When a provider is disabled:
|
||||
|
||||
- It won't be loaded even if environment variables are set
|
||||
- It won't be loaded even if API keys are configured through `opencode auth login`
|
||||
- The provider's models won't appear in the model selection list
|
||||
|
||||
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
@@ -237,11 +244,29 @@ You can disable providers that are loaded automatically through the `disabled_pr
|
||||
}
|
||||
```
|
||||
|
||||
The `disabled_providers` option accepts an array of provider IDs. When a provider is disabled:
|
||||
---
|
||||
|
||||
- It won't be loaded even if environment variables are set
|
||||
- It won't be loaded even if API keys are configured through `opencode auth login`
|
||||
- The provider's models won't appear in the model selection list
|
||||
### Formatters
|
||||
|
||||
You can configure code formatters through the `formatter` option. See [Formatters documentation](/docs/formatters) for more details.
|
||||
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"formatter": {
|
||||
"prettier": {
|
||||
"disabled": true
|
||||
},
|
||||
"custom-prettier": {
|
||||
"command": ["npx", "prettier", "--write", "$FILE"],
|
||||
"environment": {
|
||||
"NODE_ENV": "development"
|
||||
},
|
||||
"extensions": [".js", ".ts", ".jsx", ".tsx"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
---
|
||||
title: Formatters
|
||||
description: Code formatting with opencode
|
||||
---
|
||||
|
||||
opencode automatically formats code files after they are written or edited using language-specific formatters. This ensures consistent code style across your project without manual intervention.
|
||||
|
||||
## Built-in Formatters
|
||||
|
||||
opencode comes with several built-in formatters for popular languages and frameworks:
|
||||
|
||||
| Formatter | Languages/Extensions | Requirements |
|
||||
| -------------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------- |
|
||||
| gofmt | .go | `gofmt` command available |
|
||||
| mix | .ex, .exs, .eex, .heex, .leex, .neex, .sface | `mix` command available |
|
||||
| prettier | .js, .jsx, .ts, .tsx, .html, .css, .md, .json, .yaml, and [more](https://prettier.io/docs/en/index.html) | `prettier` dependency in package.json |
|
||||
| biome | .js, .jsx, .ts, .tsx, .html, .css, .md, .json, .yaml, and [more](https://biomejs.dev/) | `biome.json` config file |
|
||||
| zig | .zig, .zon | `zig` command available |
|
||||
| clang-format | .c, .cpp, .h, .hpp, .ino, and [more](https://clang.llvm.org/docs/ClangFormat.html) | `.clang-format` config file |
|
||||
| ktlint | .kt, .kts | `ktlint` command available |
|
||||
| ruff | .py, .pyi | `ruff` command available with config |
|
||||
| rubocop | .rb, .rake, .gemspec, .ru | `rubocop` command available |
|
||||
| standardrb | .rb, .rake, .gemspec, .ru | `standardrb` command available |
|
||||
| htmlbeautifier | .erb, .html.erb | `htmlbeautifier` command available |
|
||||
|
||||
Formatters are automatically enabled when their requirements are met in your project environment.
|
||||
|
||||
## Configuration
|
||||
|
||||
You can customize formatters through the `formatter` section in your `opencode.json` configuration file.
|
||||
|
||||
### Disabling Formatters
|
||||
|
||||
To disable a specific formatter, set its `disabled` property to `true`:
|
||||
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"formatter": {
|
||||
"prettier": {
|
||||
"disabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Formatters
|
||||
|
||||
You can override the default formatters or add new ones by specifying the command, environment variables, and file extensions:
|
||||
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"formatter": {
|
||||
"custom-prettier": {
|
||||
"command": ["npx", "prettier", "--write", "$FILE"],
|
||||
"environment": {
|
||||
"NODE_ENV": "development"
|
||||
},
|
||||
"extensions": [".js", ".ts", ".jsx", ".tsx"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `$FILE` placeholder in the command will be replaced with the path to the file being formatted.
|
||||
|
||||
### Configuration Options
|
||||
|
||||
Each formatter configuration supports these properties:
|
||||
|
||||
| Property | Type | Description |
|
||||
| ------------- | -------- | ------------------------------------------------------- |
|
||||
| `disabled` | boolean | Set to `true` to disable the formatter |
|
||||
| `command` | string[] | The command to run for formatting |
|
||||
| `environment` | object | Environment variables to set when running the formatter |
|
||||
| `extensions` | string[] | File extensions this formatter should handle |
|
||||
|
||||
## How It Works
|
||||
|
||||
When opencode writes or edits a file, it:
|
||||
|
||||
1. Checks the file extension against all enabled formatters
|
||||
2. Runs the appropriate formatter command on the file
|
||||
3. Applies the formatting changes automatically
|
||||
|
||||
This process happens seamlessly in the background, ensuring your code maintains consistent formatting without requiring manual steps.
|
||||
@@ -106,7 +106,7 @@ Here are some examples of how you can use opencode in GitHub.
|
||||
|
||||
And opencode will create a new branch, implement the changes, and open a PR with the changes.
|
||||
|
||||
- Review PRs and make changes
|
||||
- **Review PRs and make changes**
|
||||
|
||||
Leave the following comment on a GitHub PR.
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ Let's get started.
|
||||
To use opencode, you'll need:
|
||||
|
||||
1. A modern terminal emulator like:
|
||||
|
||||
- [WezTerm](https://wezterm.org), cross-platform
|
||||
- [Alacritty](https://alacritty.org), cross-platform
|
||||
- [Ghostty](https://ghostty.org), Linux and macOS
|
||||
@@ -138,7 +139,7 @@ You should commit your project's `AGENTS.md` file to Git.
|
||||
:::
|
||||
|
||||
This helps opencode understand the project structure and the coding patterns
|
||||
used.
|
||||
used.
|
||||
|
||||
---
|
||||
|
||||
@@ -178,22 +179,22 @@ You can ask opencode to add new features to your project. Though we first recomm
|
||||
instead suggest _how_ it'll implement the feature.
|
||||
|
||||
Switch to it using the **Tab** key. You'll see an indicator for this in the lower right corner.
|
||||
|
||||
|
||||
```bash frame="none" title="Switch to Plan mode"
|
||||
<TAB>
|
||||
```
|
||||
|
||||
Now let's describe what we want it to do.
|
||||
|
||||
|
||||
```txt frame="none"
|
||||
When a user deletes a note, we'd like to flag it as deleted in the database.
|
||||
Then create a screen that shows all the recently deleted notes.
|
||||
From this screen, the user can undelete a note or permanently delete it.
|
||||
```
|
||||
|
||||
|
||||
You want to give opencode enough details to understand what you want. It helps
|
||||
to talk to it like you are talking to a junior developer on your team.
|
||||
|
||||
|
||||
:::tip
|
||||
Give opencode plenty of context and examples to help it understand what you
|
||||
want.
|
||||
@@ -202,7 +203,7 @@ You can ask opencode to add new features to your project. Though we first recomm
|
||||
2. **Iterate on the plan**
|
||||
|
||||
Once it gives you a plan, you can give it feedback or add more details.
|
||||
|
||||
|
||||
```txt frame="none"
|
||||
We'd like to design this new screen using a design I've used before.
|
||||
[Image #1] Take a look at this image and use it as a reference.
|
||||
@@ -216,16 +217,16 @@ You can ask opencode to add new features to your project. Though we first recomm
|
||||
do this by dragging and dropping an image into the terminal.
|
||||
|
||||
3. **Build the feature**
|
||||
|
||||
|
||||
Once you feel comfortable with the plan, switch back to _Build mode_ by
|
||||
hitting the **Tab** key again.
|
||||
|
||||
|
||||
```bash frame="none"
|
||||
<TAB>
|
||||
```
|
||||
|
||||
|
||||
And asking it to make the changes.
|
||||
|
||||
|
||||
```bash frame="none"
|
||||
Sounds good! Go ahead and make the changes.
|
||||
```
|
||||
@@ -271,4 +272,4 @@ Here's an [example conversation](https://opencode.ai/s/4XP1fce5) with opencode.
|
||||
|
||||
And that's it! You are now a pro at using opencode.
|
||||
|
||||
To make it your own, we recommend [picking a theme](/docs/themes), [customizing the keybinds](/docs/keybinds), or playing around with the [opencode config](/docs/config).
|
||||
To make it your own, we recommend [picking a theme](/docs/themes), [customizing the keybinds](/docs/keybinds), [configuring code formatters](/docs/formatters), or playing around with the [opencode config](/docs/config).
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
---
|
||||
title: LSP servers
|
||||
---
|
||||
|
||||
opencode integrates with _Language Server Protocol_, or LSP to improve how the LLM interacts with your codebase.
|
||||
|
||||
LSP servers for different languages give the LLM:
|
||||
|
||||
- **Diagnostics**: These include things like errors and lint warnings. So the LLM can generate code that has fewer mistakes without having to run the code.
|
||||
- **Quick actions**: The LSP can allow the LLM to better navigate the codebase through features like _go-to-definition_ and _find references_.
|
||||
|
||||
## Auto-detection
|
||||
|
||||
By default, opencode will **automatically detect** the languages used in your project and add the right LSP servers.
|
||||
|
||||
## Manual configuration
|
||||
|
||||
You can also manually configure LSP servers by adding them under the `lsp` section in your opencode config.
|
||||
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"lsp": {
|
||||
"go": {
|
||||
"disabled": false,
|
||||
"command": "gopls"
|
||||
},
|
||||
"typescript": {
|
||||
"disabled": false,
|
||||
"command": "typescript-language-server",
|
||||
"args": ["--stdio"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,79 @@
|
||||
---
|
||||
title: LSP Servers
|
||||
description: Language Server Protocol integration with opencode
|
||||
---
|
||||
|
||||
opencode integrates with Language Server Protocol (LSP) to enhance how the LLM interacts with your codebase. LSP servers provide intelligent code analysis and editing capabilities for different programming languages.
|
||||
|
||||
## Built-in LSP Servers
|
||||
|
||||
opencode comes with several built-in LSP servers for popular languages:
|
||||
|
||||
| LSP Server | Languages/Extensions | Requirements |
|
||||
| ---------- | -------------------------------------------- | ----------------------------------- |
|
||||
| typescript | .ts, .tsx, .js, .jsx, .mjs, .cjs, .mts, .cts | `typescript` dependency in project |
|
||||
| gopls | .go | `go` command available |
|
||||
| ruby-lsp | .rb, .rake, .gemspec, .ru | `ruby` and `gem` commands available |
|
||||
| pyright | .py, .pyi | `pyright` dependency installed |
|
||||
| elixir-ls | .ex, .exs | `elixir` command available |
|
||||
| zls | .zig, .zon | `zig` command available |
|
||||
| csharp | .cs | `.NET SDK` installed |
|
||||
|
||||
LSP servers are automatically enabled when their requirements are met in your project environment.
|
||||
|
||||
## Configuration
|
||||
|
||||
You can customize LSP servers through the `lsp` section in your `opencode.json` configuration file.
|
||||
|
||||
### Disabling LSP Servers
|
||||
|
||||
To disable a specific LSP server, set its configuration to `{ "disabled": true }`:
|
||||
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"lsp": {
|
||||
"typescript": {
|
||||
"disabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Custom LSP Servers
|
||||
|
||||
You can add custom LSP servers by specifying the command and file extensions:
|
||||
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"lsp": {
|
||||
"custom-lsp": {
|
||||
"command": ["custom-lsp-server", "--stdio"],
|
||||
"extensions": [".custom"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration Options
|
||||
|
||||
Each LSP server configuration supports these properties:
|
||||
|
||||
| Property | Type | Description |
|
||||
| ---------------- | -------- | ------------------------------------------------- |
|
||||
| `disabled` | boolean | Set to `true` to disable the LSP server |
|
||||
| `command` | string[] | The command to start the LSP server |
|
||||
| `extensions` | string[] | File extensions this LSP server should handle |
|
||||
| `env` | object | Environment variables to set when starting server |
|
||||
| `initialization` | object | Initialization options to send to the LSP server |
|
||||
|
||||
## How It Works
|
||||
|
||||
When opencode opens a file, it:
|
||||
|
||||
1. Checks the file extension against all enabled LSP servers
|
||||
2. Starts the appropriate LSP server if not already running
|
||||
3. Provides intelligent code analysis and editing capabilities
|
||||
|
||||
This integration allows the LLM to better understand your codebase through features like diagnostics, go-to-definition, and find-references.
|
||||
@@ -66,9 +66,11 @@ If you've configured a [custom provider](/docs/providers#custom), the `provider_
|
||||
|
||||
## Loading models
|
||||
|
||||
When opencode starts up, it checks for the following:
|
||||
When opencode starts up, it checks for models in the following priority order:
|
||||
|
||||
1. The model list in the opencode config.
|
||||
1. The `--model` or `-m` command line flag. The format is the same as in the config file: `provider_id/model_id`.
|
||||
|
||||
2. The model list in the opencode config.
|
||||
|
||||
```json title="opencode.json"
|
||||
{
|
||||
@@ -79,6 +81,6 @@ When opencode starts up, it checks for the following:
|
||||
|
||||
The format here is `provider/model`.
|
||||
|
||||
2. The last used model.
|
||||
3. The last used model.
|
||||
|
||||
3. The first model using an internal priority.
|
||||
4. The first model using an internal priority.
|
||||
|
||||
@@ -46,6 +46,8 @@ This mode is useful when you want the AI to analyze code, suggest changes, or cr
|
||||
|
||||
You can switch between modes during a session using the _Tab_ key. Or your configured `switch_mode` keybind.
|
||||
|
||||
See also: [Formatters](/docs/formatters) for information about code formatting configuration.
|
||||
|
||||
---
|
||||
|
||||
## Configure
|
||||
@@ -99,6 +101,7 @@ tools:
|
||||
---
|
||||
|
||||
You are in code review mode. Focus on:
|
||||
|
||||
- Code quality and best practices
|
||||
- Potential bugs and edge cases
|
||||
- Performance implications
|
||||
@@ -281,6 +284,7 @@ tools:
|
||||
You are in debug mode. Your primary goal is to help investigate and diagnose issues.
|
||||
|
||||
Focus on:
|
||||
|
||||
- Understanding the problem through careful analysis
|
||||
- Using bash commands to inspect system state
|
||||
- Reading relevant files and logs
|
||||
@@ -304,6 +308,7 @@ tools:
|
||||
You are in refactoring mode. Focus on improving code quality without changing functionality.
|
||||
|
||||
Priorities:
|
||||
|
||||
- Improve code readability and maintainability
|
||||
- Apply consistent naming conventions
|
||||
- Reduce code duplication
|
||||
|
||||
@@ -1,144 +1,90 @@
|
||||
---
|
||||
title: Permissions
|
||||
description: Control what AI agents can do in your codebase.
|
||||
description: Control what agents can do in your codebase.
|
||||
---
|
||||
|
||||
The opencode permissions system provides granular control over what actions AI agents can perform in your codebase. It allows you to configure explicit approval requirements for sensitive operations like file editing, bash commands, and more.
|
||||
By default, opencode **allows all operations** without requiring explicit approval.
|
||||
|
||||
## How it works
|
||||
The permissions system provides granular control to restrict what actions AI agents can perform in your codebase, allowing you to configure explicit approval requirements for sensitive operations like file editing, bash commands, and more.
|
||||
|
||||
The permissions system works by intercepting tool calls and checking if user approval is required before executing potentially sensitive operations. When a tool requests permission, it creates a permission request that must be approved by the user.
|
||||
---
|
||||
|
||||
```typescript
|
||||
// Example of how a tool requests permission
|
||||
await Permission.ask({
|
||||
type: "edit",
|
||||
sessionID: ctx.sessionID,
|
||||
messageID: ctx.messageID,
|
||||
callID: ctx.callID,
|
||||
title: "Edit this file: " + filePath,
|
||||
metadata: {
|
||||
filePath,
|
||||
diff,
|
||||
},
|
||||
})
|
||||
```
|
||||
## Configure
|
||||
|
||||
When a permission is requested, the system checks the configuration to determine if approval is needed. If approval is required, the user is prompted to allow or deny the action.
|
||||
Permissions are configured in your `opencode.json` file under the `permission` key. Here are the available options.
|
||||
|
||||
## Configuration
|
||||
---
|
||||
|
||||
Permissions are configured in your `opencode.json` file under the `permission` key. Here are the available options:
|
||||
### edit
|
||||
|
||||
### permission.edit
|
||||
Use the `permission.edit` key to control whether file editing operations require user approval.
|
||||
|
||||
Controls whether file editing operations require user approval.
|
||||
- `"ask"` - Prompt for approval before editing files
|
||||
- `"allow"` - Allow all file editing operations without approval
|
||||
|
||||
```json title="opencode.json"
|
||||
```json title="opencode.json" {4}
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"permission": {
|
||||
"edit": "ask"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `"ask"` - Prompt user for approval before editing files
|
||||
- `"allow"` - Allow all file editing operations without approval
|
||||
---
|
||||
|
||||
### permission.bash
|
||||
### bash
|
||||
|
||||
Controls whether bash commands require user approval. This can be configured globally or with specific patterns.
|
||||
Controls whether bash commands require user approval.
|
||||
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"permission": {
|
||||
"bash": "ask"
|
||||
}
|
||||
}
|
||||
```
|
||||
:::tip
|
||||
You can specify which commands you want to have run without approval.
|
||||
:::
|
||||
|
||||
Or with specific patterns:
|
||||
This can be configured globally or with specific patterns. Setting this to `"ask"` is the strictest mode, requiring approval for all bash commands.
|
||||
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"permission": {
|
||||
"bash": {
|
||||
"git *": "allow",
|
||||
"npm install": "ask",
|
||||
"*": "ask"
|
||||
For example.
|
||||
|
||||
- **Ask for approval for all commands**
|
||||
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"permission": {
|
||||
"bash": "ask"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
## Configuration examples
|
||||
- **Approve specific commands**
|
||||
|
||||
### Basic permission configuration
|
||||
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"permission": {
|
||||
"edit": "ask",
|
||||
"bash": "ask"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Advanced bash permission configuration
|
||||
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"permission": {
|
||||
"edit": "ask",
|
||||
"bash": {
|
||||
"git status": "allow",
|
||||
"git diff": "allow",
|
||||
"git add *": "ask",
|
||||
"git commit*": "ask",
|
||||
"npm install": "ask",
|
||||
"npm run build": "allow",
|
||||
"ls": "allow",
|
||||
"pwd": "allow",
|
||||
"*": "ask"
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"permission": {
|
||||
"bash": {
|
||||
"git status": "allow",
|
||||
"git diff": "allow",
|
||||
"npm run build": "allow",
|
||||
"ls": "allow",
|
||||
"pwd": "allow"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
### Permissive configuration (development only)
|
||||
- **Use wildcard patterns to restrict specific commands**
|
||||
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"permission": {
|
||||
"edit": "allow",
|
||||
"bash": "allow"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Strict configuration
|
||||
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"permission": {
|
||||
"edit": "ask",
|
||||
"bash": {
|
||||
"*": "ask"
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"permission": {
|
||||
"bash": {
|
||||
"git push": "ask",
|
||||
"*": "allow"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
## Best practices
|
||||
|
||||
1. **Start with "ask"**: Begin with asking for permissions and adjust based on your workflow
|
||||
2. **Use patterns wisely**: Create specific patterns for commands you trust
|
||||
3. **Review regularly**: Periodically review your permission settings
|
||||
4. **Be specific**: Use specific patterns rather than broad wildcards when possible
|
||||
5. **Document exceptions**: Comment your configuration to explain why certain permissions are set
|
||||
|
||||
This permissions system ensures that you maintain control over what AI agents can do in your codebase while providing flexibility for trusted operations.
|
||||
This configuration allows all commands by default (`"*": "allow"`) but requires approval for `git push` commands.
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
title: Plugins
|
||||
description: Extend opencode with custom plugins.
|
||||
---
|
||||
|
||||
Plugins allow you to extend opencode's functionality by hooking into various events and customizing behavior. You can create plugins to add new features, integrate with external services, or modify opencode's default behavior.
|
||||
|
||||
---
|
||||
|
||||
## Creating a Plugin
|
||||
|
||||
A plugin is a JavaScript/TypeScript module that exports one or more plugin
|
||||
functions. Each function receives a context object and returns a hooks object.
|
||||
They are loaded from the `.opencode/plugin` directory either in your proejct or
|
||||
globally in `~/.config/opencode/plugin`.
|
||||
|
||||
### Basic Structure
|
||||
|
||||
```typescript title=".opencode/plugin/example.js"
|
||||
export const MyPlugin = async ({ app, client, $ }) => {
|
||||
console.log("Plugin initialized!")
|
||||
|
||||
return {
|
||||
// Hook implementations go here
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The plugin function receives:
|
||||
|
||||
- `app` - The opencode application instance
|
||||
- `client` - An opencode SDK client for interacting with the AI
|
||||
- `$` - Bun's shell API for executing commands
|
||||
|
||||
### TypeScript Support
|
||||
|
||||
For TypeScript plugins, you can import types from the plugin package:
|
||||
|
||||
```typescript title="my-plugin.ts"
|
||||
import type { Plugin } from "@opencode-ai/plugin"
|
||||
|
||||
export const MyPlugin: Plugin = async ({ app, client, $ }) => {
|
||||
return {
|
||||
// Type-safe hook implementations
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
### Notification Plugin
|
||||
|
||||
Send notifications when certain events occur:
|
||||
|
||||
```javascript title=".opencode/plugin/notification.js"
|
||||
export const NotificationPlugin = async ({ client, $ }) => {
|
||||
return {
|
||||
event: async ({ event }) => {
|
||||
// Send notification on session completion
|
||||
if (event.type === "session.idle") {
|
||||
await $`osascript -e 'display notification "Session completed!" with title "opencode"'`
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### .env Protection
|
||||
|
||||
Integrate with external services:
|
||||
|
||||
```javascript title=".opencode/plugin/slack.js"
|
||||
export const EnvProtection = async ({ client, $ }) => {
|
||||
return {
|
||||
tool: {
|
||||
execute: {
|
||||
before: async (input, output) => {
|
||||
if (input.tool === "read" && output.args.filePath.includes(".env")) {
|
||||
throw new Error("Do not read .env files")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -45,6 +45,126 @@ You can customize the base URL for any provider by setting the `baseURL` option.
|
||||
|
||||
---
|
||||
|
||||
## Custom provider
|
||||
|
||||
To add any **OpenAI-compatible** provider that's not listed in `opencode auth login`:
|
||||
|
||||
:::tip
|
||||
You can use any OpenAI-compatible provider with opencode. Most modern AI providers offer OpenAI-compatible APIs.
|
||||
:::
|
||||
|
||||
1. Run `opencode auth login` and scroll down to **Other**.
|
||||
|
||||
```bash
|
||||
$ opencode auth login
|
||||
|
||||
┌ Add credential
|
||||
│
|
||||
◆ Select provider
|
||||
│ ...
|
||||
│ ● Other
|
||||
└
|
||||
```
|
||||
|
||||
2. Enter a unique ID for the provider.
|
||||
|
||||
```bash
|
||||
$ opencode auth login
|
||||
|
||||
┌ Add credential
|
||||
│
|
||||
◇ Enter provider id
|
||||
│ myprovider
|
||||
└
|
||||
```
|
||||
|
||||
:::note
|
||||
Choose a memorable ID, you'll use this in your config file.
|
||||
:::
|
||||
|
||||
3. Enter your API key for the provider.
|
||||
|
||||
```bash
|
||||
$ opencode auth login
|
||||
|
||||
┌ Add credential
|
||||
│
|
||||
▲ This only stores a credential for myprovider - you will need configure it in opencode.json, check the docs for examples.
|
||||
│
|
||||
◇ Enter your API key
|
||||
│ sk-...
|
||||
└
|
||||
```
|
||||
|
||||
4. Create or update your `opencode.json` file in your project directory:
|
||||
|
||||
```json title="opencode.json" ""myprovider"" {5-15}
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"provider": {
|
||||
"myprovider": {
|
||||
"npm": "@ai-sdk/openai-compatible",
|
||||
"name": "My AI ProviderDisplay Name",
|
||||
"options": {
|
||||
"baseURL": "https://api.myprovider.com/v1"
|
||||
},
|
||||
"models": {
|
||||
"my-model-name": {
|
||||
"name": "My Model Display Name"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Here are the configuration options:
|
||||
|
||||
- **npm**: AI SDK package to use, `@ai-sdk/openai-compatible` for OpenAI-compatible providers
|
||||
- **name**: Display name in UI.
|
||||
- **models**: Available models.
|
||||
- **options.baseURL**: API endpoint URL.
|
||||
- **options.apiKey**: Optionally set the API key, if not using auth.
|
||||
- **options.headers**: Optionally set custom headers.
|
||||
|
||||
More on the advanced options in the example below.
|
||||
|
||||
5. Run the `/models` command and your custom provider and models will appear in the selection list.
|
||||
|
||||
---
|
||||
|
||||
##### Example
|
||||
|
||||
Here's an example setting the `apiKey` and `headers` options.
|
||||
|
||||
```json title="opencode.json" {9,11}
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"provider": {
|
||||
"myprovider": {
|
||||
"npm": "@ai-sdk/openai-compatible",
|
||||
"name": "My AI ProviderDisplay Name",
|
||||
"options": {
|
||||
"baseURL": "https://api.myprovider.com/v1",
|
||||
"apiKey": "{env:ANTHROPIC_API_KEY}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer custom-token"
|
||||
}
|
||||
},
|
||||
"models": {
|
||||
"my-model-name": {
|
||||
"name": "My Model Display Name"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We are setting the `apiKey` using the `env` variable syntax, [learn more](/docs/config#env-vars).
|
||||
|
||||
---
|
||||
|
||||
## Directory
|
||||
|
||||
Let's look at some of the providers in detail. If you'd like to add a provider to the
|
||||
@@ -117,8 +237,30 @@ $ opencode auth login
|
||||
└
|
||||
```
|
||||
|
||||
This will ask you login with your Anthropic account in your browser. Now all the
|
||||
the Anthropic models should be available when you use the `/models` command.
|
||||
Here you can select the **Claude Pro/Max** option and it'll open your browser
|
||||
and ask you to authenticate.
|
||||
|
||||
```bash
|
||||
$ opencode auth login
|
||||
┌ Add credential
|
||||
│
|
||||
◇ Select provider
|
||||
│ Anthropic
|
||||
│
|
||||
◆ Login method
|
||||
│ ● Claude Pro/Max
|
||||
│ ○ Create API Key
|
||||
│ ○ Manually enter API Key
|
||||
└
|
||||
```
|
||||
|
||||
Now all the the Anthropic models should be available when you use the `/models` command.
|
||||
|
||||
##### Using API keys
|
||||
|
||||
You can also select **Create API Key** if you don't have a Pro/Max subscription. It'll also open your browser and ask you to login to Anthropic and give you a code you can paste in your terminal.
|
||||
|
||||
Or if you already have an API key, you can select **Manually enter API Key** and paste it in your terminal.
|
||||
|
||||
---
|
||||
|
||||
@@ -185,6 +327,114 @@ the Anthropic models should be available when you use the `/models` command.
|
||||
|
||||
---
|
||||
|
||||
### Cerebras
|
||||
|
||||
1. Head over to the [Cerebras console](https://inference.cerebras.ai/), create an account, and generate an API key.
|
||||
|
||||
2. Run `opencode auth login` and select **Cerebras**.
|
||||
|
||||
```bash
|
||||
$ opencode auth login
|
||||
|
||||
┌ Add credential
|
||||
│
|
||||
◆ Select provider
|
||||
│ ● Cerebras
|
||||
│ ...
|
||||
└
|
||||
```
|
||||
|
||||
3. Enter your Cerebras API key.
|
||||
|
||||
```bash
|
||||
$ opencode auth login
|
||||
|
||||
┌ Add credential
|
||||
│
|
||||
◇ Select provider
|
||||
│ Cerebras
|
||||
│
|
||||
◇ Enter your API key
|
||||
│ _
|
||||
└
|
||||
```
|
||||
|
||||
4. Run the `/models` command to select a model like _Qwen 3 Coder 480B_.
|
||||
|
||||
---
|
||||
|
||||
### DeepSeek
|
||||
|
||||
1. Head over to the [DeepSeek console](https://platform.deepseek.com/), create an account, and click **Create new API key**.
|
||||
|
||||
2. Run `opencode auth login` and select **DeepSeek**.
|
||||
|
||||
```bash
|
||||
$ opencode auth login
|
||||
|
||||
┌ Add credential
|
||||
│
|
||||
◆ Select provider
|
||||
│ ● DeepSeek
|
||||
│ ...
|
||||
└
|
||||
```
|
||||
|
||||
3. Enter your DeepSeek API key.
|
||||
|
||||
```bash
|
||||
$ opencode auth login
|
||||
|
||||
┌ Add credential
|
||||
│
|
||||
◇ Select provider
|
||||
│ DeepSeek
|
||||
│
|
||||
◇ Enter your API key
|
||||
│ _
|
||||
└
|
||||
```
|
||||
|
||||
4. Run the `/models` command to select a DeepSeek model like _DeepSeek Reasoner_.
|
||||
|
||||
---
|
||||
|
||||
### Fireworks AI
|
||||
|
||||
1. Head over to the [Fireworks AI console](https://app.fireworks.ai/), create an account, and click **Create API Key**.
|
||||
|
||||
2. Run `opencode auth login` and select **Fireworks AI**.
|
||||
|
||||
```bash
|
||||
$ opencode auth login
|
||||
|
||||
┌ Add credential
|
||||
│
|
||||
◆ Select provider
|
||||
│ ● Fireworks AI
|
||||
│ ...
|
||||
└
|
||||
```
|
||||
|
||||
3. Enter your Fireworks AI API key.
|
||||
|
||||
```bash
|
||||
$ opencode auth login
|
||||
|
||||
┌ Add credential
|
||||
│
|
||||
◇ Select provider
|
||||
│ Fireworks AI
|
||||
│
|
||||
◇ Enter your API key
|
||||
│ _
|
||||
└
|
||||
```
|
||||
|
||||
4. Run the `/models` command to select a model like _Kimi K2 Instruct_.
|
||||
|
||||
---
|
||||
|
||||
### GitHub Copilot
|
||||
|
||||
To use your GitHub Copilot subscription with opencode:
|
||||
@@ -290,6 +540,78 @@ In this example:
|
||||
|
||||
---
|
||||
|
||||
### Moonshot AI
|
||||
|
||||
To use Kimi K2 from Moonshot AI:
|
||||
|
||||
1. Head over to the [Moonshot AI console](https://platform.moonshot.ai/console), create an account, and click **Create API key**.
|
||||
|
||||
2. Run `opencode auth login` and select **Other**.
|
||||
|
||||
```bash
|
||||
$ opencode auth login
|
||||
|
||||
┌ Add credential
|
||||
│
|
||||
◆ Select provider
|
||||
│ ...
|
||||
│ ● Other
|
||||
└
|
||||
```
|
||||
|
||||
3. Enter `moonshot` as the provider ID.
|
||||
|
||||
```bash
|
||||
$ opencode auth login
|
||||
|
||||
┌ Add credential
|
||||
│
|
||||
◇ Select provider
|
||||
│ Other
|
||||
│
|
||||
◇ Enter provider id
|
||||
│ moonshot
|
||||
└
|
||||
```
|
||||
|
||||
4. Enter your Moonshot API key.
|
||||
|
||||
```bash
|
||||
$ opencode auth login
|
||||
|
||||
┌ Add credential
|
||||
│
|
||||
◇ Enter your API key
|
||||
│ sk-...
|
||||
└
|
||||
```
|
||||
|
||||
5. Configure Moonshot in your opencode config.
|
||||
|
||||
```json title="opencode.json" ""moonshot"" {5-15}
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"provider": {
|
||||
"moonshot": {
|
||||
"npm": "@ai-sdk/openai-compatible",
|
||||
"name": "Moonshot AI",
|
||||
"options": {
|
||||
"baseURL": "https://api.moonshot.ai/v1"
|
||||
},
|
||||
"models": {
|
||||
"kimi-k2-0711-preview": {
|
||||
"name": "Kimi K2"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
6. Run the `/models` command to select _Kimi K2_.
|
||||
|
||||
---
|
||||
|
||||
### Ollama
|
||||
|
||||
You can configure opencode to use local models through Ollama.
|
||||
@@ -437,13 +759,11 @@ https://platform.openai.com/api-keys
|
||||
|
||||
---
|
||||
|
||||
### Cerebras
|
||||
### Together AI
|
||||
|
||||
Cerebras offers fast inference with generous free tiers and competitive pricing.
|
||||
1. Head over to the [Together AI console](https://api.together.ai), create an account, and click **Add Key**.
|
||||
|
||||
1. Head over to the [Cerebras console](https://inference.cerebras.ai/), create an account, and generate an API key.
|
||||
|
||||
2. Run `opencode auth login` and select **Other**.
|
||||
2. Run `opencode auth login` and select **Together AI**.
|
||||
|
||||
```bash
|
||||
$ opencode auth login
|
||||
@@ -451,12 +771,12 @@ Cerebras offers fast inference with generous free tiers and competitive pricing.
|
||||
┌ Add credential
|
||||
│
|
||||
◆ Select provider
|
||||
│ ● Together AI
|
||||
│ ...
|
||||
│ ● Other
|
||||
└
|
||||
```
|
||||
|
||||
3. Enter `cerebras` as the provider ID.
|
||||
3. Enter your Together AI API key.
|
||||
|
||||
```bash
|
||||
$ opencode auth login
|
||||
@@ -464,61 +784,22 @@ Cerebras offers fast inference with generous free tiers and competitive pricing.
|
||||
┌ Add credential
|
||||
│
|
||||
◇ Select provider
|
||||
│ Other
|
||||
│
|
||||
◇ Enter provider id
|
||||
│ cerebras
|
||||
└
|
||||
```
|
||||
|
||||
4. Enter your Cerebras API key.
|
||||
|
||||
```bash
|
||||
$ opencode auth login
|
||||
|
||||
┌ Add credential
|
||||
│ Together AI
|
||||
│
|
||||
◇ Enter your API key
|
||||
│ csk-...
|
||||
│ _
|
||||
└
|
||||
```
|
||||
|
||||
5. Configure Cerebras in your opencode config.
|
||||
|
||||
```json title="opencode.json" "cerebras" {5-19}
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"provider": {
|
||||
"cerebras": {
|
||||
"npm": "@ai-sdk/cerebras",
|
||||
"name": "Cerebras",
|
||||
"options": {
|
||||
"baseURL": "https://api.cerebras.ai/v1"
|
||||
},
|
||||
"models": {
|
||||
"qwen-3-235b-a22b": {
|
||||
"name": "Qwen-3-235b-a22b"
|
||||
},
|
||||
"llama-3.3-70b": {
|
||||
"name": "Llama-3.3-70b"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
6. Run the `/models` command to select a Cerebras model.
|
||||
4. Run the `/models` command to select a model like _Kimi K2 Instruct_.
|
||||
|
||||
---
|
||||
|
||||
### DeepSeek
|
||||
### Zhipu AI
|
||||
|
||||
DeepSeek offers powerful reasoning models at competitive prices.
|
||||
1. Head over to the [Zhipu API console](https://z.ai/manage-apikey/apikey-list), create an account, and click **Create a new API key**.
|
||||
|
||||
1. Head over to the [DeepSeek console](https://platform.deepseek.com/), create an account, and generate an API key.
|
||||
|
||||
2. Run `opencode auth login` and select **Other**.
|
||||
2. Run `opencode auth login` and select **Zhipu AI**.
|
||||
|
||||
```bash
|
||||
$ opencode auth login
|
||||
@@ -526,12 +807,12 @@ DeepSeek offers powerful reasoning models at competitive prices.
|
||||
┌ Add credential
|
||||
│
|
||||
◆ Select provider
|
||||
│ ● Zhipu AI
|
||||
│ ...
|
||||
│ ● Other
|
||||
└
|
||||
```
|
||||
|
||||
3. Enter `deepseek` as the provider ID.
|
||||
3. Enter your Zhipu AI API key.
|
||||
|
||||
```bash
|
||||
$ opencode auth login
|
||||
@@ -539,286 +820,14 @@ DeepSeek offers powerful reasoning models at competitive prices.
|
||||
┌ Add credential
|
||||
│
|
||||
◇ Select provider
|
||||
│ Other
|
||||
│
|
||||
◇ Enter provider id
|
||||
│ deepseek
|
||||
└
|
||||
```
|
||||
|
||||
4. Enter your DeepSeek API key.
|
||||
|
||||
```bash
|
||||
$ opencode auth login
|
||||
|
||||
┌ Add credential
|
||||
│ Zhipu AI
|
||||
│
|
||||
◇ Enter your API key
|
||||
│ sk-...
|
||||
│ _
|
||||
└
|
||||
```
|
||||
|
||||
5. Configure DeepSeek in your opencode config.
|
||||
|
||||
```json title="opencode.json" "deepseek" {5-17}
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"provider": {
|
||||
"deepseek": {
|
||||
"npm": "@ai-sdk/openai-compatible",
|
||||
"name": "DeepSeek",
|
||||
"options": {
|
||||
"baseURL": "https://api.deepseek.com/v1"
|
||||
},
|
||||
"models": {
|
||||
"deepseek-reasoner": {
|
||||
"name": "DeepSeek Reasoner"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
6. Run the `/models` command to select a DeepSeek model.
|
||||
|
||||
---
|
||||
|
||||
### Moonshot AI (Kimi)
|
||||
|
||||
Moonshot AI offers the Kimi models with long context capabilities.
|
||||
|
||||
1. Head over to the [Moonshot AI console](https://platform.moonshot.cn/), create an account, and generate an API key.
|
||||
|
||||
2. Run `opencode auth login` and select **Other**.
|
||||
|
||||
```bash
|
||||
$ opencode auth login
|
||||
|
||||
┌ Add credential
|
||||
│
|
||||
◆ Select provider
|
||||
│ ...
|
||||
│ ● Other
|
||||
└
|
||||
```
|
||||
|
||||
3. Enter `moonshot` as the provider ID.
|
||||
|
||||
```bash
|
||||
$ opencode auth login
|
||||
|
||||
┌ Add credential
|
||||
│
|
||||
◇ Select provider
|
||||
│ Other
|
||||
│
|
||||
◇ Enter provider id
|
||||
│ moonshot
|
||||
└
|
||||
```
|
||||
|
||||
4. Enter your Moonshot API key.
|
||||
|
||||
```bash
|
||||
$ opencode auth login
|
||||
|
||||
┌ Add credential
|
||||
│
|
||||
◇ Enter your API key
|
||||
│ sk-...
|
||||
└
|
||||
```
|
||||
|
||||
5. Configure Moonshot in your opencode config.
|
||||
|
||||
```json title="opencode.json" "moonshot" {5-17}
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"provider": {
|
||||
"moonshot": {
|
||||
"npm": "@ai-sdk/openai-compatible",
|
||||
"name": "Moonshot AI",
|
||||
"options": {
|
||||
"baseURL": "https://api.moonshot.cn/v1"
|
||||
},
|
||||
"models": {
|
||||
"kimi-k2-0711-preview": {
|
||||
"name": "Kimi K2"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
6. Run the `/models` command to select a Kimi model.
|
||||
|
||||
---
|
||||
|
||||
### Custom
|
||||
|
||||
To add any **OpenAI-compatible** provider that's not listed in `opencode auth login`:
|
||||
|
||||
:::tip
|
||||
You can use any OpenAI-compatible provider with opencode. Most modern AI providers offer OpenAI-compatible APIs.
|
||||
:::
|
||||
|
||||
1. Run `opencode auth login` and scroll down to **Other**.
|
||||
|
||||
```bash
|
||||
$ opencode auth login
|
||||
|
||||
┌ Add credential
|
||||
│
|
||||
◆ Select provider
|
||||
│ ...
|
||||
│ ● Other
|
||||
└
|
||||
```
|
||||
|
||||
2. Enter a unique ID for the provider.
|
||||
|
||||
```bash
|
||||
$ opencode auth login
|
||||
|
||||
┌ Add credential
|
||||
│
|
||||
◇ Enter provider id
|
||||
│ myprovider
|
||||
└
|
||||
```
|
||||
|
||||
:::note
|
||||
Choose a memorable ID, you'll use this in your config file.
|
||||
:::
|
||||
|
||||
3. Enter your API key for the provider.
|
||||
|
||||
```bash
|
||||
$ opencode auth login
|
||||
|
||||
┌ Add credential
|
||||
│
|
||||
▲ This only stores a credential for myprovider - you will need configure it in opencode.json, check the docs for examples.
|
||||
│
|
||||
◇ Enter your API key
|
||||
│ sk-...
|
||||
└
|
||||
```
|
||||
|
||||
4. Create or update your `opencode.json` file in your project directory:
|
||||
|
||||
```json title="opencode.json" ""myprovider"" {5-15}
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"provider": {
|
||||
"myprovider": {
|
||||
"npm": "@ai-sdk/openai-compatible",
|
||||
"name": "My AI ProviderDisplay Name",
|
||||
"options": {
|
||||
"baseURL": "https://api.myprovider.com/v1"
|
||||
},
|
||||
"models": {
|
||||
"my-model-name": {
|
||||
"name": "My Model Display Name"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Here are the configuration options:
|
||||
|
||||
- **npm**: AI SDK package to use, `@ai-sdk/openai-compatible` for OpenAI-compatible providers
|
||||
- **name**: Display name in UI.
|
||||
- **models**: Available models.
|
||||
- **options.baseURL**: API endpoint URL.
|
||||
- **options.apiKey**: Optionally set the API key, if not using auth.
|
||||
- **options.headers**: Optionally set custom headers.
|
||||
|
||||
More on the advanced options in the example below.
|
||||
|
||||
5. Run the `/models` command and your custom provider and models will appear in the selection list.
|
||||
|
||||
---
|
||||
|
||||
##### Example
|
||||
|
||||
Here's an example setting the `apiKey` and `headers` options.
|
||||
|
||||
```json title="opencode.json" {9,11}
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"provider": {
|
||||
"myprovider": {
|
||||
"npm": "@ai-sdk/openai-compatible",
|
||||
"name": "My AI ProviderDisplay Name",
|
||||
"options": {
|
||||
"baseURL": "https://api.myprovider.com/v1",
|
||||
"apiKey": "{env:ANTHROPIC_API_KEY}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer custom-token"
|
||||
}
|
||||
},
|
||||
"models": {
|
||||
"my-model-name": {
|
||||
"name": "My Model Display Name"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We are setting the `apiKey` using the `env` variable syntax, [learn more](/docs/config#env-vars).
|
||||
|
||||
#### Common Examples
|
||||
|
||||
**Together AI:**
|
||||
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"provider": {
|
||||
"together": {
|
||||
"npm": "@ai-sdk/openai-compatible",
|
||||
"name": "Together AI",
|
||||
"options": {
|
||||
"baseURL": "https://api.together.xyz/v1"
|
||||
},
|
||||
"models": {
|
||||
"meta-llama/Llama-3.2-11B-Vision-Instruct-Turbo": {
|
||||
"name": "Llama 3.2 11B Vision"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Fireworks AI:**
|
||||
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"provider": {
|
||||
"fireworks": {
|
||||
"npm": "@ai-sdk/openai-compatible",
|
||||
"name": "Fireworks AI",
|
||||
"options": {
|
||||
"baseURL": "https://api.fireworks.ai/inference/v1"
|
||||
},
|
||||
"models": {
|
||||
"accounts/fireworks/models/llama-v3p1-70b-instruct": {
|
||||
"name": "Llama 3.1 70B"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
4. Run the `/models` command to select a model like _GLM-4.5_.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -67,6 +67,13 @@ const ogImage = `${config.socialCard}/opencode-share/${encodedTitle}.png?model=$
|
||||
content: "opencode - The AI coding agent built for the terminal.",
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: "meta",
|
||||
attrs: {
|
||||
name: "robots",
|
||||
content: "noindex, nofollow, noarchive, nosnippet",
|
||||
}
|
||||
},
|
||||
{
|
||||
tag: "meta",
|
||||
attrs: {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
diff --git a/node_modules/marked-shiki/.bun-tag-5eae3435af8a0229 b/.bun-tag-5eae3435af8a0229
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
|
||||
diff --git a/dist/index.js b/dist/index.js
|
||||
index 535885e8569626d08f205e76c4944e4ebd0decab..92b5695c1b05fc7db5d26128c8f948d43c91f88a 100644
|
||||
--- a/dist/index.js
|
||||
+++ b/dist/index.js
|
||||
@@ -4,7 +4,7 @@ function o(s = {}) {
|
||||
async: !0,
|
||||
async walkTokens(t) {
|
||||
if (t.type !== "code" || typeof e != "function") return;
|
||||
- const [a = "text", ...i] = t.lang.split(" "), { text: c } = t, r = await e(c, a, i), l = n ? n.replace("%l", String(a).toUpperCase()).replace("%s", r).replace("%t", c) : r;
|
||||
+ const [a = "text", ...i] = t.lang?.split(" "), { text: c } = t, r = await e(c, a, i), l = n ? n.replace("%l", String(a).toUpperCase()).replace("%s", r).replace("%t", c) : r;
|
||||
Object.assign(t, {
|
||||
type: "html",
|
||||
block: !0,
|
||||
+32
-5
@@ -2,6 +2,8 @@
|
||||
|
||||
import { $ } from "bun"
|
||||
|
||||
console.log("=== publishing ===\n")
|
||||
|
||||
const snapshot = process.env["OPENCODE_SNAPSHOT"] === "true"
|
||||
const version = snapshot
|
||||
? `0.0.0-${new Date().toISOString().slice(0, 16).replace(/[-:T]/g, "")}`
|
||||
@@ -10,19 +12,44 @@ if (!version) {
|
||||
throw new Error("OPENCODE_VERSION is required")
|
||||
}
|
||||
process.env["OPENCODE_VERSION"] = version
|
||||
console.log("version:", version)
|
||||
|
||||
const pkgjsons = await Array.fromAsync(
|
||||
new Bun.Glob("**/package.json").scan({
|
||||
absolute: true,
|
||||
}),
|
||||
).then((arr) => arr.filter((x) => !x.includes("node_modules") && !x.includes("dist")))
|
||||
|
||||
const tree = await $`git add . && git write-tree`.text().then((x) => x.trim())
|
||||
for (const file of pkgjsons) {
|
||||
let pkg = await Bun.file(file).text()
|
||||
pkg = pkg.replaceAll(/"version": "[^"]+"/g, `"version": "${version}"`)
|
||||
console.log("updated:", file)
|
||||
await Bun.file(file).write(pkg)
|
||||
}
|
||||
|
||||
console.log("\n=== opencode ===\n")
|
||||
await import(`../packages/opencode/script/publish.ts`)
|
||||
|
||||
console.log("\n=== sdk ===\n")
|
||||
await import(`../packages/sdk/js/script/publish.ts`)
|
||||
// await import(`../packages/sdk/stainless/generate.ts`)
|
||||
|
||||
console.log("\n=== plugin ===\n")
|
||||
await import(`../packages/plugin/script/publish.ts`)
|
||||
|
||||
if (!snapshot) {
|
||||
await $`git commit -am "Release v${version}"`
|
||||
await $`git commit -am "release: v${version}"`
|
||||
await $`git tag v${version}`
|
||||
await $`git push origin HEAD --tags`
|
||||
await $`git push origin HEAD --tags --no-verify`
|
||||
}
|
||||
if (snapshot) {
|
||||
await $`git checkout -b snapshot-${version}`
|
||||
await $`git commit --allow-empty -m "Snapshot release v${version}"`
|
||||
await $`git tag v${version}`
|
||||
await $`git push origin v${version}`
|
||||
await $`git reset --soft HEAD~1`
|
||||
await $`git push origin v${version} --no-verify`
|
||||
await $`git checkout dev`
|
||||
await $`git branch -D snapshot-${version}`
|
||||
for (const file of pkgjsons) {
|
||||
await $`git checkout ${tree} ${file}`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "opencode",
|
||||
"displayName": "opencode",
|
||||
"description": "opencode for VS Code",
|
||||
"version": "0.0.0",
|
||||
"version": "0.3.126",
|
||||
"publisher": "sst-dev",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -57,7 +57,7 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
})
|
||||
|
||||
terminal.show()
|
||||
terminal.sendText(`OPENCODE_THEME=system OPENCODE_CALLER=vscode opencode --port ${port}`)
|
||||
terminal.sendText(`OPENCODE_CALLER=vscode opencode --port ${port}`)
|
||||
|
||||
const fileRef = getActiveFile()
|
||||
if (!fileRef) return
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user