Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e41ca74b9 | ||
|
|
8c05eb22b1 | ||
|
|
f607353be6 | ||
|
|
af3c97f192 | ||
|
|
26e14ce628 | ||
|
|
57ad1814e3 | ||
|
|
4f60ea6108 | ||
|
|
775d288027 | ||
|
|
bc4968abbb | ||
|
|
acb92fcd34 | ||
|
|
c9bbea4266 | ||
|
|
65e1186efe | ||
|
|
8faa2ffcf8 | ||
|
|
bdfd8f8b0f | ||
|
|
2f35c40bb5 | ||
|
|
6650aa6f35 | ||
|
|
8798a77a72 | ||
|
|
6eb2bdd665 | ||
|
|
f97197bdb0 | ||
|
|
5a0b3ee673 | ||
|
|
3bb10773e6 | ||
|
|
558590712d | ||
|
|
d76e1448f0 | ||
|
|
026b3cc88d | ||
|
|
28e5557bf4 | ||
|
|
9a8da20a97 | ||
|
|
63f5669eb5 | ||
|
|
427cc3e153 | ||
|
|
aedd760141 | ||
|
|
6da9fb8fb9 | ||
|
|
b73e0240a5 | ||
|
|
5f2a7c630b | ||
|
|
7988f52231 |
@@ -29,7 +29,8 @@
|
|||||||
<a href="README.ru.md">Русский</a> |
|
<a href="README.ru.md">Русский</a> |
|
||||||
<a href="README.ar.md">العربية</a> |
|
<a href="README.ar.md">العربية</a> |
|
||||||
<a href="README.no.md">Norsk</a> |
|
<a href="README.no.md">Norsk</a> |
|
||||||
<a href="README.br.md">Português (Brasil)</a>
|
<a href="README.br.md">Português (Brasil)</a> |
|
||||||
|
<a href="README.th.md">ไทย</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://opencode.ai)
|
[](https://opencode.ai)
|
||||||
|
|||||||
+134
@@ -0,0 +1,134 @@
|
|||||||
|
<p align="center">
|
||||||
|
<a href="https://opencode.ai">
|
||||||
|
<picture>
|
||||||
|
<source srcset="packages/console/app/src/asset/logo-ornate-dark.svg" media="(prefers-color-scheme: dark)">
|
||||||
|
<source srcset="packages/console/app/src/asset/logo-ornate-light.svg" media="(prefers-color-scheme: light)">
|
||||||
|
<img src="packages/console/app/src/asset/logo-ornate-light.svg" alt="OpenCode logo">
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p align="center">เอเจนต์การเขียนโค้ดด้วย AI แบบโอเพนซอร์ส</p>
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://opencode.ai/discord"><img alt="Discord" src="https://img.shields.io/discord/1391832426048651334?style=flat-square&label=discord" /></a>
|
||||||
|
<a href="https://www.npmjs.com/package/opencode-ai"><img alt="npm" src="https://img.shields.io/npm/v/opencode-ai?style=flat-square" /></a>
|
||||||
|
<a href="https://github.com/anomalyco/opencode/actions/workflows/publish.yml"><img alt="สถานะการสร้าง" src="https://img.shields.io/github/actions/workflow/status/anomalyco/opencode/publish.yml?style=flat-square&branch=dev" /></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="README.md">English</a> |
|
||||||
|
<a href="README.zh.md">简体中文</a> |
|
||||||
|
<a href="README.zht.md">繁體中文</a> |
|
||||||
|
<a href="README.ko.md">한국어</a> |
|
||||||
|
<a href="README.de.md">Deutsch</a> |
|
||||||
|
<a href="README.es.md">Español</a> |
|
||||||
|
<a href="README.fr.md">Français</a> |
|
||||||
|
<a href="README.it.md">Italiano</a> |
|
||||||
|
<a href="README.da.md">Dansk</a> |
|
||||||
|
<a href="README.ja.md">日本語</a> |
|
||||||
|
<a href="README.pl.md">Polski</a> |
|
||||||
|
<a href="README.ru.md">Русский</a> |
|
||||||
|
<a href="README.ar.md">العربية</a> |
|
||||||
|
<a href="README.no.md">Norsk</a> |
|
||||||
|
<a href="README.br.md">Português (Brasil)</a> |
|
||||||
|
<a href="README.th.md">ไทย</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
[](https://opencode.ai)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### การติดตั้ง
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# YOLO
|
||||||
|
curl -fsSL https://opencode.ai/install | bash
|
||||||
|
|
||||||
|
# ตัวจัดการแพ็กเกจ
|
||||||
|
npm i -g opencode-ai@latest # หรือ bun/pnpm/yarn
|
||||||
|
scoop install opencode # Windows
|
||||||
|
choco install opencode # Windows
|
||||||
|
brew install anomalyco/tap/opencode # macOS และ Linux (แนะนำ อัปเดตเสมอ)
|
||||||
|
brew install opencode # macOS และ Linux (brew formula อย่างเป็นทางการ อัปเดตน้อยกว่า)
|
||||||
|
paru -S opencode-bin # Arch Linux
|
||||||
|
mise use -g opencode # ระบบปฏิบัติการใดก็ได้
|
||||||
|
nix run nixpkgs#opencode # หรือ github:anomalyco/opencode สำหรับสาขาพัฒนาล่าสุด
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> ลบเวอร์ชันที่เก่ากว่า 0.1.x ก่อนติดตั้ง
|
||||||
|
|
||||||
|
### แอปพลิเคชันเดสก์ท็อป (เบต้า)
|
||||||
|
|
||||||
|
OpenCode มีให้ใช้งานเป็นแอปพลิเคชันเดสก์ท็อป ดาวน์โหลดโดยตรงจาก [หน้ารุ่น](https://github.com/anomalyco/opencode/releases) หรือ [opencode.ai/download](https://opencode.ai/download)
|
||||||
|
|
||||||
|
| แพลตฟอร์ม | ดาวน์โหลด |
|
||||||
|
| --------------------- | ------------------------------------- |
|
||||||
|
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
||||||
|
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
||||||
|
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||||
|
| Linux | `.deb`, `.rpm`, หรือ AppImage |
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# macOS (Homebrew)
|
||||||
|
brew install --cask opencode-desktop
|
||||||
|
# Windows (Scoop)
|
||||||
|
scoop bucket add extras; scoop install extras/opencode-desktop
|
||||||
|
```
|
||||||
|
|
||||||
|
#### ไดเรกทอรีการติดตั้ง
|
||||||
|
|
||||||
|
สคริปต์การติดตั้งจะใช้ลำดับความสำคัญตามเส้นทางการติดตั้ง:
|
||||||
|
|
||||||
|
1. `$OPENCODE_INSTALL_DIR` - ไดเรกทอรีการติดตั้งที่กำหนดเอง
|
||||||
|
2. `$XDG_BIN_DIR` - เส้นทางที่สอดคล้องกับ XDG Base Directory Specification
|
||||||
|
3. `$HOME/bin` - ไดเรกทอรีไบนารีผู้ใช้มาตรฐาน (หากมีอยู่หรือสามารถสร้างได้)
|
||||||
|
4. `$HOME/.opencode/bin` - ค่าสำรองเริ่มต้น
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# ตัวอย่าง
|
||||||
|
OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash
|
||||||
|
XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
### เอเจนต์
|
||||||
|
|
||||||
|
OpenCode รวมเอเจนต์ในตัวสองตัวที่คุณสามารถสลับได้ด้วยปุ่ม `Tab`
|
||||||
|
|
||||||
|
- **build** - เอเจนต์เริ่มต้น มีสิทธิ์เข้าถึงแบบเต็มสำหรับงานพัฒนา
|
||||||
|
- **plan** - เอเจนต์อ่านอย่างเดียวสำหรับการวิเคราะห์และการสำรวจโค้ด
|
||||||
|
- ปฏิเสธการแก้ไขไฟล์โดยค่าเริ่มต้น
|
||||||
|
- ขอสิทธิ์ก่อนเรียกใช้คำสั่ง bash
|
||||||
|
- เหมาะสำหรับสำรวจโค้ดเบสที่ไม่คุ้นเคยหรือวางแผนการเปลี่ยนแปลง
|
||||||
|
|
||||||
|
นอกจากนี้ยังมีเอเจนต์ย่อย **general** สำหรับการค้นหาที่ซับซ้อนและงานหลายขั้นตอน
|
||||||
|
ใช้ภายในและสามารถเรียกใช้ได้โดยใช้ `@general` ในข้อความ
|
||||||
|
|
||||||
|
เรียนรู้เพิ่มเติมเกี่ยวกับ [เอเจนต์](https://opencode.ai/docs/agents)
|
||||||
|
|
||||||
|
### เอกสารประกอบ
|
||||||
|
|
||||||
|
สำหรับข้อมูลเพิ่มเติมเกี่ยวกับวิธีกำหนดค่า OpenCode [**ไปที่เอกสารของเรา**](https://opencode.ai/docs)
|
||||||
|
|
||||||
|
### การมีส่วนร่วม
|
||||||
|
|
||||||
|
หากคุณสนใจที่จะมีส่วนร่วมใน OpenCode โปรดอ่าน [เอกสารการมีส่วนร่วม](./CONTRIBUTING.md) ก่อนส่ง Pull Request
|
||||||
|
|
||||||
|
### การสร้างบน OpenCode
|
||||||
|
|
||||||
|
หากคุณทำงานในโปรเจกต์ที่เกี่ยวข้องกับ OpenCode และใช้ "opencode" เป็นส่วนหนึ่งของชื่อ เช่น "opencode-dashboard" หรือ "opencode-mobile" โปรดเพิ่มหมายเหตุใน README ของคุณเพื่อชี้แจงว่าไม่ได้สร้างโดยทีม OpenCode และไม่ได้เกี่ยวข้องกับเราในทางใด
|
||||||
|
|
||||||
|
### คำถามที่พบบ่อย
|
||||||
|
|
||||||
|
#### ต่างจาก Claude Code อย่างไร?
|
||||||
|
|
||||||
|
คล้ายกับ Claude Code มากในแง่ความสามารถ นี่คือความแตกต่างหลัก:
|
||||||
|
|
||||||
|
- โอเพนซอร์ส 100%
|
||||||
|
- ไม่ผูกมัดกับผู้ให้บริการใดๆ แม้ว่าเราจะแนะนำโมเดลที่เราจัดหาให้ผ่าน [OpenCode Zen](https://opencode.ai/zen) OpenCode สามารถใช้กับ Claude, OpenAI, Google หรือแม้กระทั่งโมเดลในเครื่องได้ เมื่อโมเดลพัฒนาช่องว่างระหว่างพวกมันจะปิดลงและราคาจะลดลง ดังนั้นการไม่ผูกมัดกับผู้ให้บริการจึงสำคัญ
|
||||||
|
- รองรับ LSP ใช้งานได้ทันทีหลังการติดตั้งโดยไม่ต้องปรับแต่งหรือเปลี่ยนแปลงฟังก์ชันการทำงานใด ๆ
|
||||||
|
- เน้นที่ TUI OpenCode สร้างโดยผู้ใช้ neovim และผู้สร้าง [terminal.shop](https://terminal.shop) เราจะผลักดันขีดจำกัดของสิ่งที่เป็นไปได้ในเทอร์มินัล
|
||||||
|
- สถาปัตยกรรมไคลเอนต์/เซิร์ฟเวอร์ ตัวอย่างเช่น อาจอนุญาตให้ OpenCode ทำงานบนคอมพิวเตอร์ของคุณ ในขณะที่คุณสามารถขับเคลื่อนจากระยะไกลผ่านแอปมือถือ หมายความว่า TUI frontend เป็นหนึ่งในไคลเอนต์ที่เป็นไปได้เท่านั้น
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**ร่วมชุมชนของเรา** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)
|
||||||
@@ -213,3 +213,4 @@
|
|||||||
| 2026-01-25 | 6,639,082 (+268,063) | 2,187,853 (+30,983) | 8,826,935 (+299,046) |
|
| 2026-01-25 | 6,639,082 (+268,063) | 2,187,853 (+30,983) | 8,826,935 (+299,046) |
|
||||||
| 2026-01-26 | 6,941,620 (+302,538) | 2,232,115 (+44,262) | 9,173,735 (+346,800) |
|
| 2026-01-26 | 6,941,620 (+302,538) | 2,232,115 (+44,262) | 9,173,735 (+346,800) |
|
||||||
| 2026-01-27 | 7,208,093 (+266,473) | 2,280,762 (+48,647) | 9,488,855 (+315,120) |
|
| 2026-01-27 | 7,208,093 (+266,473) | 2,280,762 (+48,647) | 9,488,855 (+315,120) |
|
||||||
|
| 2026-01-28 | 7,489,370 (+281,277) | 2,314,849 (+34,087) | 9,804,219 (+315,364) |
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
},
|
},
|
||||||
"packages/app": {
|
"packages/app": {
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.1.37",
|
"version": "1.1.40",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
@@ -73,7 +73,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/app": {
|
"packages/console/app": {
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"version": "1.1.37",
|
"version": "1.1.40",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cloudflare/vite-plugin": "1.15.2",
|
"@cloudflare/vite-plugin": "1.15.2",
|
||||||
"@ibm/plex": "6.4.1",
|
"@ibm/plex": "6.4.1",
|
||||||
@@ -107,7 +107,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/core": {
|
"packages/console/core": {
|
||||||
"name": "@opencode-ai/console-core",
|
"name": "@opencode-ai/console-core",
|
||||||
"version": "1.1.37",
|
"version": "1.1.40",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-sts": "3.782.0",
|
"@aws-sdk/client-sts": "3.782.0",
|
||||||
"@jsx-email/render": "1.1.1",
|
"@jsx-email/render": "1.1.1",
|
||||||
@@ -134,7 +134,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/function": {
|
"packages/console/function": {
|
||||||
"name": "@opencode-ai/console-function",
|
"name": "@opencode-ai/console-function",
|
||||||
"version": "1.1.37",
|
"version": "1.1.40",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/anthropic": "2.0.0",
|
"@ai-sdk/anthropic": "2.0.0",
|
||||||
"@ai-sdk/openai": "2.0.2",
|
"@ai-sdk/openai": "2.0.2",
|
||||||
@@ -158,7 +158,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/mail": {
|
"packages/console/mail": {
|
||||||
"name": "@opencode-ai/console-mail",
|
"name": "@opencode-ai/console-mail",
|
||||||
"version": "1.1.37",
|
"version": "1.1.40",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jsx-email/all": "2.2.3",
|
"@jsx-email/all": "2.2.3",
|
||||||
"@jsx-email/cli": "1.4.3",
|
"@jsx-email/cli": "1.4.3",
|
||||||
@@ -182,7 +182,7 @@
|
|||||||
},
|
},
|
||||||
"packages/desktop": {
|
"packages/desktop": {
|
||||||
"name": "@opencode-ai/desktop",
|
"name": "@opencode-ai/desktop",
|
||||||
"version": "1.1.37",
|
"version": "1.1.40",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/app": "workspace:*",
|
"@opencode-ai/app": "workspace:*",
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
@@ -212,7 +212,7 @@
|
|||||||
},
|
},
|
||||||
"packages/enterprise": {
|
"packages/enterprise": {
|
||||||
"name": "@opencode-ai/enterprise",
|
"name": "@opencode-ai/enterprise",
|
||||||
"version": "1.1.37",
|
"version": "1.1.40",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
"@opencode-ai/util": "workspace:*",
|
"@opencode-ai/util": "workspace:*",
|
||||||
@@ -241,7 +241,7 @@
|
|||||||
},
|
},
|
||||||
"packages/function": {
|
"packages/function": {
|
||||||
"name": "@opencode-ai/function",
|
"name": "@opencode-ai/function",
|
||||||
"version": "1.1.37",
|
"version": "1.1.40",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/auth-app": "8.0.1",
|
"@octokit/auth-app": "8.0.1",
|
||||||
"@octokit/rest": "catalog:",
|
"@octokit/rest": "catalog:",
|
||||||
@@ -257,7 +257,7 @@
|
|||||||
},
|
},
|
||||||
"packages/opencode": {
|
"packages/opencode": {
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"version": "1.1.37",
|
"version": "1.1.40",
|
||||||
"bin": {
|
"bin": {
|
||||||
"opencode": "./bin/opencode",
|
"opencode": "./bin/opencode",
|
||||||
},
|
},
|
||||||
@@ -361,7 +361,7 @@
|
|||||||
},
|
},
|
||||||
"packages/plugin": {
|
"packages/plugin": {
|
||||||
"name": "@opencode-ai/plugin",
|
"name": "@opencode-ai/plugin",
|
||||||
"version": "1.1.37",
|
"version": "1.1.40",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
@@ -381,7 +381,7 @@
|
|||||||
},
|
},
|
||||||
"packages/sdk/js": {
|
"packages/sdk/js": {
|
||||||
"name": "@opencode-ai/sdk",
|
"name": "@opencode-ai/sdk",
|
||||||
"version": "1.1.37",
|
"version": "1.1.40",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@hey-api/openapi-ts": "0.90.10",
|
"@hey-api/openapi-ts": "0.90.10",
|
||||||
"@tsconfig/node22": "catalog:",
|
"@tsconfig/node22": "catalog:",
|
||||||
@@ -392,7 +392,7 @@
|
|||||||
},
|
},
|
||||||
"packages/slack": {
|
"packages/slack": {
|
||||||
"name": "@opencode-ai/slack",
|
"name": "@opencode-ai/slack",
|
||||||
"version": "1.1.37",
|
"version": "1.1.40",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@slack/bolt": "^3.17.1",
|
"@slack/bolt": "^3.17.1",
|
||||||
@@ -405,7 +405,7 @@
|
|||||||
},
|
},
|
||||||
"packages/ui": {
|
"packages/ui": {
|
||||||
"name": "@opencode-ai/ui",
|
"name": "@opencode-ai/ui",
|
||||||
"version": "1.1.37",
|
"version": "1.1.40",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
@@ -447,7 +447,7 @@
|
|||||||
},
|
},
|
||||||
"packages/util": {
|
"packages/util": {
|
||||||
"name": "@opencode-ai/util",
|
"name": "@opencode-ai/util",
|
||||||
"version": "1.1.37",
|
"version": "1.1.40",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
},
|
},
|
||||||
@@ -458,7 +458,7 @@
|
|||||||
},
|
},
|
||||||
"packages/web": {
|
"packages/web": {
|
||||||
"name": "@opencode-ai/web",
|
"name": "@opencode-ai/web",
|
||||||
"version": "1.1.37",
|
"version": "1.1.40",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/cloudflare": "12.6.3",
|
"@astrojs/cloudflare": "12.6.3",
|
||||||
"@astrojs/markdown-remark": "6.3.1",
|
"@astrojs/markdown-remark": "6.3.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.1.37",
|
"version": "1.1.40",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -0,0 +1,424 @@
|
|||||||
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
|
import { Dialog } from "@opencode-ai/ui/dialog"
|
||||||
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
|
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
||||||
|
import { TextField } from "@opencode-ai/ui/text-field"
|
||||||
|
import { showToast } from "@opencode-ai/ui/toast"
|
||||||
|
import { For } from "solid-js"
|
||||||
|
import { createStore, produce } from "solid-js/store"
|
||||||
|
import { Link } from "@/components/link"
|
||||||
|
import { useGlobalSDK } from "@/context/global-sdk"
|
||||||
|
import { useGlobalSync } from "@/context/global-sync"
|
||||||
|
import { useLanguage } from "@/context/language"
|
||||||
|
import { DialogSelectProvider } from "./dialog-select-provider"
|
||||||
|
|
||||||
|
const PROVIDER_ID = /^[a-z0-9][a-z0-9-_]*$/
|
||||||
|
const OPENAI_COMPATIBLE = "@ai-sdk/openai-compatible"
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
back?: "providers" | "close"
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DialogCustomProvider(props: Props) {
|
||||||
|
const dialog = useDialog()
|
||||||
|
const globalSync = useGlobalSync()
|
||||||
|
const globalSDK = useGlobalSDK()
|
||||||
|
const language = useLanguage()
|
||||||
|
|
||||||
|
const [form, setForm] = createStore({
|
||||||
|
providerID: "",
|
||||||
|
name: "",
|
||||||
|
baseURL: "",
|
||||||
|
apiKey: "",
|
||||||
|
models: [{ id: "", name: "" }],
|
||||||
|
headers: [{ key: "", value: "" }],
|
||||||
|
saving: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const [errors, setErrors] = createStore({
|
||||||
|
providerID: undefined as string | undefined,
|
||||||
|
name: undefined as string | undefined,
|
||||||
|
baseURL: undefined as string | undefined,
|
||||||
|
models: [{} as { id?: string; name?: string }],
|
||||||
|
headers: [{} as { key?: string; value?: string }],
|
||||||
|
})
|
||||||
|
|
||||||
|
const goBack = () => {
|
||||||
|
if (props.back === "close") {
|
||||||
|
dialog.close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dialog.show(() => <DialogSelectProvider />)
|
||||||
|
}
|
||||||
|
|
||||||
|
const addModel = () => {
|
||||||
|
setForm(
|
||||||
|
"models",
|
||||||
|
produce((draft) => {
|
||||||
|
draft.push({ id: "", name: "" })
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
setErrors(
|
||||||
|
"models",
|
||||||
|
produce((draft) => {
|
||||||
|
draft.push({})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeModel = (index: number) => {
|
||||||
|
if (form.models.length <= 1) return
|
||||||
|
setForm(
|
||||||
|
"models",
|
||||||
|
produce((draft) => {
|
||||||
|
draft.splice(index, 1)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
setErrors(
|
||||||
|
"models",
|
||||||
|
produce((draft) => {
|
||||||
|
draft.splice(index, 1)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const addHeader = () => {
|
||||||
|
setForm(
|
||||||
|
"headers",
|
||||||
|
produce((draft) => {
|
||||||
|
draft.push({ key: "", value: "" })
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
setErrors(
|
||||||
|
"headers",
|
||||||
|
produce((draft) => {
|
||||||
|
draft.push({})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeHeader = (index: number) => {
|
||||||
|
if (form.headers.length <= 1) return
|
||||||
|
setForm(
|
||||||
|
"headers",
|
||||||
|
produce((draft) => {
|
||||||
|
draft.splice(index, 1)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
setErrors(
|
||||||
|
"headers",
|
||||||
|
produce((draft) => {
|
||||||
|
draft.splice(index, 1)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const validate = () => {
|
||||||
|
const providerID = form.providerID.trim()
|
||||||
|
const name = form.name.trim()
|
||||||
|
const baseURL = form.baseURL.trim()
|
||||||
|
const apiKey = form.apiKey.trim()
|
||||||
|
|
||||||
|
const env = apiKey.match(/^\{env:([^}]+)\}$/)?.[1]?.trim()
|
||||||
|
const key = apiKey && !env ? apiKey : undefined
|
||||||
|
|
||||||
|
const idError = !providerID
|
||||||
|
? "Provider ID is required"
|
||||||
|
: !PROVIDER_ID.test(providerID)
|
||||||
|
? "Use lowercase letters, numbers, hyphens, or underscores"
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
const nameError = !name ? "Display name is required" : undefined
|
||||||
|
const urlError = !baseURL
|
||||||
|
? "Base URL is required"
|
||||||
|
: !/^https?:\/\//.test(baseURL)
|
||||||
|
? "Must start with http:// or https://"
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
const disabled = (globalSync.data.config.disabled_providers ?? []).includes(providerID)
|
||||||
|
const existingProvider = globalSync.data.provider.all.find((p) => p.id === providerID)
|
||||||
|
const existsError = idError
|
||||||
|
? undefined
|
||||||
|
: existingProvider && !disabled
|
||||||
|
? "That provider ID already exists"
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
const seenModels = new Set<string>()
|
||||||
|
const modelErrors = form.models.map((m) => {
|
||||||
|
const id = m.id.trim()
|
||||||
|
const modelIdError = !id
|
||||||
|
? "Required"
|
||||||
|
: seenModels.has(id)
|
||||||
|
? "Duplicate"
|
||||||
|
: (() => {
|
||||||
|
seenModels.add(id)
|
||||||
|
return undefined
|
||||||
|
})()
|
||||||
|
const modelNameError = !m.name.trim() ? "Required" : undefined
|
||||||
|
return { id: modelIdError, name: modelNameError }
|
||||||
|
})
|
||||||
|
const modelsValid = modelErrors.every((m) => !m.id && !m.name)
|
||||||
|
const models = Object.fromEntries(form.models.map((m) => [m.id.trim(), { name: m.name.trim() }]))
|
||||||
|
|
||||||
|
const seenHeaders = new Set<string>()
|
||||||
|
const headerErrors = form.headers.map((h) => {
|
||||||
|
const key = h.key.trim()
|
||||||
|
const value = h.value.trim()
|
||||||
|
|
||||||
|
if (!key && !value) return {}
|
||||||
|
const keyError = !key
|
||||||
|
? "Required"
|
||||||
|
: seenHeaders.has(key.toLowerCase())
|
||||||
|
? "Duplicate"
|
||||||
|
: (() => {
|
||||||
|
seenHeaders.add(key.toLowerCase())
|
||||||
|
return undefined
|
||||||
|
})()
|
||||||
|
const valueError = !value ? "Required" : undefined
|
||||||
|
return { key: keyError, value: valueError }
|
||||||
|
})
|
||||||
|
const headersValid = headerErrors.every((h) => !h.key && !h.value)
|
||||||
|
const headers = Object.fromEntries(
|
||||||
|
form.headers
|
||||||
|
.map((h) => ({ key: h.key.trim(), value: h.value.trim() }))
|
||||||
|
.filter((h) => !!h.key && !!h.value)
|
||||||
|
.map((h) => [h.key, h.value]),
|
||||||
|
)
|
||||||
|
|
||||||
|
setErrors(
|
||||||
|
produce((draft) => {
|
||||||
|
draft.providerID = idError ?? existsError
|
||||||
|
draft.name = nameError
|
||||||
|
draft.baseURL = urlError
|
||||||
|
draft.models = modelErrors
|
||||||
|
draft.headers = headerErrors
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const ok = !idError && !existsError && !nameError && !urlError && modelsValid && headersValid
|
||||||
|
if (!ok) return
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
baseURL,
|
||||||
|
...(Object.keys(headers).length ? { headers } : {}),
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
providerID,
|
||||||
|
name,
|
||||||
|
key,
|
||||||
|
config: {
|
||||||
|
npm: OPENAI_COMPATIBLE,
|
||||||
|
name,
|
||||||
|
...(env ? { env: [env] } : {}),
|
||||||
|
options,
|
||||||
|
models,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const save = async (e: SubmitEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
if (form.saving) return
|
||||||
|
|
||||||
|
const result = validate()
|
||||||
|
if (!result) return
|
||||||
|
|
||||||
|
setForm("saving", true)
|
||||||
|
|
||||||
|
const disabledProviders = globalSync.data.config.disabled_providers ?? []
|
||||||
|
const nextDisabled = disabledProviders.filter((id) => id !== result.providerID)
|
||||||
|
|
||||||
|
const auth = result.key
|
||||||
|
? globalSDK.client.auth.set({
|
||||||
|
providerID: result.providerID,
|
||||||
|
auth: {
|
||||||
|
type: "api",
|
||||||
|
key: result.key,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: Promise.resolve()
|
||||||
|
|
||||||
|
auth
|
||||||
|
.then(() =>
|
||||||
|
globalSync.updateConfig({ provider: { [result.providerID]: result.config }, disabled_providers: nextDisabled }),
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
dialog.close()
|
||||||
|
showToast({
|
||||||
|
variant: "success",
|
||||||
|
icon: "circle-check",
|
||||||
|
title: language.t("provider.connect.toast.connected.title", { provider: result.name }),
|
||||||
|
description: language.t("provider.connect.toast.connected.description", { provider: result.name }),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((err: unknown) => {
|
||||||
|
const message = err instanceof Error ? err.message : String(err)
|
||||||
|
showToast({ title: language.t("common.requestFailed"), description: message })
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setForm("saving", false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
title={
|
||||||
|
<IconButton
|
||||||
|
tabIndex={-1}
|
||||||
|
icon="arrow-left"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={goBack}
|
||||||
|
aria-label={language.t("common.goBack")}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
transition
|
||||||
|
>
|
||||||
|
<div class="flex flex-col gap-6 px-2.5 pb-3 overflow-y-auto max-h-[60vh]">
|
||||||
|
<div class="px-2.5 flex gap-4 items-center">
|
||||||
|
<ProviderIcon id="synthetic" class="size-5 shrink-0 icon-strong-base" />
|
||||||
|
<div class="text-16-medium text-text-strong">Custom provider</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form onSubmit={save} class="px-2.5 pb-6 flex flex-col gap-6">
|
||||||
|
<p class="text-14-regular text-text-base">
|
||||||
|
Configure an OpenAI-compatible provider. See the{" "}
|
||||||
|
<Link href="https://opencode.ai/docs/providers/#custom-provider" tabIndex={-1}>
|
||||||
|
provider config docs
|
||||||
|
</Link>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<TextField
|
||||||
|
autofocus
|
||||||
|
label="Provider ID"
|
||||||
|
placeholder="myprovider"
|
||||||
|
description="Lowercase letters, numbers, hyphens, or underscores"
|
||||||
|
value={form.providerID}
|
||||||
|
onChange={setForm.bind(null, "providerID")}
|
||||||
|
validationState={errors.providerID ? "invalid" : undefined}
|
||||||
|
error={errors.providerID}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Display name"
|
||||||
|
placeholder="My AI Provider"
|
||||||
|
value={form.name}
|
||||||
|
onChange={setForm.bind(null, "name")}
|
||||||
|
validationState={errors.name ? "invalid" : undefined}
|
||||||
|
error={errors.name}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Base URL"
|
||||||
|
placeholder="https://api.myprovider.com/v1"
|
||||||
|
value={form.baseURL}
|
||||||
|
onChange={setForm.bind(null, "baseURL")}
|
||||||
|
validationState={errors.baseURL ? "invalid" : undefined}
|
||||||
|
error={errors.baseURL}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="API key"
|
||||||
|
placeholder="API key"
|
||||||
|
description="Optional. Leave empty if you manage auth via headers."
|
||||||
|
value={form.apiKey}
|
||||||
|
onChange={setForm.bind(null, "apiKey")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
<label class="text-12-medium text-text-weak">Models</label>
|
||||||
|
<For each={form.models}>
|
||||||
|
{(m, i) => (
|
||||||
|
<div class="flex gap-2 items-start">
|
||||||
|
<div class="flex-1">
|
||||||
|
<TextField
|
||||||
|
label="ID"
|
||||||
|
hideLabel
|
||||||
|
placeholder="model-id"
|
||||||
|
value={m.id}
|
||||||
|
onChange={(v) => setForm("models", i(), "id", v)}
|
||||||
|
validationState={errors.models[i()]?.id ? "invalid" : undefined}
|
||||||
|
error={errors.models[i()]?.id}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<TextField
|
||||||
|
label="Name"
|
||||||
|
hideLabel
|
||||||
|
placeholder="Display Name"
|
||||||
|
value={m.name}
|
||||||
|
onChange={(v) => setForm("models", i(), "name", v)}
|
||||||
|
validationState={errors.models[i()]?.name ? "invalid" : undefined}
|
||||||
|
error={errors.models[i()]?.name}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<IconButton
|
||||||
|
type="button"
|
||||||
|
icon="trash"
|
||||||
|
variant="ghost"
|
||||||
|
class="mt-1.5"
|
||||||
|
onClick={() => removeModel(i())}
|
||||||
|
disabled={form.models.length <= 1}
|
||||||
|
aria-label="Remove model"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
<Button type="button" size="small" variant="ghost" icon="plus-small" onClick={addModel} class="self-start">
|
||||||
|
Add model
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
<label class="text-12-medium text-text-weak">Headers (optional)</label>
|
||||||
|
<For each={form.headers}>
|
||||||
|
{(h, i) => (
|
||||||
|
<div class="flex gap-2 items-start">
|
||||||
|
<div class="flex-1">
|
||||||
|
<TextField
|
||||||
|
label="Header"
|
||||||
|
hideLabel
|
||||||
|
placeholder="Header-Name"
|
||||||
|
value={h.key}
|
||||||
|
onChange={(v) => setForm("headers", i(), "key", v)}
|
||||||
|
validationState={errors.headers[i()]?.key ? "invalid" : undefined}
|
||||||
|
error={errors.headers[i()]?.key}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<TextField
|
||||||
|
label="Value"
|
||||||
|
hideLabel
|
||||||
|
placeholder="value"
|
||||||
|
value={h.value}
|
||||||
|
onChange={(v) => setForm("headers", i(), "value", v)}
|
||||||
|
validationState={errors.headers[i()]?.value ? "invalid" : undefined}
|
||||||
|
error={errors.headers[i()]?.value}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<IconButton
|
||||||
|
type="button"
|
||||||
|
icon="trash"
|
||||||
|
variant="ghost"
|
||||||
|
class="mt-1.5"
|
||||||
|
onClick={() => removeHeader(i())}
|
||||||
|
disabled={form.headers.length <= 1}
|
||||||
|
aria-label="Remove header"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
<Button type="button" size="small" variant="ghost" icon="plus-small" onClick={addHeader} class="self-start">
|
||||||
|
Add header
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button class="w-auto self-start" type="submit" size="large" variant="primary" disabled={form.saving}>
|
||||||
|
{form.saving ? "Saving..." : language.t("common.submit")}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -44,7 +44,7 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
|
|||||||
"session.previous",
|
"session.previous",
|
||||||
"session.next",
|
"session.next",
|
||||||
"terminal.toggle",
|
"terminal.toggle",
|
||||||
"fileTree.toggle",
|
"review.toggle",
|
||||||
]
|
]
|
||||||
const limit = 5
|
const limit = 5
|
||||||
|
|
||||||
@@ -162,6 +162,7 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
|
|||||||
const value = file.tab(path)
|
const value = file.tab(path)
|
||||||
tabs().open(value)
|
tabs().open(value)
|
||||||
file.load(path)
|
file.load(path)
|
||||||
|
layout.fileTree.open()
|
||||||
layout.fileTree.setTab("all")
|
layout.fileTree.setTab("all")
|
||||||
props.onOpenFile?.(path)
|
props.onOpenFile?.(path)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,17 @@ import { Dialog } from "@opencode-ai/ui/dialog"
|
|||||||
import { List } from "@opencode-ai/ui/list"
|
import { List } from "@opencode-ai/ui/list"
|
||||||
import { Tag } from "@opencode-ai/ui/tag"
|
import { Tag } from "@opencode-ai/ui/tag"
|
||||||
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
||||||
import { IconName } from "@opencode-ai/ui/icons/provider"
|
import { iconNames, type IconName } from "@opencode-ai/ui/icons/provider"
|
||||||
import { DialogConnectProvider } from "./dialog-connect-provider"
|
import { DialogConnectProvider } from "./dialog-connect-provider"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
|
import { DialogCustomProvider } from "./dialog-custom-provider"
|
||||||
|
|
||||||
|
const CUSTOM_ID = "_custom"
|
||||||
|
|
||||||
|
function icon(id: string): IconName {
|
||||||
|
if (iconNames.includes(id as IconName)) return id as IconName
|
||||||
|
return "synthetic"
|
||||||
|
}
|
||||||
|
|
||||||
export const DialogSelectProvider: Component = () => {
|
export const DialogSelectProvider: Component = () => {
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
@@ -26,11 +34,13 @@ export const DialogSelectProvider: Component = () => {
|
|||||||
key={(x) => x?.id}
|
key={(x) => x?.id}
|
||||||
items={() => {
|
items={() => {
|
||||||
language.locale()
|
language.locale()
|
||||||
return providers.all()
|
return [{ id: CUSTOM_ID, name: "Custom provider" }, ...providers.all()]
|
||||||
}}
|
}}
|
||||||
filterKeys={["id", "name"]}
|
filterKeys={["id", "name"]}
|
||||||
groupBy={(x) => (popularProviders.includes(x.id) ? popularGroup() : otherGroup())}
|
groupBy={(x) => (popularProviders.includes(x.id) ? popularGroup() : otherGroup())}
|
||||||
sortBy={(a, b) => {
|
sortBy={(a, b) => {
|
||||||
|
if (a.id === CUSTOM_ID) return -1
|
||||||
|
if (b.id === CUSTOM_ID) return 1
|
||||||
if (popularProviders.includes(a.id) && popularProviders.includes(b.id))
|
if (popularProviders.includes(a.id) && popularProviders.includes(b.id))
|
||||||
return popularProviders.indexOf(a.id) - popularProviders.indexOf(b.id)
|
return popularProviders.indexOf(a.id) - popularProviders.indexOf(b.id)
|
||||||
return a.name.localeCompare(b.name)
|
return a.name.localeCompare(b.name)
|
||||||
@@ -43,13 +53,20 @@ export const DialogSelectProvider: Component = () => {
|
|||||||
}}
|
}}
|
||||||
onSelect={(x) => {
|
onSelect={(x) => {
|
||||||
if (!x) return
|
if (!x) return
|
||||||
|
if (x.id === CUSTOM_ID) {
|
||||||
|
dialog.show(() => <DialogCustomProvider back="providers" />)
|
||||||
|
return
|
||||||
|
}
|
||||||
dialog.show(() => <DialogConnectProvider provider={x.id} />)
|
dialog.show(() => <DialogConnectProvider provider={x.id} />)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(i) => (
|
{(i) => (
|
||||||
<div class="px-1.25 w-full flex items-center gap-x-3">
|
<div class="px-1.25 w-full flex items-center gap-x-3">
|
||||||
<ProviderIcon data-slot="list-item-extra-icon" id={i.id as IconName} />
|
<ProviderIcon data-slot="list-item-extra-icon" id={icon(i.id)} />
|
||||||
<span>{i.name}</span>
|
<span>{i.name}</span>
|
||||||
|
<Show when={i.id === CUSTOM_ID}>
|
||||||
|
<Tag>{language.t("settings.providers.tag.custom")}</Tag>
|
||||||
|
</Show>
|
||||||
<Show when={i.id === "opencode"}>
|
<Show when={i.id === "opencode"}>
|
||||||
<Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
|
<Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
@@ -189,12 +189,13 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
|
|
||||||
const wantsReview = item.commentOrigin === "review" || (item.commentOrigin !== "file" && commentInReview(item.path))
|
const wantsReview = item.commentOrigin === "review" || (item.commentOrigin !== "file" && commentInReview(item.path))
|
||||||
if (wantsReview) {
|
if (wantsReview) {
|
||||||
|
layout.fileTree.open()
|
||||||
layout.fileTree.setTab("changes")
|
layout.fileTree.setTab("changes")
|
||||||
if (!layout.fileTree.opened()) tabs().open("review")
|
|
||||||
requestAnimationFrame(() => comments.setFocus(focus))
|
requestAnimationFrame(() => comments.setFocus(focus))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
layout.fileTree.open()
|
||||||
layout.fileTree.setTab("all")
|
layout.fileTree.setTab("all")
|
||||||
const tab = files.tab(item.path)
|
const tab = files.tab(item.path)
|
||||||
tabs().open(tab)
|
tabs().open(tab)
|
||||||
@@ -1037,13 +1038,17 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ctrl = event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey
|
||||||
|
|
||||||
if (store.popover) {
|
if (store.popover) {
|
||||||
if (event.key === "Tab") {
|
if (event.key === "Tab") {
|
||||||
selectPopoverActive()
|
selectPopoverActive()
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (event.key === "ArrowUp" || event.key === "ArrowDown" || event.key === "Enter") {
|
const nav = event.key === "ArrowUp" || event.key === "ArrowDown" || event.key === "Enter"
|
||||||
|
const ctrlNav = ctrl && (event.key === "n" || event.key === "p")
|
||||||
|
if (nav || ctrlNav) {
|
||||||
if (store.popover === "at") {
|
if (store.popover === "at") {
|
||||||
atOnKeyDown(event)
|
atOnKeyDown(event)
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
@@ -1057,8 +1062,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ctrl = event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey
|
|
||||||
|
|
||||||
if (ctrl && event.code === "KeyG") {
|
if (ctrl && event.code === "KeyG") {
|
||||||
if (store.popover) {
|
if (store.popover) {
|
||||||
setStore("popover", null)
|
setStore("popover", null)
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
|
|||||||
|
|
||||||
const openContext = () => {
|
const openContext = () => {
|
||||||
if (!params.id) return
|
if (!params.id) return
|
||||||
|
layout.fileTree.open()
|
||||||
layout.fileTree.setTab("all")
|
layout.fileTree.setTab("all")
|
||||||
tabs().open("context")
|
tabs().open("context")
|
||||||
tabs().setActive("context")
|
tabs().setActive("context")
|
||||||
|
|||||||
@@ -280,17 +280,14 @@ export function SessionHeader() {
|
|||||||
</TooltipKeybind>
|
</TooltipKeybind>
|
||||||
</div>
|
</div>
|
||||||
<div class="hidden md:block shrink-0">
|
<div class="hidden md:block shrink-0">
|
||||||
<TooltipKeybind
|
<TooltipKeybind title={language.t("command.review.toggle")} keybind={command.keybind("review.toggle")}>
|
||||||
title={language.t("command.fileTree.toggle")}
|
|
||||||
keybind={command.keybind("fileTree.toggle")}
|
|
||||||
>
|
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="group/file-tree-toggle size-6 p-0"
|
class="group/file-tree-toggle size-6 p-0"
|
||||||
onClick={() => layout.fileTree.toggle()}
|
onClick={() => layout.fileTree.toggle()}
|
||||||
aria-label={language.t("command.fileTree.toggle")}
|
aria-label={language.t("command.review.toggle")}
|
||||||
aria-expanded={layout.fileTree.opened()}
|
aria-expanded={layout.fileTree.opened()}
|
||||||
aria-controls="file-tree-panel"
|
aria-controls="review-panel"
|
||||||
>
|
>
|
||||||
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
|
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
|
||||||
<Icon
|
<Icon
|
||||||
|
|||||||
@@ -3,13 +3,15 @@ import { useDialog } from "@opencode-ai/ui/context/dialog"
|
|||||||
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
||||||
import { Tag } from "@opencode-ai/ui/tag"
|
import { Tag } from "@opencode-ai/ui/tag"
|
||||||
import { showToast } from "@opencode-ai/ui/toast"
|
import { showToast } from "@opencode-ai/ui/toast"
|
||||||
import type { IconName } from "@opencode-ai/ui/icons/provider"
|
import { iconNames, type IconName } from "@opencode-ai/ui/icons/provider"
|
||||||
import { popularProviders, useProviders } from "@/hooks/use-providers"
|
import { popularProviders, useProviders } from "@/hooks/use-providers"
|
||||||
import { createMemo, type Component, For, Show } from "solid-js"
|
import { createMemo, type Component, For, Show } from "solid-js"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { useGlobalSDK } from "@/context/global-sdk"
|
import { useGlobalSDK } from "@/context/global-sdk"
|
||||||
|
import { useGlobalSync } from "@/context/global-sync"
|
||||||
import { DialogConnectProvider } from "./dialog-connect-provider"
|
import { DialogConnectProvider } from "./dialog-connect-provider"
|
||||||
import { DialogSelectProvider } from "./dialog-select-provider"
|
import { DialogSelectProvider } from "./dialog-select-provider"
|
||||||
|
import { DialogCustomProvider } from "./dialog-custom-provider"
|
||||||
|
|
||||||
type ProviderSource = "env" | "api" | "config" | "custom"
|
type ProviderSource = "env" | "api" | "config" | "custom"
|
||||||
type ProviderMeta = { source?: ProviderSource }
|
type ProviderMeta = { source?: ProviderSource }
|
||||||
@@ -18,8 +20,14 @@ export const SettingsProviders: Component = () => {
|
|||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const globalSDK = useGlobalSDK()
|
const globalSDK = useGlobalSDK()
|
||||||
|
const globalSync = useGlobalSync()
|
||||||
const providers = useProviders()
|
const providers = useProviders()
|
||||||
|
|
||||||
|
const icon = (id: string): IconName => {
|
||||||
|
if (iconNames.includes(id as IconName)) return id as IconName
|
||||||
|
return "synthetic"
|
||||||
|
}
|
||||||
|
|
||||||
const connected = createMemo(() => {
|
const connected = createMemo(() => {
|
||||||
return providers
|
return providers
|
||||||
.connected()
|
.connected()
|
||||||
@@ -42,14 +50,53 @@ export const SettingsProviders: Component = () => {
|
|||||||
const current = source(item)
|
const current = source(item)
|
||||||
if (current === "env") return language.t("settings.providers.tag.environment")
|
if (current === "env") return language.t("settings.providers.tag.environment")
|
||||||
if (current === "api") return language.t("provider.connect.method.apiKey")
|
if (current === "api") return language.t("provider.connect.method.apiKey")
|
||||||
if (current === "config") return language.t("settings.providers.tag.config")
|
if (current === "config") {
|
||||||
|
const id = (item as { id?: string }).id
|
||||||
|
if (id && isConfigCustom(id)) return language.t("settings.providers.tag.custom")
|
||||||
|
return language.t("settings.providers.tag.config")
|
||||||
|
}
|
||||||
if (current === "custom") return language.t("settings.providers.tag.custom")
|
if (current === "custom") return language.t("settings.providers.tag.custom")
|
||||||
return language.t("settings.providers.tag.other")
|
return language.t("settings.providers.tag.other")
|
||||||
}
|
}
|
||||||
|
|
||||||
const canDisconnect = (item: unknown) => source(item) !== "env"
|
const canDisconnect = (item: unknown) => source(item) !== "env"
|
||||||
|
|
||||||
|
const isConfigCustom = (providerID: string) => {
|
||||||
|
const provider = globalSync.data.config.provider?.[providerID]
|
||||||
|
if (!provider) return false
|
||||||
|
if (provider.npm !== "@ai-sdk/openai-compatible") return false
|
||||||
|
if (!provider.models || Object.keys(provider.models).length === 0) return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const disableProvider = async (providerID: string, name: string) => {
|
||||||
|
const before = globalSync.data.config.disabled_providers ?? []
|
||||||
|
const next = before.includes(providerID) ? before : [...before, providerID]
|
||||||
|
globalSync.set("config", "disabled_providers", next)
|
||||||
|
|
||||||
|
await globalSync
|
||||||
|
.updateConfig({ disabled_providers: next })
|
||||||
|
.then(() => {
|
||||||
|
showToast({
|
||||||
|
variant: "success",
|
||||||
|
icon: "circle-check",
|
||||||
|
title: language.t("provider.disconnect.toast.disconnected.title", { provider: name }),
|
||||||
|
description: language.t("provider.disconnect.toast.disconnected.description", { provider: name }),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((err: unknown) => {
|
||||||
|
globalSync.set("config", "disabled_providers", before)
|
||||||
|
const message = err instanceof Error ? err.message : String(err)
|
||||||
|
showToast({ title: language.t("common.requestFailed"), description: message })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const disconnect = async (providerID: string, name: string) => {
|
const disconnect = async (providerID: string, name: string) => {
|
||||||
|
if (isConfigCustom(providerID)) {
|
||||||
|
await globalSDK.client.auth.remove({ providerID }).catch(() => undefined)
|
||||||
|
await disableProvider(providerID, name)
|
||||||
|
return
|
||||||
|
}
|
||||||
await globalSDK.client.auth
|
await globalSDK.client.auth
|
||||||
.remove({ providerID })
|
.remove({ providerID })
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
@@ -91,7 +138,7 @@ export const SettingsProviders: Component = () => {
|
|||||||
{(item) => (
|
{(item) => (
|
||||||
<div class="group flex items-center justify-between gap-4 h-16 border-b border-border-weak-base last:border-none">
|
<div class="group flex items-center justify-between gap-4 h-16 border-b border-border-weak-base last:border-none">
|
||||||
<div class="flex items-center gap-3 min-w-0">
|
<div class="flex items-center gap-3 min-w-0">
|
||||||
<ProviderIcon id={item.id as IconName} class="size-5 shrink-0 icon-strong-base" />
|
<ProviderIcon id={icon(item.id)} class="size-5 shrink-0 icon-strong-base" />
|
||||||
<span class="text-14-medium text-text-strong truncate">{item.name}</span>
|
<span class="text-14-medium text-text-strong truncate">{item.name}</span>
|
||||||
<Tag>{type(item)}</Tag>
|
<Tag>{type(item)}</Tag>
|
||||||
</div>
|
</div>
|
||||||
@@ -122,7 +169,7 @@ export const SettingsProviders: Component = () => {
|
|||||||
<div class="flex items-center justify-between gap-4 h-16 border-b border-border-weak-base last:border-none">
|
<div class="flex items-center justify-between gap-4 h-16 border-b border-border-weak-base last:border-none">
|
||||||
<div class="flex flex-col min-w-0">
|
<div class="flex flex-col min-w-0">
|
||||||
<div class="flex items-center gap-x-3">
|
<div class="flex items-center gap-x-3">
|
||||||
<ProviderIcon id={item.id as IconName} class="size-5 shrink-0 icon-strong-base" />
|
<ProviderIcon id={icon(item.id)} class="size-5 shrink-0 icon-strong-base" />
|
||||||
<span class="text-14-medium text-text-strong">{item.name}</span>
|
<span class="text-14-medium text-text-strong">{item.name}</span>
|
||||||
<Show when={item.id === "opencode"}>
|
<Show when={item.id === "opencode"}>
|
||||||
<Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
|
<Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
|
||||||
@@ -177,6 +224,27 @@ export const SettingsProviders: Component = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-between gap-4 h-16 border-b border-border-weak-base last:border-none">
|
||||||
|
<div class="flex flex-col min-w-0">
|
||||||
|
<div class="flex items-center gap-x-3">
|
||||||
|
<ProviderIcon id={icon("synthetic")} class="size-5 shrink-0 icon-strong-base" />
|
||||||
|
<span class="text-14-medium text-text-strong">Custom provider</span>
|
||||||
|
<Tag>{language.t("settings.providers.tag.custom")}</Tag>
|
||||||
|
</div>
|
||||||
|
<span class="text-12-regular text-text-weak pl-8">Add an OpenAI-compatible provider by base URL.</span>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
size="large"
|
||||||
|
variant="secondary"
|
||||||
|
icon="plus-small"
|
||||||
|
onClick={() => {
|
||||||
|
dialog.show(() => <DialogCustomProvider back="close" />)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{language.t("common.connect")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -188,7 +188,74 @@ function createGlobalSync() {
|
|||||||
config: {},
|
config: {},
|
||||||
reload: undefined,
|
reload: undefined,
|
||||||
})
|
})
|
||||||
let bootstrapQueue: string[] = []
|
|
||||||
|
const queued = new Set<string>()
|
||||||
|
let root = false
|
||||||
|
let running = false
|
||||||
|
let timer: ReturnType<typeof setTimeout> | undefined
|
||||||
|
|
||||||
|
const paused = () => untrack(() => globalStore.reload) !== undefined
|
||||||
|
|
||||||
|
const tick = () => new Promise<void>((resolve) => setTimeout(resolve, 0))
|
||||||
|
|
||||||
|
const take = (count: number) => {
|
||||||
|
if (queued.size === 0) return [] as string[]
|
||||||
|
const items: string[] = []
|
||||||
|
for (const item of queued) {
|
||||||
|
queued.delete(item)
|
||||||
|
items.push(item)
|
||||||
|
if (items.length >= count) break
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
const schedule = () => {
|
||||||
|
if (timer) return
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
timer = undefined
|
||||||
|
void drain()
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
const push = (directory: string) => {
|
||||||
|
if (!directory) return
|
||||||
|
queued.add(directory)
|
||||||
|
if (paused()) return
|
||||||
|
schedule()
|
||||||
|
}
|
||||||
|
|
||||||
|
const refresh = () => {
|
||||||
|
root = true
|
||||||
|
if (paused()) return
|
||||||
|
schedule()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function drain() {
|
||||||
|
if (running) return
|
||||||
|
running = true
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
if (paused()) return
|
||||||
|
|
||||||
|
if (root) {
|
||||||
|
root = false
|
||||||
|
await bootstrap()
|
||||||
|
await tick()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const dirs = take(2)
|
||||||
|
if (dirs.length === 0) return
|
||||||
|
|
||||||
|
await Promise.all(dirs.map((dir) => bootstrapInstance(dir)))
|
||||||
|
await tick()
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
running = false
|
||||||
|
if (paused()) return
|
||||||
|
if (root || queued.size) schedule()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (!projectCacheReady()) return
|
if (!projectCacheReady()) return
|
||||||
@@ -210,14 +277,8 @@ function createGlobalSync() {
|
|||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (globalStore.reload !== "complete") return
|
if (globalStore.reload !== "complete") return
|
||||||
if (bootstrapQueue.length) {
|
|
||||||
for (const directory of bootstrapQueue) {
|
|
||||||
bootstrapInstance(directory)
|
|
||||||
}
|
|
||||||
bootstrap()
|
|
||||||
}
|
|
||||||
bootstrapQueue = []
|
|
||||||
setGlobalStore("reload", undefined)
|
setGlobalStore("reload", undefined)
|
||||||
|
refresh()
|
||||||
})
|
})
|
||||||
|
|
||||||
const children: Record<string, [Store<State>, SetStoreFunction<State>]> = {}
|
const children: Record<string, [Store<State>, SetStoreFunction<State>]> = {}
|
||||||
@@ -584,9 +645,8 @@ function createGlobalSync() {
|
|||||||
if (directory === "global") {
|
if (directory === "global") {
|
||||||
switch (event?.type) {
|
switch (event?.type) {
|
||||||
case "global.disposed": {
|
case "global.disposed": {
|
||||||
if (globalStore.reload) return
|
refresh()
|
||||||
bootstrap()
|
return
|
||||||
break
|
|
||||||
}
|
}
|
||||||
case "project.updated": {
|
case "project.updated": {
|
||||||
const result = Binary.search(globalStore.project, event.properties.id, (s) => s.id)
|
const result = Binary.search(globalStore.project, event.properties.id, (s) => s.id)
|
||||||
@@ -647,12 +707,8 @@ function createGlobalSync() {
|
|||||||
|
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case "server.instance.disposed": {
|
case "server.instance.disposed": {
|
||||||
if (globalStore.reload) {
|
push(directory)
|
||||||
bootstrapQueue.push(directory)
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
bootstrapInstance(directory)
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
case "session.created": {
|
case "session.created": {
|
||||||
const info = event.properties.info
|
const info = event.properties.info
|
||||||
@@ -893,6 +949,10 @@ function createGlobalSync() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
onCleanup(unsub)
|
onCleanup(unsub)
|
||||||
|
onCleanup(() => {
|
||||||
|
if (!timer) return
|
||||||
|
clearTimeout(timer)
|
||||||
|
})
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const health = await globalSDK.client.global
|
const health = await globalSDK.client.global
|
||||||
@@ -916,7 +976,7 @@ function createGlobalSync() {
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
retry(() =>
|
retry(() =>
|
||||||
globalSDK.client.config.get().then((x) => {
|
globalSDK.client.global.config.get().then((x) => {
|
||||||
setGlobalStore("config", x.data!)
|
setGlobalStore("config", x.data!)
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
@@ -999,13 +1059,13 @@ function createGlobalSync() {
|
|||||||
},
|
},
|
||||||
child,
|
child,
|
||||||
bootstrap,
|
bootstrap,
|
||||||
updateConfig: async (config: Config) => {
|
updateConfig: (config: Config) => {
|
||||||
setGlobalStore("reload", "pending")
|
setGlobalStore("reload", "pending")
|
||||||
const response = await globalSDK.client.config.update({ config })
|
return globalSDK.client.global.config.update({ config }).finally(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setGlobalStore("reload", "complete")
|
setGlobalStore("reload", "complete")
|
||||||
}, 1000)
|
}, 1000)
|
||||||
return response
|
})
|
||||||
},
|
},
|
||||||
project: {
|
project: {
|
||||||
loadSessions,
|
loadSessions,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { dict as ru } from "@/i18n/ru"
|
|||||||
import { dict as ar } from "@/i18n/ar"
|
import { dict as ar } from "@/i18n/ar"
|
||||||
import { dict as no } from "@/i18n/no"
|
import { dict as no } from "@/i18n/no"
|
||||||
import { dict as br } from "@/i18n/br"
|
import { dict as br } from "@/i18n/br"
|
||||||
|
import { dict as th } from "@/i18n/th"
|
||||||
import { dict as uiEn } from "@opencode-ai/ui/i18n/en"
|
import { dict as uiEn } from "@opencode-ai/ui/i18n/en"
|
||||||
import { dict as uiZh } from "@opencode-ai/ui/i18n/zh"
|
import { dict as uiZh } from "@opencode-ai/ui/i18n/zh"
|
||||||
import { dict as uiZht } from "@opencode-ai/ui/i18n/zht"
|
import { dict as uiZht } from "@opencode-ai/ui/i18n/zht"
|
||||||
@@ -31,13 +32,45 @@ import { dict as uiRu } from "@opencode-ai/ui/i18n/ru"
|
|||||||
import { dict as uiAr } from "@opencode-ai/ui/i18n/ar"
|
import { dict as uiAr } from "@opencode-ai/ui/i18n/ar"
|
||||||
import { dict as uiNo } from "@opencode-ai/ui/i18n/no"
|
import { dict as uiNo } from "@opencode-ai/ui/i18n/no"
|
||||||
import { dict as uiBr } from "@opencode-ai/ui/i18n/br"
|
import { dict as uiBr } from "@opencode-ai/ui/i18n/br"
|
||||||
|
import { dict as uiTh } from "@opencode-ai/ui/i18n/th"
|
||||||
|
|
||||||
export type Locale = "en" | "zh" | "zht" | "ko" | "de" | "es" | "fr" | "da" | "ja" | "pl" | "ru" | "ar" | "no" | "br"
|
export type Locale =
|
||||||
|
| "en"
|
||||||
|
| "zh"
|
||||||
|
| "zht"
|
||||||
|
| "ko"
|
||||||
|
| "de"
|
||||||
|
| "es"
|
||||||
|
| "fr"
|
||||||
|
| "da"
|
||||||
|
| "ja"
|
||||||
|
| "pl"
|
||||||
|
| "ru"
|
||||||
|
| "ar"
|
||||||
|
| "no"
|
||||||
|
| "br"
|
||||||
|
| "th"
|
||||||
|
|
||||||
type RawDictionary = typeof en & typeof uiEn
|
type RawDictionary = typeof en & typeof uiEn
|
||||||
type Dictionary = i18n.Flatten<RawDictionary>
|
type Dictionary = i18n.Flatten<RawDictionary>
|
||||||
|
|
||||||
const LOCALES: readonly Locale[] = ["en", "zh", "zht", "ko", "de", "es", "fr", "da", "ja", "pl", "ru", "ar", "no", "br"]
|
const LOCALES: readonly Locale[] = [
|
||||||
|
"en",
|
||||||
|
"zh",
|
||||||
|
"zht",
|
||||||
|
"ko",
|
||||||
|
"de",
|
||||||
|
"es",
|
||||||
|
"fr",
|
||||||
|
"da",
|
||||||
|
"ja",
|
||||||
|
"pl",
|
||||||
|
"ru",
|
||||||
|
"ar",
|
||||||
|
"no",
|
||||||
|
"br",
|
||||||
|
"th",
|
||||||
|
]
|
||||||
|
|
||||||
function detectLocale(): Locale {
|
function detectLocale(): Locale {
|
||||||
if (typeof navigator !== "object") return "en"
|
if (typeof navigator !== "object") return "en"
|
||||||
@@ -65,6 +98,7 @@ function detectLocale(): Locale {
|
|||||||
)
|
)
|
||||||
return "no"
|
return "no"
|
||||||
if (language.toLowerCase().startsWith("pt")) return "br"
|
if (language.toLowerCase().startsWith("pt")) return "br"
|
||||||
|
if (language.toLowerCase().startsWith("th")) return "th"
|
||||||
}
|
}
|
||||||
|
|
||||||
return "en"
|
return "en"
|
||||||
@@ -94,6 +128,7 @@ export const { use: useLanguage, provider: LanguageProvider } = createSimpleCont
|
|||||||
if (store.locale === "ar") return "ar"
|
if (store.locale === "ar") return "ar"
|
||||||
if (store.locale === "no") return "no"
|
if (store.locale === "no") return "no"
|
||||||
if (store.locale === "br") return "br"
|
if (store.locale === "br") return "br"
|
||||||
|
if (store.locale === "th") return "th"
|
||||||
return "en"
|
return "en"
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -118,6 +153,7 @@ export const { use: useLanguage, provider: LanguageProvider } = createSimpleCont
|
|||||||
if (locale() === "ar") return { ...base, ...i18n.flatten({ ...ar, ...uiAr }) }
|
if (locale() === "ar") return { ...base, ...i18n.flatten({ ...ar, ...uiAr }) }
|
||||||
if (locale() === "no") return { ...base, ...i18n.flatten({ ...no, ...uiNo }) }
|
if (locale() === "no") return { ...base, ...i18n.flatten({ ...no, ...uiNo }) }
|
||||||
if (locale() === "br") return { ...base, ...i18n.flatten({ ...br, ...uiBr }) }
|
if (locale() === "br") return { ...base, ...i18n.flatten({ ...br, ...uiBr }) }
|
||||||
|
if (locale() === "th") return { ...base, ...i18n.flatten({ ...th, ...uiTh }) }
|
||||||
return { ...base, ...i18n.flatten({ ...ko, ...uiKo }) }
|
return { ...base, ...i18n.flatten({ ...ko, ...uiKo }) }
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -138,6 +174,7 @@ export const { use: useLanguage, provider: LanguageProvider } = createSimpleCont
|
|||||||
ar: "language.ar",
|
ar: "language.ar",
|
||||||
no: "language.no",
|
no: "language.no",
|
||||||
br: "language.br",
|
br: "language.br",
|
||||||
|
th: "language.th",
|
||||||
}
|
}
|
||||||
|
|
||||||
const label = (value: Locale) => t(labelKey[value])
|
const label = (value: Locale) => t(labelKey[value])
|
||||||
|
|||||||
@@ -624,10 +624,11 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
const tabs = createMemo(() => store.sessionTabs[key()] ?? { all: [] })
|
const tabs = createMemo(() => store.sessionTabs[key()] ?? { all: [] })
|
||||||
return {
|
return {
|
||||||
tabs,
|
tabs,
|
||||||
active: createMemo(() => tabs().active),
|
active: createMemo(() => (tabs().active === "review" ? undefined : tabs().active)),
|
||||||
all: createMemo(() => tabs().all.filter((tab) => tab !== "review")),
|
all: createMemo(() => tabs().all.filter((tab) => tab !== "review")),
|
||||||
setActive(tab: string | undefined) {
|
setActive(tab: string | undefined) {
|
||||||
const session = key()
|
const session = key()
|
||||||
|
if (tab === "review") return
|
||||||
if (!store.sessionTabs[session]) {
|
if (!store.sessionTabs[session]) {
|
||||||
setStore("sessionTabs", session, { all: [], active: tab })
|
setStore("sessionTabs", session, { all: [], active: tab })
|
||||||
} else {
|
} else {
|
||||||
@@ -644,18 +645,10 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async open(tab: string) {
|
async open(tab: string) {
|
||||||
|
if (tab === "review") return
|
||||||
const session = key()
|
const session = key()
|
||||||
const current = store.sessionTabs[session] ?? { all: [] }
|
const current = store.sessionTabs[session] ?? { all: [] }
|
||||||
|
|
||||||
if (tab === "review") {
|
|
||||||
if (!store.sessionTabs[session]) {
|
|
||||||
setStore("sessionTabs", session, { all: current.all, active: tab })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
setStore("sessionTabs", session, "active", tab)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tab === "context") {
|
if (tab === "context") {
|
||||||
const all = [tab, ...current.all.filter((x) => x !== tab)]
|
const all = [tab, ...current.all.filter((x) => x !== tab)]
|
||||||
if (!store.sessionTabs[session]) {
|
if (!store.sessionTabs[session]) {
|
||||||
|
|||||||
@@ -331,6 +331,7 @@ export const dict = {
|
|||||||
"language.ar": "العربية",
|
"language.ar": "العربية",
|
||||||
"language.no": "Norsk",
|
"language.no": "Norsk",
|
||||||
"language.br": "Português (Brasil)",
|
"language.br": "Português (Brasil)",
|
||||||
|
"language.th": "ไทย",
|
||||||
|
|
||||||
"toast.language.title": "لغة",
|
"toast.language.title": "لغة",
|
||||||
"toast.language.description": "تم التبديل إلى {{language}}",
|
"toast.language.description": "تم التبديل إلى {{language}}",
|
||||||
|
|||||||
@@ -330,6 +330,7 @@ export const dict = {
|
|||||||
"language.ar": "العربية",
|
"language.ar": "العربية",
|
||||||
"language.no": "Norsk",
|
"language.no": "Norsk",
|
||||||
"language.br": "Português (Brasil)",
|
"language.br": "Português (Brasil)",
|
||||||
|
"language.th": "ไทย",
|
||||||
|
|
||||||
"toast.language.title": "Idioma",
|
"toast.language.title": "Idioma",
|
||||||
"toast.language.description": "Alterado para {{language}}",
|
"toast.language.description": "Alterado para {{language}}",
|
||||||
|
|||||||
@@ -332,6 +332,7 @@ export const dict = {
|
|||||||
"language.ar": "العربية",
|
"language.ar": "العربية",
|
||||||
"language.no": "Norsk",
|
"language.no": "Norsk",
|
||||||
"language.br": "Português (Brasil)",
|
"language.br": "Português (Brasil)",
|
||||||
|
"language.th": "ไทย",
|
||||||
|
|
||||||
"toast.language.title": "Sprog",
|
"toast.language.title": "Sprog",
|
||||||
"toast.language.description": "Skiftede til {{language}}",
|
"toast.language.description": "Skiftede til {{language}}",
|
||||||
|
|||||||
@@ -338,6 +338,7 @@ export const dict = {
|
|||||||
"language.ar": "العربية",
|
"language.ar": "العربية",
|
||||||
"language.no": "Norsk",
|
"language.no": "Norsk",
|
||||||
"language.br": "Português (Brasil)",
|
"language.br": "Português (Brasil)",
|
||||||
|
"language.th": "ไทย",
|
||||||
|
|
||||||
"toast.language.title": "Sprache",
|
"toast.language.title": "Sprache",
|
||||||
"toast.language.description": "Zu {{language}} gewechselt",
|
"toast.language.description": "Zu {{language}} gewechselt",
|
||||||
|
|||||||
@@ -337,6 +337,7 @@ export const dict = {
|
|||||||
"language.ar": "العربية",
|
"language.ar": "العربية",
|
||||||
"language.no": "Norsk",
|
"language.no": "Norsk",
|
||||||
"language.br": "Português (Brasil)",
|
"language.br": "Português (Brasil)",
|
||||||
|
"language.th": "ไทย",
|
||||||
|
|
||||||
"toast.language.title": "Language",
|
"toast.language.title": "Language",
|
||||||
"toast.language.description": "Switched to {{language}}",
|
"toast.language.description": "Switched to {{language}}",
|
||||||
|
|||||||
@@ -333,6 +333,7 @@ export const dict = {
|
|||||||
"language.ar": "العربية",
|
"language.ar": "العربية",
|
||||||
"language.no": "Norsk",
|
"language.no": "Norsk",
|
||||||
"language.br": "Português (Brasil)",
|
"language.br": "Português (Brasil)",
|
||||||
|
"language.th": "ไทย",
|
||||||
|
|
||||||
"toast.language.title": "Idioma",
|
"toast.language.title": "Idioma",
|
||||||
"toast.language.description": "Cambiado a {{language}}",
|
"toast.language.description": "Cambiado a {{language}}",
|
||||||
|
|||||||
@@ -333,6 +333,7 @@ export const dict = {
|
|||||||
"language.ar": "العربية",
|
"language.ar": "العربية",
|
||||||
"language.no": "Norsk",
|
"language.no": "Norsk",
|
||||||
"language.br": "Português (Brasil)",
|
"language.br": "Português (Brasil)",
|
||||||
|
"language.th": "ไทย",
|
||||||
|
|
||||||
"toast.language.title": "Langue",
|
"toast.language.title": "Langue",
|
||||||
"toast.language.description": "Passé à {{language}}",
|
"toast.language.description": "Passé à {{language}}",
|
||||||
|
|||||||
@@ -331,6 +331,7 @@ export const dict = {
|
|||||||
"language.ar": "العربية",
|
"language.ar": "العربية",
|
||||||
"language.no": "Norsk",
|
"language.no": "Norsk",
|
||||||
"language.br": "Português (Brasil)",
|
"language.br": "Português (Brasil)",
|
||||||
|
"language.th": "ไทย",
|
||||||
|
|
||||||
"toast.language.title": "言語",
|
"toast.language.title": "言語",
|
||||||
"toast.language.description": "{{language}}に切り替えました",
|
"toast.language.description": "{{language}}に切り替えました",
|
||||||
|
|||||||
@@ -334,6 +334,7 @@ export const dict = {
|
|||||||
"language.ar": "العربية",
|
"language.ar": "العربية",
|
||||||
"language.no": "Norsk",
|
"language.no": "Norsk",
|
||||||
"language.br": "Português (Brasil)",
|
"language.br": "Português (Brasil)",
|
||||||
|
"language.th": "ไทย",
|
||||||
|
|
||||||
"toast.language.title": "언어",
|
"toast.language.title": "언어",
|
||||||
"toast.language.description": "{{language}}(으)로 전환됨",
|
"toast.language.description": "{{language}}(으)로 전환됨",
|
||||||
|
|||||||
@@ -334,6 +334,7 @@ export const dict = {
|
|||||||
"language.ar": "العربية",
|
"language.ar": "العربية",
|
||||||
"language.no": "Norsk",
|
"language.no": "Norsk",
|
||||||
"language.br": "Português (Brasil)",
|
"language.br": "Português (Brasil)",
|
||||||
|
"language.th": "ไทย",
|
||||||
|
|
||||||
"toast.language.title": "Språk",
|
"toast.language.title": "Språk",
|
||||||
"toast.language.description": "Byttet til {{language}}",
|
"toast.language.description": "Byttet til {{language}}",
|
||||||
|
|||||||
@@ -332,6 +332,7 @@ export const dict = {
|
|||||||
"language.ar": "العربية",
|
"language.ar": "العربية",
|
||||||
"language.no": "Norsk",
|
"language.no": "Norsk",
|
||||||
"language.br": "Português (Brasil)",
|
"language.br": "Português (Brasil)",
|
||||||
|
"language.th": "ไทย",
|
||||||
|
|
||||||
"toast.language.title": "Język",
|
"toast.language.title": "Język",
|
||||||
"toast.language.description": "Przełączono na {{language}}",
|
"toast.language.description": "Przełączono na {{language}}",
|
||||||
|
|||||||
@@ -333,6 +333,7 @@ export const dict = {
|
|||||||
"language.ar": "العربية",
|
"language.ar": "العربية",
|
||||||
"language.no": "Norsk",
|
"language.no": "Norsk",
|
||||||
"language.br": "Português (Brasil)",
|
"language.br": "Português (Brasil)",
|
||||||
|
"language.th": "ไทย",
|
||||||
|
|
||||||
"toast.language.title": "Язык",
|
"toast.language.title": "Язык",
|
||||||
"toast.language.description": "Переключено на {{language}}",
|
"toast.language.description": "Переключено на {{language}}",
|
||||||
|
|||||||
@@ -0,0 +1,718 @@
|
|||||||
|
export const dict = {
|
||||||
|
"command.category.suggested": "แนะนำ",
|
||||||
|
"command.category.view": "มุมมอง",
|
||||||
|
"command.category.project": "โปรเจกต์",
|
||||||
|
"command.category.provider": "ผู้ให้บริการ",
|
||||||
|
"command.category.server": "เซิร์ฟเวอร์",
|
||||||
|
"command.category.session": "เซสชัน",
|
||||||
|
"command.category.theme": "ธีม",
|
||||||
|
"command.category.language": "ภาษา",
|
||||||
|
"command.category.file": "ไฟล์",
|
||||||
|
"command.category.context": "บริบท",
|
||||||
|
"command.category.terminal": "เทอร์มินัล",
|
||||||
|
"command.category.model": "โมเดล",
|
||||||
|
"command.category.mcp": "MCP",
|
||||||
|
"command.category.agent": "เอเจนต์",
|
||||||
|
"command.category.permissions": "สิทธิ์",
|
||||||
|
"command.category.workspace": "พื้นที่ทำงาน",
|
||||||
|
"command.category.settings": "การตั้งค่า",
|
||||||
|
|
||||||
|
"theme.scheme.system": "ระบบ",
|
||||||
|
"theme.scheme.light": "สว่าง",
|
||||||
|
"theme.scheme.dark": "มืด",
|
||||||
|
|
||||||
|
"command.sidebar.toggle": "สลับแถบข้าง",
|
||||||
|
"command.project.open": "เปิดโปรเจกต์",
|
||||||
|
"command.provider.connect": "เชื่อมต่อผู้ให้บริการ",
|
||||||
|
"command.server.switch": "สลับเซิร์ฟเวอร์",
|
||||||
|
"command.settings.open": "เปิดการตั้งค่า",
|
||||||
|
"command.session.previous": "เซสชันก่อนหน้า",
|
||||||
|
"command.session.next": "เซสชันถัดไป",
|
||||||
|
"command.session.archive": "จัดเก็บเซสชัน",
|
||||||
|
|
||||||
|
"command.palette": "คำสั่งค้นหา",
|
||||||
|
|
||||||
|
"command.theme.cycle": "เปลี่ยนธีม",
|
||||||
|
"command.theme.set": "ใช้ธีม: {{theme}}",
|
||||||
|
"command.theme.scheme.cycle": "เปลี่ยนโทนสี",
|
||||||
|
"command.theme.scheme.set": "ใช้โทนสี: {{scheme}}",
|
||||||
|
|
||||||
|
"command.language.cycle": "เปลี่ยนภาษา",
|
||||||
|
"command.language.set": "ใช้ภาษา: {{language}}",
|
||||||
|
|
||||||
|
"command.session.new": "เซสชันใหม่",
|
||||||
|
"command.file.open": "เปิดไฟล์",
|
||||||
|
"command.file.open.description": "ค้นหาไฟล์และคำสั่ง",
|
||||||
|
"command.context.addSelection": "เพิ่มส่วนที่เลือกไปยังบริบท",
|
||||||
|
"command.context.addSelection.description": "เพิ่มบรรทัดที่เลือกจากไฟล์ปัจจุบัน",
|
||||||
|
"command.terminal.toggle": "สลับเทอร์มินัล",
|
||||||
|
"command.fileTree.toggle": "สลับต้นไม้ไฟล์",
|
||||||
|
"command.review.toggle": "สลับการตรวจสอบ",
|
||||||
|
"command.terminal.new": "เทอร์มินัลใหม่",
|
||||||
|
"command.terminal.new.description": "สร้างแท็บเทอร์มินัลใหม่",
|
||||||
|
"command.steps.toggle": "สลับขั้นตอน",
|
||||||
|
"command.steps.toggle.description": "แสดงหรือซ่อนขั้นตอนสำหรับข้อความปัจจุบัน",
|
||||||
|
"command.message.previous": "ข้อความก่อนหน้า",
|
||||||
|
"command.message.previous.description": "ไปที่ข้อความผู้ใช้ก่อนหน้า",
|
||||||
|
"command.message.next": "ข้อความถัดไป",
|
||||||
|
"command.message.next.description": "ไปที่ข้อความผู้ใช้ถัดไป",
|
||||||
|
"command.model.choose": "เลือกโมเดล",
|
||||||
|
"command.model.choose.description": "เลือกโมเดลอื่น",
|
||||||
|
"command.mcp.toggle": "สลับ MCPs",
|
||||||
|
"command.mcp.toggle.description": "สลับ MCPs",
|
||||||
|
"command.agent.cycle": "เปลี่ยนเอเจนต์",
|
||||||
|
"command.agent.cycle.description": "สลับไปยังเอเจนต์ถัดไป",
|
||||||
|
"command.agent.cycle.reverse": "เปลี่ยนเอเจนต์ย้อนกลับ",
|
||||||
|
"command.agent.cycle.reverse.description": "สลับไปยังเอเจนต์ก่อนหน้า",
|
||||||
|
"command.model.variant.cycle": "เปลี่ยนความพยายามในการคิด",
|
||||||
|
"command.model.variant.cycle.description": "สลับไปยังระดับความพยายามถัดไป",
|
||||||
|
"command.permissions.autoaccept.enable": "ยอมรับการแก้ไขโดยอัตโนมัติ",
|
||||||
|
"command.permissions.autoaccept.disable": "หยุดยอมรับการแก้ไขโดยอัตโนมัติ",
|
||||||
|
"command.session.undo": "ยกเลิก",
|
||||||
|
"command.session.undo.description": "ยกเลิกข้อความล่าสุด",
|
||||||
|
"command.session.redo": "ทำซ้ำ",
|
||||||
|
"command.session.redo.description": "ทำซ้ำข้อความที่ถูกยกเลิกล่าสุด",
|
||||||
|
"command.session.compact": "บีบอัดเซสชัน",
|
||||||
|
"command.session.compact.description": "สรุปเซสชันเพื่อลดขนาดบริบท",
|
||||||
|
"command.session.fork": "แตกแขนงจากข้อความ",
|
||||||
|
"command.session.fork.description": "สร้างเซสชันใหม่จากข้อความก่อนหน้า",
|
||||||
|
"command.session.share": "แชร์เซสชัน",
|
||||||
|
"command.session.share.description": "แชร์เซสชันนี้และคัดลอก URL ไปยังคลิปบอร์ด",
|
||||||
|
"command.session.unshare": "ยกเลิกการแชร์เซสชัน",
|
||||||
|
"command.session.unshare.description": "หยุดการแชร์เซสชันนี้",
|
||||||
|
|
||||||
|
"palette.search.placeholder": "ค้นหาไฟล์และคำสั่ง",
|
||||||
|
"palette.empty": "ไม่พบผลลัพธ์",
|
||||||
|
"palette.group.commands": "คำสั่ง",
|
||||||
|
"palette.group.files": "ไฟล์",
|
||||||
|
|
||||||
|
"dialog.provider.search.placeholder": "ค้นหาผู้ให้บริการ",
|
||||||
|
"dialog.provider.empty": "ไม่พบผู้ให้บริการ",
|
||||||
|
"dialog.provider.group.popular": "ยอดนิยม",
|
||||||
|
"dialog.provider.group.other": "อื่น ๆ",
|
||||||
|
"dialog.provider.tag.recommended": "แนะนำ",
|
||||||
|
"dialog.provider.opencode.note": "โมเดลที่คัดสรร รวมถึง Claude, GPT, Gemini และอื่น ๆ",
|
||||||
|
"dialog.provider.anthropic.note": "เข้าถึงโมเดล Claude โดยตรง รวมถึง Pro และ Max",
|
||||||
|
"dialog.provider.copilot.note": "โมเดล Claude สำหรับการช่วยเหลือในการเขียนโค้ด",
|
||||||
|
"dialog.provider.openai.note": "โมเดล GPT สำหรับงาน AI ทั่วไปที่รวดเร็วและมีความสามารถ",
|
||||||
|
"dialog.provider.google.note": "โมเดล Gemini สำหรับการตอบสนองที่รวดเร็วและมีโครงสร้าง",
|
||||||
|
"dialog.provider.openrouter.note": "เข้าถึงโมเดลที่รองรับทั้งหมดจากผู้ให้บริการเดียว",
|
||||||
|
"dialog.provider.vercel.note": "การเข้าถึงโมเดล AI แบบรวมด้วยการกำหนดเส้นทางอัจฉริยะ",
|
||||||
|
|
||||||
|
"dialog.model.select.title": "เลือกโมเดล",
|
||||||
|
"dialog.model.search.placeholder": "ค้นหาโมเดล",
|
||||||
|
"dialog.model.empty": "ไม่พบผลลัพธ์โมเดล",
|
||||||
|
"dialog.model.manage": "จัดการโมเดล",
|
||||||
|
"dialog.model.manage.description": "ปรับแต่งโมเดลที่จะปรากฏในตัวเลือกโมเดล",
|
||||||
|
|
||||||
|
"dialog.model.unpaid.freeModels.title": "โมเดลฟรีที่จัดหาให้โดย OpenCode",
|
||||||
|
"dialog.model.unpaid.addMore.title": "เพิ่มโมเดลเพิ่มเติมจากผู้ให้บริการยอดนิยม",
|
||||||
|
|
||||||
|
"dialog.provider.viewAll": "แสดงผู้ให้บริการเพิ่มเติม",
|
||||||
|
|
||||||
|
"provider.connect.title": "เชื่อมต่อ {{provider}}",
|
||||||
|
"provider.connect.title.anthropicProMax": "เข้าสู่ระบบด้วย Claude Pro/Max",
|
||||||
|
"provider.connect.selectMethod": "เลือกวิธีการเข้าสู่ระบบสำหรับ {{provider}}",
|
||||||
|
"provider.connect.method.apiKey": "คีย์ API",
|
||||||
|
"provider.connect.status.inProgress": "กำลังอนุญาต...",
|
||||||
|
"provider.connect.status.waiting": "รอการอนุญาต...",
|
||||||
|
"provider.connect.status.failed": "การอนุญาตล้มเหลว: {{error}}",
|
||||||
|
"provider.connect.apiKey.description":
|
||||||
|
"ป้อนคีย์ API ของ {{provider}} เพื่อเชื่อมต่อบัญชีและใช้โมเดล {{provider}} ใน OpenCode",
|
||||||
|
"provider.connect.apiKey.label": "คีย์ API ของ {{provider}}",
|
||||||
|
"provider.connect.apiKey.placeholder": "คีย์ API",
|
||||||
|
"provider.connect.apiKey.required": "ต้องใช้คีย์ API",
|
||||||
|
"provider.connect.opencodeZen.line1":
|
||||||
|
"OpenCode Zen ให้คุณเข้าถึงชุดโมเดลที่เชื่อถือได้และปรับแต่งแล้วสำหรับเอเจนต์การเขียนโค้ด",
|
||||||
|
"provider.connect.opencodeZen.line2":
|
||||||
|
"ด้วยคีย์ API เดียวคุณจะได้รับการเข้าถึงโมเดล เช่น Claude, GPT, Gemini, GLM และอื่น ๆ",
|
||||||
|
"provider.connect.opencodeZen.visit.prefix": "เยี่ยมชม ",
|
||||||
|
"provider.connect.opencodeZen.visit.link": "opencode.ai/zen",
|
||||||
|
"provider.connect.opencodeZen.visit.suffix": " เพื่อรวบรวมคีย์ API ของคุณ",
|
||||||
|
"provider.connect.oauth.code.visit.prefix": "เยี่ยมชม ",
|
||||||
|
"provider.connect.oauth.code.visit.link": "ลิงก์นี้",
|
||||||
|
"provider.connect.oauth.code.visit.suffix":
|
||||||
|
" เพื่อรวบรวมรหัสการอนุญาตของคุณเพื่อเชื่อมต่อบัญชีและใช้โมเดล {{provider}} ใน OpenCode",
|
||||||
|
"provider.connect.oauth.code.label": "รหัสการอนุญาต {{method}}",
|
||||||
|
"provider.connect.oauth.code.placeholder": "รหัสการอนุญาต",
|
||||||
|
"provider.connect.oauth.code.required": "ต้องใช้รหัสการอนุญาต",
|
||||||
|
"provider.connect.oauth.code.invalid": "รหัสการอนุญาตไม่ถูกต้อง",
|
||||||
|
"provider.connect.oauth.auto.visit.prefix": "เยี่ยมชม ",
|
||||||
|
"provider.connect.oauth.auto.visit.link": "ลิงก์นี้",
|
||||||
|
"provider.connect.oauth.auto.visit.suffix":
|
||||||
|
" และป้อนรหัสด้านล่างเพื่อเชื่อมต่อบัญชีและใช้โมเดล {{provider}} ใน OpenCode",
|
||||||
|
"provider.connect.oauth.auto.confirmationCode": "รหัสยืนยัน",
|
||||||
|
"provider.connect.toast.connected.title": "{{provider}} ที่เชื่อมต่อแล้ว",
|
||||||
|
"provider.connect.toast.connected.description": "โมเดล {{provider}} พร้อมใช้งานแล้ว",
|
||||||
|
|
||||||
|
"provider.disconnect.toast.disconnected.title": "{{provider}} ที่ยกเลิกการเชื่อมต่อแล้ว",
|
||||||
|
"provider.disconnect.toast.disconnected.description": "โมเดล {{provider}} ไม่พร้อมใช้งานอีกต่อไป",
|
||||||
|
|
||||||
|
"model.tag.free": "ฟรี",
|
||||||
|
"model.tag.latest": "ล่าสุด",
|
||||||
|
"model.provider.anthropic": "Anthropic",
|
||||||
|
"model.provider.openai": "OpenAI",
|
||||||
|
"model.provider.google": "Google",
|
||||||
|
"model.provider.xai": "xAI",
|
||||||
|
"model.provider.meta": "Meta",
|
||||||
|
"model.input.text": "ข้อความ",
|
||||||
|
"model.input.image": "รูปภาพ",
|
||||||
|
"model.input.audio": "เสียง",
|
||||||
|
"model.input.video": "วิดีโอ",
|
||||||
|
"model.input.pdf": "pdf",
|
||||||
|
"model.tooltip.allows": "อนุญาต: {{inputs}}",
|
||||||
|
"model.tooltip.reasoning.allowed": "อนุญาตการใช้เหตุผล",
|
||||||
|
"model.tooltip.reasoning.none": "ไม่มีการใช้เหตุผล",
|
||||||
|
"model.tooltip.context": "ขีดจำกัดบริบท {{limit}}",
|
||||||
|
|
||||||
|
"common.search.placeholder": "ค้นหา",
|
||||||
|
"common.goBack": "ย้อนกลับ",
|
||||||
|
"common.loading": "กำลังโหลด",
|
||||||
|
"common.loading.ellipsis": "...",
|
||||||
|
"common.cancel": "ยกเลิก",
|
||||||
|
"common.connect": "เชื่อมต่อ",
|
||||||
|
"common.disconnect": "ยกเลิกการเชื่อมต่อ",
|
||||||
|
"common.submit": "ส่ง",
|
||||||
|
"common.save": "บันทึก",
|
||||||
|
"common.saving": "กำลังบันทึก...",
|
||||||
|
"common.default": "ค่าเริ่มต้น",
|
||||||
|
"common.attachment": "ไฟล์แนบ",
|
||||||
|
|
||||||
|
"prompt.placeholder.shell": "ป้อนคำสั่งเชลล์...",
|
||||||
|
"prompt.placeholder.normal": 'ถามอะไรก็ได้... "{{example}}"',
|
||||||
|
"prompt.placeholder.summarizeComments": "สรุปความคิดเห็น…",
|
||||||
|
"prompt.placeholder.summarizeComment": "สรุปความคิดเห็น…",
|
||||||
|
"prompt.mode.shell": "เชลล์",
|
||||||
|
"prompt.mode.shell.exit": "กด esc เพื่อออก",
|
||||||
|
|
||||||
|
"prompt.example.1": "แก้ไข TODO ในโค้ดเบส",
|
||||||
|
"prompt.example.2": "เทคโนโลยีของโปรเจกต์นี้คืออะไร?",
|
||||||
|
"prompt.example.3": "แก้ไขการทดสอบที่เสีย",
|
||||||
|
"prompt.example.4": "อธิบายวิธีการทำงานของการตรวจสอบสิทธิ์",
|
||||||
|
"prompt.example.5": "ค้นหาและแก้ไขช่องโหว่ความปลอดภัย",
|
||||||
|
"prompt.example.6": "เพิ่มการทดสอบหน่วยสำหรับบริการผู้ใช้",
|
||||||
|
"prompt.example.7": "ปรับโครงสร้างฟังก์ชันนี้ให้อ่านง่ายขึ้น",
|
||||||
|
"prompt.example.8": "ข้อผิดพลาดนี้หมายความว่าอะไร?",
|
||||||
|
"prompt.example.9": "ช่วยฉันดีบักปัญหานี้",
|
||||||
|
"prompt.example.10": "สร้างเอกสาร API",
|
||||||
|
"prompt.example.11": "ปรับปรุงการสืบค้นฐานข้อมูล",
|
||||||
|
"prompt.example.12": "เพิ่มการตรวจสอบข้อมูลนำเข้า",
|
||||||
|
"prompt.example.13": "สร้างคอมโพเนนต์ใหม่สำหรับ...",
|
||||||
|
"prompt.example.14": "ฉันจะทำให้โปรเจกต์นี้ทำงานได้อย่างไร?",
|
||||||
|
"prompt.example.15": "ตรวจสอบโค้ดของฉันเพื่อแนวทางปฏิบัติที่ดีที่สุด",
|
||||||
|
"prompt.example.16": "เพิ่มการจัดการข้อผิดพลาดในฟังก์ชันนี้",
|
||||||
|
"prompt.example.17": "อธิบายรูปแบบ regex นี้",
|
||||||
|
"prompt.example.18": "แปลงสิ่งนี้เป็น TypeScript",
|
||||||
|
"prompt.example.19": "เพิ่มการบันทึกทั่วทั้งโค้ดเบส",
|
||||||
|
"prompt.example.20": "มีการพึ่งพาอะไรที่ล้าสมัยอยู่?",
|
||||||
|
"prompt.example.21": "ช่วยฉันเขียนสคริปต์การย้ายข้อมูล",
|
||||||
|
"prompt.example.22": "ใช้งานแคชสำหรับจุดสิ้นสุดนี้",
|
||||||
|
"prompt.example.23": "เพิ่มการแบ่งหน้าในรายการนี้",
|
||||||
|
"prompt.example.24": "สร้างคำสั่ง CLI สำหรับ...",
|
||||||
|
"prompt.example.25": "ตัวแปรสภาพแวดล้อมทำงานอย่างไรที่นี่?",
|
||||||
|
|
||||||
|
"prompt.popover.emptyResults": "ไม่พบผลลัพธ์ที่ตรงกัน",
|
||||||
|
"prompt.popover.emptyCommands": "ไม่พบคำสั่งที่ตรงกัน",
|
||||||
|
"prompt.dropzone.label": "วางรูปภาพหรือ PDF ที่นี่",
|
||||||
|
"prompt.slash.badge.custom": "กำหนดเอง",
|
||||||
|
"prompt.context.active": "ใช้งานอยู่",
|
||||||
|
"prompt.context.includeActiveFile": "รวมไฟล์ที่ใช้งานอยู่",
|
||||||
|
"prompt.context.removeActiveFile": "เอาไฟล์ที่ใช้งานอยู่ออกจากบริบท",
|
||||||
|
"prompt.context.removeFile": "เอาไฟล์ออกจากบริบท",
|
||||||
|
"prompt.action.attachFile": "แนบไฟล์",
|
||||||
|
"prompt.attachment.remove": "เอาไฟล์แนบออก",
|
||||||
|
"prompt.action.send": "ส่ง",
|
||||||
|
"prompt.action.stop": "หยุด",
|
||||||
|
|
||||||
|
"prompt.toast.pasteUnsupported.title": "การวางไม่รองรับ",
|
||||||
|
"prompt.toast.pasteUnsupported.description": "สามารถวางรูปภาพหรือ PDF เท่านั้น",
|
||||||
|
"prompt.toast.modelAgentRequired.title": "เลือกเอเจนต์และโมเดล",
|
||||||
|
"prompt.toast.modelAgentRequired.description": "เลือกเอเจนต์และโมเดลก่อนส่งพร้อมท์",
|
||||||
|
"prompt.toast.worktreeCreateFailed.title": "ไม่สามารถสร้าง worktree",
|
||||||
|
"prompt.toast.sessionCreateFailed.title": "ไม่สามารถสร้างเซสชัน",
|
||||||
|
"prompt.toast.shellSendFailed.title": "ไม่สามารถส่งคำสั่งเชลล์",
|
||||||
|
"prompt.toast.commandSendFailed.title": "ไม่สามารถส่งคำสั่ง",
|
||||||
|
"prompt.toast.promptSendFailed.title": "ไม่สามารถส่งพร้อมท์",
|
||||||
|
|
||||||
|
"dialog.mcp.title": "MCPs",
|
||||||
|
"dialog.mcp.description": "{{enabled}} จาก {{total}} ที่เปิดใช้งาน",
|
||||||
|
"dialog.mcp.empty": "ไม่มี MCP ที่กำหนดค่า",
|
||||||
|
|
||||||
|
"dialog.lsp.empty": "LSPs ตรวจจับอัตโนมัติจากประเภทไฟล์",
|
||||||
|
"dialog.plugins.empty": "ปลั๊กอินที่กำหนดค่าใน opencode.json",
|
||||||
|
|
||||||
|
"mcp.status.connected": "เชื่อมต่อแล้ว",
|
||||||
|
"mcp.status.failed": "ล้มเหลว",
|
||||||
|
"mcp.status.needs_auth": "ต้องการการตรวจสอบสิทธิ์",
|
||||||
|
"mcp.status.disabled": "ปิดใช้งาน",
|
||||||
|
|
||||||
|
"dialog.fork.empty": "ไม่มีข้อความให้แตกแขนง",
|
||||||
|
|
||||||
|
"dialog.directory.search.placeholder": "ค้นหาโฟลเดอร์",
|
||||||
|
"dialog.directory.empty": "ไม่พบโฟลเดอร์",
|
||||||
|
|
||||||
|
"dialog.server.title": "เซิร์ฟเวอร์",
|
||||||
|
"dialog.server.description": "สลับเซิร์ฟเวอร์ OpenCode ที่แอปนี้เชื่อมต่อด้วย",
|
||||||
|
"dialog.server.search.placeholder": "ค้นหาเซิร์ฟเวอร์",
|
||||||
|
"dialog.server.empty": "ยังไม่มีเซิร์ฟเวอร์",
|
||||||
|
"dialog.server.add.title": "เพิ่มเซิร์ฟเวอร์",
|
||||||
|
"dialog.server.add.url": "URL เซิร์ฟเวอร์",
|
||||||
|
"dialog.server.add.placeholder": "http://localhost:4096",
|
||||||
|
"dialog.server.add.error": "ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์",
|
||||||
|
"dialog.server.add.checking": "กำลังตรวจสอบ...",
|
||||||
|
"dialog.server.add.button": "เพิ่มเซิร์ฟเวอร์",
|
||||||
|
"dialog.server.default.title": "เซิร์ฟเวอร์เริ่มต้น",
|
||||||
|
"dialog.server.default.description":
|
||||||
|
"เชื่อมต่อกับเซิร์ฟเวอร์นี้เมื่อเปิดแอปแทนการเริ่มเซิร์ฟเวอร์ในเครื่อง ต้องรีสตาร์ท",
|
||||||
|
"dialog.server.default.none": "ไม่ได้เลือกเซิร์ฟเวอร์",
|
||||||
|
"dialog.server.default.set": "ตั้งเซิร์ฟเวอร์ปัจจุบันเป็นค่าเริ่มต้น",
|
||||||
|
"dialog.server.default.clear": "ล้าง",
|
||||||
|
"dialog.server.action.remove": "เอาเซิร์ฟเวอร์ออก",
|
||||||
|
|
||||||
|
"dialog.server.menu.edit": "แก้ไข",
|
||||||
|
"dialog.server.menu.default": "ตั้งเป็นค่าเริ่มต้น",
|
||||||
|
"dialog.server.menu.defaultRemove": "เอาค่าเริ่มต้นออก",
|
||||||
|
"dialog.server.menu.delete": "ลบ",
|
||||||
|
"dialog.server.current": "เซิร์ฟเวอร์ปัจจุบัน",
|
||||||
|
"dialog.server.status.default": "ค่าเริ่มต้น",
|
||||||
|
|
||||||
|
"dialog.project.edit.title": "แก้ไขโปรเจกต์",
|
||||||
|
"dialog.project.edit.name": "ชื่อ",
|
||||||
|
"dialog.project.edit.icon": "ไอคอน",
|
||||||
|
"dialog.project.edit.icon.alt": "ไอคอนโปรเจกต์",
|
||||||
|
"dialog.project.edit.icon.hint": "คลิกหรือลากรูปภาพ",
|
||||||
|
"dialog.project.edit.icon.recommended": "แนะนำ: 128x128px",
|
||||||
|
"dialog.project.edit.color": "สี",
|
||||||
|
"dialog.project.edit.color.select": "เลือกสี {{color}}",
|
||||||
|
"dialog.project.edit.worktree.startup": "สคริปต์เริ่มต้นพื้นที่ทำงาน",
|
||||||
|
"dialog.project.edit.worktree.startup.description": "ทำงานหลังจากสร้างพื้นที่ทำงานใหม่ (worktree)",
|
||||||
|
"dialog.project.edit.worktree.startup.placeholder": "เช่น bun install",
|
||||||
|
|
||||||
|
"context.breakdown.title": "การแบ่งบริบท",
|
||||||
|
"context.breakdown.note": 'การแบ่งโดยประมาณของโทเค็นนำเข้า "อื่น ๆ" รวมถึงคำนิยามเครื่องมือและโอเวอร์เฮด',
|
||||||
|
"context.breakdown.system": "ระบบ",
|
||||||
|
"context.breakdown.user": "ผู้ใช้",
|
||||||
|
"context.breakdown.assistant": "ผู้ช่วย",
|
||||||
|
"context.breakdown.tool": "การเรียกเครื่องมือ",
|
||||||
|
"context.breakdown.other": "อื่น ๆ",
|
||||||
|
|
||||||
|
"context.systemPrompt.title": "พร้อมท์ระบบ",
|
||||||
|
"context.rawMessages.title": "ข้อความดิบ",
|
||||||
|
|
||||||
|
"context.stats.session": "เซสชัน",
|
||||||
|
"context.stats.messages": "ข้อความ",
|
||||||
|
"context.stats.provider": "ผู้ให้บริการ",
|
||||||
|
"context.stats.model": "โมเดล",
|
||||||
|
"context.stats.limit": "ขีดจำกัดบริบท",
|
||||||
|
"context.stats.totalTokens": "โทเค็นทั้งหมด",
|
||||||
|
"context.stats.usage": "การใช้งาน",
|
||||||
|
"context.stats.inputTokens": "โทเค็นนำเข้า",
|
||||||
|
"context.stats.outputTokens": "โทเค็นส่งออก",
|
||||||
|
"context.stats.reasoningTokens": "โทเค็นการใช้เหตุผล",
|
||||||
|
"context.stats.cacheTokens": "โทเค็นแคช (อ่าน/เขียน)",
|
||||||
|
"context.stats.userMessages": "ข้อความผู้ใช้",
|
||||||
|
"context.stats.assistantMessages": "ข้อความผู้ช่วย",
|
||||||
|
"context.stats.totalCost": "ต้นทุนทั้งหมด",
|
||||||
|
"context.stats.sessionCreated": "สร้างเซสชันเมื่อ",
|
||||||
|
"context.stats.lastActivity": "กิจกรรมล่าสุด",
|
||||||
|
|
||||||
|
"context.usage.tokens": "โทเค็น",
|
||||||
|
"context.usage.usage": "การใช้งาน",
|
||||||
|
"context.usage.cost": "ต้นทุน",
|
||||||
|
"context.usage.clickToView": "คลิกเพื่อดูบริบท",
|
||||||
|
"context.usage.view": "ดูการใช้บริบท",
|
||||||
|
|
||||||
|
"language.en": "อังกฤษ",
|
||||||
|
"language.zh": "จีนตัวย่อ",
|
||||||
|
"language.zht": "จีนตัวเต็ม",
|
||||||
|
"language.ko": "เกาหลี",
|
||||||
|
"language.de": "เยอรมัน",
|
||||||
|
"language.es": "สเปน",
|
||||||
|
"language.fr": "ฝรั่งเศส",
|
||||||
|
"language.da": "เดนมาร์ก",
|
||||||
|
"language.ja": "ญี่ปุ่น",
|
||||||
|
"language.pl": "โปแลนด์",
|
||||||
|
"language.ru": "รัสเซีย",
|
||||||
|
"language.ar": "อาหรับ",
|
||||||
|
"language.no": "นอร์เวย์",
|
||||||
|
"language.br": "โปรตุเกส (บราซิล)",
|
||||||
|
"language.th": "ไทย",
|
||||||
|
|
||||||
|
"toast.language.title": "ภาษา",
|
||||||
|
"toast.language.description": "สลับไปที่ {{language}}",
|
||||||
|
|
||||||
|
"toast.theme.title": "สลับธีมแล้ว",
|
||||||
|
"toast.scheme.title": "โทนสี",
|
||||||
|
|
||||||
|
"toast.permissions.autoaccept.on.title": "กำลังยอมรับการแก้ไขโดยอัตโนมัติ",
|
||||||
|
"toast.permissions.autoaccept.on.description": "สิทธิ์การแก้ไขและเขียนจะได้รับการอนุมัติโดยอัตโนมัติ",
|
||||||
|
"toast.permissions.autoaccept.off.title": "หยุดยอมรับการแก้ไขโดยอัตโนมัติ",
|
||||||
|
"toast.permissions.autoaccept.off.description": "สิทธิ์การแก้ไขและเขียนจะต้องได้รับการอนุมัติ",
|
||||||
|
|
||||||
|
"toast.model.none.title": "ไม่ได้เลือกโมเดล",
|
||||||
|
"toast.model.none.description": "เชื่อมต่อผู้ให้บริการเพื่อสรุปเซสชันนี้",
|
||||||
|
|
||||||
|
"toast.file.loadFailed.title": "ไม่สามารถโหลดไฟล์",
|
||||||
|
"toast.file.listFailed.title": "ไม่สามารถแสดงรายการไฟล์",
|
||||||
|
|
||||||
|
"toast.context.noLineSelection.title": "ไม่มีการเลือกบรรทัด",
|
||||||
|
"toast.context.noLineSelection.description": "เลือกช่วงบรรทัดในแท็บไฟล์ก่อน",
|
||||||
|
|
||||||
|
"toast.session.share.copyFailed.title": "ไม่สามารถคัดลอก URL ไปยังคลิปบอร์ด",
|
||||||
|
"toast.session.share.success.title": "แชร์เซสชันแล้ว",
|
||||||
|
"toast.session.share.success.description": "คัดลอก URL แชร์ไปยังคลิปบอร์ดแล้ว!",
|
||||||
|
"toast.session.share.failed.title": "ไม่สามารถแชร์เซสชัน",
|
||||||
|
"toast.session.share.failed.description": "เกิดข้อผิดพลาดระหว่างการแชร์เซสชัน",
|
||||||
|
|
||||||
|
"toast.session.unshare.success.title": "ยกเลิกการแชร์เซสชันแล้ว",
|
||||||
|
"toast.session.unshare.success.description": "ยกเลิกการแชร์เซสชันสำเร็จ!",
|
||||||
|
"toast.session.unshare.failed.title": "ไม่สามารถยกเลิกการแชร์เซสชัน",
|
||||||
|
"toast.session.unshare.failed.description": "เกิดข้อผิดพลาดระหว่างการยกเลิกการแชร์เซสชัน",
|
||||||
|
|
||||||
|
"toast.session.listFailed.title": "ไม่สามารถโหลดเซสชันสำหรับ {{project}}",
|
||||||
|
|
||||||
|
"toast.update.title": "มีการอัปเดต",
|
||||||
|
"toast.update.description": "เวอร์ชันใหม่ของ OpenCode ({{version}}) พร้อมใช้งานสำหรับติดตั้ง",
|
||||||
|
"toast.update.action.installRestart": "ติดตั้งและรีสตาร์ท",
|
||||||
|
"toast.update.action.notYet": "ยังไม่",
|
||||||
|
|
||||||
|
"error.page.title": "เกิดข้อผิดพลาด",
|
||||||
|
"error.page.description": "เกิดข้อผิดพลาดระหว่างการโหลดแอปพลิเคชัน",
|
||||||
|
"error.page.details.label": "รายละเอียดข้อผิดพลาด",
|
||||||
|
"error.page.action.restart": "รีสตาร์ท",
|
||||||
|
"error.page.action.checking": "กำลังตรวจสอบ...",
|
||||||
|
"error.page.action.checkUpdates": "ตรวจสอบการอัปเดต",
|
||||||
|
"error.page.action.updateTo": "อัปเดตเป็น {{version}}",
|
||||||
|
"error.page.report.prefix": "โปรดรายงานข้อผิดพลาดนี้ให้ทีม OpenCode",
|
||||||
|
"error.page.report.discord": "บน Discord",
|
||||||
|
"error.page.version": "เวอร์ชัน: {{version}}",
|
||||||
|
|
||||||
|
"error.dev.rootNotFound": "ไม่พบองค์ประกอบรูท คุณลืมเพิ่มใน index.html หรือบางทีแอตทริบิวต์ id อาจสะกดผิด?",
|
||||||
|
|
||||||
|
"error.globalSync.connectFailed": "ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ มีเซิร์ฟเวอร์ทำงานอยู่ที่ `{{url}}` หรือไม่?",
|
||||||
|
|
||||||
|
"error.chain.unknown": "ข้อผิดพลาดที่ไม่รู้จัก",
|
||||||
|
"error.chain.causedBy": "สาเหตุ:",
|
||||||
|
"error.chain.apiError": "ข้อผิดพลาด API",
|
||||||
|
"error.chain.status": "สถานะ: {{status}}",
|
||||||
|
"error.chain.retryable": "สามารถลองใหม่: {{retryable}}",
|
||||||
|
"error.chain.responseBody": "เนื้อหาการตอบสนอง:\n{{body}}",
|
||||||
|
"error.chain.didYouMean": "คุณหมายถึง: {{suggestions}}",
|
||||||
|
"error.chain.modelNotFound": "ไม่พบโมเดล: {{provider}}/{{model}}",
|
||||||
|
"error.chain.checkConfig": "ตรวจสอบการกำหนดค่าของคุณ (opencode.json) ชื่อผู้ให้บริการ/โมเดล",
|
||||||
|
"error.chain.mcpFailed": 'เซิร์ฟเวอร์ MCP "{{name}}" ล้มเหลว โปรดทราบว่า OpenCode ยังไม่รองรับการตรวจสอบสิทธิ์ MCP',
|
||||||
|
"error.chain.providerAuthFailed": "การตรวจสอบสิทธิ์ผู้ให้บริการล้มเหลว ({{provider}}): {{message}}",
|
||||||
|
"error.chain.providerInitFailed": 'ไม่สามารถเริ่มต้นผู้ให้บริการ "{{provider}}" ตรวจสอบข้อมูลรับรองและการกำหนดค่า',
|
||||||
|
"error.chain.configJsonInvalid": "ไฟล์กำหนดค่าที่ {{path}} ไม่ใช่ JSON(C) ที่ถูกต้อง",
|
||||||
|
"error.chain.configJsonInvalidWithMessage": "ไฟล์กำหนดค่าที่ {{path}} ไม่ใช่ JSON(C) ที่ถูกต้อง: {{message}}",
|
||||||
|
"error.chain.configDirectoryTypo":
|
||||||
|
'ไดเรกทอรี "{{dir}}" ใน {{path}} ไม่ถูกต้อง เปลี่ยนชื่อไดเรกทอรีเป็น "{{suggestion}}" หรือเอาออก นี่เป็นการสะกดผิดทั่วไป',
|
||||||
|
"error.chain.configFrontmatterError": "ไม่สามารถแยกวิเคราะห์ frontmatter ใน {{path}}:\n{{message}}",
|
||||||
|
"error.chain.configInvalid": "ไฟล์กำหนดค่าที่ {{path}} ไม่ถูกต้อง",
|
||||||
|
"error.chain.configInvalidWithMessage": "ไฟล์กำหนดค่าที่ {{path}} ไม่ถูกต้อง: {{message}}",
|
||||||
|
|
||||||
|
"notification.permission.title": "ต้องการสิทธิ์",
|
||||||
|
"notification.permission.description": "{{sessionTitle}} ใน {{projectName}} ต้องการสิทธิ์",
|
||||||
|
"notification.question.title": "คำถาม",
|
||||||
|
"notification.question.description": "{{sessionTitle}} ใน {{projectName}} มีคำถาม",
|
||||||
|
"notification.action.goToSession": "ไปที่เซสชัน",
|
||||||
|
|
||||||
|
"notification.session.responseReady.title": "การตอบสนองพร้อม",
|
||||||
|
"notification.session.error.title": "ข้อผิดพลาดเซสชัน",
|
||||||
|
"notification.session.error.fallbackDescription": "เกิดข้อผิดพลาด",
|
||||||
|
|
||||||
|
"home.recentProjects": "โปรเจกต์ล่าสุด",
|
||||||
|
"home.empty.title": "ไม่มีโปรเจกต์ล่าสุด",
|
||||||
|
"home.empty.description": "เริ่มต้นโดยเปิดโปรเจกต์ในเครื่อง",
|
||||||
|
|
||||||
|
"session.tab.session": "เซสชัน",
|
||||||
|
"session.tab.review": "ตรวจสอบ",
|
||||||
|
"session.tab.context": "บริบท",
|
||||||
|
"session.panel.reviewAndFiles": "ตรวจสอบและไฟล์",
|
||||||
|
"session.review.filesChanged": "{{count}} ไฟล์ที่เปลี่ยนแปลง",
|
||||||
|
"session.review.change.one": "การเปลี่ยนแปลง",
|
||||||
|
"session.review.change.other": "การเปลี่ยนแปลง",
|
||||||
|
"session.review.loadingChanges": "กำลังโหลดการเปลี่ยนแปลง...",
|
||||||
|
"session.review.empty": "ยังไม่มีการเปลี่ยนแปลงในเซสชันนี้",
|
||||||
|
"session.review.noChanges": "ไม่มีการเปลี่ยนแปลง",
|
||||||
|
|
||||||
|
"session.files.selectToOpen": "เลือกไฟล์เพื่อเปิด",
|
||||||
|
"session.files.all": "ไฟล์ทั้งหมด",
|
||||||
|
|
||||||
|
"session.messages.renderEarlier": "แสดงข้อความก่อนหน้า",
|
||||||
|
"session.messages.loadingEarlier": "กำลังโหลดข้อความก่อนหน้า...",
|
||||||
|
"session.messages.loadEarlier": "โหลดข้อความก่อนหน้า",
|
||||||
|
"session.messages.loading": "กำลังโหลดข้อความ...",
|
||||||
|
"session.messages.jumpToLatest": "ไปที่ล่าสุด",
|
||||||
|
|
||||||
|
"session.context.addToContext": "เพิ่ม {{selection}} ไปยังบริบท",
|
||||||
|
|
||||||
|
"session.new.worktree.main": "สาขาหลัก",
|
||||||
|
"session.new.worktree.mainWithBranch": "สาขาหลัก ({{branch}})",
|
||||||
|
"session.new.worktree.create": "สร้าง worktree ใหม่",
|
||||||
|
"session.new.lastModified": "แก้ไขล่าสุด",
|
||||||
|
|
||||||
|
"session.header.search.placeholder": "ค้นหา {{project}}",
|
||||||
|
"session.header.searchFiles": "ค้นหาไฟล์",
|
||||||
|
|
||||||
|
"status.popover.trigger": "สถานะ",
|
||||||
|
"status.popover.ariaLabel": "การกำหนดค่าเซิร์ฟเวอร์",
|
||||||
|
"status.popover.tab.servers": "เซิร์ฟเวอร์",
|
||||||
|
"status.popover.tab.mcp": "MCP",
|
||||||
|
"status.popover.tab.lsp": "LSP",
|
||||||
|
"status.popover.tab.plugins": "ปลั๊กอิน",
|
||||||
|
"status.popover.action.manageServers": "จัดการเซิร์ฟเวอร์",
|
||||||
|
|
||||||
|
"session.share.popover.title": "เผยแพร่บนเว็บ",
|
||||||
|
"session.share.popover.description.shared": "เซสชันนี้เป็นสาธารณะบนเว็บ สามารถเข้าถึงได้โดยผู้ที่มีลิงก์",
|
||||||
|
"session.share.popover.description.unshared": "แชร์เซสชันสาธารณะบนเว็บ จะเข้าถึงได้โดยผู้ที่มีลิงก์",
|
||||||
|
"session.share.action.share": "แชร์",
|
||||||
|
"session.share.action.publish": "เผยแพร่",
|
||||||
|
"session.share.action.publishing": "กำลังเผยแพร่...",
|
||||||
|
"session.share.action.unpublish": "ยกเลิกการเผยแพร่",
|
||||||
|
"session.share.action.unpublishing": "กำลังยกเลิกการเผยแพร่...",
|
||||||
|
"session.share.action.view": "ดู",
|
||||||
|
"session.share.copy.copied": "คัดลอกแล้ว",
|
||||||
|
"session.share.copy.copyLink": "คัดลอกลิงก์",
|
||||||
|
|
||||||
|
"lsp.tooltip.none": "ไม่มีเซิร์ฟเวอร์ LSP",
|
||||||
|
"lsp.label.connected": "{{count}} LSP",
|
||||||
|
|
||||||
|
"prompt.loading": "กำลังโหลดพร้อมท์...",
|
||||||
|
"terminal.loading": "กำลังโหลดเทอร์มินัล...",
|
||||||
|
"terminal.title": "เทอร์มินัล",
|
||||||
|
"terminal.title.numbered": "เทอร์มินัล {{number}}",
|
||||||
|
"terminal.close": "ปิดเทอร์มินัล",
|
||||||
|
"terminal.connectionLost.title": "การเชื่อมต่อขาดหาย",
|
||||||
|
"terminal.connectionLost.description": "การเชื่อมต่อเทอร์มินัลถูกขัดจังหวะ อาจเกิดขึ้นเมื่อเซิร์ฟเวอร์รีสตาร์ท",
|
||||||
|
|
||||||
|
"common.closeTab": "ปิดแท็บ",
|
||||||
|
"common.dismiss": "ปิด",
|
||||||
|
"common.requestFailed": "คำขอล้มเหลว",
|
||||||
|
"common.moreOptions": "ตัวเลือกเพิ่มเติม",
|
||||||
|
"common.learnMore": "เรียนรู้เพิ่มเติม",
|
||||||
|
"common.rename": "เปลี่ยนชื่อ",
|
||||||
|
"common.reset": "รีเซ็ต",
|
||||||
|
"common.archive": "จัดเก็บ",
|
||||||
|
"common.delete": "ลบ",
|
||||||
|
"common.close": "ปิด",
|
||||||
|
"common.edit": "แก้ไข",
|
||||||
|
"common.loadMore": "โหลดเพิ่มเติม",
|
||||||
|
"common.key.esc": "ESC",
|
||||||
|
|
||||||
|
"sidebar.menu.toggle": "สลับเมนู",
|
||||||
|
"sidebar.nav.projectsAndSessions": "โปรเจกต์และเซสชัน",
|
||||||
|
"sidebar.settings": "การตั้งค่า",
|
||||||
|
"sidebar.help": "ช่วยเหลือ",
|
||||||
|
"sidebar.workspaces.enable": "เปิดใช้งานพื้นที่ทำงาน",
|
||||||
|
"sidebar.workspaces.disable": "ปิดใช้งานพื้นที่ทำงาน",
|
||||||
|
"sidebar.gettingStarted.title": "เริ่มต้นใช้งาน",
|
||||||
|
"sidebar.gettingStarted.line1": "OpenCode รวมถึงโมเดลฟรีเพื่อให้คุณเริ่มต้นได้ทันที",
|
||||||
|
"sidebar.gettingStarted.line2": "เชื่อมต่อผู้ให้บริการใด ๆ เพื่อใช้โมเดล รวมถึง Claude, GPT, Gemini ฯลฯ",
|
||||||
|
"sidebar.project.recentSessions": "เซสชันล่าสุด",
|
||||||
|
"sidebar.project.viewAllSessions": "ดูเซสชันทั้งหมด",
|
||||||
|
|
||||||
|
"app.name.desktop": "OpenCode Desktop",
|
||||||
|
|
||||||
|
"settings.section.desktop": "เดสก์ท็อป",
|
||||||
|
"settings.section.server": "เซิร์ฟเวอร์",
|
||||||
|
"settings.tab.general": "ทั่วไป",
|
||||||
|
"settings.tab.shortcuts": "ทางลัด",
|
||||||
|
|
||||||
|
"settings.general.section.appearance": "รูปลักษณ์",
|
||||||
|
"settings.general.section.notifications": "การแจ้งเตือนระบบ",
|
||||||
|
"settings.general.section.updates": "การอัปเดต",
|
||||||
|
"settings.general.section.sounds": "เสียงเอฟเฟกต์",
|
||||||
|
|
||||||
|
"settings.general.row.language.title": "ภาษา",
|
||||||
|
"settings.general.row.language.description": "เปลี่ยนภาษาที่แสดงสำหรับ OpenCode",
|
||||||
|
"settings.general.row.appearance.title": "รูปลักษณ์",
|
||||||
|
"settings.general.row.appearance.description": "ปรับแต่งวิธีการที่ OpenCode มีลักษณะบนอุปกรณ์ของคุณ",
|
||||||
|
"settings.general.row.theme.title": "ธีม",
|
||||||
|
"settings.general.row.theme.description": "ปรับแต่งวิธีการที่ OpenCode มีธีม",
|
||||||
|
"settings.general.row.font.title": "ฟอนต์",
|
||||||
|
"settings.general.row.font.description": "ปรับแต่งฟอนต์โมโนที่ใช้ในบล็อกโค้ด",
|
||||||
|
|
||||||
|
"settings.general.row.releaseNotes.title": "บันทึกการอัปเดต",
|
||||||
|
"settings.general.row.releaseNotes.description": "แสดงป๊อปอัพ What's New หลังจากอัปเดต",
|
||||||
|
"font.option.ibmPlexMono": "IBM Plex Mono",
|
||||||
|
"font.option.cascadiaCode": "Cascadia Code",
|
||||||
|
"font.option.firaCode": "Fira Code",
|
||||||
|
"font.option.hack": "Hack",
|
||||||
|
"font.option.inconsolata": "Inconsolata",
|
||||||
|
"font.option.intelOneMono": "Intel One Mono",
|
||||||
|
"font.option.iosevka": "Iosevka",
|
||||||
|
"font.option.jetbrainsMono": "JetBrains Mono",
|
||||||
|
"font.option.mesloLgs": "Meslo LGS",
|
||||||
|
"font.option.robotoMono": "Roboto Mono",
|
||||||
|
"font.option.sourceCodePro": "Source Code Pro",
|
||||||
|
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||||
|
"sound.option.alert01": "เสียงเตือน 01",
|
||||||
|
"sound.option.alert02": "เสียงเตือน 02",
|
||||||
|
"sound.option.alert03": "เสียงเตือน 03",
|
||||||
|
"sound.option.alert04": "เสียงเตือน 04",
|
||||||
|
"sound.option.alert05": "เสียงเตือน 05",
|
||||||
|
"sound.option.alert06": "เสียงเตือน 06",
|
||||||
|
"sound.option.alert07": "เสียงเตือน 07",
|
||||||
|
"sound.option.alert08": "เสียงเตือน 08",
|
||||||
|
"sound.option.alert09": "เสียงเตือน 09",
|
||||||
|
"sound.option.alert10": "เสียงเตือน 10",
|
||||||
|
"sound.option.bipbop01": "Bip-bop 01",
|
||||||
|
"sound.option.bipbop02": "Bip-bop 02",
|
||||||
|
"sound.option.bipbop03": "Bip-bop 03",
|
||||||
|
"sound.option.bipbop04": "Bip-bop 04",
|
||||||
|
"sound.option.bipbop05": "Bip-bop 05",
|
||||||
|
"sound.option.bipbop06": "Bip-bop 06",
|
||||||
|
"sound.option.bipbop07": "Bip-bop 07",
|
||||||
|
"sound.option.bipbop08": "Bip-bop 08",
|
||||||
|
"sound.option.bipbop09": "Bip-bop 09",
|
||||||
|
"sound.option.bipbop10": "Bip-bop 10",
|
||||||
|
"sound.option.staplebops01": "Staplebops 01",
|
||||||
|
"sound.option.staplebops02": "Staplebops 02",
|
||||||
|
"sound.option.staplebops03": "Staplebops 03",
|
||||||
|
"sound.option.staplebops04": "Staplebops 04",
|
||||||
|
"sound.option.staplebops05": "Staplebops 05",
|
||||||
|
"sound.option.staplebops06": "Staplebops 06",
|
||||||
|
"sound.option.staplebops07": "Staplebops 07",
|
||||||
|
"sound.option.nope01": "Nope 01",
|
||||||
|
"sound.option.nope02": "Nope 02",
|
||||||
|
"sound.option.nope03": "Nope 03",
|
||||||
|
"sound.option.nope04": "Nope 04",
|
||||||
|
"sound.option.nope05": "Nope 05",
|
||||||
|
"sound.option.nope06": "Nope 06",
|
||||||
|
"sound.option.nope07": "Nope 07",
|
||||||
|
"sound.option.nope08": "Nope 08",
|
||||||
|
"sound.option.nope09": "Nope 09",
|
||||||
|
"sound.option.nope10": "Nope 10",
|
||||||
|
"sound.option.nope11": "Nope 11",
|
||||||
|
"sound.option.nope12": "Nope 12",
|
||||||
|
"sound.option.yup01": "Yup 01",
|
||||||
|
"sound.option.yup02": "Yup 02",
|
||||||
|
"sound.option.yup03": "Yup 03",
|
||||||
|
"sound.option.yup04": "Yup 04",
|
||||||
|
"sound.option.yup05": "Yup 05",
|
||||||
|
"sound.option.yup06": "Yup 06",
|
||||||
|
|
||||||
|
"settings.general.notifications.agent.title": "เอเจนต์",
|
||||||
|
"settings.general.notifications.agent.description": "แสดงการแจ้งเตือนระบบเมื่อเอเจนต์เสร็จสิ้นหรือต้องการความสนใจ",
|
||||||
|
"settings.general.notifications.permissions.title": "สิทธิ์",
|
||||||
|
"settings.general.notifications.permissions.description": "แสดงการแจ้งเตือนระบบเมื่อต้องการสิทธิ์",
|
||||||
|
"settings.general.notifications.errors.title": "ข้อผิดพลาด",
|
||||||
|
"settings.general.notifications.errors.description": "แสดงการแจ้งเตือนระบบเมื่อเกิดข้อผิดพลาด",
|
||||||
|
|
||||||
|
"settings.general.sounds.agent.title": "เอเจนต์",
|
||||||
|
"settings.general.sounds.agent.description": "เล่นเสียงเมื่อเอเจนต์เสร็จสิ้นหรือต้องการความสนใจ",
|
||||||
|
"settings.general.sounds.permissions.title": "สิทธิ์",
|
||||||
|
"settings.general.sounds.permissions.description": "เล่นเสียงเมื่อต้องการสิทธิ์",
|
||||||
|
"settings.general.sounds.errors.title": "ข้อผิดพลาด",
|
||||||
|
"settings.general.sounds.errors.description": "เล่นเสียงเมื่อเกิดข้อผิดพลาด",
|
||||||
|
|
||||||
|
"settings.shortcuts.title": "ทางลัดแป้นพิมพ์",
|
||||||
|
"settings.shortcuts.reset.button": "รีเซ็ตเป็นค่าเริ่มต้น",
|
||||||
|
"settings.shortcuts.reset.toast.title": "รีเซ็ตทางลัดแล้ว",
|
||||||
|
"settings.shortcuts.reset.toast.description": "รีเซ็ตทางลัดแป้นพิมพ์เป็นค่าเริ่มต้นแล้ว",
|
||||||
|
"settings.shortcuts.conflict.title": "ทางลัดใช้งานอยู่แล้ว",
|
||||||
|
"settings.shortcuts.conflict.description": "{{keybind}} ถูกกำหนดให้กับ {{titles}} แล้ว",
|
||||||
|
"settings.shortcuts.unassigned": "ไม่ได้กำหนด",
|
||||||
|
"settings.shortcuts.pressKeys": "กดปุ่ม",
|
||||||
|
"settings.shortcuts.search.placeholder": "ค้นหาทางลัด",
|
||||||
|
"settings.shortcuts.search.empty": "ไม่พบทางลัด",
|
||||||
|
|
||||||
|
"settings.shortcuts.group.general": "ทั่วไป",
|
||||||
|
"settings.shortcuts.group.session": "เซสชัน",
|
||||||
|
"settings.shortcuts.group.navigation": "การนำทาง",
|
||||||
|
"settings.shortcuts.group.modelAndAgent": "โมเดลและเอเจนต์",
|
||||||
|
"settings.shortcuts.group.terminal": "เทอร์มินัล",
|
||||||
|
"settings.shortcuts.group.prompt": "พร้อมท์",
|
||||||
|
|
||||||
|
"settings.providers.title": "ผู้ให้บริการ",
|
||||||
|
"settings.providers.description": "การตั้งค่าผู้ให้บริการจะสามารถกำหนดค่าได้ที่นี่",
|
||||||
|
"settings.providers.section.connected": "ผู้ให้บริการที่เชื่อมต่อ",
|
||||||
|
"settings.providers.connected.empty": "ไม่มีผู้ให้บริการที่เชื่อมต่อ",
|
||||||
|
"settings.providers.section.popular": "ผู้ให้บริการยอดนิยม",
|
||||||
|
"settings.providers.tag.environment": "สภาพแวดล้อม",
|
||||||
|
"settings.providers.tag.config": "กำหนดค่า",
|
||||||
|
"settings.providers.tag.custom": "กำหนดเอง",
|
||||||
|
"settings.providers.tag.other": "อื่น ๆ",
|
||||||
|
"settings.models.title": "โมเดล",
|
||||||
|
"settings.models.description": "การตั้งค่าโมเดลจะสามารถกำหนดค่าได้ที่นี่",
|
||||||
|
"settings.agents.title": "เอเจนต์",
|
||||||
|
"settings.agents.description": "การตั้งค่าเอเจนต์จะสามารถกำหนดค่าได้ที่นี่",
|
||||||
|
"settings.commands.title": "คำสั่ง",
|
||||||
|
"settings.commands.description": "การตั้งค่าคำสั่งจะสามารถกำหนดค่าได้ที่นี่",
|
||||||
|
"settings.mcp.title": "MCP",
|
||||||
|
"settings.mcp.description": "การตั้งค่า MCP จะสามารถกำหนดค่าได้ที่นี่",
|
||||||
|
|
||||||
|
"settings.permissions.title": "สิทธิ์",
|
||||||
|
"settings.permissions.description": "ควบคุมเครื่องมือที่เซิร์ฟเวอร์สามารถใช้โดยค่าเริ่มต้น",
|
||||||
|
"settings.permissions.section.tools": "เครื่องมือ",
|
||||||
|
"settings.permissions.toast.updateFailed.title": "ไม่สามารถอัปเดตสิทธิ์",
|
||||||
|
|
||||||
|
"settings.permissions.action.allow": "อนุญาต",
|
||||||
|
"settings.permissions.action.ask": "ถาม",
|
||||||
|
"settings.permissions.action.deny": "ปฏิเสธ",
|
||||||
|
|
||||||
|
"settings.permissions.tool.read.title": "อ่าน",
|
||||||
|
"settings.permissions.tool.read.description": "อ่านไฟล์ (ตรงกับเส้นทางไฟล์)",
|
||||||
|
"settings.permissions.tool.edit.title": "แก้ไข",
|
||||||
|
"settings.permissions.tool.edit.description": "แก้ไขไฟล์ รวมถึงการแก้ไข เขียน แพตช์ และแก้ไขหลายรายการ",
|
||||||
|
"settings.permissions.tool.glob.title": "Glob",
|
||||||
|
"settings.permissions.tool.glob.description": "จับคู่ไฟล์โดยใช้รูปแบบ glob",
|
||||||
|
"settings.permissions.tool.grep.title": "Grep",
|
||||||
|
"settings.permissions.tool.grep.description": "ค้นหาเนื้อหาไฟล์โดยใช้นิพจน์ทั่วไป",
|
||||||
|
"settings.permissions.tool.list.title": "รายการ",
|
||||||
|
"settings.permissions.tool.list.description": "แสดงรายการไฟล์ภายในไดเรกทอรี",
|
||||||
|
"settings.permissions.tool.bash.title": "Bash",
|
||||||
|
"settings.permissions.tool.bash.description": "เรียกใช้คำสั่งเชลล์",
|
||||||
|
"settings.permissions.tool.task.title": "งาน",
|
||||||
|
"settings.permissions.tool.task.description": "เปิดเอเจนต์ย่อย",
|
||||||
|
"settings.permissions.tool.skill.title": "ทักษะ",
|
||||||
|
"settings.permissions.tool.skill.description": "โหลดทักษะตามชื่อ",
|
||||||
|
"settings.permissions.tool.lsp.title": "LSP",
|
||||||
|
"settings.permissions.tool.lsp.description": "เรียกใช้การสืบค้นเซิร์ฟเวอร์ภาษา",
|
||||||
|
"settings.permissions.tool.todoread.title": "อ่านรายการงาน",
|
||||||
|
"settings.permissions.tool.todoread.description": "อ่านรายการงาน",
|
||||||
|
"settings.permissions.tool.todowrite.title": "เขียนรายการงาน",
|
||||||
|
"settings.permissions.tool.todowrite.description": "อัปเดตรายการงาน",
|
||||||
|
"settings.permissions.tool.webfetch.title": "ดึงข้อมูลจากเว็บ",
|
||||||
|
"settings.permissions.tool.webfetch.description": "ดึงเนื้อหาจาก URL",
|
||||||
|
"settings.permissions.tool.websearch.title": "ค้นหาเว็บ",
|
||||||
|
"settings.permissions.tool.websearch.description": "ค้นหาบนเว็บ",
|
||||||
|
"settings.permissions.tool.codesearch.title": "ค้นหาโค้ด",
|
||||||
|
"settings.permissions.tool.codesearch.description": "ค้นหาโค้ดบนเว็บ",
|
||||||
|
"settings.permissions.tool.external_directory.title": "ไดเรกทอรีภายนอก",
|
||||||
|
"settings.permissions.tool.external_directory.description": "เข้าถึงไฟล์นอกไดเรกทอรีโปรเจกต์",
|
||||||
|
"settings.permissions.tool.doom_loop.title": "Doom Loop",
|
||||||
|
"settings.permissions.tool.doom_loop.description": "ตรวจจับการเรียกเครื่องมือซ้ำด้วยข้อมูลนำเข้าเหมือนกัน",
|
||||||
|
|
||||||
|
"session.delete.failed.title": "ไม่สามารถลบเซสชัน",
|
||||||
|
"session.delete.title": "ลบเซสชัน",
|
||||||
|
"session.delete.confirm": 'ลบเซสชัน "{{name}}" หรือไม่?',
|
||||||
|
"session.delete.button": "ลบเซสชัน",
|
||||||
|
|
||||||
|
"workspace.new": "พื้นที่ทำงานใหม่",
|
||||||
|
"workspace.type.local": "ในเครื่อง",
|
||||||
|
"workspace.type.sandbox": "แซนด์บ็อกซ์",
|
||||||
|
"workspace.create.failed.title": "ไม่สามารถสร้างพื้นที่ทำงาน",
|
||||||
|
"workspace.delete.failed.title": "ไม่สามารถลบพื้นที่ทำงาน",
|
||||||
|
"workspace.resetting.title": "กำลังรีเซ็ตพื้นที่ทำงาน",
|
||||||
|
"workspace.resetting.description": "อาจใช้เวลาประมาณหนึ่งนาที",
|
||||||
|
"workspace.reset.failed.title": "ไม่สามารถรีเซ็ตพื้นที่ทำงาน",
|
||||||
|
"workspace.reset.success.title": "รีเซ็ตพื้นที่ทำงานแล้ว",
|
||||||
|
"workspace.reset.success.description": "พื้นที่ทำงานตรงกับสาขาเริ่มต้นแล้ว",
|
||||||
|
"workspace.error.stillPreparing": "พื้นที่ทำงานกำลังเตรียมอยู่",
|
||||||
|
"workspace.status.checking": "กำลังตรวจสอบการเปลี่ยนแปลงที่ไม่ได้ผสาน...",
|
||||||
|
"workspace.status.error": "ไม่สามารถตรวจสอบสถานะ git",
|
||||||
|
"workspace.status.clean": "ไม่ตรวจพบการเปลี่ยนแปลงที่ไม่ได้ผสาน",
|
||||||
|
"workspace.status.dirty": "ตรวจพบการเปลี่ยนแปลงที่ไม่ได้ผสานในพื้นที่ทำงานนี้",
|
||||||
|
"workspace.delete.title": "ลบพื้นที่ทำงาน",
|
||||||
|
"workspace.delete.confirm": 'ลบพื้นที่ทำงาน "{{name}}" หรือไม่?',
|
||||||
|
"workspace.delete.button": "ลบพื้นที่ทำงาน",
|
||||||
|
"workspace.reset.title": "รีเซ็ตพื้นที่ทำงาน",
|
||||||
|
"workspace.reset.confirm": 'รีเซ็ตพื้นที่ทำงาน "{{name}}" หรือไม่?',
|
||||||
|
"workspace.reset.button": "รีเซ็ตพื้นที่ทำงาน",
|
||||||
|
"workspace.reset.archived.none": "ไม่มีเซสชันที่ใช้งานอยู่จะถูกจัดเก็บ",
|
||||||
|
"workspace.reset.archived.one": "1 เซสชันจะถูกจัดเก็บ",
|
||||||
|
"workspace.reset.archived.many": "{{count}} เซสชันจะถูกจัดเก็บ",
|
||||||
|
"workspace.reset.note": "สิ่งนี้จะรีเซ็ตพื้นที่ทำงานให้ตรงกับสาขาเริ่มต้น",
|
||||||
|
}
|
||||||
@@ -330,6 +330,7 @@ export const dict = {
|
|||||||
"language.ar": "العربية",
|
"language.ar": "العربية",
|
||||||
"language.no": "Norsk",
|
"language.no": "Norsk",
|
||||||
"language.br": "Português (Brasil)",
|
"language.br": "Português (Brasil)",
|
||||||
|
"language.th": "ไทย",
|
||||||
|
|
||||||
"toast.language.title": "语言",
|
"toast.language.title": "语言",
|
||||||
"toast.language.description": "已切换到{{language}}",
|
"toast.language.description": "已切换到{{language}}",
|
||||||
|
|||||||
@@ -331,6 +331,7 @@ export const dict = {
|
|||||||
"language.ar": "العربية",
|
"language.ar": "العربية",
|
||||||
"language.no": "Norsk",
|
"language.no": "Norsk",
|
||||||
"language.br": "Português (Brasil)",
|
"language.br": "Português (Brasil)",
|
||||||
|
"language.th": "ไทย",
|
||||||
|
|
||||||
"toast.language.title": "語言",
|
"toast.language.title": "語言",
|
||||||
"toast.language.description": "已切換到 {{language}}",
|
"toast.language.description": "已切換到 {{language}}",
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import { IconButton } from "@opencode-ai/ui/icon-button"
|
|||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
|
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
|
||||||
import { DiffChanges } from "@opencode-ai/ui/diff-changes"
|
|
||||||
import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
|
import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
|
||||||
import { Tabs } from "@opencode-ai/ui/tabs"
|
import { Tabs } from "@opencode-ai/ui/tabs"
|
||||||
import { useCodeComponent } from "@opencode-ai/ui/context/code"
|
import { useCodeComponent } from "@opencode-ai/ui/context/code"
|
||||||
@@ -325,6 +324,7 @@ export default function Page() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isDesktop = createMediaQuery("(min-width: 768px)")
|
const isDesktop = createMediaQuery("(min-width: 768px)")
|
||||||
|
const centered = createMemo(() => isDesktop() && !layout.fileTree.opened())
|
||||||
|
|
||||||
function normalizeTab(tab: string) {
|
function normalizeTab(tab: string) {
|
||||||
if (!tab.startsWith("file://")) return tab
|
if (!tab.startsWith("file://")) return tab
|
||||||
@@ -730,8 +730,8 @@ export default function Page() {
|
|||||||
onSelect: () => view().terminal.toggle(),
|
onSelect: () => view().terminal.toggle(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "fileTree.toggle",
|
id: "review.toggle",
|
||||||
title: language.t("command.fileTree.toggle"),
|
title: language.t("command.review.toggle"),
|
||||||
description: "",
|
description: "",
|
||||||
category: language.t("command.category.view"),
|
category: language.t("command.category.view"),
|
||||||
keybind: "mod+shift+r",
|
keybind: "mod+shift+r",
|
||||||
@@ -1097,12 +1097,11 @@ export default function Page() {
|
|||||||
}, 0)
|
}, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
const reviewTab = createMemo(() => isDesktop() && !layout.fileTree.opened())
|
|
||||||
const contextOpen = createMemo(() => tabs().active() === "context" || tabs().all().includes("context"))
|
const contextOpen = createMemo(() => tabs().active() === "context" || tabs().all().includes("context"))
|
||||||
const openedTabs = createMemo(() =>
|
const openedTabs = createMemo(() =>
|
||||||
tabs()
|
tabs()
|
||||||
.all()
|
.all()
|
||||||
.filter((tab) => tab !== "context" && tab !== "review"),
|
.filter((tab) => tab !== "context"),
|
||||||
)
|
)
|
||||||
|
|
||||||
const mobileChanges = createMemo(() => !isDesktop() && store.mobileTab === "changes")
|
const mobileChanges = createMemo(() => !isDesktop() && store.mobileTab === "changes")
|
||||||
@@ -1271,56 +1270,29 @@ export default function Page() {
|
|||||||
const activeTab = createMemo(() => {
|
const activeTab = createMemo(() => {
|
||||||
const active = tabs().active()
|
const active = tabs().active()
|
||||||
if (active === "context") return "context"
|
if (active === "context") return "context"
|
||||||
if (active === "review" && reviewTab()) return "review"
|
|
||||||
if (active && file.pathFromTab(active)) return normalizeTab(active)
|
if (active && file.pathFromTab(active)) return normalizeTab(active)
|
||||||
|
|
||||||
const first = openedTabs()[0]
|
const first = openedTabs()[0]
|
||||||
if (first) return first
|
if (first) return first
|
||||||
if (contextOpen()) return "context"
|
if (contextOpen()) return "context"
|
||||||
if (reviewTab() && hasReview()) return "review"
|
|
||||||
return "empty"
|
return "empty"
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (!layout.ready()) return
|
if (!layout.ready()) return
|
||||||
if (tabs().active()) return
|
if (tabs().active()) return
|
||||||
if (openedTabs().length === 0 && !contextOpen() && !(reviewTab() && hasReview())) return
|
if (openedTabs().length === 0 && !contextOpen()) return
|
||||||
|
|
||||||
const next = activeTab()
|
const next = activeTab()
|
||||||
if (next === "empty") return
|
if (next === "empty") return
|
||||||
tabs().setActive(next)
|
tabs().setActive(next)
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(
|
|
||||||
on(
|
|
||||||
() => layout.fileTree.opened(),
|
|
||||||
(opened, prev) => {
|
|
||||||
if (prev === undefined) return
|
|
||||||
if (!isDesktop()) return
|
|
||||||
|
|
||||||
if (opened) {
|
|
||||||
const active = tabs().active()
|
|
||||||
const tab = active === "review" || (!active && hasReview()) ? "changes" : "all"
|
|
||||||
layout.fileTree.setTab(tab)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fileTreeTab() !== "changes") return
|
|
||||||
tabs().setActive("review")
|
|
||||||
},
|
|
||||||
{ defer: true },
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const id = params.id
|
const id = params.id
|
||||||
if (!id) return
|
if (!id) return
|
||||||
|
|
||||||
const wants = isDesktop()
|
const wants = isDesktop() ? layout.fileTree.opened() && fileTreeTab() === "changes" : store.mobileTab === "changes"
|
||||||
? layout.fileTree.opened()
|
|
||||||
? fileTreeTab() === "changes"
|
|
||||||
: activeTab() === "review"
|
|
||||||
: store.mobileTab === "changes"
|
|
||||||
if (!wants) return
|
if (!wants) return
|
||||||
if (sync.data.session_diff[id] !== undefined) return
|
if (sync.data.session_diff[id] !== undefined) return
|
||||||
if (sync.status === "loading") return
|
if (sync.status === "loading") return
|
||||||
@@ -1789,10 +1761,11 @@ export default function Page() {
|
|||||||
<div
|
<div
|
||||||
classList={{
|
classList={{
|
||||||
"@container relative shrink-0 flex flex-col min-h-0 h-full bg-background-stronger": true,
|
"@container relative shrink-0 flex flex-col min-h-0 h-full bg-background-stronger": true,
|
||||||
"flex-1 md:flex-none pt-6 md:pt-3": true,
|
"flex-1 pt-6 md:pt-3": true,
|
||||||
|
"md:flex-none": layout.fileTree.opened(),
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
width: isDesktop() ? `${layout.session.width()}px` : "100%",
|
width: isDesktop() && layout.fileTree.opened() ? `${layout.session.width()}px` : "100%",
|
||||||
"--prompt-height": store.promptHeight ? `${store.promptHeight}px` : undefined,
|
"--prompt-height": store.promptHeight ? `${store.promptHeight}px` : undefined,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -1967,6 +1940,7 @@ export default function Page() {
|
|||||||
"sticky top-0 z-30 bg-background-stronger": true,
|
"sticky top-0 z-30 bg-background-stronger": true,
|
||||||
"w-full": true,
|
"w-full": true,
|
||||||
"px-4 md:px-6": true,
|
"px-4 md:px-6": true,
|
||||||
|
"md:max-w-200 md:mx-auto": centered(),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class="h-10 flex items-center gap-1">
|
<div class="h-10 flex items-center gap-1">
|
||||||
@@ -1991,7 +1965,13 @@ export default function Page() {
|
|||||||
<div
|
<div
|
||||||
ref={autoScroll.contentRef}
|
ref={autoScroll.contentRef}
|
||||||
role="log"
|
role="log"
|
||||||
class="flex flex-col gap-32 items-start justify-start w-full mt-0 pb-[calc(var(--prompt-height,8rem)+64px)] md:pb-[calc(var(--prompt-height,10rem)+64px)] transition-[margin]"
|
class="flex flex-col gap-32 items-start justify-start pb-[calc(var(--prompt-height,8rem)+64px)] md:pb-[calc(var(--prompt-height,10rem)+64px)] transition-[margin]"
|
||||||
|
classList={{
|
||||||
|
"w-full": true,
|
||||||
|
"md:max-w-200 md:mx-auto": centered(),
|
||||||
|
"mt-0.5": centered(),
|
||||||
|
"mt-0": !centered(),
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Show when={store.turnStart > 0}>
|
<Show when={store.turnStart > 0}>
|
||||||
<div class="w-full flex justify-center">
|
<div class="w-full flex justify-center">
|
||||||
@@ -2039,7 +2019,10 @@ export default function Page() {
|
|||||||
<div
|
<div
|
||||||
id={anchor(message.id)}
|
id={anchor(message.id)}
|
||||||
data-message-id={message.id}
|
data-message-id={message.id}
|
||||||
class="min-w-0 w-full max-w-full"
|
classList={{
|
||||||
|
"min-w-0 w-full max-w-full": true,
|
||||||
|
"md:max-w-200": centered(),
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<SessionTurn
|
<SessionTurn
|
||||||
sessionID={params.id!}
|
sessionID={params.id!}
|
||||||
@@ -2092,7 +2075,12 @@ export default function Page() {
|
|||||||
ref={(el) => (promptDock = el)}
|
ref={(el) => (promptDock = el)}
|
||||||
class="absolute inset-x-0 bottom-0 pt-12 pb-4 flex flex-col justify-center items-center z-50 px-4 md:px-0 bg-gradient-to-t from-background-stronger via-background-stronger to-transparent pointer-events-none"
|
class="absolute inset-x-0 bottom-0 pt-12 pb-4 flex flex-col justify-center items-center z-50 px-4 md:px-0 bg-gradient-to-t from-background-stronger via-background-stronger to-transparent pointer-events-none"
|
||||||
>
|
>
|
||||||
<div class="w-full px-4 pointer-events-auto">
|
<div
|
||||||
|
classList={{
|
||||||
|
"w-full px-4 pointer-events-auto": true,
|
||||||
|
"md:max-w-200 md:mx-auto": centered(),
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Show when={request()} keyed>
|
<Show when={request()} keyed>
|
||||||
{(perm) => (
|
{(perm) => (
|
||||||
<div data-component="tool-part-wrapper" data-permission="true" class="mb-3">
|
<div data-component="tool-part-wrapper" data-permission="true" class="mb-3">
|
||||||
@@ -2163,7 +2151,7 @@ export default function Page() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Show when={isDesktop()}>
|
<Show when={isDesktop() && layout.fileTree.opened()}>
|
||||||
<ResizeHandle
|
<ResizeHandle
|
||||||
direction="horizontal"
|
direction="horizontal"
|
||||||
size={layout.session.width()}
|
size={layout.session.width()}
|
||||||
@@ -2175,7 +2163,7 @@ export default function Page() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Desktop side panel - hidden on mobile */}
|
{/* Desktop side panel - hidden on mobile */}
|
||||||
<Show when={isDesktop()}>
|
<Show when={isDesktop() && layout.fileTree.opened()}>
|
||||||
<aside
|
<aside
|
||||||
id="review-panel"
|
id="review-panel"
|
||||||
aria-label={language.t("session.panel.reviewAndFiles")}
|
aria-label={language.t("session.panel.reviewAndFiles")}
|
||||||
@@ -2183,7 +2171,7 @@ export default function Page() {
|
|||||||
>
|
>
|
||||||
<div class="flex-1 min-w-0 h-full">
|
<div class="flex-1 min-w-0 h-full">
|
||||||
<Show
|
<Show
|
||||||
when={layout.fileTree.opened() && fileTreeTab() === "changes"}
|
when={fileTreeTab() === "changes"}
|
||||||
fallback={
|
fallback={
|
||||||
<DragDropProvider
|
<DragDropProvider
|
||||||
onDragStart={handleDragStart}
|
onDragStart={handleDragStart}
|
||||||
@@ -2196,23 +2184,6 @@ export default function Page() {
|
|||||||
<Tabs value={activeTab()} onChange={openTab}>
|
<Tabs value={activeTab()} onChange={openTab}>
|
||||||
<div class="sticky top-0 shrink-0 flex">
|
<div class="sticky top-0 shrink-0 flex">
|
||||||
<Tabs.List>
|
<Tabs.List>
|
||||||
<Show when={reviewTab()}>
|
|
||||||
<Tabs.Trigger value="review" classes={{ button: "!pl-6" }}>
|
|
||||||
<div class="flex items-center gap-3">
|
|
||||||
<Show when={hasReview() && diffsReady()}>
|
|
||||||
<DiffChanges changes={diffs()} variant="bars" />
|
|
||||||
</Show>
|
|
||||||
<div class="flex items-center gap-1.5">
|
|
||||||
<div>{language.t("session.tab.review")}</div>
|
|
||||||
<Show when={hasReview()}>
|
|
||||||
<div class="text-12-medium text-text-strong h-4 px-2 flex flex-col items-center justify-center rounded-full bg-surface-base">
|
|
||||||
{reviewCount()}
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Tabs.Trigger>
|
|
||||||
</Show>
|
|
||||||
<Show when={contextOpen()}>
|
<Show when={contextOpen()}>
|
||||||
<Tabs.Trigger
|
<Tabs.Trigger
|
||||||
value="context"
|
value="context"
|
||||||
@@ -2261,12 +2232,6 @@ export default function Page() {
|
|||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Show when={reviewTab()}>
|
|
||||||
<Tabs.Content value="review" class="flex flex-col h-full overflow-hidden contain-strict">
|
|
||||||
<Show when={activeTab() === "review"}>{reviewPanel()}</Show>
|
|
||||||
</Tabs.Content>
|
|
||||||
</Show>
|
|
||||||
|
|
||||||
<Tabs.Content value="empty" class="flex flex-col h-full overflow-hidden contain-strict">
|
<Tabs.Content value="empty" class="flex flex-col h-full overflow-hidden contain-strict">
|
||||||
<Show when={activeTab() === "empty"}>
|
<Show when={activeTab() === "empty"}>
|
||||||
<div class="relative pt-2 flex-1 min-h-0 overflow-hidden">
|
<div class="relative pt-2 flex-1 min-h-0 overflow-hidden">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"version": "1.1.37",
|
"version": "1.1.40",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode-ai/console-core",
|
"name": "@opencode-ai/console-core",
|
||||||
"version": "1.1.37",
|
"version": "1.1.40",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-function",
|
"name": "@opencode-ai/console-function",
|
||||||
"version": "1.1.37",
|
"version": "1.1.40",
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-mail",
|
"name": "@opencode-ai/console-mail",
|
||||||
"version": "1.1.37",
|
"version": "1.1.40",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jsx-email/all": "2.2.3",
|
"@jsx-email/all": "2.2.3",
|
||||||
"@jsx-email/cli": "1.4.3",
|
"@jsx-email/cli": "1.4.3",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/desktop",
|
"name": "@opencode-ai/desktop",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.1.37",
|
"version": "1.1.40",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/enterprise",
|
"name": "@opencode-ai/enterprise",
|
||||||
"version": "1.1.37",
|
"version": "1.1.40",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
id = "opencode"
|
id = "opencode"
|
||||||
name = "OpenCode"
|
name = "OpenCode"
|
||||||
description = "The open source coding agent."
|
description = "The open source coding agent."
|
||||||
version = "1.1.37"
|
version = "1.1.40"
|
||||||
schema_version = 1
|
schema_version = 1
|
||||||
authors = ["Anomaly"]
|
authors = ["Anomaly"]
|
||||||
repository = "https://github.com/anomalyco/opencode"
|
repository = "https://github.com/anomalyco/opencode"
|
||||||
@@ -11,26 +11,26 @@ name = "OpenCode"
|
|||||||
icon = "./icons/opencode.svg"
|
icon = "./icons/opencode.svg"
|
||||||
|
|
||||||
[agent_servers.opencode.targets.darwin-aarch64]
|
[agent_servers.opencode.targets.darwin-aarch64]
|
||||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.37/opencode-darwin-arm64.zip"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.40/opencode-darwin-arm64.zip"
|
||||||
cmd = "./opencode"
|
cmd = "./opencode"
|
||||||
args = ["acp"]
|
args = ["acp"]
|
||||||
|
|
||||||
[agent_servers.opencode.targets.darwin-x86_64]
|
[agent_servers.opencode.targets.darwin-x86_64]
|
||||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.37/opencode-darwin-x64.zip"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.40/opencode-darwin-x64.zip"
|
||||||
cmd = "./opencode"
|
cmd = "./opencode"
|
||||||
args = ["acp"]
|
args = ["acp"]
|
||||||
|
|
||||||
[agent_servers.opencode.targets.linux-aarch64]
|
[agent_servers.opencode.targets.linux-aarch64]
|
||||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.37/opencode-linux-arm64.tar.gz"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.40/opencode-linux-arm64.tar.gz"
|
||||||
cmd = "./opencode"
|
cmd = "./opencode"
|
||||||
args = ["acp"]
|
args = ["acp"]
|
||||||
|
|
||||||
[agent_servers.opencode.targets.linux-x86_64]
|
[agent_servers.opencode.targets.linux-x86_64]
|
||||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.37/opencode-linux-x64.tar.gz"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.40/opencode-linux-x64.tar.gz"
|
||||||
cmd = "./opencode"
|
cmd = "./opencode"
|
||||||
args = ["acp"]
|
args = ["acp"]
|
||||||
|
|
||||||
[agent_servers.opencode.targets.windows-x86_64]
|
[agent_servers.opencode.targets.windows-x86_64]
|
||||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.37/opencode-windows-x64.zip"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.40/opencode-windows-x64.zip"
|
||||||
cmd = "./opencode.exe"
|
cmd = "./opencode.exe"
|
||||||
args = ["acp"]
|
args = ["acp"]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/function",
|
"name": "@opencode-ai/function",
|
||||||
"version": "1.1.37",
|
"version": "1.1.40",
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"version": "1.1.37",
|
"version": "1.1.40",
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import path from "path"
|
import path from "path"
|
||||||
import { Global } from "../global"
|
import { Global } from "../global"
|
||||||
import fs from "fs/promises"
|
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
|
|
||||||
export const OAUTH_DUMMY_KEY = "opencode-oauth-dummy-key"
|
export const OAUTH_DUMMY_KEY = "opencode-oauth-dummy-key"
|
||||||
@@ -59,15 +58,13 @@ export namespace Auth {
|
|||||||
export async function set(key: string, info: Info) {
|
export async function set(key: string, info: Info) {
|
||||||
const file = Bun.file(filepath)
|
const file = Bun.file(filepath)
|
||||||
const data = await all()
|
const data = await all()
|
||||||
await Bun.write(file, JSON.stringify({ ...data, [key]: info }, null, 2))
|
await Bun.write(file, JSON.stringify({ ...data, [key]: info }, null, 2), { mode: 0o600 })
|
||||||
await fs.chmod(file.name!, 0o600)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function remove(key: string) {
|
export async function remove(key: string) {
|
||||||
const file = Bun.file(filepath)
|
const file = Bun.file(filepath)
|
||||||
const data = await all()
|
const data = await all()
|
||||||
delete data[key]
|
delete data[key]
|
||||||
await Bun.write(file, JSON.stringify(data, null, 2))
|
await Bun.write(file, JSON.stringify(data, null, 2), { mode: 0o600 })
|
||||||
await fs.chmod(file.name!, 0o600)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
import { TextAttributes, RGBA } from "@opentui/core"
|
import { TextAttributes, RGBA } from "@opentui/core"
|
||||||
import { For, type JSX } from "solid-js"
|
import { For, type JSX } from "solid-js"
|
||||||
import { useTheme, tint } from "@tui/context/theme"
|
import { useTheme, tint } from "@tui/context/theme"
|
||||||
|
import { logo, marks } from "@/cli/logo"
|
||||||
|
|
||||||
// Shadow markers (rendered chars in parens):
|
// Shadow markers (rendered chars in parens):
|
||||||
// _ = full shadow cell (space with bg=shadow)
|
// _ = full shadow cell (space with bg=shadow)
|
||||||
// ^ = letter top, shadow bottom (▀ with fg=letter, bg=shadow)
|
// ^ = letter top, shadow bottom (▀ with fg=letter, bg=shadow)
|
||||||
// ~ = shadow top only (▀ with fg=shadow)
|
// ~ = shadow top only (▀ with fg=shadow)
|
||||||
const SHADOW_MARKER = /[_^~]/
|
const SHADOW_MARKER = new RegExp(`[${marks}]`)
|
||||||
|
|
||||||
const LOGO_LEFT = [` `, `█▀▀█ █▀▀█ █▀▀█ █▀▀▄`, `█__█ █__█ █^^^ █__█`, `▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀~~▀`]
|
|
||||||
|
|
||||||
const LOGO_RIGHT = [` ▄ `, `█▀▀▀ █▀▀█ █▀▀█ █▀▀█`, `█___ █__█ █__█ █^^^`, `▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀`]
|
|
||||||
|
|
||||||
export function Logo() {
|
export function Logo() {
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
@@ -75,11 +72,11 @@ export function Logo() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<box>
|
<box>
|
||||||
<For each={LOGO_LEFT}>
|
<For each={logo.left}>
|
||||||
{(line, index) => (
|
{(line, index) => (
|
||||||
<box flexDirection="row" gap={1}>
|
<box flexDirection="row" gap={1}>
|
||||||
<box flexDirection="row">{renderLine(line, theme.textMuted, false)}</box>
|
<box flexDirection="row">{renderLine(line, theme.textMuted, false)}</box>
|
||||||
<box flexDirection="row">{renderLine(LOGO_RIGHT[index()], theme.text, true)}</box>
|
<box flexDirection="row">{renderLine(logo.right[index()], theme.text, true)}</box>
|
||||||
</box>
|
</box>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ import { DialogTimeline } from "./dialog-timeline"
|
|||||||
import { DialogForkFromTimeline } from "./dialog-fork-from-timeline"
|
import { DialogForkFromTimeline } from "./dialog-fork-from-timeline"
|
||||||
import { DialogSessionRename } from "../../component/dialog-session-rename"
|
import { DialogSessionRename } from "../../component/dialog-session-rename"
|
||||||
import { Sidebar } from "./sidebar"
|
import { Sidebar } from "./sidebar"
|
||||||
|
import { Flag } from "@/flag/flag"
|
||||||
import { LANGUAGE_EXTENSIONS } from "@/lsp/language"
|
import { LANGUAGE_EXTENSIONS } from "@/lsp/language"
|
||||||
import parsers from "../../../../../../parsers-config.ts"
|
import parsers from "../../../../../../parsers-config.ts"
|
||||||
import { Clipboard } from "../../util/clipboard"
|
import { Clipboard } from "../../util/clipboard"
|
||||||
@@ -1338,15 +1339,27 @@ function TextPart(props: { last: boolean; part: TextPart; message: AssistantMess
|
|||||||
return (
|
return (
|
||||||
<Show when={props.part.text.trim()}>
|
<Show when={props.part.text.trim()}>
|
||||||
<box id={"text-" + props.part.id} paddingLeft={3} marginTop={1} flexShrink={0}>
|
<box id={"text-" + props.part.id} paddingLeft={3} marginTop={1} flexShrink={0}>
|
||||||
<code
|
<Switch>
|
||||||
filetype="markdown"
|
<Match when={Flag.OPENCODE_EXPERIMENTAL_MARKDOWN}>
|
||||||
drawUnstyledText={false}
|
<markdown
|
||||||
streaming={true}
|
syntaxStyle={syntax()}
|
||||||
syntaxStyle={syntax()}
|
streaming={true}
|
||||||
content={props.part.text.trim()}
|
content={props.part.text.trim()}
|
||||||
conceal={ctx.conceal()}
|
conceal={ctx.conceal()}
|
||||||
fg={theme.text}
|
/>
|
||||||
/>
|
</Match>
|
||||||
|
<Match when={!Flag.OPENCODE_EXPERIMENTAL_MARKDOWN}>
|
||||||
|
<code
|
||||||
|
filetype="markdown"
|
||||||
|
drawUnstyledText={false}
|
||||||
|
streaming={true}
|
||||||
|
syntaxStyle={syntax()}
|
||||||
|
content={props.part.text.trim()}
|
||||||
|
conceal={ctx.conceal()}
|
||||||
|
fg={theme.text}
|
||||||
|
/>
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
</box>
|
</box>
|
||||||
</Show>
|
</Show>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export const logo = {
|
||||||
|
left: [" ", "█▀▀█ █▀▀█ █▀▀█ █▀▀▄", "█__█ █__█ █^^^ █__█", "▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀~~▀"],
|
||||||
|
right: [" ▄ ", "█▀▀▀ █▀▀█ █▀▀█ █▀▀█", "█___ █__█ █__█ █^^^", "▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀"],
|
||||||
|
}
|
||||||
|
|
||||||
|
export const marks = "_^~"
|
||||||
@@ -1,15 +1,9 @@
|
|||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { EOL } from "os"
|
import { EOL } from "os"
|
||||||
import { NamedError } from "@opencode-ai/util/error"
|
import { NamedError } from "@opencode-ai/util/error"
|
||||||
|
import { logo as glyphs } from "./logo"
|
||||||
|
|
||||||
export namespace UI {
|
export namespace UI {
|
||||||
const LOGO = [
|
|
||||||
[` `, ` ▄ `],
|
|
||||||
[`█▀▀█ █▀▀█ █▀▀█ █▀▀▄ `, `█▀▀▀ █▀▀█ █▀▀█ █▀▀█`],
|
|
||||||
[`█░░█ █░░█ █▀▀▀ █░░█ `, `█░░░ █░░█ █░░█ █▀▀▀`],
|
|
||||||
[`▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀ ▀ `, `▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀`],
|
|
||||||
]
|
|
||||||
|
|
||||||
export const CancelledError = NamedError.create("UICancelledError", z.void())
|
export const CancelledError = NamedError.create("UICancelledError", z.void())
|
||||||
|
|
||||||
export const Style = {
|
export const Style = {
|
||||||
@@ -47,15 +41,50 @@ export namespace UI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function logo(pad?: string) {
|
export function logo(pad?: string) {
|
||||||
const result = []
|
const result: string[] = []
|
||||||
for (const row of LOGO) {
|
const reset = "\x1b[0m"
|
||||||
if (pad) result.push(pad)
|
const left = {
|
||||||
result.push(Bun.color("gray", "ansi"))
|
fg: Bun.color("gray", "ansi") ?? "",
|
||||||
result.push(row[0])
|
shadow: "\x1b[38;5;235m",
|
||||||
result.push("\x1b[0m")
|
bg: "\x1b[48;5;235m",
|
||||||
result.push(row[1])
|
|
||||||
result.push(EOL)
|
|
||||||
}
|
}
|
||||||
|
const right = {
|
||||||
|
fg: reset,
|
||||||
|
shadow: "\x1b[38;5;238m",
|
||||||
|
bg: "\x1b[48;5;238m",
|
||||||
|
}
|
||||||
|
const gap = " "
|
||||||
|
const draw = (line: string, fg: string, shadow: string, bg: string) => {
|
||||||
|
const parts: string[] = []
|
||||||
|
for (const char of line) {
|
||||||
|
if (char === "_") {
|
||||||
|
parts.push(bg, " ", reset)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (char === "^") {
|
||||||
|
parts.push(fg, bg, "▀", reset)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (char === "~") {
|
||||||
|
parts.push(shadow, "▀", reset)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (char === " ") {
|
||||||
|
parts.push(" ")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts.push(fg, char, reset)
|
||||||
|
}
|
||||||
|
return parts.join("")
|
||||||
|
}
|
||||||
|
glyphs.left.forEach((row, index) => {
|
||||||
|
if (pad) result.push(pad)
|
||||||
|
result.push(draw(row, left.fg, left.shadow, left.bg))
|
||||||
|
result.push(gap)
|
||||||
|
const other = glyphs.right[index] ?? ""
|
||||||
|
result.push(draw(other, right.fg, right.shadow, right.bg))
|
||||||
|
result.push(EOL)
|
||||||
|
})
|
||||||
return result.join("").trimEnd()
|
return result.join("").trimEnd()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1104,20 +1104,23 @@ export namespace Config {
|
|||||||
mergeDeep(await loadFile(path.join(Global.Path.config, "opencode.jsonc"))),
|
mergeDeep(await loadFile(path.join(Global.Path.config, "opencode.jsonc"))),
|
||||||
)
|
)
|
||||||
|
|
||||||
await import(path.join(Global.Path.config, "config"), {
|
const legacy = path.join(Global.Path.config, "config")
|
||||||
with: {
|
if (existsSync(legacy)) {
|
||||||
type: "toml",
|
await import(pathToFileURL(legacy).href, {
|
||||||
},
|
with: {
|
||||||
})
|
type: "toml",
|
||||||
.then(async (mod) => {
|
},
|
||||||
const { provider, model, ...rest } = mod.default
|
|
||||||
if (provider && model) result.model = `${provider}/${model}`
|
|
||||||
result["$schema"] = "https://opencode.ai/config.json"
|
|
||||||
result = mergeDeep(result, rest)
|
|
||||||
await Bun.write(path.join(Global.Path.config, "config.json"), JSON.stringify(result, null, 2))
|
|
||||||
await fs.unlink(path.join(Global.Path.config, "config"))
|
|
||||||
})
|
})
|
||||||
.catch(() => {})
|
.then(async (mod) => {
|
||||||
|
const { provider, model, ...rest } = mod.default
|
||||||
|
if (provider && model) result.model = `${provider}/${model}`
|
||||||
|
result["$schema"] = "https://opencode.ai/config.json"
|
||||||
|
result = mergeDeep(result, rest)
|
||||||
|
await Bun.write(path.join(Global.Path.config, "config.json"), JSON.stringify(result, null, 2))
|
||||||
|
await fs.unlink(legacy)
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
@@ -1341,24 +1344,35 @@ export namespace Config {
|
|||||||
throw new JsonError({ path: filepath }, { cause: err })
|
throw new JsonError({ path: filepath }, { cause: err })
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!filepath.endsWith(".jsonc")) {
|
const next = await (async () => {
|
||||||
const existing = parseConfig(before, filepath)
|
if (!filepath.endsWith(".jsonc")) {
|
||||||
await Bun.write(filepath, JSON.stringify(mergeDeep(existing, config), null, 2))
|
const existing = parseConfig(before, filepath)
|
||||||
} else {
|
const merged = mergeDeep(existing, config)
|
||||||
const next = patchJsonc(before, config)
|
await Bun.write(filepath, JSON.stringify(merged, null, 2))
|
||||||
parseConfig(next, filepath)
|
return merged
|
||||||
await Bun.write(filepath, next)
|
}
|
||||||
}
|
|
||||||
|
const updated = patchJsonc(before, config)
|
||||||
|
const merged = parseConfig(updated, filepath)
|
||||||
|
await Bun.write(filepath, updated)
|
||||||
|
return merged
|
||||||
|
})()
|
||||||
|
|
||||||
global.reset()
|
global.reset()
|
||||||
await Instance.disposeAll()
|
|
||||||
GlobalBus.emit("event", {
|
void Instance.disposeAll()
|
||||||
directory: "global",
|
.catch(() => undefined)
|
||||||
payload: {
|
.finally(() => {
|
||||||
type: Event.Disposed.type,
|
GlobalBus.emit("event", {
|
||||||
properties: {},
|
directory: "global",
|
||||||
},
|
payload: {
|
||||||
})
|
type: Event.Disposed.type,
|
||||||
|
properties: {},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return next
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function directories() {
|
export async function directories() {
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ export namespace Flag {
|
|||||||
export const OPENCODE_EXPERIMENTAL_LSP_TOOL = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_LSP_TOOL")
|
export const OPENCODE_EXPERIMENTAL_LSP_TOOL = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_LSP_TOOL")
|
||||||
export const OPENCODE_DISABLE_FILETIME_CHECK = truthy("OPENCODE_DISABLE_FILETIME_CHECK")
|
export const OPENCODE_DISABLE_FILETIME_CHECK = truthy("OPENCODE_DISABLE_FILETIME_CHECK")
|
||||||
export const OPENCODE_EXPERIMENTAL_PLAN_MODE = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_PLAN_MODE")
|
export const OPENCODE_EXPERIMENTAL_PLAN_MODE = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_PLAN_MODE")
|
||||||
|
export const OPENCODE_EXPERIMENTAL_MARKDOWN = truthy("OPENCODE_EXPERIMENTAL_MARKDOWN")
|
||||||
export const OPENCODE_MODELS_URL = process.env["OPENCODE_MODELS_URL"]
|
export const OPENCODE_MODELS_URL = process.env["OPENCODE_MODELS_URL"]
|
||||||
|
|
||||||
function number(key: string) {
|
function number(key: string) {
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ await Promise.all([
|
|||||||
fs.mkdir(Global.Path.bin, { recursive: true }),
|
fs.mkdir(Global.Path.bin, { recursive: true }),
|
||||||
])
|
])
|
||||||
|
|
||||||
const CACHE_VERSION = "19"
|
const CACHE_VERSION = "21"
|
||||||
|
|
||||||
const version = await Bun.file(path.join(Global.Path.cache, "version"))
|
const version = await Bun.file(path.join(Global.Path.cache, "version"))
|
||||||
.text()
|
.text()
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import path from "path"
|
import path from "path"
|
||||||
import fs from "fs/promises"
|
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { Global } from "../global"
|
import { Global } from "../global"
|
||||||
|
|
||||||
@@ -65,16 +64,14 @@ export namespace McpAuth {
|
|||||||
if (serverUrl) {
|
if (serverUrl) {
|
||||||
entry.serverUrl = serverUrl
|
entry.serverUrl = serverUrl
|
||||||
}
|
}
|
||||||
await Bun.write(file, JSON.stringify({ ...data, [mcpName]: entry }, null, 2))
|
await Bun.write(file, JSON.stringify({ ...data, [mcpName]: entry }, null, 2), { mode: 0o600 })
|
||||||
await fs.chmod(file.name!, 0o600)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function remove(mcpName: string): Promise<void> {
|
export async function remove(mcpName: string): Promise<void> {
|
||||||
const file = Bun.file(filepath)
|
const file = Bun.file(filepath)
|
||||||
const data = await all()
|
const data = await all()
|
||||||
delete data[mcpName]
|
delete data[mcpName]
|
||||||
await Bun.write(file, JSON.stringify(data, null, 2))
|
await Bun.write(file, JSON.stringify(data, null, 2), { mode: 0o600 })
|
||||||
await fs.chmod(file.name!, 0o600)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateTokens(mcpName: string, tokens: Tokens, serverUrl?: string): Promise<void> {
|
export async function updateTokens(mcpName: string, tokens: Tokens, serverUrl?: string): Promise<void> {
|
||||||
|
|||||||
@@ -61,12 +61,13 @@ export async function CopilotAuthPlugin(input: PluginInput): Promise<Hooks> {
|
|||||||
const info = await getAuth()
|
const info = await getAuth()
|
||||||
if (info.type !== "oauth") return fetch(request, init)
|
if (info.type !== "oauth") return fetch(request, init)
|
||||||
|
|
||||||
|
const url = request instanceof URL ? request.href : request.toString()
|
||||||
const { isVision, isAgent } = iife(() => {
|
const { isVision, isAgent } = iife(() => {
|
||||||
try {
|
try {
|
||||||
const body = typeof init?.body === "string" ? JSON.parse(init.body) : init?.body
|
const body = typeof init?.body === "string" ? JSON.parse(init.body) : init?.body
|
||||||
|
|
||||||
// Completions API
|
// Completions API
|
||||||
if (body?.messages) {
|
if (body?.messages && url.includes("completions")) {
|
||||||
const last = body.messages[body.messages.length - 1]
|
const last = body.messages[body.messages.length - 1]
|
||||||
return {
|
return {
|
||||||
isVision: body.messages.some(
|
isVision: body.messages.some(
|
||||||
@@ -88,6 +89,28 @@ export async function CopilotAuthPlugin(input: PluginInput): Promise<Hooks> {
|
|||||||
isAgent: last?.role !== "user",
|
isAgent: last?.role !== "user",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Messages API
|
||||||
|
if (body?.messages) {
|
||||||
|
const last = body.messages[body.messages.length - 1]
|
||||||
|
const hasNonToolCalls =
|
||||||
|
Array.isArray(last?.content) && last.content.some((part: any) => part?.type !== "tool_result")
|
||||||
|
return {
|
||||||
|
isVision: body.messages.some(
|
||||||
|
(item: any) =>
|
||||||
|
Array.isArray(item?.content) &&
|
||||||
|
item.content.some(
|
||||||
|
(part: any) =>
|
||||||
|
part?.type === "image" ||
|
||||||
|
// images can be nested inside tool_result content
|
||||||
|
(part?.type === "tool_result" &&
|
||||||
|
Array.isArray(part?.content) &&
|
||||||
|
part.content.some((nested: any) => nested?.type === "image")),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
isAgent: !(last?.role === "user" && hasNonToolCalls),
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
return { isVision: false, isAgent: false }
|
return { isVision: false, isAgent: false }
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { CopilotAuthPlugin } from "./copilot"
|
|||||||
export namespace Plugin {
|
export namespace Plugin {
|
||||||
const log = Log.create({ service: "plugin" })
|
const log = Log.create({ service: "plugin" })
|
||||||
|
|
||||||
const BUILTIN = ["opencode-anthropic-auth@0.0.10", "@gitlab/opencode-gitlab-auth@1.3.2"]
|
const BUILTIN = ["opencode-anthropic-auth@0.0.13", "@gitlab/opencode-gitlab-auth@1.3.2"]
|
||||||
|
|
||||||
// Built-in plugins that are directly imported (not installed from npm)
|
// Built-in plugins that are directly imported (not installed from npm)
|
||||||
const INTERNAL_PLUGINS: PluginInstance[] = [CodexAuthPlugin, CopilotAuthPlugin]
|
const INTERNAL_PLUGINS: PluginInstance[] = [CodexAuthPlugin, CopilotAuthPlugin]
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ interface Context {
|
|||||||
const context = Context.create<Context>("instance")
|
const context = Context.create<Context>("instance")
|
||||||
const cache = new Map<string, Promise<Context>>()
|
const cache = new Map<string, Promise<Context>>()
|
||||||
|
|
||||||
|
const disposal = {
|
||||||
|
all: undefined as Promise<void> | undefined,
|
||||||
|
}
|
||||||
|
|
||||||
export const Instance = {
|
export const Instance = {
|
||||||
async provide<R>(input: { directory: string; init?: () => Promise<any>; fn: () => R }): Promise<R> {
|
async provide<R>(input: { directory: string; init?: () => Promise<any>; fn: () => R }): Promise<R> {
|
||||||
let existing = cache.get(input.directory)
|
let existing = cache.get(input.directory)
|
||||||
@@ -77,15 +81,34 @@ export const Instance = {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
async disposeAll() {
|
async disposeAll() {
|
||||||
Log.Default.info("disposing all instances")
|
if (disposal.all) return disposal.all
|
||||||
for (const [_key, value] of cache) {
|
|
||||||
const awaited = await value.catch(() => {})
|
disposal.all = iife(async () => {
|
||||||
if (awaited) {
|
Log.Default.info("disposing all instances")
|
||||||
await context.provide(await value, async () => {
|
const entries = [...cache.entries()]
|
||||||
|
for (const [key, value] of entries) {
|
||||||
|
if (cache.get(key) !== value) continue
|
||||||
|
|
||||||
|
const ctx = await value.catch((error) => {
|
||||||
|
Log.Default.warn("instance dispose failed", { key, error })
|
||||||
|
return undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!ctx) {
|
||||||
|
if (cache.get(key) === value) cache.delete(key)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cache.get(key) !== value) continue
|
||||||
|
|
||||||
|
await context.provide(ctx, async () => {
|
||||||
await Instance.dispose()
|
await Instance.dispose()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}).finally(() => {
|
||||||
cache.clear()
|
disposal.all = undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
return disposal.all
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,20 +46,24 @@ export namespace State {
|
|||||||
}, 10000).unref()
|
}, 10000).unref()
|
||||||
|
|
||||||
const tasks: Promise<void>[] = []
|
const tasks: Promise<void>[] = []
|
||||||
for (const entry of entries.values()) {
|
for (const [init, entry] of entries) {
|
||||||
if (!entry.dispose) continue
|
if (!entry.dispose) continue
|
||||||
|
|
||||||
|
const label = typeof init === "function" ? init.name : String(init)
|
||||||
|
|
||||||
const task = Promise.resolve(entry.state)
|
const task = Promise.resolve(entry.state)
|
||||||
.then((state) => entry.dispose!(state))
|
.then((state) => entry.dispose!(state))
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
log.error("Error while disposing state:", { error, key })
|
log.error("Error while disposing state:", { error, key, init: label })
|
||||||
})
|
})
|
||||||
|
|
||||||
tasks.push(task)
|
tasks.push(task)
|
||||||
}
|
}
|
||||||
|
await Promise.all(tasks)
|
||||||
|
|
||||||
entries.clear()
|
entries.clear()
|
||||||
recordsByKey.delete(key)
|
recordsByKey.delete(key)
|
||||||
await Promise.all(tasks)
|
|
||||||
disposalFinished = true
|
disposalFinished = true
|
||||||
log.info("state disposal completed", { key })
|
log.info("state disposal completed", { key })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Hono } from "hono"
|
import { Hono } from "hono"
|
||||||
import { describeRoute, resolver } from "hono-openapi"
|
import { describeRoute, resolver, validator } from "hono-openapi"
|
||||||
import { streamSSE } from "hono/streaming"
|
import { streamSSE } from "hono/streaming"
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { BusEvent } from "@/bus/bus-event"
|
import { BusEvent } from "@/bus/bus-event"
|
||||||
@@ -8,6 +8,8 @@ import { Instance } from "../../project/instance"
|
|||||||
import { Installation } from "@/installation"
|
import { Installation } from "@/installation"
|
||||||
import { Log } from "../../util/log"
|
import { Log } from "../../util/log"
|
||||||
import { lazy } from "../../util/lazy"
|
import { lazy } from "../../util/lazy"
|
||||||
|
import { Config } from "../../config/config"
|
||||||
|
import { errors } from "../error"
|
||||||
|
|
||||||
const log = Log.create({ service: "server" })
|
const log = Log.create({ service: "server" })
|
||||||
|
|
||||||
@@ -103,6 +105,52 @@ export const GlobalRoutes = lazy(() =>
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
.get(
|
||||||
|
"/config",
|
||||||
|
describeRoute({
|
||||||
|
summary: "Get global configuration",
|
||||||
|
description: "Retrieve the current global OpenCode configuration settings and preferences.",
|
||||||
|
operationId: "global.config.get",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Get global config info",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: resolver(Config.Info),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (c) => {
|
||||||
|
return c.json(await Config.getGlobal())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.patch(
|
||||||
|
"/config",
|
||||||
|
describeRoute({
|
||||||
|
summary: "Update global configuration",
|
||||||
|
description: "Update global OpenCode configuration settings and preferences.",
|
||||||
|
operationId: "global.config.update",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successfully updated global config",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: resolver(Config.Info),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...errors(400),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
validator("json", Config.Info),
|
||||||
|
async (c) => {
|
||||||
|
const config = c.req.valid("json")
|
||||||
|
const next = await Config.updateGlobal(config)
|
||||||
|
return c.json(next)
|
||||||
|
},
|
||||||
|
)
|
||||||
.post(
|
.post(
|
||||||
"/dispose",
|
"/dispose",
|
||||||
describeRoute({
|
describeRoute({
|
||||||
|
|||||||
@@ -122,6 +122,68 @@ export namespace Server {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.route("/global", GlobalRoutes())
|
.route("/global", GlobalRoutes())
|
||||||
|
.put(
|
||||||
|
"/auth/:providerID",
|
||||||
|
describeRoute({
|
||||||
|
summary: "Set auth credentials",
|
||||||
|
description: "Set authentication credentials",
|
||||||
|
operationId: "auth.set",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successfully set authentication credentials",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: resolver(z.boolean()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...errors(400),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
validator(
|
||||||
|
"param",
|
||||||
|
z.object({
|
||||||
|
providerID: z.string(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
validator("json", Auth.Info),
|
||||||
|
async (c) => {
|
||||||
|
const providerID = c.req.valid("param").providerID
|
||||||
|
const info = c.req.valid("json")
|
||||||
|
await Auth.set(providerID, info)
|
||||||
|
return c.json(true)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.delete(
|
||||||
|
"/auth/:providerID",
|
||||||
|
describeRoute({
|
||||||
|
summary: "Remove auth credentials",
|
||||||
|
description: "Remove authentication credentials",
|
||||||
|
operationId: "auth.remove",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successfully removed authentication credentials",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: resolver(z.boolean()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...errors(400),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
validator(
|
||||||
|
"param",
|
||||||
|
z.object({
|
||||||
|
providerID: z.string(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
async (c) => {
|
||||||
|
const providerID = c.req.valid("param").providerID
|
||||||
|
await Auth.remove(providerID)
|
||||||
|
return c.json(true)
|
||||||
|
},
|
||||||
|
)
|
||||||
.use(async (c, next) => {
|
.use(async (c, next) => {
|
||||||
let directory = c.req.query("directory") || c.req.header("x-opencode-directory") || process.cwd()
|
let directory = c.req.query("directory") || c.req.header("x-opencode-directory") || process.cwd()
|
||||||
try {
|
try {
|
||||||
@@ -409,68 +471,6 @@ export namespace Server {
|
|||||||
return c.json(await Format.status())
|
return c.json(await Format.status())
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.put(
|
|
||||||
"/auth/:providerID",
|
|
||||||
describeRoute({
|
|
||||||
summary: "Set auth credentials",
|
|
||||||
description: "Set authentication credentials",
|
|
||||||
operationId: "auth.set",
|
|
||||||
responses: {
|
|
||||||
200: {
|
|
||||||
description: "Successfully set authentication credentials",
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: resolver(z.boolean()),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
...errors(400),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
validator(
|
|
||||||
"param",
|
|
||||||
z.object({
|
|
||||||
providerID: z.string(),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
validator("json", Auth.Info),
|
|
||||||
async (c) => {
|
|
||||||
const providerID = c.req.valid("param").providerID
|
|
||||||
const info = c.req.valid("json")
|
|
||||||
await Auth.set(providerID, info)
|
|
||||||
return c.json(true)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.delete(
|
|
||||||
"/auth/:providerID",
|
|
||||||
describeRoute({
|
|
||||||
summary: "Remove auth credentials",
|
|
||||||
description: "Remove authentication credentials",
|
|
||||||
operationId: "auth.remove",
|
|
||||||
responses: {
|
|
||||||
200: {
|
|
||||||
description: "Successfully removed authentication credentials",
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: resolver(z.boolean()),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
...errors(400),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
validator(
|
|
||||||
"param",
|
|
||||||
z.object({
|
|
||||||
providerID: z.string(),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
async (c) => {
|
|
||||||
const providerID = c.req.valid("param").providerID
|
|
||||||
await Auth.remove(providerID)
|
|
||||||
return c.json(true)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.get(
|
.get(
|
||||||
"/event",
|
"/event",
|
||||||
describeRoute({
|
describeRoute({
|
||||||
|
|||||||
@@ -41,6 +41,32 @@ async function resolveRelative(instruction: string): Promise<string[]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export namespace InstructionPrompt {
|
export namespace InstructionPrompt {
|
||||||
|
const state = Instance.state(() => {
|
||||||
|
return {
|
||||||
|
claims: new Map<string, Set<string>>(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function isClaimed(messageID: string, filepath: string) {
|
||||||
|
const claimed = state().claims.get(messageID)
|
||||||
|
if (!claimed) return false
|
||||||
|
return claimed.has(filepath)
|
||||||
|
}
|
||||||
|
|
||||||
|
function claim(messageID: string, filepath: string) {
|
||||||
|
const current = state()
|
||||||
|
let claimed = current.claims.get(messageID)
|
||||||
|
if (!claimed) {
|
||||||
|
claimed = new Set()
|
||||||
|
current.claims.set(messageID, claimed)
|
||||||
|
}
|
||||||
|
claimed.add(filepath)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clear(messageID: string) {
|
||||||
|
state().claims.delete(messageID)
|
||||||
|
}
|
||||||
|
|
||||||
export async function systemPaths() {
|
export async function systemPaths() {
|
||||||
const config = await Config.get()
|
const config = await Config.get()
|
||||||
const paths = new Set<string>()
|
const paths = new Set<string>()
|
||||||
@@ -137,7 +163,7 @@ export namespace InstructionPrompt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function resolve(messages: MessageV2.WithParts[], filepath: string) {
|
export async function resolve(messages: MessageV2.WithParts[], filepath: string, messageID: string) {
|
||||||
const system = await systemPaths()
|
const system = await systemPaths()
|
||||||
const already = loaded(messages)
|
const already = loaded(messages)
|
||||||
const results: { filepath: string; content: string }[] = []
|
const results: { filepath: string; content: string }[] = []
|
||||||
@@ -147,7 +173,8 @@ export namespace InstructionPrompt {
|
|||||||
|
|
||||||
while (current.startsWith(root)) {
|
while (current.startsWith(root)) {
|
||||||
const found = await find(current)
|
const found = await find(current)
|
||||||
if (found && !system.has(found) && !already.has(found)) {
|
if (found && !system.has(found) && !already.has(found) && !isClaimed(messageID, found)) {
|
||||||
|
claim(messageID, found)
|
||||||
const content = await Bun.file(found)
|
const content = await Bun.file(found)
|
||||||
.text()
|
.text()
|
||||||
.catch(() => undefined)
|
.catch(() => undefined)
|
||||||
|
|||||||
@@ -176,6 +176,8 @@ export namespace MessageV2 {
|
|||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
command: z.string().optional(),
|
command: z.string().optional(),
|
||||||
|
}).meta({
|
||||||
|
ref: "SubtaskPart",
|
||||||
})
|
})
|
||||||
export type SubtaskPart = z.infer<typeof SubtaskPart>
|
export type SubtaskPart = z.infer<typeof SubtaskPart>
|
||||||
|
|
||||||
|
|||||||
@@ -551,6 +551,7 @@ export namespace SessionPrompt {
|
|||||||
model,
|
model,
|
||||||
abort,
|
abort,
|
||||||
})
|
})
|
||||||
|
using _ = defer(() => InstructionPrompt.clear(processor.message.id))
|
||||||
|
|
||||||
// Check if user explicitly invoked an agent via @ in this turn
|
// Check if user explicitly invoked an agent via @ in this turn
|
||||||
const lastUserMsg = msgs.findLast((m) => m.info.role === "user")
|
const lastUserMsg = msgs.findLast((m) => m.info.role === "user")
|
||||||
@@ -839,6 +840,7 @@ export namespace SessionPrompt {
|
|||||||
system: input.system,
|
system: input.system,
|
||||||
variant: input.variant,
|
variant: input.variant,
|
||||||
}
|
}
|
||||||
|
using _ = defer(() => InstructionPrompt.clear(info.id))
|
||||||
|
|
||||||
const parts = await Promise.all(
|
const parts = await Promise.all(
|
||||||
input.parts.map(async (part): Promise<MessageV2.Part[]> => {
|
input.parts.map(async (part): Promise<MessageV2.Part[]> => {
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export const ReadTool = Tool.define("read", {
|
|||||||
throw new Error(`File not found: ${filepath}`)
|
throw new Error(`File not found: ${filepath}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const instructions = await InstructionPrompt.resolve(ctx.messages, filepath)
|
const instructions = await InstructionPrompt.resolve(ctx.messages, filepath, ctx.messageID)
|
||||||
|
|
||||||
// Exclude SVG (XML-based) and vnd.fastbidsheet (.fbs extension, commonly FlatBuffers schema files)
|
// Exclude SVG (XML-based) and vnd.fastbidsheet (.fbs extension, commonly FlatBuffers schema files)
|
||||||
const isImage =
|
const isImage =
|
||||||
|
|||||||
@@ -159,8 +159,10 @@ export const TaskTool = Tool.define("task", async (ctx) => {
|
|||||||
...Object.fromEntries((config.experimental?.primary_tools ?? []).map((t) => [t, false])),
|
...Object.fromEntries((config.experimental?.primary_tools ?? []).map((t) => [t, false])),
|
||||||
},
|
},
|
||||||
parts: promptParts,
|
parts: promptParts,
|
||||||
|
}).finally(() => {
|
||||||
|
unsub()
|
||||||
})
|
})
|
||||||
unsub()
|
|
||||||
const messages = await Session.messages({ sessionID: session.id })
|
const messages = await Session.messages({ sessionID: session.id })
|
||||||
const summary = messages
|
const summary = messages
|
||||||
.filter((x) => x.info.role === "assistant")
|
.filter((x) => x.info.role === "assistant")
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ describe("InstructionPrompt.resolve", () => {
|
|||||||
const system = await InstructionPrompt.systemPaths()
|
const system = await InstructionPrompt.systemPaths()
|
||||||
expect(system.has(path.join(tmp.path, "AGENTS.md"))).toBe(true)
|
expect(system.has(path.join(tmp.path, "AGENTS.md"))).toBe(true)
|
||||||
|
|
||||||
const results = await InstructionPrompt.resolve([], path.join(tmp.path, "src", "file.ts"))
|
const results = await InstructionPrompt.resolve([], path.join(tmp.path, "src", "file.ts"), "test-message-1")
|
||||||
expect(results).toEqual([])
|
expect(results).toEqual([])
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -37,7 +37,11 @@ describe("InstructionPrompt.resolve", () => {
|
|||||||
const system = await InstructionPrompt.systemPaths()
|
const system = await InstructionPrompt.systemPaths()
|
||||||
expect(system.has(path.join(tmp.path, "subdir", "AGENTS.md"))).toBe(false)
|
expect(system.has(path.join(tmp.path, "subdir", "AGENTS.md"))).toBe(false)
|
||||||
|
|
||||||
const results = await InstructionPrompt.resolve([], path.join(tmp.path, "subdir", "nested", "file.ts"))
|
const results = await InstructionPrompt.resolve(
|
||||||
|
[],
|
||||||
|
path.join(tmp.path, "subdir", "nested", "file.ts"),
|
||||||
|
"test-message-2",
|
||||||
|
)
|
||||||
expect(results.length).toBe(1)
|
expect(results.length).toBe(1)
|
||||||
expect(results[0].filepath).toBe(path.join(tmp.path, "subdir", "AGENTS.md"))
|
expect(results[0].filepath).toBe(path.join(tmp.path, "subdir", "AGENTS.md"))
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode-ai/plugin",
|
"name": "@opencode-ai/plugin",
|
||||||
"version": "1.1.37",
|
"version": "1.1.40",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode-ai/sdk",
|
"name": "@opencode-ai/sdk",
|
||||||
"version": "1.1.37",
|
"version": "1.1.40",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import type {
|
|||||||
AuthSetErrors,
|
AuthSetErrors,
|
||||||
AuthSetResponses,
|
AuthSetResponses,
|
||||||
CommandListResponses,
|
CommandListResponses,
|
||||||
Config as Config2,
|
Config as Config3,
|
||||||
ConfigGetResponses,
|
ConfigGetResponses,
|
||||||
ConfigProvidersResponses,
|
ConfigProvidersResponses,
|
||||||
ConfigUpdateErrors,
|
ConfigUpdateErrors,
|
||||||
@@ -34,6 +34,9 @@ import type {
|
|||||||
FindSymbolsResponses,
|
FindSymbolsResponses,
|
||||||
FindTextResponses,
|
FindTextResponses,
|
||||||
FormatterStatusResponses,
|
FormatterStatusResponses,
|
||||||
|
GlobalConfigGetResponses,
|
||||||
|
GlobalConfigUpdateErrors,
|
||||||
|
GlobalConfigUpdateResponses,
|
||||||
GlobalDisposeResponses,
|
GlobalDisposeResponses,
|
||||||
GlobalEventResponses,
|
GlobalEventResponses,
|
||||||
GlobalHealthResponses,
|
GlobalHealthResponses,
|
||||||
@@ -215,6 +218,44 @@ class HeyApiRegistry<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class Config extends HeyApiClient {
|
||||||
|
/**
|
||||||
|
* Get global configuration
|
||||||
|
*
|
||||||
|
* Retrieve the current global OpenCode configuration settings and preferences.
|
||||||
|
*/
|
||||||
|
public get<ThrowOnError extends boolean = false>(options?: Options<never, ThrowOnError>) {
|
||||||
|
return (options?.client ?? this.client).get<GlobalConfigGetResponses, unknown, ThrowOnError>({
|
||||||
|
url: "/global/config",
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update global configuration
|
||||||
|
*
|
||||||
|
* Update global OpenCode configuration settings and preferences.
|
||||||
|
*/
|
||||||
|
public update<ThrowOnError extends boolean = false>(
|
||||||
|
parameters?: {
|
||||||
|
config?: Config3
|
||||||
|
},
|
||||||
|
options?: Options<never, ThrowOnError>,
|
||||||
|
) {
|
||||||
|
const params = buildClientParams([parameters], [{ args: [{ key: "config", map: "body" }] }])
|
||||||
|
return (options?.client ?? this.client).patch<GlobalConfigUpdateResponses, GlobalConfigUpdateErrors, ThrowOnError>({
|
||||||
|
url: "/global/config",
|
||||||
|
...options,
|
||||||
|
...params,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
...options?.headers,
|
||||||
|
...params.headers,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class Global extends HeyApiClient {
|
export class Global extends HeyApiClient {
|
||||||
/**
|
/**
|
||||||
* Get health
|
* Get health
|
||||||
@@ -251,6 +292,67 @@ export class Global extends HeyApiClient {
|
|||||||
...options,
|
...options,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _config?: Config
|
||||||
|
get config(): Config {
|
||||||
|
return (this._config ??= new Config({ client: this.client }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Auth extends HeyApiClient {
|
||||||
|
/**
|
||||||
|
* Remove auth credentials
|
||||||
|
*
|
||||||
|
* Remove authentication credentials
|
||||||
|
*/
|
||||||
|
public remove<ThrowOnError extends boolean = false>(
|
||||||
|
parameters: {
|
||||||
|
providerID: string
|
||||||
|
},
|
||||||
|
options?: Options<never, ThrowOnError>,
|
||||||
|
) {
|
||||||
|
const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "providerID" }] }])
|
||||||
|
return (options?.client ?? this.client).delete<AuthRemoveResponses, AuthRemoveErrors, ThrowOnError>({
|
||||||
|
url: "/auth/{providerID}",
|
||||||
|
...options,
|
||||||
|
...params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set auth credentials
|
||||||
|
*
|
||||||
|
* Set authentication credentials
|
||||||
|
*/
|
||||||
|
public set<ThrowOnError extends boolean = false>(
|
||||||
|
parameters: {
|
||||||
|
providerID: string
|
||||||
|
auth?: Auth3
|
||||||
|
},
|
||||||
|
options?: Options<never, ThrowOnError>,
|
||||||
|
) {
|
||||||
|
const params = buildClientParams(
|
||||||
|
[parameters],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
args: [
|
||||||
|
{ in: "path", key: "providerID" },
|
||||||
|
{ key: "auth", map: "body" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
return (options?.client ?? this.client).put<AuthSetResponses, AuthSetErrors, ThrowOnError>({
|
||||||
|
url: "/auth/{providerID}",
|
||||||
|
...options,
|
||||||
|
...params,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
...options?.headers,
|
||||||
|
...params.headers,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Project extends HeyApiClient {
|
export class Project extends HeyApiClient {
|
||||||
@@ -541,7 +643,7 @@ export class Pty extends HeyApiClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Config extends HeyApiClient {
|
export class Config2 extends HeyApiClient {
|
||||||
/**
|
/**
|
||||||
* Get configuration
|
* Get configuration
|
||||||
*
|
*
|
||||||
@@ -569,7 +671,7 @@ export class Config extends HeyApiClient {
|
|||||||
public update<ThrowOnError extends boolean = false>(
|
public update<ThrowOnError extends boolean = false>(
|
||||||
parameters?: {
|
parameters?: {
|
||||||
directory?: string
|
directory?: string
|
||||||
config?: Config2
|
config?: Config3
|
||||||
},
|
},
|
||||||
options?: Options<never, ThrowOnError>,
|
options?: Options<never, ThrowOnError>,
|
||||||
) {
|
) {
|
||||||
@@ -2238,7 +2340,7 @@ export class File extends HeyApiClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Auth extends HeyApiClient {
|
export class Auth2 extends HeyApiClient {
|
||||||
/**
|
/**
|
||||||
* Remove MCP OAuth
|
* Remove MCP OAuth
|
||||||
*
|
*
|
||||||
@@ -2482,9 +2584,9 @@ export class Mcp extends HeyApiClient {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private _auth?: Auth
|
private _auth?: Auth2
|
||||||
get auth(): Auth {
|
get auth(): Auth2 {
|
||||||
return (this._auth ??= new Auth({ client: this.client }))
|
return (this._auth ??= new Auth2({ client: this.client }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3055,75 +3157,6 @@ export class Formatter extends HeyApiClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Auth2 extends HeyApiClient {
|
|
||||||
/**
|
|
||||||
* Remove auth credentials
|
|
||||||
*
|
|
||||||
* Remove authentication credentials
|
|
||||||
*/
|
|
||||||
public remove<ThrowOnError extends boolean = false>(
|
|
||||||
parameters: {
|
|
||||||
providerID: string
|
|
||||||
directory?: string
|
|
||||||
},
|
|
||||||
options?: Options<never, ThrowOnError>,
|
|
||||||
) {
|
|
||||||
const params = buildClientParams(
|
|
||||||
[parameters],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
args: [
|
|
||||||
{ in: "path", key: "providerID" },
|
|
||||||
{ in: "query", key: "directory" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
)
|
|
||||||
return (options?.client ?? this.client).delete<AuthRemoveResponses, AuthRemoveErrors, ThrowOnError>({
|
|
||||||
url: "/auth/{providerID}",
|
|
||||||
...options,
|
|
||||||
...params,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set auth credentials
|
|
||||||
*
|
|
||||||
* Set authentication credentials
|
|
||||||
*/
|
|
||||||
public set<ThrowOnError extends boolean = false>(
|
|
||||||
parameters: {
|
|
||||||
providerID: string
|
|
||||||
directory?: string
|
|
||||||
auth?: Auth3
|
|
||||||
},
|
|
||||||
options?: Options<never, ThrowOnError>,
|
|
||||||
) {
|
|
||||||
const params = buildClientParams(
|
|
||||||
[parameters],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
args: [
|
|
||||||
{ in: "path", key: "providerID" },
|
|
||||||
{ in: "query", key: "directory" },
|
|
||||||
{ key: "auth", map: "body" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
)
|
|
||||||
return (options?.client ?? this.client).put<AuthSetResponses, AuthSetErrors, ThrowOnError>({
|
|
||||||
url: "/auth/{providerID}",
|
|
||||||
...options,
|
|
||||||
...params,
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
...options?.headers,
|
|
||||||
...params.headers,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Event extends HeyApiClient {
|
export class Event extends HeyApiClient {
|
||||||
/**
|
/**
|
||||||
* Subscribe to events
|
* Subscribe to events
|
||||||
@@ -3158,6 +3191,11 @@ export class OpencodeClient extends HeyApiClient {
|
|||||||
return (this._global ??= new Global({ client: this.client }))
|
return (this._global ??= new Global({ client: this.client }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _auth?: Auth
|
||||||
|
get auth(): Auth {
|
||||||
|
return (this._auth ??= new Auth({ client: this.client }))
|
||||||
|
}
|
||||||
|
|
||||||
private _project?: Project
|
private _project?: Project
|
||||||
get project(): Project {
|
get project(): Project {
|
||||||
return (this._project ??= new Project({ client: this.client }))
|
return (this._project ??= new Project({ client: this.client }))
|
||||||
@@ -3168,9 +3206,9 @@ export class OpencodeClient extends HeyApiClient {
|
|||||||
return (this._pty ??= new Pty({ client: this.client }))
|
return (this._pty ??= new Pty({ client: this.client }))
|
||||||
}
|
}
|
||||||
|
|
||||||
private _config?: Config
|
private _config?: Config2
|
||||||
get config(): Config {
|
get config(): Config2 {
|
||||||
return (this._config ??= new Config({ client: this.client }))
|
return (this._config ??= new Config2({ client: this.client }))
|
||||||
}
|
}
|
||||||
|
|
||||||
private _tool?: Tool
|
private _tool?: Tool
|
||||||
@@ -3268,11 +3306,6 @@ export class OpencodeClient extends HeyApiClient {
|
|||||||
return (this._formatter ??= new Formatter({ client: this.client }))
|
return (this._formatter ??= new Formatter({ client: this.client }))
|
||||||
}
|
}
|
||||||
|
|
||||||
private _auth?: Auth2
|
|
||||||
get auth(): Auth2 {
|
|
||||||
return (this._auth ??= new Auth2({ client: this.client }))
|
|
||||||
}
|
|
||||||
|
|
||||||
private _event?: Event
|
private _event?: Event
|
||||||
get event(): Event {
|
get event(): Event {
|
||||||
return (this._event ??= new Event({ client: this.client }))
|
return (this._event ??= new Event({ client: this.client }))
|
||||||
|
|||||||
@@ -233,6 +233,21 @@ export type TextPart = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SubtaskPart = {
|
||||||
|
id: string
|
||||||
|
sessionID: string
|
||||||
|
messageID: string
|
||||||
|
type: "subtask"
|
||||||
|
prompt: string
|
||||||
|
description: string
|
||||||
|
agent: string
|
||||||
|
model?: {
|
||||||
|
providerID: string
|
||||||
|
modelID: string
|
||||||
|
}
|
||||||
|
command?: string
|
||||||
|
}
|
||||||
|
|
||||||
export type ReasoningPart = {
|
export type ReasoningPart = {
|
||||||
id: string
|
id: string
|
||||||
sessionID: string
|
sessionID: string
|
||||||
@@ -449,20 +464,7 @@ export type CompactionPart = {
|
|||||||
|
|
||||||
export type Part =
|
export type Part =
|
||||||
| TextPart
|
| TextPart
|
||||||
| {
|
| SubtaskPart
|
||||||
id: string
|
|
||||||
sessionID: string
|
|
||||||
messageID: string
|
|
||||||
type: "subtask"
|
|
||||||
prompt: string
|
|
||||||
description: string
|
|
||||||
agent: string
|
|
||||||
model?: {
|
|
||||||
providerID: string
|
|
||||||
modelID: string
|
|
||||||
}
|
|
||||||
command?: string
|
|
||||||
}
|
|
||||||
| ReasoningPart
|
| ReasoningPart
|
||||||
| FilePart
|
| FilePart
|
||||||
| ToolPart
|
| ToolPart
|
||||||
@@ -930,21 +932,6 @@ export type GlobalEvent = {
|
|||||||
payload: Event
|
payload: Event
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BadRequestError = {
|
|
||||||
data: unknown
|
|
||||||
errors: Array<{
|
|
||||||
[key: string]: unknown
|
|
||||||
}>
|
|
||||||
success: false
|
|
||||||
}
|
|
||||||
|
|
||||||
export type NotFoundError = {
|
|
||||||
name: "NotFoundError"
|
|
||||||
data: {
|
|
||||||
message: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom keybind configurations
|
* Custom keybind configurations
|
||||||
*/
|
*/
|
||||||
@@ -1826,6 +1813,43 @@ export type Config = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type BadRequestError = {
|
||||||
|
data: unknown
|
||||||
|
errors: Array<{
|
||||||
|
[key: string]: unknown
|
||||||
|
}>
|
||||||
|
success: false
|
||||||
|
}
|
||||||
|
|
||||||
|
export type OAuth = {
|
||||||
|
type: "oauth"
|
||||||
|
refresh: string
|
||||||
|
access: string
|
||||||
|
expires: number
|
||||||
|
accountId?: string
|
||||||
|
enterpriseUrl?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ApiAuth = {
|
||||||
|
type: "api"
|
||||||
|
key: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WellKnownAuth = {
|
||||||
|
type: "wellknown"
|
||||||
|
key: string
|
||||||
|
token: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Auth = OAuth | ApiAuth | WellKnownAuth
|
||||||
|
|
||||||
|
export type NotFoundError = {
|
||||||
|
name: "NotFoundError"
|
||||||
|
data: {
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export type Model = {
|
export type Model = {
|
||||||
id: string
|
id: string
|
||||||
providerID: string
|
providerID: string
|
||||||
@@ -2142,28 +2166,6 @@ export type FormatterStatus = {
|
|||||||
enabled: boolean
|
enabled: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type OAuth = {
|
|
||||||
type: "oauth"
|
|
||||||
refresh: string
|
|
||||||
access: string
|
|
||||||
expires: number
|
|
||||||
accountId?: string
|
|
||||||
enterpriseUrl?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ApiAuth = {
|
|
||||||
type: "api"
|
|
||||||
key: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type WellKnownAuth = {
|
|
||||||
type: "wellknown"
|
|
||||||
key: string
|
|
||||||
token: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Auth = OAuth | ApiAuth | WellKnownAuth
|
|
||||||
|
|
||||||
export type GlobalHealthData = {
|
export type GlobalHealthData = {
|
||||||
body?: never
|
body?: never
|
||||||
path?: never
|
path?: never
|
||||||
@@ -2199,6 +2201,47 @@ export type GlobalEventResponses = {
|
|||||||
|
|
||||||
export type GlobalEventResponse = GlobalEventResponses[keyof GlobalEventResponses]
|
export type GlobalEventResponse = GlobalEventResponses[keyof GlobalEventResponses]
|
||||||
|
|
||||||
|
export type GlobalConfigGetData = {
|
||||||
|
body?: never
|
||||||
|
path?: never
|
||||||
|
query?: never
|
||||||
|
url: "/global/config"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GlobalConfigGetResponses = {
|
||||||
|
/**
|
||||||
|
* Get global config info
|
||||||
|
*/
|
||||||
|
200: Config
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GlobalConfigGetResponse = GlobalConfigGetResponses[keyof GlobalConfigGetResponses]
|
||||||
|
|
||||||
|
export type GlobalConfigUpdateData = {
|
||||||
|
body?: Config
|
||||||
|
path?: never
|
||||||
|
query?: never
|
||||||
|
url: "/global/config"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GlobalConfigUpdateErrors = {
|
||||||
|
/**
|
||||||
|
* Bad request
|
||||||
|
*/
|
||||||
|
400: BadRequestError
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GlobalConfigUpdateError = GlobalConfigUpdateErrors[keyof GlobalConfigUpdateErrors]
|
||||||
|
|
||||||
|
export type GlobalConfigUpdateResponses = {
|
||||||
|
/**
|
||||||
|
* Successfully updated global config
|
||||||
|
*/
|
||||||
|
200: Config
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GlobalConfigUpdateResponse = GlobalConfigUpdateResponses[keyof GlobalConfigUpdateResponses]
|
||||||
|
|
||||||
export type GlobalDisposeData = {
|
export type GlobalDisposeData = {
|
||||||
body?: never
|
body?: never
|
||||||
path?: never
|
path?: never
|
||||||
@@ -2215,6 +2258,60 @@ export type GlobalDisposeResponses = {
|
|||||||
|
|
||||||
export type GlobalDisposeResponse = GlobalDisposeResponses[keyof GlobalDisposeResponses]
|
export type GlobalDisposeResponse = GlobalDisposeResponses[keyof GlobalDisposeResponses]
|
||||||
|
|
||||||
|
export type AuthRemoveData = {
|
||||||
|
body?: never
|
||||||
|
path: {
|
||||||
|
providerID: string
|
||||||
|
}
|
||||||
|
query?: never
|
||||||
|
url: "/auth/{providerID}"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AuthRemoveErrors = {
|
||||||
|
/**
|
||||||
|
* Bad request
|
||||||
|
*/
|
||||||
|
400: BadRequestError
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AuthRemoveError = AuthRemoveErrors[keyof AuthRemoveErrors]
|
||||||
|
|
||||||
|
export type AuthRemoveResponses = {
|
||||||
|
/**
|
||||||
|
* Successfully removed authentication credentials
|
||||||
|
*/
|
||||||
|
200: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AuthRemoveResponse = AuthRemoveResponses[keyof AuthRemoveResponses]
|
||||||
|
|
||||||
|
export type AuthSetData = {
|
||||||
|
body?: Auth
|
||||||
|
path: {
|
||||||
|
providerID: string
|
||||||
|
}
|
||||||
|
query?: never
|
||||||
|
url: "/auth/{providerID}"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AuthSetErrors = {
|
||||||
|
/**
|
||||||
|
* Bad request
|
||||||
|
*/
|
||||||
|
400: BadRequestError
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AuthSetError = AuthSetErrors[keyof AuthSetErrors]
|
||||||
|
|
||||||
|
export type AuthSetResponses = {
|
||||||
|
/**
|
||||||
|
* Successfully set authentication credentials
|
||||||
|
*/
|
||||||
|
200: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AuthSetResponse = AuthSetResponses[keyof AuthSetResponses]
|
||||||
|
|
||||||
export type ProjectListData = {
|
export type ProjectListData = {
|
||||||
body?: never
|
body?: never
|
||||||
path?: never
|
path?: never
|
||||||
@@ -4867,64 +4964,6 @@ export type FormatterStatusResponses = {
|
|||||||
|
|
||||||
export type FormatterStatusResponse = FormatterStatusResponses[keyof FormatterStatusResponses]
|
export type FormatterStatusResponse = FormatterStatusResponses[keyof FormatterStatusResponses]
|
||||||
|
|
||||||
export type AuthRemoveData = {
|
|
||||||
body?: never
|
|
||||||
path: {
|
|
||||||
providerID: string
|
|
||||||
}
|
|
||||||
query?: {
|
|
||||||
directory?: string
|
|
||||||
}
|
|
||||||
url: "/auth/{providerID}"
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AuthRemoveErrors = {
|
|
||||||
/**
|
|
||||||
* Bad request
|
|
||||||
*/
|
|
||||||
400: BadRequestError
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AuthRemoveError = AuthRemoveErrors[keyof AuthRemoveErrors]
|
|
||||||
|
|
||||||
export type AuthRemoveResponses = {
|
|
||||||
/**
|
|
||||||
* Successfully removed authentication credentials
|
|
||||||
*/
|
|
||||||
200: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AuthRemoveResponse = AuthRemoveResponses[keyof AuthRemoveResponses]
|
|
||||||
|
|
||||||
export type AuthSetData = {
|
|
||||||
body?: Auth
|
|
||||||
path: {
|
|
||||||
providerID: string
|
|
||||||
}
|
|
||||||
query?: {
|
|
||||||
directory?: string
|
|
||||||
}
|
|
||||||
url: "/auth/{providerID}"
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AuthSetErrors = {
|
|
||||||
/**
|
|
||||||
* Bad request
|
|
||||||
*/
|
|
||||||
400: BadRequestError
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AuthSetError = AuthSetErrors[keyof AuthSetErrors]
|
|
||||||
|
|
||||||
export type AuthSetResponses = {
|
|
||||||
/**
|
|
||||||
* Successfully set authentication credentials
|
|
||||||
*/
|
|
||||||
200: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AuthSetResponse = AuthSetResponses[keyof AuthSetResponses]
|
|
||||||
|
|
||||||
export type EventSubscribeData = {
|
export type EventSubscribeData = {
|
||||||
body?: never
|
body?: never
|
||||||
path?: never
|
path?: never
|
||||||
|
|||||||
+315
-259
@@ -66,6 +66,73 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/global/config": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "global.config.get",
|
||||||
|
"summary": "Get global configuration",
|
||||||
|
"description": "Retrieve the current global OpenCode configuration settings and preferences.",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Get global config info",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Config"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-codeSamples": [
|
||||||
|
{
|
||||||
|
"lang": "js",
|
||||||
|
"source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.global.config.get({\n ...\n})"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"patch": {
|
||||||
|
"operationId": "global.config.update",
|
||||||
|
"summary": "Update global configuration",
|
||||||
|
"description": "Update global OpenCode configuration settings and preferences.",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successfully updated global config",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Config"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/BadRequestError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Config"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-codeSamples": [
|
||||||
|
{
|
||||||
|
"lang": "js",
|
||||||
|
"source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.global.config.update({\n ...\n})"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/global/dispose": {
|
"/global/dispose": {
|
||||||
"post": {
|
"post": {
|
||||||
"operationId": "global.dispose",
|
"operationId": "global.dispose",
|
||||||
@@ -91,6 +158,103 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/auth/{providerID}": {
|
||||||
|
"put": {
|
||||||
|
"operationId": "auth.set",
|
||||||
|
"summary": "Set auth credentials",
|
||||||
|
"description": "Set authentication credentials",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successfully set authentication credentials",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/BadRequestError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "providerID",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Auth"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-codeSamples": [
|
||||||
|
{
|
||||||
|
"lang": "js",
|
||||||
|
"source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.auth.set({\n ...\n})"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"operationId": "auth.remove",
|
||||||
|
"summary": "Remove auth credentials",
|
||||||
|
"description": "Remove authentication credentials",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successfully removed authentication credentials",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/BadRequestError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "providerID",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"x-codeSamples": [
|
||||||
|
{
|
||||||
|
"lang": "js",
|
||||||
|
"source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.auth.remove({\n ...\n})"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/project": {
|
"/project": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "project.list",
|
"operationId": "project.list",
|
||||||
@@ -5650,117 +5814,6 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/auth/{providerID}": {
|
|
||||||
"put": {
|
|
||||||
"operationId": "auth.set",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"in": "query",
|
|
||||||
"name": "directory",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"in": "path",
|
|
||||||
"name": "providerID",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"summary": "Set auth credentials",
|
|
||||||
"description": "Set authentication credentials",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Successfully set authentication credentials",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Bad request",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/BadRequestError"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"requestBody": {
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/Auth"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"x-codeSamples": [
|
|
||||||
{
|
|
||||||
"lang": "js",
|
|
||||||
"source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.auth.set({\n ...\n})"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"delete": {
|
|
||||||
"operationId": "auth.remove",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"in": "query",
|
|
||||||
"name": "directory",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"in": "path",
|
|
||||||
"name": "providerID",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"summary": "Remove auth credentials",
|
|
||||||
"description": "Remove authentication credentials",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Successfully removed authentication credentials",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Bad request",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/BadRequestError"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"x-codeSamples": [
|
|
||||||
{
|
|
||||||
"lang": "js",
|
|
||||||
"source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.auth.remove({\n ...\n})"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/event": {
|
"/event": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "event.subscribe",
|
"operationId": "event.subscribe",
|
||||||
@@ -6449,6 +6502,49 @@
|
|||||||
},
|
},
|
||||||
"required": ["id", "sessionID", "messageID", "type", "text"]
|
"required": ["id", "sessionID", "messageID", "type", "text"]
|
||||||
},
|
},
|
||||||
|
"SubtaskPart": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"sessionID": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"messageID": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"const": "subtask"
|
||||||
|
},
|
||||||
|
"prompt": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"agent": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"providerID": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"modelID": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["providerID", "modelID"]
|
||||||
|
},
|
||||||
|
"command": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["id", "sessionID", "messageID", "type", "prompt", "description", "agent"]
|
||||||
|
},
|
||||||
"ReasoningPart": {
|
"ReasoningPart": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -7072,47 +7168,7 @@
|
|||||||
"$ref": "#/components/schemas/TextPart"
|
"$ref": "#/components/schemas/TextPart"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "object",
|
"$ref": "#/components/schemas/SubtaskPart"
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"sessionID": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"messageID": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"type": {
|
|
||||||
"type": "string",
|
|
||||||
"const": "subtask"
|
|
||||||
},
|
|
||||||
"prompt": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"description": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"agent": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"model": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"providerID": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"modelID": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["providerID", "modelID"]
|
|
||||||
},
|
|
||||||
"command": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["id", "sessionID", "messageID", "type", "prompt", "description", "agent"]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "#/components/schemas/ReasoningPart"
|
"$ref": "#/components/schemas/ReasoningPart"
|
||||||
@@ -8352,46 +8408,6 @@
|
|||||||
},
|
},
|
||||||
"required": ["directory", "payload"]
|
"required": ["directory", "payload"]
|
||||||
},
|
},
|
||||||
"BadRequestError": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"data": {},
|
|
||||||
"errors": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "object",
|
|
||||||
"propertyNames": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"additionalProperties": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"success": {
|
|
||||||
"type": "boolean",
|
|
||||||
"const": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["data", "errors", "success"]
|
|
||||||
},
|
|
||||||
"NotFoundError": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"name": {
|
|
||||||
"type": "string",
|
|
||||||
"const": "NotFoundError"
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"message": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["message"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["name", "data"]
|
|
||||||
},
|
|
||||||
"KeybindsConfig": {
|
"KeybindsConfig": {
|
||||||
"description": "Custom keybind configurations",
|
"description": "Custom keybind configurations",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -9898,6 +9914,113 @@
|
|||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
|
"BadRequestError": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {},
|
||||||
|
"errors": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"propertyNames": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"additionalProperties": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"type": "boolean",
|
||||||
|
"const": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["data", "errors", "success"]
|
||||||
|
},
|
||||||
|
"OAuth": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"const": "oauth"
|
||||||
|
},
|
||||||
|
"refresh": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"access": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"expires": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"accountId": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"enterpriseUrl": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["type", "refresh", "access", "expires"]
|
||||||
|
},
|
||||||
|
"ApiAuth": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"const": "api"
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["type", "key"]
|
||||||
|
},
|
||||||
|
"WellKnownAuth": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"const": "wellknown"
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["type", "key", "token"]
|
||||||
|
},
|
||||||
|
"Auth": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/OAuth"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/ApiAuth"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/WellKnownAuth"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"NotFoundError": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"const": "NotFoundError"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["message"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["name", "data"]
|
||||||
|
},
|
||||||
"Model": {
|
"Model": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -10824,73 +10947,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["name", "extensions", "enabled"]
|
"required": ["name", "extensions", "enabled"]
|
||||||
},
|
|
||||||
"OAuth": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"type": {
|
|
||||||
"type": "string",
|
|
||||||
"const": "oauth"
|
|
||||||
},
|
|
||||||
"refresh": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"access": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"expires": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"accountId": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"enterpriseUrl": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["type", "refresh", "access", "expires"]
|
|
||||||
},
|
|
||||||
"ApiAuth": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"type": {
|
|
||||||
"type": "string",
|
|
||||||
"const": "api"
|
|
||||||
},
|
|
||||||
"key": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["type", "key"]
|
|
||||||
},
|
|
||||||
"WellKnownAuth": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"type": {
|
|
||||||
"type": "string",
|
|
||||||
"const": "wellknown"
|
|
||||||
},
|
|
||||||
"key": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"token": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["type", "key", "token"]
|
|
||||||
},
|
|
||||||
"Auth": {
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/OAuth"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/ApiAuth"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/WellKnownAuth"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/slack",
|
"name": "@opencode-ai/slack",
|
||||||
"version": "1.1.37",
|
"version": "1.1.40",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/ui",
|
"name": "@opencode-ai/ui",
|
||||||
"version": "1.1.37",
|
"version": "1.1.40",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
export const dict = {
|
||||||
|
"ui.sessionReview.title": "การเปลี่ยนแปลงเซสชัน",
|
||||||
|
"ui.sessionReview.diffStyle.unified": "แบบรวม",
|
||||||
|
"ui.sessionReview.diffStyle.split": "แบบแยก",
|
||||||
|
"ui.sessionReview.expandAll": "ขยายทั้งหมด",
|
||||||
|
"ui.sessionReview.collapseAll": "ย่อทั้งหมด",
|
||||||
|
"ui.sessionReview.change.added": "เพิ่ม",
|
||||||
|
"ui.sessionReview.change.removed": "ลบ",
|
||||||
|
|
||||||
|
"ui.lineComment.label.prefix": "แสดงความคิดเห็นบน ",
|
||||||
|
"ui.lineComment.label.suffix": "",
|
||||||
|
"ui.lineComment.editorLabel.prefix": "กำลังแสดงความคิดเห็นบน ",
|
||||||
|
"ui.lineComment.editorLabel.suffix": "",
|
||||||
|
"ui.lineComment.placeholder": "เพิ่มความคิดเห็น",
|
||||||
|
"ui.lineComment.submit": "แสดงความคิดเห็น",
|
||||||
|
|
||||||
|
"ui.sessionTurn.steps.show": "แสดงขั้นตอน",
|
||||||
|
"ui.sessionTurn.steps.hide": "ซ่อนขั้นตอน",
|
||||||
|
"ui.sessionTurn.summary.response": "การตอบสนอง",
|
||||||
|
"ui.sessionTurn.diff.showMore": "แสดงการเปลี่ยนแปลงเพิ่มเติม ({{count}})",
|
||||||
|
|
||||||
|
"ui.sessionTurn.retry.retrying": "กำลังลองใหม่",
|
||||||
|
"ui.sessionTurn.retry.inSeconds": "ใน {{seconds}}วิ",
|
||||||
|
|
||||||
|
"ui.sessionTurn.status.delegating": "มอบหมายงาน",
|
||||||
|
"ui.sessionTurn.status.planning": "วางแผนขั้นตอนถัดไป",
|
||||||
|
"ui.sessionTurn.status.gatheringContext": "รวบรวมบริบท",
|
||||||
|
"ui.sessionTurn.status.searchingCodebase": "กำลังค้นหาโค้ดเบส",
|
||||||
|
"ui.sessionTurn.status.searchingWeb": "กำลังค้นหาบนเว็บ",
|
||||||
|
"ui.sessionTurn.status.makingEdits": "กำลังแก้ไข",
|
||||||
|
"ui.sessionTurn.status.runningCommands": "กำลังเรียกใช้คำสั่ง",
|
||||||
|
"ui.sessionTurn.status.thinking": "กำลังคิด",
|
||||||
|
"ui.sessionTurn.status.thinkingWithTopic": "กำลังคิด - {{topic}}",
|
||||||
|
"ui.sessionTurn.status.gatheringThoughts": "รวบรวมความคิด",
|
||||||
|
"ui.sessionTurn.status.consideringNextSteps": "พิจารณาขั้นตอนถัดไป",
|
||||||
|
|
||||||
|
"ui.messagePart.diagnostic.error": "ข้อผิดพลาด",
|
||||||
|
"ui.messagePart.title.edit": "แก้ไข",
|
||||||
|
"ui.messagePart.title.write": "เขียน",
|
||||||
|
"ui.messagePart.option.typeOwnAnswer": "พิมพ์คำตอบของคุณเอง",
|
||||||
|
"ui.messagePart.review.title": "ตรวจสอบคำตอบของคุณ",
|
||||||
|
|
||||||
|
"ui.list.loading": "กำลังโหลด",
|
||||||
|
"ui.list.empty": "ไม่มีผลลัพธ์",
|
||||||
|
"ui.list.clearFilter": "ล้างตัวกรอง",
|
||||||
|
"ui.list.emptyWithFilter.prefix": "ไม่มีผลลัพธ์สำหรับ",
|
||||||
|
"ui.list.emptyWithFilter.suffix": "",
|
||||||
|
|
||||||
|
"ui.messageNav.newMessage": "ข้อความใหม่",
|
||||||
|
|
||||||
|
"ui.textField.copyToClipboard": "คัดลอกไปยังคลิปบอร์ด",
|
||||||
|
"ui.textField.copyLink": "คัดลอกลิงก์",
|
||||||
|
"ui.textField.copied": "คัดลอกแล้ว",
|
||||||
|
|
||||||
|
"ui.imagePreview.alt": "ตัวอย่างรูปภาพ",
|
||||||
|
|
||||||
|
"ui.tool.read": "อ่าน",
|
||||||
|
"ui.tool.list": "รายการ",
|
||||||
|
"ui.tool.glob": "Glob",
|
||||||
|
"ui.tool.grep": "Grep",
|
||||||
|
"ui.tool.webfetch": "ดึงจากเว็บ",
|
||||||
|
"ui.tool.shell": "เชลล์",
|
||||||
|
"ui.tool.patch": "แพตช์",
|
||||||
|
"ui.tool.todos": "รายการงาน",
|
||||||
|
"ui.tool.todos.read": "อ่านรายการงาน",
|
||||||
|
"ui.tool.questions": "คำถาม",
|
||||||
|
"ui.tool.agent": "เอเจนต์ {{type}}",
|
||||||
|
|
||||||
|
"ui.common.file.one": "ไฟล์",
|
||||||
|
"ui.common.file.other": "ไฟล์",
|
||||||
|
"ui.common.question.one": "คำถาม",
|
||||||
|
"ui.common.question.other": "คำถาม",
|
||||||
|
|
||||||
|
"ui.common.add": "เพิ่ม",
|
||||||
|
"ui.common.cancel": "ยกเลิก",
|
||||||
|
"ui.common.confirm": "ยืนยัน",
|
||||||
|
"ui.common.dismiss": "ปิด",
|
||||||
|
"ui.common.close": "ปิด",
|
||||||
|
"ui.common.next": "ถัดไป",
|
||||||
|
"ui.common.submit": "ส่ง",
|
||||||
|
|
||||||
|
"ui.permission.deny": "ปฏิเสธ",
|
||||||
|
"ui.permission.allowAlways": "อนุญาตเสมอ",
|
||||||
|
"ui.permission.allowOnce": "อนุญาตครั้งเดียว",
|
||||||
|
|
||||||
|
"ui.message.expand": "ขยายข้อความ",
|
||||||
|
"ui.message.collapse": "ย่อข้อความ",
|
||||||
|
"ui.message.copy": "คัดลอก",
|
||||||
|
"ui.message.copied": "คัดลอกแล้ว!",
|
||||||
|
"ui.message.attachment.alt": "ไฟล์แนบ",
|
||||||
|
|
||||||
|
"ui.patch.action.deleted": "ลบ",
|
||||||
|
"ui.patch.action.created": "สร้าง",
|
||||||
|
"ui.patch.action.moved": "ย้าย",
|
||||||
|
"ui.patch.action.patched": "แพตช์",
|
||||||
|
|
||||||
|
"ui.question.subtitle.answered": "{{count}} ตอบแล้ว",
|
||||||
|
"ui.question.answer.none": "(ไม่มีคำตอบ)",
|
||||||
|
"ui.question.review.notAnswered": "(ไม่ได้ตอบ)",
|
||||||
|
"ui.question.multiHint": "(เลือกทั้งหมดที่ใช้)",
|
||||||
|
"ui.question.custom.placeholder": "พิมพ์คำตอบของคุณ...",
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/util",
|
"name": "@opencode-ai/util",
|
||||||
"version": "1.1.37",
|
"version": "1.1.40",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "@opencode-ai/web",
|
"name": "@opencode-ai/web",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"version": "1.1.37",
|
"version": "1.1.40",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "astro dev",
|
"dev": "astro dev",
|
||||||
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",
|
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",
|
||||||
|
|||||||
@@ -15,37 +15,38 @@ You can also check out [awesome-opencode](https://github.com/awesome-opencode/aw
|
|||||||
|
|
||||||
## Plugins
|
## Plugins
|
||||||
|
|
||||||
| Name | Description |
|
| Name | Description |
|
||||||
| -------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
|
| -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
|
||||||
| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | Automatically inject Helicone session headers for request grouping |
|
| [opencode-daytona](https://github.com/jamesmurdza/daytona/tree/main/libs/opencode-plugin) | Automatically run OpenCode sessions in isolated Daytona sandboxes with git sync and live previews |
|
||||||
| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | Auto-inject TypeScript/Svelte types into file reads with lookup tools |
|
| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | Automatically inject Helicone session headers for request grouping |
|
||||||
| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | Use your ChatGPT Plus/Pro subscription instead of API credits |
|
| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | Auto-inject TypeScript/Svelte types into file reads with lookup tools |
|
||||||
| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | Use your existing Gemini plan instead of API billing |
|
| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | Use your ChatGPT Plus/Pro subscription instead of API credits |
|
||||||
| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | Use Antigravity's free models instead of API billing |
|
| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | Use your existing Gemini plan instead of API billing |
|
||||||
| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | Multi-branch devcontainer isolation with shallow clones and auto-assigned ports |
|
| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | Use Antigravity's free models instead of API billing |
|
||||||
| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Google Antigravity OAuth Plugin, with support for Google Search, and more robust API handling |
|
| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | Multi-branch devcontainer isolation with shallow clones and auto-assigned ports |
|
||||||
| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | Optimize token usage by pruning obsolete tool outputs |
|
| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Google Antigravity OAuth Plugin, with support for Google Search, and more robust API handling |
|
||||||
| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | Add native websearch support for supported providers with Google grounded style |
|
| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | Optimize token usage by pruning obsolete tool outputs |
|
||||||
| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | Enables AI agents to run background processes in a PTY, send interactive input to them. |
|
| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | Add native websearch support for supported providers with Google grounded style |
|
||||||
| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instructions for non-interactive shell commands - prevents hangs from TTY-dependent operations |
|
| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | Enables AI agents to run background processes in a PTY, send interactive input to them. |
|
||||||
| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Track OpenCode usage with Wakatime |
|
| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instructions for non-interactive shell commands - prevents hangs from TTY-dependent operations |
|
||||||
| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Clean up markdown tables produced by LLMs |
|
| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Track OpenCode usage with Wakatime |
|
||||||
| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 10x faster code editing with Morph Fast Apply API and lazy edit markers |
|
| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Clean up markdown tables produced by LLMs |
|
||||||
| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Background agents, pre-built LSP/AST/MCP tools, curated agents, Claude Code compatible |
|
| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 10x faster code editing with Morph Fast Apply API and lazy edit markers |
|
||||||
| [opencode-notificator](https://github.com/panta82/opencode-notificator) | Desktop notifications and sound alerts for OpenCode sessions |
|
| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Background agents, pre-built LSP/AST/MCP tools, curated agents, Claude Code compatible |
|
||||||
| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Desktop notifications and sound alerts for permission, completion, and error events |
|
| [opencode-notificator](https://github.com/panta82/opencode-notificator) | Desktop notifications and sound alerts for OpenCode sessions |
|
||||||
| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | AI-powered automatic Zellij session naming based on OpenCode context |
|
| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Desktop notifications and sound alerts for permission, completion, and error events |
|
||||||
| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | Allow OpenCode agents to lazy load prompts on demand with skill discovery and injection |
|
| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | AI-powered automatic Zellij session naming based on OpenCode context |
|
||||||
| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | Persistent memory across sessions using Supermemory |
|
| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | Allow OpenCode agents to lazy load prompts on demand with skill discovery and injection |
|
||||||
| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | Interactive plan review with visual annotation and private/offline sharing |
|
| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | Persistent memory across sessions using Supermemory |
|
||||||
| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | Extend opencode /commands into a powerful orchestration system with granular flow control |
|
| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | Interactive plan review with visual annotation and private/offline sharing |
|
||||||
| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | Schedule recurring jobs using launchd (Mac) or systemd (Linux) with cron syntax |
|
| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | Extend opencode /commands into a powerful orchestration system with granular flow control |
|
||||||
| [micode](https://github.com/vtemian/micode) | Structured Brainstorm → Plan → Implement workflow with session continuity |
|
| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | Schedule recurring jobs using launchd (Mac) or systemd (Linux) with cron syntax |
|
||||||
| [octto](https://github.com/vtemian/octto) | Interactive browser UI for AI brainstorming with multi-question forms |
|
| [micode](https://github.com/vtemian/micode) | Structured Brainstorm → Plan → Implement workflow with session continuity |
|
||||||
| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Claude Code-style background agents with async delegation and context persistence |
|
| [octto](https://github.com/vtemian/octto) | Interactive browser UI for AI brainstorming with multi-question forms |
|
||||||
| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | Native OS notifications for OpenCode – know when tasks complete |
|
| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Claude Code-style background agents with async delegation and context persistence |
|
||||||
| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | Bundled multi-agent orchestration harness – 16 components, one install |
|
| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | Native OS notifications for OpenCode – know when tasks complete |
|
||||||
| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | Zero-friction git worktrees for OpenCode |
|
| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | Bundled multi-agent orchestration harness – 16 components, one install |
|
||||||
|
| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | Zero-friction git worktrees for OpenCode |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ We support a pay-as-you-go model. Below are the prices **per 1M tokens**.
|
|||||||
| MiniMax M2.1 | $0.30 | $1.20 | $0.10 | - |
|
| MiniMax M2.1 | $0.30 | $1.20 | $0.10 | - |
|
||||||
| GLM 4.7 | $0.60 | $2.20 | $0.10 | - |
|
| GLM 4.7 | $0.60 | $2.20 | $0.10 | - |
|
||||||
| GLM 4.6 | $0.60 | $2.20 | $0.10 | - |
|
| GLM 4.6 | $0.60 | $2.20 | $0.10 | - |
|
||||||
| Kimi K2.5 | $1.20 | $1.20 | $0.60 | - |
|
| Kimi K2.5 | $0.60 | $3.00 | $0.10 | - |
|
||||||
| Kimi K2 Thinking | $0.40 | $2.50 | - | - |
|
| Kimi K2 Thinking | $0.40 | $2.50 | - | - |
|
||||||
| Kimi K2 | $0.40 | $2.50 | - | - |
|
| Kimi K2 | $0.40 | $2.50 | - | - |
|
||||||
| Qwen3 Coder 480B | $0.45 | $1.50 | - | - |
|
| Qwen3 Coder 480B | $0.45 | $1.50 | - | - |
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
#!/usr/bin/env bun
|
#!/usr/bin/env bun
|
||||||
|
|
||||||
// import { Script } from "@opencode-ai/script"
|
import { Script } from "@opencode-ai/script"
|
||||||
import { $ } from "bun"
|
import { $ } from "bun"
|
||||||
|
|
||||||
// if (!Script.preview) {
|
if (!Script.preview) {
|
||||||
// await $`gh release edit v${Script.version} --draft=false`
|
await $`gh release edit v${Script.version} --draft=false`
|
||||||
// }
|
}
|
||||||
|
|
||||||
await $`bun install`
|
await $`bun install`
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"displayName": "opencode",
|
"displayName": "opencode",
|
||||||
"description": "opencode for VS Code",
|
"description": "opencode for VS Code",
|
||||||
"version": "1.1.37",
|
"version": "1.1.40",
|
||||||
"publisher": "sst-dev",
|
"publisher": "sst-dev",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
Reference in New Issue
Block a user