Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e05c3b7a76 | ||
|
|
f99904bc1c | ||
|
|
b796d6763f | ||
|
|
c1250abdf8 | ||
|
|
ebe51534a1 | ||
|
|
b8bbee4718 | ||
|
|
8f852b396f | ||
|
|
ae4d089c06 | ||
|
|
5110fbdaf9 | ||
|
|
e6ddb474fc | ||
|
|
0dc71774ce | ||
|
|
b470466e30 | ||
|
|
d1f9311931 | ||
|
|
1c58023df9 | ||
|
|
4e0aa58b7e | ||
|
|
23ee34b35f | ||
|
|
674c9a5220 | ||
|
|
54c86ed43a | ||
|
|
676d75ee75 | ||
|
|
70dc0a12f2 | ||
|
|
d579c5e8aa | ||
|
|
ee91f31313 | ||
|
|
57b3051024 | ||
|
|
ae5cf3cc23 | ||
|
|
68e1b3c46c |
@@ -3,7 +3,7 @@ name: deploy
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dontlook
|
||||
- dev
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
@@ -61,9 +61,56 @@ The Models.dev dataset is also used to detect common environment variables like
|
||||
|
||||
If there are additional providers you want to use you can submit a PR to the [Models.dev repo](https://github.com/sst/models.dev). If configuring just for yourself check out the Config section below.
|
||||
|
||||
### Global Config
|
||||
|
||||
Some basic configuration is available in the global config file.
|
||||
|
||||
```toml
|
||||
# ~/.config/opencode/config
|
||||
theme = "opencode"
|
||||
provider = "anthropic"
|
||||
model = "claude-sonnet-4-20250514"
|
||||
autoupdate = true
|
||||
```
|
||||
|
||||
You can also extend the models.dev database with your own providers by mirroring the structure found [here](https://github.com/sst/models.dev/tree/dev/providers/anthropic)
|
||||
|
||||
Start with a `provider.toml` file in `~/.config/opencode/providers`
|
||||
|
||||
```toml
|
||||
# ~/.config/opencode/providers/openrouter/provider.toml
|
||||
[provider]
|
||||
name = "OpenRouter"
|
||||
env = ["OPENROUTER_API_KEY"]
|
||||
npm = "@openrouter/ai-sdk-provider"
|
||||
|
||||
[options]
|
||||
baseURL = "https://api.openrouter.ai" # optional settings
|
||||
```
|
||||
|
||||
And models in `~/.config/opencode/providers/openrouter/models/[model-id]`
|
||||
|
||||
```toml
|
||||
# ~/.config/opencode/providers/openrouter/models/anthropic/claude-3.5-sonnet.toml
|
||||
name = "Claude 4 Sonnet"
|
||||
attachment = true
|
||||
reasoning = false
|
||||
temperature = true
|
||||
|
||||
[cost]
|
||||
input = 3.00
|
||||
output = 15.00
|
||||
inputCached = 3.75
|
||||
outputCached = 0.30
|
||||
|
||||
[limit]
|
||||
context = 200_000
|
||||
output = 50_000
|
||||
```
|
||||
|
||||
### Project Config
|
||||
|
||||
Project configuration is optional. You can place an `opencode.json` file in the root of your repo, and it'll be loaded.
|
||||
Project configuration is optional. You can place an `opencode.json` file in the root of your repo and is meant to be checked in and shared with your team.
|
||||
|
||||
```json title="opencode.json"
|
||||
{
|
||||
@@ -106,9 +153,7 @@ You can use opencode with any provider listed at [here](https://ai-sdk.dev/provi
|
||||
"baseURL": "http://localhost:11434/v1"
|
||||
},
|
||||
"models": {
|
||||
"llama2": {
|
||||
"name": "llama2"
|
||||
}
|
||||
"llama2": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
"zod-openapi": "4.2.4",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ai-sdk/anthropic": "1.2.12",
|
||||
"@tsconfig/bun": "1.0.7",
|
||||
"@types/bun": "latest",
|
||||
"@types/turndown": "5.0.5",
|
||||
@@ -74,7 +75,7 @@
|
||||
"sharp": "0.32.5",
|
||||
"shiki": "3.4.2",
|
||||
"solid-js": "1.9.7",
|
||||
"toolbeam-docs-theme": "0.2.4",
|
||||
"toolbeam-docs-theme": "0.3.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "catalog:",
|
||||
@@ -86,6 +87,9 @@
|
||||
"sharp",
|
||||
"esbuild",
|
||||
],
|
||||
"patchedDependencies": {
|
||||
"ai@4.3.16": "patches/ai@4.3.16.patch",
|
||||
},
|
||||
"overrides": {
|
||||
"zod": "3.24.2",
|
||||
},
|
||||
@@ -96,6 +100,8 @@
|
||||
"zod": "3.24.2",
|
||||
},
|
||||
"packages": {
|
||||
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@1.2.12", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-YSzjlko7JvuiyQFmI9RN1tNZdEiZxc+6xld/0tq/VkJaHpEzGAb1yiNxxvmYVcjvfu/PcvCxAAYXmTYQQ63IHQ=="],
|
||||
|
||||
"@ai-sdk/provider": ["@ai-sdk/provider@1.1.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg=="],
|
||||
|
||||
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@2.2.8", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "nanoid": "^3.3.8", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA=="],
|
||||
@@ -1488,7 +1494,7 @@
|
||||
|
||||
"token-types": ["token-types@6.0.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA=="],
|
||||
|
||||
"toolbeam-docs-theme": ["toolbeam-docs-theme@0.2.4", "", { "peerDependencies": { "@astrojs/starlight": "^0.34.3", "astro": "^5.7.13" } }, "sha512-W5mdbcgRpTBDFyEdcU81USs3MFZoXMInpSznc/AFZCwqz8atk4iBNDIlhvihpGHY54Nf5crKmZwJjxVojkHFvA=="],
|
||||
"toolbeam-docs-theme": ["toolbeam-docs-theme@0.3.0", "", { "peerDependencies": { "@astrojs/starlight": "^0.34.3", "astro": "^5.7.13" } }, "sha512-qlBkKRp8HVYV7p7jaG9lT2lvQY7c8b9czZ0tnsJUrN2TBTtEyFJymCdkhhpZNC9U4oGZ7lLk0glRJHrndWvVsg=="],
|
||||
|
||||
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
|
||||
|
||||
|
||||
+4
-1
@@ -37,5 +37,8 @@
|
||||
"esbuild",
|
||||
"protobufjs",
|
||||
"sharp"
|
||||
]
|
||||
],
|
||||
"patchedDependencies": {
|
||||
"ai@4.3.16": "patches/ai@4.3.16.patch"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
"@types/turndown": "5.0.5",
|
||||
"@types/yargs": "17.0.33",
|
||||
"typescript": "catalog:",
|
||||
"zod-to-json-schema": "3.24.5"
|
||||
"zod-to-json-schema": "3.24.5",
|
||||
"@ai-sdk/anthropic": "1.2.12"
|
||||
},
|
||||
"dependencies": {
|
||||
"@clack/prompts": "0.11.0",
|
||||
|
||||
@@ -18,6 +18,7 @@ export namespace App {
|
||||
data: z.string(),
|
||||
root: z.string(),
|
||||
cwd: z.string(),
|
||||
state: z.string(),
|
||||
}),
|
||||
time: z.object({
|
||||
initialized: z.number().optional(),
|
||||
@@ -32,7 +33,7 @@ export namespace App {
|
||||
|
||||
const APP_JSON = "app.json"
|
||||
|
||||
async function create(input: { cwd: string; version: string }) {
|
||||
async function create(input: { cwd: string }) {
|
||||
log.info("creating", {
|
||||
cwd: input.cwd,
|
||||
})
|
||||
@@ -49,9 +50,7 @@ export namespace App {
|
||||
const stateFile = Bun.file(path.join(data, APP_JSON))
|
||||
const state = (await stateFile.json().catch(() => ({}))) as {
|
||||
initialized: number
|
||||
version: string
|
||||
}
|
||||
state.version = input.version
|
||||
await stateFile.write(JSON.stringify(state))
|
||||
|
||||
const services = new Map<
|
||||
@@ -70,13 +69,13 @@ export namespace App {
|
||||
git: git !== undefined,
|
||||
path: {
|
||||
config: Global.Path.config,
|
||||
state: Global.Path.state,
|
||||
data,
|
||||
root: git ?? input.cwd,
|
||||
cwd: input.cwd,
|
||||
},
|
||||
}
|
||||
const result = {
|
||||
version: input.version,
|
||||
services,
|
||||
info,
|
||||
}
|
||||
@@ -108,7 +107,7 @@ export namespace App {
|
||||
}
|
||||
|
||||
export async function provide<T>(
|
||||
input: { cwd: string; version: string },
|
||||
input: { cwd: string },
|
||||
cb: (app: Info) => Promise<T>,
|
||||
) {
|
||||
const app = await create(input)
|
||||
@@ -124,12 +123,11 @@ export namespace App {
|
||||
}
|
||||
|
||||
export async function initialize() {
|
||||
const { info, version } = ctx.use()
|
||||
const { info } = ctx.use()
|
||||
info.time.initialized = Date.now()
|
||||
await Bun.write(
|
||||
path.join(info.path.data, APP_JSON),
|
||||
JSON.stringify({
|
||||
version,
|
||||
initialized: Date.now(),
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -48,6 +48,7 @@ export namespace AuthAnthropic {
|
||||
await Auth.set("anthropic", {
|
||||
type: "oauth",
|
||||
refresh: json.refresh_token as string,
|
||||
access: json.access_token as string,
|
||||
expires: Date.now() + json.expires_in * 1000,
|
||||
})
|
||||
}
|
||||
@@ -55,6 +56,7 @@ export namespace AuthAnthropic {
|
||||
export async function access() {
|
||||
const info = await Auth.get("anthropic")
|
||||
if (!info || info.type !== "oauth") return
|
||||
if (info.access && info.expires > Date.now()) return info.access
|
||||
const response = await fetch(
|
||||
"https://console.anthropic.com/v1/oauth/token",
|
||||
{
|
||||
@@ -74,6 +76,7 @@ export namespace AuthAnthropic {
|
||||
await Auth.set("anthropic", {
|
||||
type: "oauth",
|
||||
refresh: json.refresh_token as string,
|
||||
access: json.access_token as string,
|
||||
expires: Date.now() + json.expires_in * 1000,
|
||||
})
|
||||
return json.access_token as string
|
||||
|
||||
@@ -7,6 +7,7 @@ export namespace Auth {
|
||||
export const Oauth = z.object({
|
||||
type: z.literal("oauth"),
|
||||
refresh: z.string(),
|
||||
access: z.string(),
|
||||
expires: z.number(),
|
||||
})
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Server } from "../../server/server"
|
||||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
import type { CommandModule } from "yargs"
|
||||
import { Config } from "../../config/config"
|
||||
|
||||
export const GenerateCommand = {
|
||||
command: "generate",
|
||||
|
||||
@@ -6,7 +6,6 @@ import { Session } from "../../session"
|
||||
import { Share } from "../../share/share"
|
||||
import { Message } from "../../session/message"
|
||||
import { UI } from "../ui"
|
||||
import { VERSION } from "../version"
|
||||
import { cmd } from "./cmd"
|
||||
import { GlobalConfig } from "../../global/config"
|
||||
import { Flag } from "../../flag/flag"
|
||||
@@ -48,7 +47,6 @@ export const RunCommand = cmd({
|
||||
await App.provide(
|
||||
{
|
||||
cwd: process.cwd(),
|
||||
version: VERSION,
|
||||
},
|
||||
async () => {
|
||||
await Share.init()
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { App } from "../../app/app"
|
||||
import { LSP } from "../../lsp"
|
||||
import { VERSION } from "../version"
|
||||
import { cmd } from "./cmd"
|
||||
|
||||
export const ScrapCommand = cmd({
|
||||
@@ -8,9 +7,12 @@ export const ScrapCommand = cmd({
|
||||
builder: (yargs) =>
|
||||
yargs.positional("file", { type: "string", demandOption: true }),
|
||||
async handler(args) {
|
||||
await App.provide({ cwd: process.cwd(), version: VERSION }, async (app) => {
|
||||
await LSP.touchFile(args.file, true)
|
||||
console.log(await LSP.diagnostics())
|
||||
})
|
||||
await App.provide(
|
||||
{ cwd: process.cwd() },
|
||||
async () => {
|
||||
await LSP.touchFile(args.file, true)
|
||||
console.log(await LSP.diagnostics())
|
||||
},
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { Argv } from "yargs"
|
||||
import { UI } from "../ui"
|
||||
import { VERSION } from "../version"
|
||||
import * as prompts from "@clack/prompts"
|
||||
import { Installation } from "../../installation"
|
||||
|
||||
@@ -27,7 +26,7 @@ export const UpgradeCommand = {
|
||||
return
|
||||
}
|
||||
const target = args.target ?? (await Installation.latest())
|
||||
prompts.log.info(`From ${VERSION} → ${target}`)
|
||||
prompts.log.info(`From ${Installation.VERSION} → ${target}`)
|
||||
const spinner = prompts.spinner()
|
||||
spinner.start("Upgrading...")
|
||||
const err = await Installation.upgrade(method, target).catch((err) => err)
|
||||
@@ -41,69 +40,5 @@ export const UpgradeCommand = {
|
||||
}
|
||||
spinner.stop("Upgrade complete")
|
||||
prompts.outro("Done")
|
||||
return
|
||||
|
||||
/*
|
||||
if (!process.execPath.includes(path.join(".opencode", "bin")) && false) {
|
||||
return
|
||||
}
|
||||
|
||||
const release = args.target
|
||||
? await specific(args.target).catch(() => {})
|
||||
: await latest().catch(() => {})
|
||||
if (!release) {
|
||||
prompts.log.error("Failed to fetch release information")
|
||||
prompts.outro("Done")
|
||||
return
|
||||
}
|
||||
|
||||
const target = release.tag_name
|
||||
|
||||
if (VERSION !== "dev" && compare(VERSION, target) >= 0) {
|
||||
prompts.log.success(`Already up to date`)
|
||||
prompts.outro("Done")
|
||||
return
|
||||
}
|
||||
|
||||
prompts.log.info(`From ${VERSION} → ${target}`)
|
||||
|
||||
const name = asset()
|
||||
const found = release.assets.find((a) => a.name === name)
|
||||
|
||||
if (!found) {
|
||||
prompts.log.error(`No binary found for platform: ${name}`)
|
||||
prompts.outro("Done")
|
||||
return
|
||||
}
|
||||
|
||||
const spinner = prompts.spinner()
|
||||
spinner.start("Downloading update...")
|
||||
|
||||
const downloadPath = await download(found.browser_download_url).catch(
|
||||
() => {},
|
||||
)
|
||||
if (!downloadPath) {
|
||||
spinner.stop("Download failed")
|
||||
prompts.log.error("Download failed")
|
||||
prompts.outro("Done")
|
||||
return
|
||||
}
|
||||
|
||||
spinner.stop("Download complete")
|
||||
|
||||
const renamed = await fs
|
||||
.rename(downloadPath, process.execPath)
|
||||
.catch(() => {})
|
||||
|
||||
if (renamed === undefined) {
|
||||
prompts.log.error("Install failed")
|
||||
await fs.unlink(downloadPath).catch(() => {})
|
||||
prompts.outro("Done")
|
||||
return
|
||||
}
|
||||
|
||||
prompts.log.success(`Successfully upgraded to ${target}`)
|
||||
prompts.outro("Done")
|
||||
*/
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
declare global {
|
||||
const OPENCODE_VERSION: string
|
||||
}
|
||||
|
||||
export const VERSION =
|
||||
typeof OPENCODE_VERSION === "string" ? OPENCODE_VERSION : "dev"
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
import { App } from "../app/app"
|
||||
import path from "path"
|
||||
import { Global } from "../global"
|
||||
import fs from "fs/promises"
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
export namespace File {
|
||||
const glob = new Bun.Glob("**/*")
|
||||
export async function search(path: string, query: string) {
|
||||
for await (const entry of glob.scan({
|
||||
cwd: path,
|
||||
onlyFiles: true,
|
||||
})) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,11 @@ import path from "path"
|
||||
|
||||
export namespace GlobalConfig {
|
||||
export const Info = z.object({
|
||||
autoupdate: z.boolean().optional(),
|
||||
autoshare: z.boolean().optional(),
|
||||
provider: z.string().optional(),
|
||||
model: z.string().optional(),
|
||||
autoupdate: z.boolean().optional(),
|
||||
autoshare: z.boolean().optional(),
|
||||
disabled_providers: z.array(z.string()).optional(),
|
||||
})
|
||||
export type Info = z.infer<typeof Info>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import fs from "fs/promises"
|
||||
import { xdgData, xdgCache, xdgConfig } from "xdg-basedir"
|
||||
import { xdgData, xdgCache, xdgConfig, xdgState } from "xdg-basedir"
|
||||
import path from "path"
|
||||
|
||||
const app = "opencode"
|
||||
@@ -7,18 +7,23 @@ const app = "opencode"
|
||||
const data = path.join(xdgData!, app)
|
||||
const cache = path.join(xdgCache!, app)
|
||||
const config = path.join(xdgConfig!, app)
|
||||
|
||||
await Promise.all([
|
||||
fs.mkdir(data, { recursive: true }),
|
||||
fs.mkdir(config, { recursive: true }),
|
||||
fs.mkdir(cache, { recursive: true }),
|
||||
])
|
||||
const state = path.join(xdgState!, app)
|
||||
|
||||
export namespace Global {
|
||||
export const Path = {
|
||||
data,
|
||||
bin: path.join(data, "bin"),
|
||||
providers: path.join(config, "providers"),
|
||||
cache,
|
||||
config,
|
||||
state,
|
||||
} as const
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
fs.mkdir(Global.Path.data, { recursive: true }),
|
||||
fs.mkdir(Global.Path.config, { recursive: true }),
|
||||
fs.mkdir(Global.Path.cache, { recursive: true }),
|
||||
fs.mkdir(Global.Path.providers, { recursive: true }),
|
||||
fs.mkdir(Global.Path.state, { recursive: true }),
|
||||
])
|
||||
|
||||
@@ -9,7 +9,6 @@ import yargs from "yargs"
|
||||
import { hideBin } from "yargs/helpers"
|
||||
import { RunCommand } from "./cli/cmd/run"
|
||||
import { GenerateCommand } from "./cli/cmd/generate"
|
||||
import { VERSION } from "./cli/version"
|
||||
import { ScrapCommand } from "./cli/cmd/scrap"
|
||||
import { Log } from "./util/log"
|
||||
import { AuthCommand, AuthLoginCommand } from "./cli/cmd/auth"
|
||||
@@ -18,21 +17,11 @@ import { Provider } from "./provider/provider"
|
||||
import { UI } from "./cli/ui"
|
||||
import { GlobalConfig } from "./global/config"
|
||||
import { Installation } from "./installation"
|
||||
;(async () => {
|
||||
if (Installation.VERSION === "dev") return
|
||||
if (Installation.isSnapshot()) return
|
||||
const config = await GlobalConfig.get()
|
||||
if (config.autoupdate === false) return
|
||||
const latest = await Installation.latest()
|
||||
if (Installation.VERSION === latest) return
|
||||
const method = await Installation.method()
|
||||
if (method === "unknown") return
|
||||
await Installation.upgrade(method, latest).catch(() => {})
|
||||
})()
|
||||
import { Bus } from "./bus"
|
||||
|
||||
const cli = yargs(hideBin(process.argv))
|
||||
.scriptName("opencode")
|
||||
.version(VERSION)
|
||||
.version(Installation.VERSION)
|
||||
.option("print-logs", {
|
||||
describe: "Print logs to stderr",
|
||||
type: "boolean",
|
||||
@@ -40,7 +29,7 @@ const cli = yargs(hideBin(process.argv))
|
||||
.middleware(async () => {
|
||||
await Log.init({ print: process.argv.includes("--print-logs") })
|
||||
Log.Default.info("opencode", {
|
||||
version: VERSION,
|
||||
version: Installation.VERSION,
|
||||
args: process.argv.slice(2),
|
||||
})
|
||||
})
|
||||
@@ -57,52 +46,65 @@ const cli = yargs(hideBin(process.argv))
|
||||
while (true) {
|
||||
const cwd = args.project ? path.resolve(args.project) : process.cwd()
|
||||
process.chdir(cwd)
|
||||
const result = await App.provide(
|
||||
{ cwd, version: VERSION },
|
||||
async (app) => {
|
||||
const providers = await Provider.list()
|
||||
if (Object.keys(providers).length === 0) {
|
||||
return "needs_provider"
|
||||
const result = await App.provide({ cwd }, async (app) => {
|
||||
const providers = await Provider.list()
|
||||
if (Object.keys(providers).length === 0) {
|
||||
return "needs_provider"
|
||||
}
|
||||
|
||||
await Share.init()
|
||||
const server = Server.listen()
|
||||
|
||||
let cmd = ["go", "run", "./main.go"]
|
||||
let cwd = new URL("../../tui/cmd/opencode", import.meta.url).pathname
|
||||
if (Bun.embeddedFiles.length > 0) {
|
||||
const blob = Bun.embeddedFiles[0] as File
|
||||
const binary = path.join(Global.Path.cache, "tui", blob.name)
|
||||
const file = Bun.file(binary)
|
||||
if (!(await file.exists())) {
|
||||
await Bun.write(file, blob, { mode: 0o755 })
|
||||
await fs.chmod(binary, 0o755)
|
||||
}
|
||||
cwd = process.cwd()
|
||||
cmd = [binary]
|
||||
}
|
||||
const proc = Bun.spawn({
|
||||
cmd: [...cmd, ...process.argv.slice(2)],
|
||||
cwd,
|
||||
stdout: "inherit",
|
||||
stderr: "inherit",
|
||||
stdin: "inherit",
|
||||
env: {
|
||||
...process.env,
|
||||
OPENCODE_SERVER: server.url.toString(),
|
||||
OPENCODE_APP_INFO: JSON.stringify(app),
|
||||
},
|
||||
onExit: () => {
|
||||
server.stop()
|
||||
},
|
||||
})
|
||||
|
||||
await Share.init()
|
||||
const server = Server.listen()
|
||||
;(async () => {
|
||||
if (Installation.VERSION === "dev") return
|
||||
if (Installation.isSnapshot()) return
|
||||
const config = await GlobalConfig.get()
|
||||
if (config.autoupdate === false) return
|
||||
const latest = await Installation.latest()
|
||||
if (Installation.VERSION === latest) return
|
||||
const method = await Installation.method()
|
||||
if (method === "unknown") return
|
||||
await Installation.upgrade(method, latest)
|
||||
.then(() => {
|
||||
Bus.publish(Installation.Event.Updated, { version: latest })
|
||||
})
|
||||
.catch(() => {})
|
||||
})()
|
||||
|
||||
let cmd = ["go", "run", "./main.go"]
|
||||
let cwd = new URL("../../tui/cmd/opencode", import.meta.url)
|
||||
.pathname
|
||||
if (Bun.embeddedFiles.length > 0) {
|
||||
const blob = Bun.embeddedFiles[0] as File
|
||||
const binary = path.join(Global.Path.cache, "tui", blob.name)
|
||||
const file = Bun.file(binary)
|
||||
if (!(await file.exists())) {
|
||||
await Bun.write(file, blob, { mode: 0o755 })
|
||||
await fs.chmod(binary, 0o755)
|
||||
}
|
||||
cwd = process.cwd()
|
||||
cmd = [binary]
|
||||
}
|
||||
const proc = Bun.spawn({
|
||||
cmd: [...cmd, ...process.argv.slice(2)],
|
||||
cwd,
|
||||
stdout: "inherit",
|
||||
stderr: "inherit",
|
||||
stdin: "inherit",
|
||||
env: {
|
||||
...process.env,
|
||||
OPENCODE_SERVER: server.url.toString(),
|
||||
OPENCODE_APP_INFO: JSON.stringify(app),
|
||||
},
|
||||
onExit: () => {
|
||||
server.stop()
|
||||
},
|
||||
})
|
||||
await proc.exited
|
||||
await server.stop()
|
||||
await proc.exited
|
||||
await server.stop()
|
||||
|
||||
return "done"
|
||||
},
|
||||
)
|
||||
return "done"
|
||||
})
|
||||
if (result === "done") break
|
||||
if (result === "needs_provider") {
|
||||
UI.empty()
|
||||
@@ -118,21 +120,20 @@ const cli = yargs(hideBin(process.argv))
|
||||
.command(ScrapCommand)
|
||||
.command(AuthCommand)
|
||||
.command(UpgradeCommand)
|
||||
.fail((msg, err) => {
|
||||
.fail((msg) => {
|
||||
if (
|
||||
msg.startsWith("Unknown argument") ||
|
||||
msg.startsWith("Not enough non-option arguments")
|
||||
) {
|
||||
cli.showHelp("log")
|
||||
}
|
||||
Log.Default.error(msg, {
|
||||
err,
|
||||
})
|
||||
})
|
||||
.strict()
|
||||
|
||||
try {
|
||||
await cli.parse()
|
||||
} catch (e) {
|
||||
Log.Default.error(e)
|
||||
Log.Default.error(e, {
|
||||
stack: e instanceof Error ? e.stack : undefined,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,10 +2,24 @@ import path from "path"
|
||||
import { $ } from "bun"
|
||||
import { z } from "zod"
|
||||
import { NamedError } from "../util/error"
|
||||
import { Bus } from "../bus"
|
||||
|
||||
declare global {
|
||||
const OPENCODE_VERSION: string
|
||||
}
|
||||
|
||||
export namespace Installation {
|
||||
export type Method = Awaited<ReturnType<typeof method>>
|
||||
|
||||
export const Event = {
|
||||
Updated: Bus.event(
|
||||
"installation.updated",
|
||||
z.object({
|
||||
version: z.string(),
|
||||
}),
|
||||
),
|
||||
}
|
||||
|
||||
export const Info = z
|
||||
.object({
|
||||
version: z.string(),
|
||||
@@ -103,6 +117,6 @@ export namespace Installation {
|
||||
export async function latest() {
|
||||
return fetch("https://api.github.com/repos/sst/opencode/releases/latest")
|
||||
.then((res) => res.json())
|
||||
.then((data) => data.tag_name.slice(1))
|
||||
.then((data) => data.tag_name.slice(1) as string)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Global } from "../global"
|
||||
import { lazy } from "../util/lazy"
|
||||
import { Log } from "../util/log"
|
||||
import path from "path"
|
||||
import { z } from "zod"
|
||||
@@ -17,8 +16,8 @@ export namespace ModelsDev {
|
||||
cost: z.object({
|
||||
input: z.number(),
|
||||
output: z.number(),
|
||||
inputCached: z.number(),
|
||||
outputCached: z.number(),
|
||||
cache_read: z.number().optional(),
|
||||
cache_write: z.number().optional(),
|
||||
}),
|
||||
limit: z.object({
|
||||
context: z.number(),
|
||||
@@ -64,30 +63,4 @@ export namespace ModelsDev {
|
||||
throw new Error(`Failed to fetch models.dev: ${result.statusText}`)
|
||||
await Bun.write(file, result)
|
||||
}
|
||||
|
||||
const aisdk = lazy(async () => {
|
||||
log.info("fetching ai-sdk")
|
||||
const response = await fetch(
|
||||
"https://registry.npmjs.org/-/v1/search?text=scope:@ai-sdk",
|
||||
)
|
||||
if (!response.ok)
|
||||
throw new Error(
|
||||
`Failed to fetch ai-sdk information: ${response.statusText}`,
|
||||
)
|
||||
const result = await response.json()
|
||||
log.info("found ai-sdk", result.objects.length)
|
||||
return result.objects
|
||||
.filter((obj: any) => obj.package.name.startsWith("@ai-sdk/"))
|
||||
.reduce((acc: any, obj: any) => {
|
||||
acc[obj.package.name] = obj
|
||||
return acc
|
||||
}, {})
|
||||
})
|
||||
|
||||
export async function pkg(providerID: string): Promise<[string, string]> {
|
||||
const packages = await aisdk()
|
||||
const match = packages[`@ai-sdk/${providerID}`]
|
||||
if (match) return [match.package.name, "latest"]
|
||||
return [providerID, "latest"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import z from "zod"
|
||||
import path from "path"
|
||||
import { App } from "../app/app"
|
||||
import { Config } from "../config/config"
|
||||
import { mergeDeep, sortBy } from "remeda"
|
||||
@@ -23,6 +24,8 @@ import { ModelsDev } from "./models"
|
||||
import { NamedError } from "../util/error"
|
||||
import { Auth } from "../auth"
|
||||
import { TaskTool } from "../tool/task"
|
||||
import { GlobalConfig } from "../global/config"
|
||||
import { Global } from "../global"
|
||||
|
||||
export namespace Provider {
|
||||
const log = Log.create({ service: "provider" })
|
||||
@@ -40,16 +43,23 @@ export namespace Provider {
|
||||
for (const model of Object.values(provider.models)) {
|
||||
model.cost = {
|
||||
input: 0,
|
||||
inputCached: 0,
|
||||
output: 0,
|
||||
outputCached: 0,
|
||||
}
|
||||
}
|
||||
return {
|
||||
apiKey: "",
|
||||
headers: {
|
||||
authorization: `Bearer ${access}`,
|
||||
"anthropic-beta": "oauth-2025-04-20",
|
||||
async fetch(input: any, init: any) {
|
||||
const access = await AuthAnthropic.access()
|
||||
const headers = {
|
||||
...init.headers,
|
||||
authorization: `Bearer ${access}`,
|
||||
"anthropic-beta": "oauth-2025-04-20",
|
||||
}
|
||||
delete headers["x-api-key"]
|
||||
return fetch(input, {
|
||||
...init,
|
||||
headers,
|
||||
})
|
||||
},
|
||||
}
|
||||
},
|
||||
@@ -91,9 +101,11 @@ export namespace Provider {
|
||||
) {
|
||||
const provider = providers[id]
|
||||
if (!provider) {
|
||||
const info = database[id]
|
||||
if (!info) return
|
||||
providers[id] = {
|
||||
source,
|
||||
info: database[id],
|
||||
info,
|
||||
options,
|
||||
}
|
||||
return
|
||||
@@ -102,9 +114,35 @@ export namespace Provider {
|
||||
provider.source = source
|
||||
}
|
||||
|
||||
for (const [providerID, provider] of Object.entries(
|
||||
config.provider ?? {},
|
||||
)) {
|
||||
const configProviders = Object.entries(config.provider ?? {})
|
||||
for await (const providerPath of new Bun.Glob("*/provider.toml").scan({
|
||||
cwd: Global.Path.providers,
|
||||
})) {
|
||||
const [providerID] = providerPath.split("/")
|
||||
const toml = await import(
|
||||
path.join(Global.Path.providers, providerPath),
|
||||
{
|
||||
with: {
|
||||
type: "toml",
|
||||
},
|
||||
}
|
||||
).then((mod) => mod.default)
|
||||
toml.models = {}
|
||||
const modelsPath = path.join(Global.Path.providers, providerID, "models")
|
||||
for await (const modelPath of new Bun.Glob("**/*.toml").scan({
|
||||
cwd: modelsPath,
|
||||
})) {
|
||||
const modelID = modelPath.slice(0, -5)
|
||||
toml.models[modelID] = await import(path.join(modelsPath, modelPath), {
|
||||
with: {
|
||||
type: "toml",
|
||||
},
|
||||
})
|
||||
}
|
||||
configProviders.unshift([providerID, toml])
|
||||
}
|
||||
|
||||
for (const [providerID, provider] of configProviders) {
|
||||
const existing = database[providerID]
|
||||
const parsed: ModelsDev.Provider = {
|
||||
id: providerID,
|
||||
@@ -140,8 +178,12 @@ export namespace Provider {
|
||||
database[providerID] = parsed
|
||||
}
|
||||
|
||||
const disabled = await GlobalConfig.get().then(
|
||||
(cfg) => new Set(cfg.disabled_providers ?? []),
|
||||
)
|
||||
// load env
|
||||
for (const [providerID, provider] of Object.entries(database)) {
|
||||
if (disabled.has(providerID)) continue
|
||||
if (provider.env.some((item) => process.env[item])) {
|
||||
mergeProvider(providerID, {}, "env")
|
||||
}
|
||||
@@ -149,6 +191,7 @@ export namespace Provider {
|
||||
|
||||
// load apikeys
|
||||
for (const [providerID, provider] of Object.entries(await Auth.all())) {
|
||||
if (disabled.has(providerID)) continue
|
||||
if (provider.type === "api") {
|
||||
mergeProvider(providerID, { apiKey: provider.key }, "api")
|
||||
}
|
||||
@@ -156,14 +199,13 @@ export namespace Provider {
|
||||
|
||||
// load custom
|
||||
for (const [providerID, fn] of Object.entries(CUSTOM_LOADERS)) {
|
||||
if (disabled.has(providerID)) continue
|
||||
const result = await fn(database[providerID])
|
||||
if (result) mergeProvider(providerID, result, "custom")
|
||||
}
|
||||
|
||||
// load config
|
||||
for (const [providerID, provider] of Object.entries(
|
||||
config.provider ?? {},
|
||||
)) {
|
||||
for (const [providerID, provider] of configProviders) {
|
||||
mergeProvider(providerID, provider.options ?? {}, "config")
|
||||
}
|
||||
|
||||
@@ -190,8 +232,8 @@ export namespace Provider {
|
||||
const s = await state()
|
||||
const existing = s.sdk.get(provider.id)
|
||||
if (existing) return existing
|
||||
const [pkg, version] = await ModelsDev.pkg(provider.npm ?? provider.id)
|
||||
const mod = await import(await BunProc.install(pkg, version))
|
||||
const pkg = provider.npm ?? provider.id
|
||||
const mod = await import(await BunProc.install(pkg, "latest"))
|
||||
const fn = mod[Object.keys(mod).find((key) => key.startsWith("create"))!]
|
||||
const loaded = fn(s.providers[provider.id]?.options)
|
||||
s.sdk.set(provider.id, loaded)
|
||||
@@ -257,7 +299,10 @@ export namespace Provider {
|
||||
}
|
||||
|
||||
export async function defaultModel() {
|
||||
const [provider] = await list().then((val) => Object.values(val))
|
||||
const cfg = await GlobalConfig.get()
|
||||
const provider = await list()
|
||||
.then((val) => Object.values(val))
|
||||
.then((x) => x.find((p) => !cfg.provider || cfg.provider === p.info.id))
|
||||
if (!provider) throw new Error("no providers found")
|
||||
const [model] = sort(Object.values(provider.info.models))
|
||||
if (!model) throw new Error("no models found")
|
||||
@@ -285,11 +330,16 @@ export namespace Provider {
|
||||
TaskTool,
|
||||
TodoReadTool,
|
||||
]
|
||||
|
||||
const TOOL_MAPPING: Record<string, Tool.Info[]> = {
|
||||
anthropic: TOOLS.filter((t) => t.id !== "opencode.patch"),
|
||||
openai: TOOLS,
|
||||
openai: TOOLS.map((t) => ({
|
||||
...t,
|
||||
parameters: optionalToNullable(t.parameters),
|
||||
})),
|
||||
google: TOOLS,
|
||||
}
|
||||
|
||||
export async function tools(providerID: string) {
|
||||
/*
|
||||
const cfg = await Config.get()
|
||||
@@ -301,6 +351,38 @@ export namespace Provider {
|
||||
return TOOL_MAPPING[providerID] ?? TOOLS
|
||||
}
|
||||
|
||||
function optionalToNullable(schema: z.ZodTypeAny): z.ZodTypeAny {
|
||||
if (schema instanceof z.ZodObject) {
|
||||
const shape = schema.shape
|
||||
const newShape: Record<string, z.ZodTypeAny> = {}
|
||||
|
||||
for (const [key, value] of Object.entries(shape)) {
|
||||
const zodValue = value as z.ZodTypeAny
|
||||
if (zodValue instanceof z.ZodOptional) {
|
||||
newShape[key] = zodValue.unwrap().nullable()
|
||||
} else {
|
||||
newShape[key] = optionalToNullable(zodValue)
|
||||
}
|
||||
}
|
||||
|
||||
return z.object(newShape)
|
||||
}
|
||||
|
||||
if (schema instanceof z.ZodArray) {
|
||||
return z.array(optionalToNullable(schema.element))
|
||||
}
|
||||
|
||||
if (schema instanceof z.ZodUnion) {
|
||||
return z.union(
|
||||
schema.options.map((option: z.ZodTypeAny) =>
|
||||
optionalToNullable(option),
|
||||
) as [z.ZodTypeAny, z.ZodTypeAny, ...z.ZodTypeAny[]],
|
||||
)
|
||||
}
|
||||
|
||||
return schema
|
||||
}
|
||||
|
||||
export const ModelNotFoundError = NamedError.create(
|
||||
"ProviderModelNotFoundError",
|
||||
z.object({
|
||||
|
||||
@@ -12,7 +12,6 @@ import { App } from "../app/app"
|
||||
import { Global } from "../global"
|
||||
import { mapValues } from "remeda"
|
||||
import { NamedError } from "../util/error"
|
||||
import { Fzf } from "../external/fzf"
|
||||
import { ModelsDev } from "../provider/models"
|
||||
import { Ripgrep } from "../external/ripgrep"
|
||||
import { Installation } from "../installation"
|
||||
|
||||
@@ -31,6 +31,7 @@ import { SystemPrompt } from "./system"
|
||||
import { Flag } from "../flag/flag"
|
||||
import type { ModelsDev } from "../provider/models"
|
||||
import { GlobalConfig } from "../global/config"
|
||||
import { Installation } from "../installation"
|
||||
|
||||
export namespace Session {
|
||||
const log = Log.create({ service: "session" })
|
||||
@@ -46,6 +47,7 @@ export namespace Session {
|
||||
})
|
||||
.optional(),
|
||||
title: z.string(),
|
||||
version: z.string(),
|
||||
time: z.object({
|
||||
created: z.number(),
|
||||
updated: z.number(),
|
||||
@@ -84,6 +86,7 @@ export namespace Session {
|
||||
export async function create(parentID?: string) {
|
||||
const result: Info = {
|
||||
id: Identifier.descending("session"),
|
||||
version: Installation.VERSION,
|
||||
parentID,
|
||||
title:
|
||||
(parentID ? "Child session - " : "New Session - ") +
|
||||
@@ -210,8 +213,9 @@ export namespace Session {
|
||||
previous.metadata.assistant.tokens.cache.write +
|
||||
previous.metadata.assistant.tokens.output
|
||||
if (
|
||||
model.info.limit.context &&
|
||||
tokens >
|
||||
(model.info.limit.context - (model.info.limit.output ?? 0)) * 0.9
|
||||
(model.info.limit.context - (model.info.limit.output ?? 0)) * 0.9
|
||||
) {
|
||||
await summarize({
|
||||
sessionID: input.sessionID,
|
||||
@@ -330,6 +334,16 @@ export namespace Session {
|
||||
sessionID: input.sessionID,
|
||||
abort: abort.signal,
|
||||
messageID: next.id,
|
||||
metadata: async (val) => {
|
||||
next.metadata.tool[opts.toolCallId] = {
|
||||
...val,
|
||||
time: {
|
||||
start: 0,
|
||||
end: 0,
|
||||
},
|
||||
}
|
||||
await updateMessage(next)
|
||||
},
|
||||
})
|
||||
next.metadata!.tool![opts.toolCallId] = {
|
||||
...result.metadata,
|
||||
@@ -496,7 +510,7 @@ export namespace Session {
|
||||
msgs.map(toUIMessage).filter((x) => x.parts.length > 0),
|
||||
),
|
||||
],
|
||||
temperature: model.info.id === "codex-mini-latest" ? undefined : 0,
|
||||
temperature: model.info.temperature ? 0 : undefined,
|
||||
tools: {
|
||||
...tools,
|
||||
},
|
||||
@@ -758,6 +772,16 @@ export namespace Session {
|
||||
cost: new Decimal(0)
|
||||
.add(new Decimal(tokens.input).mul(model.cost.input).div(1_000_000))
|
||||
.add(new Decimal(tokens.output).mul(model.cost.output).div(1_000_000))
|
||||
.add(
|
||||
new Decimal(tokens.cache.read)
|
||||
.mul(model.cost.cache_read ?? 0)
|
||||
.div(1_000_000),
|
||||
)
|
||||
.add(
|
||||
new Decimal(tokens.cache.write)
|
||||
.mul(model.cost.cache_write ?? 0)
|
||||
.div(1_000_000),
|
||||
)
|
||||
.toNumber(),
|
||||
tokens,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
you will generate a short title based on the first message a user begins a conversation with
|
||||
You will generate a short title based on the first message a user begins a conversation with
|
||||
- ensure it is not more than 50 characters long
|
||||
- the title should be a summary of the user's message
|
||||
- it should be one line long
|
||||
|
||||
@@ -24,8 +24,6 @@ export namespace Storage {
|
||||
}
|
||||
})
|
||||
|
||||
const locks = new Map<string, Promise<void>>()
|
||||
|
||||
export async function readJSON<T>(key: string) {
|
||||
return Bun.file(path.join(state().dir, key + ".json")).json() as Promise<T>
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ export const BashTool = Tool.define({
|
||||
.min(0)
|
||||
.max(MAX_TIMEOUT)
|
||||
.describe("Optional timeout in milliseconds")
|
||||
.nullable(),
|
||||
.optional(),
|
||||
description: z
|
||||
.string()
|
||||
.describe(
|
||||
|
||||
@@ -21,7 +21,7 @@ export const EditTool = Tool.define({
|
||||
),
|
||||
replaceAll: z
|
||||
.boolean()
|
||||
.nullable()
|
||||
.optional()
|
||||
.describe("Replace all occurences of old_string (default false)"),
|
||||
}),
|
||||
async execute(params, ctx) {
|
||||
@@ -87,7 +87,9 @@ export const EditTool = Tool.define({
|
||||
await file.write(contentNew)
|
||||
})()
|
||||
|
||||
const diff = createTwoFilesPatch(filepath, filepath, contentOld, contentNew)
|
||||
const diff = trimDiff(
|
||||
createTwoFilesPatch(filepath, filepath, contentOld, contentNew),
|
||||
)
|
||||
|
||||
FileTimes.read(ctx.sessionID, filepath)
|
||||
|
||||
@@ -113,3 +115,39 @@ export const EditTool = Tool.define({
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
function trimDiff(diff: string): string {
|
||||
const lines = diff.split("\n")
|
||||
const contentLines = lines.filter(
|
||||
(line) =>
|
||||
(line.startsWith("+") || line.startsWith("-") || line.startsWith(" ")) &&
|
||||
!line.startsWith("---") &&
|
||||
!line.startsWith("+++"),
|
||||
)
|
||||
|
||||
if (contentLines.length === 0) return diff
|
||||
|
||||
let min = Infinity
|
||||
for (const line of contentLines) {
|
||||
const content = line.slice(1)
|
||||
if (content.trim().length > 0) {
|
||||
const match = content.match(/^(\s*)/)
|
||||
if (match) min = Math.min(min, match[1].length)
|
||||
}
|
||||
}
|
||||
if (min === Infinity || min === 0) return diff
|
||||
const trimmedLines = lines.map((line) => {
|
||||
if (
|
||||
(line.startsWith("+") || line.startsWith("-") || line.startsWith(" ")) &&
|
||||
!line.startsWith("---") &&
|
||||
!line.startsWith("+++")
|
||||
) {
|
||||
const prefix = line[0]
|
||||
const content = line.slice(1)
|
||||
return prefix + content.slice(min)
|
||||
}
|
||||
return line
|
||||
})
|
||||
|
||||
return trimmedLines.join("\n")
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export const GlobTool = Tool.define({
|
||||
pattern: z.string().describe("The glob pattern to match files against"),
|
||||
path: z
|
||||
.string()
|
||||
.nullable()
|
||||
.optional()
|
||||
.describe(
|
||||
`The directory to search in. If not specified, the current working directory will be used. IMPORTANT: Omit this field to use the default directory. DO NOT enter "undefined" or "null" - simply omit it for the default behavior. Must be a valid directory path if provided.`,
|
||||
),
|
||||
|
||||
@@ -14,13 +14,13 @@ export const GrepTool = Tool.define({
|
||||
.describe("The regex pattern to search for in file contents"),
|
||||
path: z
|
||||
.string()
|
||||
.nullable()
|
||||
.optional()
|
||||
.describe(
|
||||
"The directory to search in. Defaults to the current working directory.",
|
||||
),
|
||||
include: z
|
||||
.string()
|
||||
.nullable()
|
||||
.optional()
|
||||
.describe(
|
||||
'File pattern to include in the search (e.g. "*.js", "*.{ts,tsx}")',
|
||||
),
|
||||
|
||||
@@ -29,11 +29,11 @@ export const ListTool = Tool.define({
|
||||
.describe(
|
||||
"The absolute path to the directory to list (must be absolute, not relative)",
|
||||
)
|
||||
.nullable(),
|
||||
.optional(),
|
||||
ignore: z
|
||||
.array(z.string())
|
||||
.describe("List of glob patterns to ignore")
|
||||
.nullable(),
|
||||
.optional(),
|
||||
}),
|
||||
async execute(params) {
|
||||
const app = App.info()
|
||||
|
||||
@@ -4,7 +4,6 @@ import * as fs from "fs/promises"
|
||||
import { Tool } from "./tool"
|
||||
import { FileTimes } from "./util/file-times"
|
||||
import DESCRIPTION from "./patch.txt"
|
||||
import { App } from "../app/app"
|
||||
|
||||
const PatchParams = z.object({
|
||||
patchText: z
|
||||
|
||||
@@ -19,11 +19,11 @@ export const ReadTool = Tool.define({
|
||||
offset: z
|
||||
.number()
|
||||
.describe("The line number to start reading from (0-based)")
|
||||
.nullable(),
|
||||
.optional(),
|
||||
limit: z
|
||||
.number()
|
||||
.describe("The number of lines to read (defaults to 2000)")
|
||||
.nullable(),
|
||||
.optional(),
|
||||
}),
|
||||
async execute(params, ctx) {
|
||||
let filePath = params.filePath
|
||||
|
||||
@@ -2,6 +2,8 @@ import { Tool } from "./tool"
|
||||
import DESCRIPTION from "./task.txt"
|
||||
import { z } from "zod"
|
||||
import { Session } from "../session"
|
||||
import { Bus } from "../bus"
|
||||
import { Message } from "../session/message"
|
||||
|
||||
export const TaskTool = Tool.define({
|
||||
id: "opencode.task",
|
||||
@@ -17,6 +19,28 @@ export const TaskTool = Tool.define({
|
||||
const msg = await Session.getMessage(ctx.sessionID, ctx.messageID)
|
||||
const metadata = msg.metadata.assistant!
|
||||
|
||||
function summary(input: Message.Info) {
|
||||
const result = []
|
||||
|
||||
for (const part of input.parts) {
|
||||
if (part.type === "tool-invocation") {
|
||||
result.push({
|
||||
toolInvocation: part.toolInvocation,
|
||||
metadata: input.metadata.tool[part.toolInvocation.toolCallId],
|
||||
})
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const unsub = Bus.subscribe(Message.Event.Updated, async (evt) => {
|
||||
if (evt.properties.info.metadata.sessionID !== ctx.sessionID) return
|
||||
ctx.metadata({
|
||||
title: params.description,
|
||||
summary: summary(evt.properties.info),
|
||||
})
|
||||
})
|
||||
|
||||
const result = await Session.chat({
|
||||
sessionID: session.id,
|
||||
modelID: metadata.modelID,
|
||||
@@ -28,10 +52,11 @@ export const TaskTool = Tool.define({
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
unsub()
|
||||
return {
|
||||
metadata: {
|
||||
title: params.description,
|
||||
summary: summary(result),
|
||||
},
|
||||
output: result.parts.findLast((x) => x.type === "text")!.text,
|
||||
}
|
||||
|
||||
@@ -5,10 +5,11 @@ export namespace Tool {
|
||||
title: string
|
||||
[key: string]: any
|
||||
}
|
||||
export type Context = {
|
||||
export type Context<M extends Metadata = Metadata> = {
|
||||
sessionID: string
|
||||
messageID: string
|
||||
abort: AbortSignal
|
||||
metadata(meta: M): void
|
||||
}
|
||||
export interface Info<
|
||||
Parameters extends StandardSchemaV1 = StandardSchemaV1,
|
||||
|
||||
@@ -22,7 +22,7 @@ export const WebFetchTool = Tool.define({
|
||||
.min(0)
|
||||
.max(MAX_TIMEOUT / 1000)
|
||||
.describe("Optional timeout in seconds (max 120)")
|
||||
.nullable(),
|
||||
.optional(),
|
||||
}),
|
||||
async execute(params, ctx) {
|
||||
// Validate URL
|
||||
|
||||
@@ -11,15 +11,16 @@ export abstract class NamedError extends Error {
|
||||
name: Name,
|
||||
data: Data,
|
||||
) {
|
||||
const schema = z
|
||||
.object({
|
||||
name: z.literal(name),
|
||||
data,
|
||||
})
|
||||
.openapi({
|
||||
ref: name,
|
||||
})
|
||||
const result = class extends NamedError {
|
||||
public static readonly Schema = z
|
||||
.object({
|
||||
name: z.literal(name),
|
||||
data: data,
|
||||
})
|
||||
.openapi({
|
||||
ref: name,
|
||||
})
|
||||
public static readonly Schema = schema
|
||||
|
||||
public readonly name = name as Name
|
||||
|
||||
@@ -40,7 +41,7 @@ export abstract class NamedError extends Error {
|
||||
}
|
||||
|
||||
schema() {
|
||||
return result.Schema
|
||||
return schema
|
||||
}
|
||||
|
||||
toObject() {
|
||||
|
||||
@@ -3,21 +3,33 @@ import { App } from "../../src/app/app"
|
||||
import { GlobTool } from "../../src/tool/glob"
|
||||
import { ListTool } from "../../src/tool/ls"
|
||||
|
||||
const ctx = {
|
||||
sessionID: "test",
|
||||
messageID: "",
|
||||
abort: AbortSignal.any([]),
|
||||
metadata: () => {},
|
||||
}
|
||||
describe("tool.glob", () => {
|
||||
test("truncate", async () => {
|
||||
await App.provide({ cwd: process.cwd(), version: "test" }, async () => {
|
||||
await App.provide({ cwd: process.cwd() }, async () => {
|
||||
let result = await GlobTool.execute(
|
||||
{ pattern: "./node_modules/**/*" },
|
||||
{ sessionID: "test" },
|
||||
{
|
||||
pattern: "./node_modules/**/*",
|
||||
path: undefined,
|
||||
},
|
||||
ctx,
|
||||
)
|
||||
expect(result.metadata.truncated).toBe(true)
|
||||
})
|
||||
})
|
||||
test("basic", async () => {
|
||||
await App.provide({ cwd: process.cwd(), version: "test" }, async () => {
|
||||
await App.provide({ cwd: process.cwd() }, async () => {
|
||||
let result = await GlobTool.execute(
|
||||
{ pattern: "*.json" },
|
||||
{ sessionID: "test" },
|
||||
{
|
||||
pattern: "*.json",
|
||||
path: undefined,
|
||||
},
|
||||
ctx,
|
||||
)
|
||||
expect(result.metadata).toMatchObject({
|
||||
truncated: false,
|
||||
@@ -29,15 +41,12 @@ describe("tool.glob", () => {
|
||||
|
||||
describe("tool.ls", () => {
|
||||
test("basic", async () => {
|
||||
const result = await App.provide(
|
||||
{ cwd: process.cwd(), version: "test" },
|
||||
async () => {
|
||||
return await ListTool.execute(
|
||||
{ path: "./example" },
|
||||
{ sessionID: "test" },
|
||||
)
|
||||
},
|
||||
)
|
||||
const result = await App.provide({ cwd: process.cwd() }, async () => {
|
||||
return await ListTool.execute(
|
||||
{ path: "./example", ignore: [".git"] },
|
||||
ctx,
|
||||
)
|
||||
})
|
||||
expect(result.output).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,298 @@
|
||||
package theme
|
||||
|
||||
import (
|
||||
"github.com/charmbracelet/lipgloss/v2"
|
||||
"github.com/charmbracelet/lipgloss/v2/compat"
|
||||
)
|
||||
|
||||
// EverforestTheme implements the Theme interface with Everforest colors.
|
||||
// It provides both dark and light variants with Medium (default) contrast.
|
||||
type EverforestTheme struct {
|
||||
BaseTheme
|
||||
}
|
||||
|
||||
// NewEverforestTheme creates a new instance of the Everforest Medium theme.
|
||||
func NewEverforestTheme() *EverforestTheme {
|
||||
// Everforest color palette - Medium variant
|
||||
// Official colors from https://github.com/sainnhe/everforest/wiki
|
||||
// Dark mode colors - using Everforest:Dark Medium contrast palette
|
||||
darkStep1 := "#2d353b" // App background
|
||||
darkStep2 := "#333c43" // Subtle background
|
||||
darkStep3 := "#343f44" // UI element background
|
||||
darkStep4 := "#3d484d" // Hovered UI element background
|
||||
darkStep5 := "#475258" // Active/Selected UI element background
|
||||
darkStep6 := "#7a8478" // Subtle borders and separators
|
||||
darkStep7 := "#859289" // UI element border and focus rings
|
||||
darkStep8 := "#9da9a0" // Hovered UI element border
|
||||
darkStep9 := "#a7c080" // Solid backgrounds
|
||||
darkStep10 := "#83c092" // Hovered solid backgrounds
|
||||
darkStep11 := "#7a8478" // Low-contrast text
|
||||
darkStep12 := "#d3c6aa" // High-contrast text
|
||||
|
||||
// Dark mode accent colors
|
||||
darkPrimary := darkStep9 // Primary uses step 9 (green)
|
||||
darkSecondary := "#7fbbb3" // Secondary (blue)
|
||||
darkAccent := "#d699b6" // Accent (purple)
|
||||
darkRed := "#e67e80" // Error (red)
|
||||
darkOrange := "#e69875" // Warning (orange)
|
||||
darkGreen := "#a7c080" // Success (green)
|
||||
darkCyan := "#83c092" // Info (aqua)
|
||||
darkYellow := "#dbbc7f" // Emphasized text
|
||||
|
||||
// Light mode colors for the Everforest:Light Medium contrast palette
|
||||
lightStep1 := "#fdf6e3" // App background
|
||||
lightStep2 := "#efebd4" // Subtle background
|
||||
lightStep3 := "#f4f0d9" // UI element background
|
||||
lightStep4 := "#efebd4" // Hovered UI element background
|
||||
lightStep5 := "#e6e2cc" // Active/Selected UI element background
|
||||
lightStep6 := "#a6b0a0" // Subtle borders and separators
|
||||
lightStep7 := "#939f91" // UI element border and focus rings
|
||||
lightStep8 := "#829181" // Hovered UI element border
|
||||
lightStep9 := "#8da101" // Solid backgrounds
|
||||
lightStep10 := "#35a77c" // Hovered solid backgrounds
|
||||
lightStep11 := "#a6b0a0" // Low-contrast text
|
||||
lightStep12 := "#5c6a72" // High-contrast text
|
||||
|
||||
// Light mode accent colors
|
||||
lightPrimary := lightStep9 // Primary uses step 9 (green)
|
||||
lightSecondary := "#3a94c5" // Secondary blue
|
||||
lightAccent := "#df69ba" // Accent purple
|
||||
lightRed := "#f85552" // Error red
|
||||
lightOrange := "#f57d26" // Warning orange
|
||||
lightGreen := "#8da101" // Success green
|
||||
lightCyan := "#35a77c" // Info aqua
|
||||
lightYellow := "#dfa000" // Emphasized text
|
||||
|
||||
// Unused variables. These could be used for hover states
|
||||
_ = darkStep4
|
||||
_ = darkStep5
|
||||
_ = darkStep10
|
||||
_ = lightStep4
|
||||
_ = lightStep5
|
||||
_ = lightStep10
|
||||
|
||||
theme := &EverforestTheme{}
|
||||
|
||||
// Base colors
|
||||
theme.PrimaryColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkPrimary),
|
||||
Light: lipgloss.Color(lightPrimary),
|
||||
}
|
||||
theme.SecondaryColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkSecondary),
|
||||
Light: lipgloss.Color(lightSecondary),
|
||||
}
|
||||
theme.AccentColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkAccent),
|
||||
Light: lipgloss.Color(lightAccent),
|
||||
}
|
||||
|
||||
// Status colors
|
||||
theme.ErrorColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkRed),
|
||||
Light: lipgloss.Color(lightRed),
|
||||
}
|
||||
theme.WarningColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkOrange),
|
||||
Light: lipgloss.Color(lightOrange),
|
||||
}
|
||||
theme.SuccessColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkGreen),
|
||||
Light: lipgloss.Color(lightGreen),
|
||||
}
|
||||
theme.InfoColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkCyan),
|
||||
Light: lipgloss.Color(lightCyan),
|
||||
}
|
||||
|
||||
// Text colors
|
||||
theme.TextColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkStep12),
|
||||
Light: lipgloss.Color(lightStep12),
|
||||
}
|
||||
theme.TextMutedColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkStep11),
|
||||
Light: lipgloss.Color(lightStep11),
|
||||
}
|
||||
|
||||
// Background colors
|
||||
theme.BackgroundColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkStep1),
|
||||
Light: lipgloss.Color(lightStep1),
|
||||
}
|
||||
theme.BackgroundSubtleColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkStep2),
|
||||
Light: lipgloss.Color(lightStep2),
|
||||
}
|
||||
theme.BackgroundElementColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkStep3),
|
||||
Light: lipgloss.Color(lightStep3),
|
||||
}
|
||||
|
||||
// Border colors
|
||||
theme.BorderColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkStep7),
|
||||
Light: lipgloss.Color(lightStep7),
|
||||
}
|
||||
theme.BorderActiveColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkStep8),
|
||||
Light: lipgloss.Color(lightStep8),
|
||||
}
|
||||
theme.BorderSubtleColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkStep6),
|
||||
Light: lipgloss.Color(lightStep6),
|
||||
}
|
||||
|
||||
// Diff view colors
|
||||
theme.DiffAddedColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color("#A7C080"),
|
||||
Light: lipgloss.Color("#8DA101"),
|
||||
}
|
||||
theme.DiffRemovedColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color("#E67E80"),
|
||||
Light: lipgloss.Color("#F85552"),
|
||||
}
|
||||
theme.DiffContextColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color("#7A8478"),
|
||||
Light: lipgloss.Color("#A6B0A0"),
|
||||
}
|
||||
theme.DiffHunkHeaderColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color("#859289"),
|
||||
Light: lipgloss.Color("#939F91"),
|
||||
}
|
||||
theme.DiffHighlightAddedColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color("#A7C080"),
|
||||
Light: lipgloss.Color("#8DA101"),
|
||||
}
|
||||
theme.DiffHighlightRemovedColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color("#E67E80"),
|
||||
Light: lipgloss.Color("#F85552"),
|
||||
}
|
||||
theme.DiffAddedBgColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color("#425047"),
|
||||
Light: lipgloss.Color("#F0F1D2"),
|
||||
}
|
||||
theme.DiffRemovedBgColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color("#543A48"),
|
||||
Light: lipgloss.Color("#FBE3DA"),
|
||||
}
|
||||
theme.DiffContextBgColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkStep2),
|
||||
Light: lipgloss.Color(lightStep2),
|
||||
}
|
||||
theme.DiffLineNumberColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkStep3),
|
||||
Light: lipgloss.Color(lightStep3),
|
||||
}
|
||||
theme.DiffAddedLineNumberBgColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color("#3A4A3F"),
|
||||
Light: lipgloss.Color("#E8F2D1"),
|
||||
}
|
||||
theme.DiffRemovedLineNumberBgColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color("#4A3A40"),
|
||||
Light: lipgloss.Color("#FBDAD2"),
|
||||
}
|
||||
|
||||
// Markdown colors
|
||||
theme.MarkdownTextColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkStep12),
|
||||
Light: lipgloss.Color(lightStep12),
|
||||
}
|
||||
theme.MarkdownHeadingColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkSecondary),
|
||||
Light: lipgloss.Color(lightSecondary),
|
||||
}
|
||||
theme.MarkdownLinkColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkPrimary),
|
||||
Light: lipgloss.Color(lightPrimary),
|
||||
}
|
||||
theme.MarkdownLinkTextColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkCyan),
|
||||
Light: lipgloss.Color(lightCyan),
|
||||
}
|
||||
theme.MarkdownCodeColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkGreen),
|
||||
Light: lipgloss.Color(lightGreen),
|
||||
}
|
||||
theme.MarkdownBlockQuoteColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkYellow),
|
||||
Light: lipgloss.Color(lightYellow),
|
||||
}
|
||||
theme.MarkdownEmphColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkYellow),
|
||||
Light: lipgloss.Color(lightYellow),
|
||||
}
|
||||
theme.MarkdownStrongColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkAccent),
|
||||
Light: lipgloss.Color(lightAccent),
|
||||
}
|
||||
theme.MarkdownHorizontalRuleColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkStep11),
|
||||
Light: lipgloss.Color(lightStep11),
|
||||
}
|
||||
theme.MarkdownListItemColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkPrimary),
|
||||
Light: lipgloss.Color(lightPrimary),
|
||||
}
|
||||
theme.MarkdownListEnumerationColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkCyan),
|
||||
Light: lipgloss.Color(lightCyan),
|
||||
}
|
||||
theme.MarkdownImageColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkPrimary),
|
||||
Light: lipgloss.Color(lightPrimary),
|
||||
}
|
||||
theme.MarkdownImageTextColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkCyan),
|
||||
Light: lipgloss.Color(lightCyan),
|
||||
}
|
||||
theme.MarkdownCodeBlockColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkStep12),
|
||||
Light: lipgloss.Color(lightStep12),
|
||||
}
|
||||
|
||||
// Syntax highlighting colors
|
||||
theme.SyntaxCommentColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkStep11),
|
||||
Light: lipgloss.Color(lightStep11),
|
||||
}
|
||||
theme.SyntaxKeywordColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkPrimary),
|
||||
Light: lipgloss.Color(lightPrimary),
|
||||
}
|
||||
theme.SyntaxFunctionColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkSecondary),
|
||||
Light: lipgloss.Color(lightSecondary),
|
||||
}
|
||||
theme.SyntaxVariableColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkRed),
|
||||
Light: lipgloss.Color(lightRed),
|
||||
}
|
||||
theme.SyntaxStringColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkGreen),
|
||||
Light: lipgloss.Color(lightGreen),
|
||||
}
|
||||
theme.SyntaxNumberColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkAccent),
|
||||
Light: lipgloss.Color(lightAccent),
|
||||
}
|
||||
theme.SyntaxTypeColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkYellow),
|
||||
Light: lipgloss.Color(lightYellow),
|
||||
}
|
||||
theme.SyntaxOperatorColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkCyan),
|
||||
Light: lipgloss.Color(lightCyan),
|
||||
}
|
||||
theme.SyntaxPunctuationColor = compat.AdaptiveColor{
|
||||
Dark: lipgloss.Color(darkStep12),
|
||||
Light: lipgloss.Color(lightStep12),
|
||||
}
|
||||
|
||||
return theme
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Register the Everforest theme with the theme manager
|
||||
RegisterTheme("everforest", NewEverforestTheme())
|
||||
}
|
||||
@@ -27,7 +27,7 @@
|
||||
"sharp": "0.32.5",
|
||||
"shiki": "3.4.2",
|
||||
"solid-js": "1.9.7",
|
||||
"toolbeam-docs-theme": "0.2.4"
|
||||
"toolbeam-docs-theme": "0.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "catalog:",
|
||||
|
||||
@@ -18,9 +18,9 @@ function CodeBlock(props: CodeBlockProps) {
|
||||
const [local, rest] = splitProps(props, ["code", "lang", "onRendered"])
|
||||
let containerRef!: HTMLDivElement
|
||||
|
||||
const [html] = createResource(async () => {
|
||||
return (await codeToHtml(local.code, {
|
||||
lang: local.lang || "text",
|
||||
const [html] = createResource(() => [local.code, local.lang], async ([code, lang]) => {
|
||||
return (await codeToHtml(code || "", {
|
||||
lang: lang || "text",
|
||||
themes: {
|
||||
light: "github-light",
|
||||
dark: "github-dark",
|
||||
|
||||
@@ -54,9 +54,3 @@ const links = config.social || [];
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style is:global>
|
||||
body > div.page > header {
|
||||
border-color: var(--sl-color-divider);
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
.codeblock {
|
||||
pre {
|
||||
--shiki-dark-bg: var(--sl-color-bg) !important;
|
||||
background-color: var(--sl-color-bg) !important;
|
||||
--shiki-dark-bg: var(--sl-color-bg-surface) !important;
|
||||
background-color: var(--sl-color-bg-surface) !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style-position: inside;
|
||||
padding-left: 0.75rem;
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
gap: 2.5rem;
|
||||
line-height: 1;
|
||||
|
||||
--sm-tool-width: 28rem;
|
||||
--md-tool-width: 40rem;
|
||||
--lg-tool-width: 56rem;
|
||||
|
||||
--term-icon: url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2060%2016'%20preserveAspectRatio%3D'xMidYMid%20meet'%3E%3Ccircle%20cx%3D'8'%20cy%3D'8'%20r%3D'8'%2F%3E%3Ccircle%20cx%3D'30'%20cy%3D'8'%20r%3D'8'%2F%3E%3Ccircle%20cx%3D'52'%20cy%3D'8'%20r%3D'8'%2F%3E%3C%2Fsvg%3E");
|
||||
}
|
||||
|
||||
@@ -37,7 +41,7 @@
|
||||
|
||||
[data-element-label] {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
letter-spacing: -0.5px;
|
||||
color: var(--sl-color-text-dimmed);
|
||||
}
|
||||
|
||||
@@ -164,30 +168,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
[data-section="system-prompt"] {
|
||||
display: flex;
|
||||
gap: 0.3125rem;
|
||||
|
||||
[data-section="icon"] {
|
||||
flex: 0 0 auto;
|
||||
color: var(--sl-color-text-dimmed);
|
||||
opacity: 0.85;
|
||||
svg {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
[data-section="content"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
button {
|
||||
line-height: 1rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.parts {
|
||||
@@ -227,6 +207,7 @@
|
||||
}
|
||||
|
||||
& > [data-section="content"] {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
padding: 0 0 0.375rem;
|
||||
display: flex;
|
||||
@@ -236,20 +217,28 @@
|
||||
[data-part-tool-body] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
|
||||
span[data-part-title] {
|
||||
[data-part-title] {
|
||||
line-height: 18px;
|
||||
font-size: 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
color: var(--sl-color-text-secondary);
|
||||
max-wdith: var(--sm-tool-width);
|
||||
|
||||
b {
|
||||
word-break: break-all;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.375rem;
|
||||
|
||||
span[data-element-label] {
|
||||
color: var(--sl-color-text-secondary);
|
||||
}
|
||||
|
||||
&[data-size="md"] {
|
||||
font-size: 0.875rem;
|
||||
b {
|
||||
color: var(--sl-color-text);
|
||||
word-break: break-all;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,7 +256,7 @@
|
||||
display: inline-grid;
|
||||
align-items: center;
|
||||
grid-template-columns: max-content max-content minmax(0, 1fr);
|
||||
max-width: 100%;
|
||||
max-width: var(--md-tool-width);
|
||||
gap: 0.25rem 0.375rem;
|
||||
|
||||
& > div:nth-child(3n + 1) {
|
||||
@@ -279,16 +268,14 @@
|
||||
|
||||
& > div:nth-child(3n + 2),
|
||||
& > div:nth-child(3n + 3) {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
& > div:nth-child(3n + 3) {
|
||||
padding-left: 0.125rem;
|
||||
color: var(--sl-color-text-dimmed);
|
||||
word-break: break-word;
|
||||
color: var(--sl-color-text-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,6 +289,11 @@
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
[data-part-tool-edit] {
|
||||
width: 100%;
|
||||
max-width: var(--lg-tool-width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,16 +317,6 @@
|
||||
& > [data-section="content"] > [data-part-tool-body] {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
[data-part-title] {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
|
||||
b {
|
||||
color: var(--sl-color-text);
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-part-type="tool-grep"] {
|
||||
@@ -342,16 +324,6 @@
|
||||
> [data-section="content"] > [data-part-tool-body] {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
[data-part-title] {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
|
||||
b {
|
||||
color: var(--sl-color-text);
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-part-type="tool-write"],
|
||||
@@ -359,7 +331,9 @@
|
||||
[data-part-type="tool-fetch"] {
|
||||
[data-part-tool-result] {
|
||||
[data-part-tool-code] {
|
||||
width: var(--md-tool-width);
|
||||
border: 1px solid var(--sl-color-divider);
|
||||
background-color: var(--sl-color-bg-surface);
|
||||
border-radius: 0.25rem;
|
||||
padding: 0.5rem calc(0.5rem + 3px);
|
||||
|
||||
@@ -372,8 +346,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
[data-part-type="tool-edit"] {
|
||||
}
|
||||
}
|
||||
|
||||
.message-text {
|
||||
@@ -384,6 +356,8 @@
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
align-self: flex-start;
|
||||
max-width: var(--md-tool-width);
|
||||
|
||||
&[data-size="sm"] {
|
||||
pre {
|
||||
@@ -411,7 +385,7 @@
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
&[data-highlight="true"] {
|
||||
&[data-invert="true"] {
|
||||
background-color: var(--sl-color-blue-high);
|
||||
|
||||
pre {
|
||||
@@ -428,6 +402,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
&[data-highlight="true"] {
|
||||
background-color: var(--sl-color-blue-low);
|
||||
}
|
||||
|
||||
&[data-expanded="true"] {
|
||||
pre {
|
||||
display: block;
|
||||
@@ -443,6 +421,50 @@
|
||||
}
|
||||
}
|
||||
|
||||
.message-lsp {
|
||||
background-color: var(--sl-color-bg-surface);
|
||||
padding: 0.5rem calc(0.5rem + 3px);
|
||||
border-radius: 0.25rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
align-self: flex-start;
|
||||
max-width: var(--md-tool-width);
|
||||
|
||||
padding: 0.5rem calc(0.5rem + 3px);
|
||||
|
||||
pre {
|
||||
--shiki-dark-bg: var(--sl-color-bg-surface) !important;
|
||||
background-color: var(--sl-color-bg-surface) !important;
|
||||
line-height: 1.4;
|
||||
font-size: 0.75rem;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
&[data-expanded="true"] {
|
||||
pre {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
&[data-expanded="false"] {
|
||||
pre {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 7;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
flex: 0 0 auto;
|
||||
padding: 2px 0;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.message-terminal {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -450,6 +472,7 @@
|
||||
gap: 0.5rem;
|
||||
|
||||
& > [data-section="body"] {
|
||||
width: var(--sm-tool-width);
|
||||
border: 1px solid var(--sl-color-divider);
|
||||
border-radius: 0.25rem;
|
||||
max-width: 100%;
|
||||
@@ -460,7 +483,7 @@
|
||||
width: 100%;
|
||||
height: 1.625rem;
|
||||
text-align: center;
|
||||
padding: 0 0.75rem 0 3.25rem;
|
||||
padding: 0 3.25rem;
|
||||
|
||||
& > span {
|
||||
max-width: min(100%, 140ch);
|
||||
@@ -491,12 +514,10 @@
|
||||
|
||||
[data-section="content"] {
|
||||
padding: 0.5rem calc(0.5rem + 3px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
|
||||
pre {
|
||||
--shiki-dark-bg: var(--sl-color-bg) !important;
|
||||
background-color: var(--sl-color-bg) !important;
|
||||
line-height: 1.6;
|
||||
font-size: 0.75rem;
|
||||
white-space: pre-wrap;
|
||||
@@ -533,6 +554,8 @@
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
align-self: flex-start;
|
||||
max-width: var(--md-tool-width);
|
||||
|
||||
button {
|
||||
flex: 0 0 auto;
|
||||
@@ -540,6 +563,10 @@
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
&[data-highlight="true"] {
|
||||
background-color: var(--sl-color-blue-low);
|
||||
}
|
||||
|
||||
&[data-expanded="true"] {
|
||||
[data-elment-markdown] {
|
||||
display: block;
|
||||
@@ -566,6 +593,7 @@
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: var(--sm-tool-width);
|
||||
border: 1px solid var(--sl-color-divider);
|
||||
border-radius: 0.25rem;
|
||||
|
||||
@@ -577,6 +605,7 @@
|
||||
padding: 0.375rem 0.625rem 0.375rem 1.75rem;
|
||||
border-bottom: 1px solid var(--sl-color-divider);
|
||||
line-height: 1.5;
|
||||
word-break: break-word;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
@@ -614,9 +643,9 @@
|
||||
}
|
||||
}
|
||||
&[data-status="completed"] {
|
||||
color: var(--sl-color-text-dimmed);
|
||||
color: var(--sl-color-text-secondary);
|
||||
|
||||
& > span { border-color: var(--sl-color-hairline); }
|
||||
& > span { border-color: var(--sl-color-green-low); }
|
||||
& > span::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
@@ -624,7 +653,7 @@
|
||||
left: 2px;
|
||||
width: calc(0.75rem - 2px - 4px);
|
||||
height: calc(0.75rem - 2px - 4px);
|
||||
box-shadow: inset 1rem 1rem var(--sl-color-divider);
|
||||
box-shadow: inset 1rem 1rem var(--sl-color-green);
|
||||
|
||||
transform-origin: bottom left;
|
||||
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
diff --git a/dist/index.mjs b/dist/index.mjs
|
||||
index 92a80377692488c4ba8801ce33e7736ad7055e43..add6281bbecaa1c03d3b48eb99aead4a7a7336b2 100644
|
||||
--- a/dist/index.mjs
|
||||
+++ b/dist/index.mjs
|
||||
@@ -1593,7 +1593,7 @@ function prepareCallSettings({
|
||||
return {
|
||||
maxTokens,
|
||||
// TODO v5 remove default 0 for temperature
|
||||
- temperature: temperature != null ? temperature : 0,
|
||||
+ temperature: temperature,
|
||||
topP,
|
||||
topK,
|
||||
presencePenalty,
|
||||
Reference in New Issue
Block a user