Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7dd0918d32 | ||
|
|
4b26b43855 | ||
|
|
9d7cfda9fe | ||
|
|
a3cf18c905 | ||
|
|
0b1a8ae699 | ||
|
|
eb70b1e5c8 | ||
|
|
00a3d818b6 | ||
|
|
2384c7e734 | ||
|
|
1bad3d9894 | ||
|
|
4f715e66dc | ||
|
|
ec001ca02f | ||
|
|
a2d3b9f0c8 | ||
|
|
9cfb6ff964 | ||
|
|
6ed661c140 | ||
|
|
9dc00edfc9 | ||
|
|
e063bf888e | ||
|
|
6f18475428 | ||
|
|
3664b09812 | ||
|
|
7050cc0ac3 | ||
|
|
4d3d63294d | ||
|
|
6bc61cbc2d | ||
|
|
01d351bebe | ||
|
|
dbba4a97aa | ||
|
|
0dc586faef | ||
|
|
f19c6b05f2 | ||
|
|
bc34f08333 | ||
|
|
b7ee16aabd | ||
|
|
ed1b0d97bf | ||
|
|
8d3b2fb821 | ||
|
|
fa991920bc | ||
|
|
5e79e3d7a5 | ||
|
|
966015c9ae | ||
|
|
61f057337a | ||
|
|
0b261054a2 | ||
|
|
e2e481cbb5 | ||
|
|
5140e83012 |
@@ -17,7 +17,7 @@ jobs:
|
||||
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
with:
|
||||
bun-version: latest
|
||||
bun-version: 1.2.17
|
||||
|
||||
- run: bun install
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.2.16
|
||||
bun-version: 1.2.17
|
||||
|
||||
- name: Install makepkg
|
||||
run: |
|
||||
|
||||
@@ -458,7 +458,7 @@
|
||||
|
||||
"@types/babel__traverse": ["@types/babel__traverse@7.20.7", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.2.16", "", { "dependencies": { "bun-types": "1.2.16" } }, "sha512-1aCZJ/6nSiViw339RsaNhkNoEloLaPzZhxMOYEa7OzRzO41IGg5n/7I43/ZIAW/c+Q6cT12Vf7fOZOoVIzb5BQ=="],
|
||||
"@types/bun": ["@types/bun@1.2.17", "", { "dependencies": { "bun-types": "1.2.17" } }, "sha512-l/BYs/JYt+cXA/0+wUhulYJB6a6p//GTPiJ7nV+QHa8iiId4HZmnu/3J/SowP5g0rTiERY2kfGKXEK5Ehltx4Q=="],
|
||||
|
||||
"@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
|
||||
|
||||
@@ -596,7 +596,7 @@
|
||||
|
||||
"buffer": ["buffer@4.9.2", "", { "dependencies": { "base64-js": "^1.0.2", "ieee754": "^1.1.4", "isarray": "^1.0.0" } }, "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg=="],
|
||||
|
||||
"bun-types": ["bun-types@1.2.16", "", { "dependencies": { "@types/node": "*" } }, "sha512-ciXLrHV4PXax9vHvUrkvun9VPVGOVwbbbBF/Ev1cXz12lyEZMoJpIJABOfPcN9gDJRaiKF9MVbSygLg4NXu3/A=="],
|
||||
"bun-types": ["bun-types@1.2.17", "", { "dependencies": { "@types/node": "*" } }, "sha512-ElC7ItwT3SCQwYZDYoAH+q6KT4Fxjl8DtZ6qDulUFBmXA8YB4xo+l54J9ZJN+k2pphfn9vk7kfubeSd5QfTVJQ=="],
|
||||
|
||||
"bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
|
||||
|
||||
|
||||
+1
-7
@@ -1,9 +1,3 @@
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"mcp": {
|
||||
"sentry": {
|
||||
"type": "remote",
|
||||
"url": "https://mcp.sentry.dev/sse"
|
||||
}
|
||||
}
|
||||
"$schema": "https://opencode.ai/config.json"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
if defined OPENCODE_BIN_PATH (
|
||||
set "resolved=%OPENCODE_BIN_PATH%"
|
||||
goto :execute
|
||||
)
|
||||
|
||||
rem Get the directory of this script
|
||||
set "script_dir=%~dp0"
|
||||
set "script_dir=%script_dir:~0,-1%"
|
||||
|
||||
rem Detect platform and architecture
|
||||
set "platform=win32"
|
||||
|
||||
rem Detect architecture
|
||||
if "%PROCESSOR_ARCHITECTURE%"=="AMD64" (
|
||||
set "arch=x64"
|
||||
) else if "%PROCESSOR_ARCHITECTURE%"=="ARM64" (
|
||||
set "arch=arm64"
|
||||
) else if "%PROCESSOR_ARCHITECTURE%"=="x86" (
|
||||
set "arch=x86"
|
||||
) else (
|
||||
set "arch=x64"
|
||||
)
|
||||
|
||||
set "name=opencode-!platform!-!arch!"
|
||||
set "binary=opencode.exe"
|
||||
|
||||
rem Search for the binary starting from script location
|
||||
set "resolved="
|
||||
set "current_dir=%script_dir%"
|
||||
|
||||
:search_loop
|
||||
set "candidate=%current_dir%\node_modules\%name%\bin\%binary%"
|
||||
if exist "%candidate%" (
|
||||
set "resolved=%candidate%"
|
||||
goto :execute
|
||||
)
|
||||
|
||||
rem Move up one directory
|
||||
for %%i in ("%current_dir%") do set "parent_dir=%%~dpi"
|
||||
set "parent_dir=%parent_dir:~0,-1%"
|
||||
|
||||
rem Check if we've reached the root
|
||||
if "%current_dir%"=="%parent_dir%" goto :not_found
|
||||
set "current_dir=%parent_dir%"
|
||||
goto :search_loop
|
||||
|
||||
:not_found
|
||||
echo It seems that your package manager failed to install the right version of the OpenCode CLI for your platform. You can try manually installing the "%name%" package >&2
|
||||
exit /b 1
|
||||
|
||||
:execute
|
||||
rem Execute the binary with all arguments
|
||||
"%resolved%" %*
|
||||
@@ -8,6 +8,9 @@
|
||||
"typecheck": "tsc --noEmit",
|
||||
"dev": "bun run ./src/index.ts"
|
||||
},
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode"
|
||||
},
|
||||
"exports": {
|
||||
"./*": "./src/*.ts"
|
||||
},
|
||||
|
||||
@@ -29,7 +29,7 @@ const targets = [
|
||||
["linux", "x64"],
|
||||
["darwin", "x64"],
|
||||
["darwin", "arm64"],
|
||||
// ["windows", "x64"],
|
||||
["windows", "x64"],
|
||||
]
|
||||
|
||||
await $`rm -rf dist`
|
||||
|
||||
@@ -46,7 +46,7 @@ export namespace App {
|
||||
const data = path.join(
|
||||
Global.Path.data,
|
||||
"project",
|
||||
git ? git.split(path.sep).join("-") : "global",
|
||||
git ? directory(git) : "global",
|
||||
)
|
||||
const stateFile = Bun.file(path.join(data, APP_JSON))
|
||||
const state = (await stateFile.json().catch(() => ({}))) as {
|
||||
@@ -133,4 +133,13 @@ export namespace App {
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
function directory(input: string): string {
|
||||
return input
|
||||
.split(path.sep)
|
||||
.filter(Boolean)
|
||||
.join("-")
|
||||
.replace(/[^A-Za-z0-9_]/g, "-")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Global } from "../global"
|
||||
import { lazy } from "../util/lazy"
|
||||
import path from "path"
|
||||
|
||||
export const AuthCopilot = lazy(async () => {
|
||||
const file = Bun.file(path.join(Global.Path.state, "plugin", "copilot.ts"))
|
||||
const response = fetch(
|
||||
"https://raw.githubusercontent.com/sst/opencode-github-copilot/refs/heads/main/auth.ts",
|
||||
)
|
||||
.then((x) => Bun.write(file, x))
|
||||
.catch(() => {})
|
||||
|
||||
if (!file.exists()) {
|
||||
const worked = await response
|
||||
if (!worked) return
|
||||
}
|
||||
const result = await import(file.name!).catch(() => {})
|
||||
if (!result) return
|
||||
return result.AuthCopilot
|
||||
})
|
||||
@@ -37,7 +37,7 @@ export namespace AuthGithubCopilot {
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "GithubCopilot/1.155.0",
|
||||
"User-Agent": "GitHubCopilotChat/0.26.7",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
client_id: CLIENT_ID,
|
||||
@@ -60,7 +60,7 @@ export namespace AuthGithubCopilot {
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "GithubCopilot/1.155.0",
|
||||
"User-Agent": "GitHubCopilotChat/0.26.7",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
client_id: CLIENT_ID,
|
||||
@@ -101,9 +101,9 @@ export namespace AuthGithubCopilot {
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
Authorization: `Bearer ${info.refresh}`,
|
||||
"User-Agent": "GithubCopilot/1.155.0",
|
||||
"Editor-Version": "vscode/1.85.1",
|
||||
"Editor-Plugin-Version": "copilot/1.155.0",
|
||||
"User-Agent": "GitHubCopilotChat/0.26.7",
|
||||
"Editor-Version": "vscode/1.99.3",
|
||||
"Editor-Plugin-Version": "copilot-chat/0.26.7",
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ export namespace BunProc {
|
||||
) {
|
||||
log.info("running", {
|
||||
cmd: [which(), ...cmd],
|
||||
options,
|
||||
...options,
|
||||
})
|
||||
const result = Bun.spawn([which(), ...cmd], {
|
||||
...options,
|
||||
@@ -26,6 +26,15 @@ export namespace BunProc {
|
||||
},
|
||||
})
|
||||
const code = await result.exited
|
||||
// @ts-ignore
|
||||
const stdout = await result.stdout.text()
|
||||
// @ts-ignore
|
||||
const stderr = await result.stderr.text()
|
||||
log.info("done", {
|
||||
code,
|
||||
stdout,
|
||||
stderr,
|
||||
})
|
||||
if (code !== 0) {
|
||||
throw new Error(`Command failed with exit code ${result.exitCode}`)
|
||||
}
|
||||
@@ -53,7 +62,7 @@ export namespace BunProc {
|
||||
if (parsed.dependencies[pkg] === version) return mod
|
||||
parsed.dependencies[pkg] = version
|
||||
await Bun.write(pkgjson, JSON.stringify(parsed, null, 2))
|
||||
await BunProc.run(["install"], {
|
||||
await BunProc.run(["install", "--registry=https://registry.npmjs.org"], {
|
||||
cwd: Global.Path.cache,
|
||||
}).catch((e) => {
|
||||
new InstallFailedError(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { AuthAnthropic } from "../../auth/anthropic"
|
||||
import { AuthGithubCopilot } from "../../auth/github-copilot"
|
||||
import { AuthCopilot } from "../../auth/copilot"
|
||||
import { Auth } from "../../auth"
|
||||
import { cmd } from "./cmd"
|
||||
import * as prompts from "@clack/prompts"
|
||||
@@ -10,7 +10,7 @@ import { map, pipe, sortBy, values } from "remeda"
|
||||
|
||||
export const AuthCommand = cmd({
|
||||
command: "auth",
|
||||
describe: "Manage credentials",
|
||||
describe: "manage credentials",
|
||||
builder: (yargs) =>
|
||||
yargs
|
||||
.command(AuthLoginCommand)
|
||||
@@ -41,7 +41,7 @@ export const AuthListCommand = cmd({
|
||||
|
||||
export const AuthLoginCommand = cmd({
|
||||
command: "login",
|
||||
describe: "login to a provider",
|
||||
describe: "log in to a provider",
|
||||
async handler() {
|
||||
UI.empty()
|
||||
prompts.intro("Add credential")
|
||||
@@ -148,9 +148,10 @@ export const AuthLoginCommand = cmd({
|
||||
}
|
||||
}
|
||||
|
||||
if (provider === "github-copilot") {
|
||||
const copilot = await AuthCopilot()
|
||||
if (provider === "github-copilot" && copilot) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 10))
|
||||
const deviceInfo = await AuthGithubCopilot.authorize()
|
||||
const deviceInfo = await copilot.authorize()
|
||||
|
||||
prompts.note(
|
||||
`Please visit: ${deviceInfo.verification}\nEnter code: ${deviceInfo.user}`,
|
||||
@@ -163,13 +164,19 @@ export const AuthLoginCommand = cmd({
|
||||
await new Promise((resolve) =>
|
||||
setTimeout(resolve, deviceInfo.interval * 1000),
|
||||
)
|
||||
const status = await AuthGithubCopilot.poll(deviceInfo.device)
|
||||
if (status === "pending") continue
|
||||
if (status === "complete") {
|
||||
const response = await copilot.poll(deviceInfo.device)
|
||||
if (response.status === "pending") continue
|
||||
if (response.status === "success") {
|
||||
await Auth.set("github-copilot", {
|
||||
type: "oauth",
|
||||
refresh: response.refresh,
|
||||
access: response.access,
|
||||
expires: response.expires,
|
||||
})
|
||||
spinner.stop("Login successful")
|
||||
break
|
||||
}
|
||||
if (status === "failed") {
|
||||
if (response.status === "failed") {
|
||||
spinner.stop("Failed to authorize", 1)
|
||||
break
|
||||
}
|
||||
@@ -195,7 +202,7 @@ export const AuthLoginCommand = cmd({
|
||||
|
||||
export const AuthLogoutCommand = cmd({
|
||||
command: "logout",
|
||||
describe: "logout from a configured provider",
|
||||
describe: "log out from a configured provider",
|
||||
async handler() {
|
||||
UI.empty()
|
||||
const credentials = await Auth.all().then((x) => Object.entries(x))
|
||||
|
||||
@@ -25,7 +25,7 @@ const TOOL: Record<string, [string, string]> = {
|
||||
|
||||
export const RunCommand = cmd({
|
||||
command: "run [message..]",
|
||||
describe: "Run opencode with a message",
|
||||
describe: "run opencode with a message",
|
||||
builder: (yargs: Argv) => {
|
||||
return yargs
|
||||
.positional("message", {
|
||||
@@ -36,12 +36,12 @@ export const RunCommand = cmd({
|
||||
})
|
||||
.option("continue", {
|
||||
alias: ["c"],
|
||||
describe: "Continue the last session",
|
||||
describe: "continue the last session",
|
||||
type: "boolean",
|
||||
})
|
||||
.option("session", {
|
||||
alias: ["s"],
|
||||
describe: "Session ID to continue",
|
||||
describe: "session id to continue",
|
||||
type: "string",
|
||||
})
|
||||
.option("share", {
|
||||
@@ -51,7 +51,7 @@ export const RunCommand = cmd({
|
||||
.option("model", {
|
||||
type: "string",
|
||||
alias: ["m"],
|
||||
describe: "Model to use in the format of provider/model",
|
||||
describe: "model to use in the format of provider/model",
|
||||
})
|
||||
},
|
||||
handler: async (args) => {
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { App } from "../../app/app"
|
||||
import { Provider } from "../../provider/provider"
|
||||
import { Server } from "../../server/server"
|
||||
import { Share } from "../../share/share"
|
||||
import { cmd } from "./cmd"
|
||||
|
||||
export const ServeCommand = cmd({
|
||||
command: "serve",
|
||||
builder: (yargs) =>
|
||||
yargs
|
||||
.option("port", {
|
||||
alias: ["p"],
|
||||
type: "number",
|
||||
describe: "port to listen on",
|
||||
default: 4096,
|
||||
})
|
||||
.option("hostname", {
|
||||
alias: ["h"],
|
||||
type: "string",
|
||||
describe: "hostname to listen on",
|
||||
default: "127.0.0.1",
|
||||
}),
|
||||
describe: "starts a headless opencode server",
|
||||
handler: async (args) => {
|
||||
const cwd = process.cwd()
|
||||
await App.provide({ cwd }, async () => {
|
||||
const providers = await Provider.list()
|
||||
if (Object.keys(providers).length === 0) {
|
||||
return "needs_provider"
|
||||
}
|
||||
|
||||
const hostname = args.hostname
|
||||
const port = args.port
|
||||
|
||||
await Share.init()
|
||||
const server = Server.listen({
|
||||
port,
|
||||
hostname,
|
||||
})
|
||||
|
||||
console.log(
|
||||
`opencode server listening on http://${server.hostname}:${server.port}`,
|
||||
)
|
||||
|
||||
await new Promise(() => {})
|
||||
|
||||
server.stop()
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -5,16 +5,16 @@ import { Installation } from "../../installation"
|
||||
|
||||
export const UpgradeCommand = {
|
||||
command: "upgrade [target]",
|
||||
describe: "upgrade opencode to the latest version or a specific version",
|
||||
describe: "upgrade opencode to the latest or a specific version",
|
||||
builder: (yargs: Argv) => {
|
||||
return yargs
|
||||
.positional("target", {
|
||||
describe: "specific version to upgrade to (e.g., '0.1.48' or 'v0.1.48')",
|
||||
describe: "version to upgrade to, for ex '0.1.48' or 'v0.1.48'",
|
||||
type: "string",
|
||||
})
|
||||
.option("method", {
|
||||
alias: "m",
|
||||
describe: "installation method to use (curl, npm, pnpm, bun, brew)",
|
||||
describe: "installation method to use",
|
||||
type: "string",
|
||||
choices: ["curl", "npm", "pnpm", "bun", "brew"],
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { z } from "zod"
|
||||
import { EOL } from "os"
|
||||
import { NamedError } from "../util/error"
|
||||
|
||||
export namespace UI {
|
||||
@@ -29,7 +30,7 @@ export namespace UI {
|
||||
|
||||
export function println(...message: string[]) {
|
||||
print(...message)
|
||||
Bun.stderr.write("\n")
|
||||
Bun.stderr.write(EOL)
|
||||
}
|
||||
|
||||
export function print(...message: string[]) {
|
||||
@@ -52,7 +53,7 @@ export namespace UI {
|
||||
result.push(row[0])
|
||||
result.push("\x1b[0m")
|
||||
result.push(row[1])
|
||||
result.push("\n")
|
||||
result.push(EOL)
|
||||
}
|
||||
return result.join("").trimEnd()
|
||||
}
|
||||
|
||||
+5
-2
@@ -116,14 +116,17 @@ export namespace Ripgrep {
|
||||
export async function files(input: {
|
||||
cwd: string
|
||||
query?: string
|
||||
glob?: string
|
||||
limit?: number
|
||||
}) {
|
||||
const commands = [`${await filepath()} --files --hidden --glob='!.git/*'`]
|
||||
const commands = [
|
||||
`${await filepath()} --files --hidden --glob='!.git/*' ${input.glob ? `--glob='${input.glob}'` : ``}`,
|
||||
]
|
||||
if (input.query)
|
||||
commands.push(`${await Fzf.filepath()} --filter=${input.query}`)
|
||||
if (input.limit) commands.push(`head -n ${input.limit}`)
|
||||
const joined = commands.join(" | ")
|
||||
const result = await $`${{ raw: joined }}`.cwd(input.cwd).text()
|
||||
const result = await $`${{ raw: joined }}`.cwd(input.cwd).nothrow().text()
|
||||
return result.split("\n").filter(Boolean)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Server } from "./server/server"
|
||||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
import { Share } from "./share/share"
|
||||
import url from "node:url"
|
||||
import { Global } from "./global"
|
||||
import yargs from "yargs"
|
||||
import { hideBin } from "yargs/helpers"
|
||||
@@ -20,15 +21,17 @@ import { Bus } from "./bus"
|
||||
import { Config } from "./config/config"
|
||||
import { NamedError } from "./util/error"
|
||||
import { FormatError } from "./cli/error"
|
||||
import { MCP } from "./mcp"
|
||||
import { ServeCommand } from "./cli/cmd/serve"
|
||||
|
||||
const cancel = new AbortController()
|
||||
|
||||
const cli = yargs(hideBin(process.argv))
|
||||
.scriptName("opencode")
|
||||
.version(Installation.VERSION)
|
||||
.help("help", "show help")
|
||||
.version("version", "show version number", Installation.VERSION)
|
||||
.alias("version", "v")
|
||||
.option("print-logs", {
|
||||
describe: "Print logs to stderr",
|
||||
describe: "print logs to stderr",
|
||||
type: "boolean",
|
||||
})
|
||||
.middleware(async () => {
|
||||
@@ -41,7 +44,7 @@ const cli = yargs(hideBin(process.argv))
|
||||
.usage("\n" + UI.logo())
|
||||
.command({
|
||||
command: "$0 [project]",
|
||||
describe: "start opencode TUI",
|
||||
describe: "start opencode tui",
|
||||
builder: (yargs) =>
|
||||
yargs.positional("project", {
|
||||
type: "string",
|
||||
@@ -58,13 +61,21 @@ const cli = yargs(hideBin(process.argv))
|
||||
}
|
||||
|
||||
await Share.init()
|
||||
const server = Server.listen()
|
||||
const server = Server.listen({
|
||||
port: 0,
|
||||
})
|
||||
|
||||
let cmd = ["go", "run", "./main.go"]
|
||||
let cwd = new URL("../../tui/cmd/opencode", import.meta.url).pathname
|
||||
let cwd = url.fileURLToPath(
|
||||
new URL("../../tui/cmd/opencode", import.meta.url),
|
||||
)
|
||||
if (Bun.embeddedFiles.length > 0) {
|
||||
const blob = Bun.embeddedFiles[0] as File
|
||||
const binary = path.join(Global.Path.cache, "tui", blob.name)
|
||||
let binaryName = blob.name
|
||||
if (process.platform === "win32" && !binaryName.endsWith(".exe")) {
|
||||
binaryName += ".exe"
|
||||
}
|
||||
const binary = path.join(Global.Path.cache, "tui", binaryName)
|
||||
const file = Bun.file(binary)
|
||||
if (!(await file.exists())) {
|
||||
await Bun.write(file, blob, { mode: 0o755 })
|
||||
@@ -95,7 +106,8 @@ const cli = yargs(hideBin(process.argv))
|
||||
if (Installation.isSnapshot()) return
|
||||
const config = await Config.global()
|
||||
if (config.autoupdate === false) return
|
||||
const latest = await Installation.latest()
|
||||
const latest = await Installation.latest().catch(() => {})
|
||||
if (!latest) return
|
||||
if (Installation.VERSION === latest) return
|
||||
const method = await Installation.method()
|
||||
if (method === "unknown") return
|
||||
@@ -126,6 +138,7 @@ const cli = yargs(hideBin(process.argv))
|
||||
.command(ScrapCommand)
|
||||
.command(AuthCommand)
|
||||
.command(UpgradeCommand)
|
||||
.command(ServeCommand)
|
||||
.fail((msg) => {
|
||||
if (
|
||||
msg.startsWith("Unknown argument") ||
|
||||
|
||||
@@ -115,7 +115,9 @@ export namespace Installation {
|
||||
case "bun":
|
||||
return $`bun install -g opencode-ai@${target}`
|
||||
case "brew":
|
||||
return $`brew install sst/tap/opencode`
|
||||
return $`brew install sst/tap/opencode`.env({
|
||||
HOMEBREW_NO_AUTO_UPDATE: "1",
|
||||
})
|
||||
default:
|
||||
throw new Error(`Unknown method: ${method}`)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export async function data() {
|
||||
const json = await fetch("https://models.dev/api.json").then((x) => x.text())
|
||||
return json
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { Global } from "../global"
|
||||
import { Log } from "../util/log"
|
||||
import path from "path"
|
||||
import { z } from "zod"
|
||||
import { data } from "./models-macro" with { type: "macro" }
|
||||
|
||||
export namespace ModelsDev {
|
||||
const log = Log.create({ service: "models.dev" })
|
||||
@@ -54,16 +55,15 @@ export namespace ModelsDev {
|
||||
refresh()
|
||||
return result as Record<string, Provider>
|
||||
}
|
||||
await refresh()
|
||||
return get()
|
||||
refresh()
|
||||
const json = await data()
|
||||
return JSON.parse(json) as Record<string, Provider>
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
const file = Bun.file(filepath)
|
||||
log.info("refreshing")
|
||||
const result = await fetch("https://models.dev/api.json")
|
||||
if (!result.ok)
|
||||
throw new Error(`Failed to fetch models.dev: ${result.statusText}`)
|
||||
await Bun.write(file, result)
|
||||
const result = await fetch("https://models.dev/api.json").catch(() => {})
|
||||
if (result && result.ok) await Bun.write(file, result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import type { Tool } from "../tool/tool"
|
||||
import { WriteTool } from "../tool/write"
|
||||
import { TodoReadTool, TodoWriteTool } from "../tool/todo"
|
||||
import { AuthAnthropic } from "../auth/anthropic"
|
||||
import { AuthGithubCopilot } from "../auth/github-copilot"
|
||||
import { AuthCopilot } from "../auth/copilot"
|
||||
import { ModelsDev } from "./models"
|
||||
import { NamedError } from "../util/error"
|
||||
import { Auth } from "../auth"
|
||||
@@ -68,8 +68,10 @@ export namespace Provider {
|
||||
}
|
||||
},
|
||||
"github-copilot": async (provider) => {
|
||||
const info = await AuthGithubCopilot.access()
|
||||
if (!info) return false
|
||||
const copilot = await AuthCopilot()
|
||||
if (!copilot) return false
|
||||
let info = await Auth.get("github-copilot")
|
||||
if (!info || info.type !== "oauth") return false
|
||||
|
||||
if (provider && provider.models) {
|
||||
for (const model of Object.values(provider.models)) {
|
||||
@@ -84,14 +86,23 @@ export namespace Provider {
|
||||
options: {
|
||||
apiKey: "",
|
||||
async fetch(input: any, init: any) {
|
||||
const token = await AuthGithubCopilot.access()
|
||||
if (!token) throw new Error("GitHub Copilot authentication expired")
|
||||
const info = await Auth.get("github-copilot")
|
||||
if (!info || info.type !== "oauth") return
|
||||
if (!info.access || info.expires < Date.now()) {
|
||||
const tokens = await copilot.access(info.refresh)
|
||||
if (!tokens)
|
||||
throw new Error("GitHub Copilot authentication expired")
|
||||
await Auth.set("github-copilot", {
|
||||
type: "oauth",
|
||||
...tokens,
|
||||
})
|
||||
info.access = tokens.access
|
||||
}
|
||||
const headers = {
|
||||
...init.headers,
|
||||
Authorization: `Bearer ${token}`,
|
||||
"User-Agent": "GithubCopilot/1.155.0",
|
||||
"Editor-Version": "vscode/1.85.1",
|
||||
"Editor-Plugin-Version": "copilot/1.155.0",
|
||||
...copilot.HEADERS,
|
||||
Authorization: `Bearer ${info.access}`,
|
||||
"Openai-Intent": "conversation-edits",
|
||||
}
|
||||
delete headers["x-api-key"]
|
||||
return fetch(input, {
|
||||
@@ -102,14 +113,6 @@ export namespace Provider {
|
||||
},
|
||||
}
|
||||
},
|
||||
openai: async () => {
|
||||
return {
|
||||
async getModel(sdk: any, modelID: string) {
|
||||
return sdk.responses(modelID)
|
||||
},
|
||||
options: {},
|
||||
}
|
||||
},
|
||||
"amazon-bedrock": async () => {
|
||||
if (!process.env["AWS_PROFILE"] && !process.env["AWS_ACCESS_KEY_ID"])
|
||||
return false
|
||||
|
||||
@@ -390,6 +390,33 @@ export namespace Server {
|
||||
return c.json(Session.abort(body.sessionID))
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/session_delete",
|
||||
describeRoute({
|
||||
description: "Delete a session and all its data",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successfully deleted session",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
zValidator(
|
||||
"json",
|
||||
z.object({
|
||||
sessionID: z.string(),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const body = c.req.valid("json")
|
||||
await Session.remove(body.sessionID)
|
||||
return c.json(true)
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/session_summarize",
|
||||
describeRoute({
|
||||
@@ -552,10 +579,10 @@ export namespace Server {
|
||||
return result
|
||||
}
|
||||
|
||||
export function listen() {
|
||||
export function listen(opts: { port: number; hostname: string }) {
|
||||
const server = Bun.serve({
|
||||
port: 0,
|
||||
hostname: "0.0.0.0",
|
||||
port: opts.port,
|
||||
hostname: opts.hostname,
|
||||
idleTimeout: 0,
|
||||
fetch: app().fetch,
|
||||
})
|
||||
|
||||
@@ -72,6 +72,12 @@ export namespace Session {
|
||||
info: Info,
|
||||
}),
|
||||
),
|
||||
Deleted: Bus.event(
|
||||
"session.deleted",
|
||||
z.object({
|
||||
info: Info,
|
||||
}),
|
||||
),
|
||||
Error: Bus.event(
|
||||
"session.error",
|
||||
z.object({
|
||||
@@ -206,6 +212,17 @@ export namespace Session {
|
||||
}
|
||||
}
|
||||
|
||||
export async function children(parentID: string) {
|
||||
const result = [] as Session.Info[]
|
||||
for await (const item of Storage.list("session/info")) {
|
||||
const sessionID = path.basename(item, ".json")
|
||||
const session = await get(sessionID)
|
||||
if (session.parentID !== parentID) continue
|
||||
result.push(session)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export function abort(sessionID: string) {
|
||||
const controller = state().pending.get(sessionID)
|
||||
if (!controller) return false
|
||||
@@ -214,6 +231,28 @@ export namespace Session {
|
||||
return true
|
||||
}
|
||||
|
||||
export async function remove(sessionID: string, emitEvent = true) {
|
||||
try {
|
||||
abort(sessionID)
|
||||
const session = await get(sessionID)
|
||||
for (const child of await children(sessionID)) {
|
||||
await remove(child.id, false)
|
||||
}
|
||||
await unshare(sessionID).catch(() => {})
|
||||
await Storage.remove(`session/info/${sessionID}`).catch(() => {})
|
||||
await Storage.removeDir(`session/message/${sessionID}/`).catch(() => {})
|
||||
state().sessions.delete(sessionID)
|
||||
state().messages.delete(sessionID)
|
||||
if (emitEvent) {
|
||||
Bus.publish(Event.Deleted, {
|
||||
info: session,
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
log.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
async function updateMessage(msg: Message.Info) {
|
||||
await Storage.writeJSON(
|
||||
"session/message/" + msg.metadata.sessionID + "/" + msg.id,
|
||||
@@ -248,7 +287,7 @@ export namespace Session {
|
||||
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,
|
||||
@@ -295,7 +334,7 @@ export namespace Session {
|
||||
draft.title = result.text
|
||||
})
|
||||
})
|
||||
.catch(() => { })
|
||||
.catch(() => {})
|
||||
}
|
||||
const msg: Message.Info = {
|
||||
role: "user",
|
||||
@@ -433,24 +472,6 @@ export namespace Session {
|
||||
}
|
||||
|
||||
let text: Message.TextPart | undefined
|
||||
await Bun.write(
|
||||
"/tmp/message.json",
|
||||
JSON.stringify(
|
||||
[
|
||||
...system.map(
|
||||
(x): CoreMessage => ({
|
||||
role: "system",
|
||||
content: x,
|
||||
}),
|
||||
),
|
||||
...convertToCoreMessages(
|
||||
msgs.map(toUIMessage).filter((x) => x.parts.length > 0),
|
||||
),
|
||||
],
|
||||
null,
|
||||
2,
|
||||
),
|
||||
)
|
||||
const result = streamText({
|
||||
onStepFinish: async (step) => {
|
||||
log.info("step finish", { finishReason: step.finishReason })
|
||||
@@ -572,7 +593,7 @@ export namespace Session {
|
||||
case "tool-call": {
|
||||
const [match] = next.parts.flatMap((p) =>
|
||||
p.type === "tool-invocation" &&
|
||||
p.toolInvocation.toolCallId === value.toolCallId
|
||||
p.toolInvocation.toolCallId === value.toolCallId
|
||||
? [p]
|
||||
: [],
|
||||
)
|
||||
|
||||
@@ -135,53 +135,55 @@ export namespace Message {
|
||||
id: z.string(),
|
||||
role: z.enum(["user", "assistant"]),
|
||||
parts: z.array(Part),
|
||||
metadata: z.object({
|
||||
time: z.object({
|
||||
created: z.number(),
|
||||
completed: z.number().optional(),
|
||||
}),
|
||||
error: z
|
||||
.discriminatedUnion("name", [
|
||||
Provider.AuthError.Schema,
|
||||
NamedError.Unknown.Schema,
|
||||
])
|
||||
.optional(),
|
||||
sessionID: z.string(),
|
||||
tool: z.record(
|
||||
z.string(),
|
||||
z
|
||||
metadata: z
|
||||
.object({
|
||||
time: z.object({
|
||||
created: z.number(),
|
||||
completed: z.number().optional(),
|
||||
}),
|
||||
error: z
|
||||
.discriminatedUnion("name", [
|
||||
Provider.AuthError.Schema,
|
||||
NamedError.Unknown.Schema,
|
||||
])
|
||||
.optional(),
|
||||
sessionID: z.string(),
|
||||
tool: z.record(
|
||||
z.string(),
|
||||
z
|
||||
.object({
|
||||
title: z.string(),
|
||||
time: z.object({
|
||||
start: z.number(),
|
||||
end: z.number(),
|
||||
}),
|
||||
})
|
||||
.catchall(z.any()),
|
||||
),
|
||||
assistant: z
|
||||
.object({
|
||||
title: z.string(),
|
||||
time: z.object({
|
||||
start: z.number(),
|
||||
end: z.number(),
|
||||
system: z.string().array(),
|
||||
modelID: z.string(),
|
||||
providerID: z.string(),
|
||||
path: z.object({
|
||||
cwd: z.string(),
|
||||
root: z.string(),
|
||||
}),
|
||||
cost: z.number(),
|
||||
summary: z.boolean().optional(),
|
||||
tokens: z.object({
|
||||
input: z.number(),
|
||||
output: z.number(),
|
||||
reasoning: z.number(),
|
||||
cache: z.object({
|
||||
read: z.number(),
|
||||
write: z.number(),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
.catchall(z.any()),
|
||||
),
|
||||
assistant: z
|
||||
.object({
|
||||
system: z.string().array(),
|
||||
modelID: z.string(),
|
||||
providerID: z.string(),
|
||||
path: z.object({
|
||||
cwd: z.string(),
|
||||
root: z.string(),
|
||||
}),
|
||||
cost: z.number(),
|
||||
summary: z.boolean().optional(),
|
||||
tokens: z.object({
|
||||
input: z.number(),
|
||||
output: z.number(),
|
||||
reasoning: z.number(),
|
||||
cache: z.object({
|
||||
read: z.number(),
|
||||
write: z.number(),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
.optional(),
|
||||
})
|
||||
.openapi({ ref: "Message.Metadata" }),
|
||||
})
|
||||
.openapi({
|
||||
ref: "Message.Info",
|
||||
|
||||
@@ -29,6 +29,11 @@ export namespace Storage {
|
||||
await fs.unlink(target).catch(() => {})
|
||||
}
|
||||
|
||||
export async function removeDir(key: string) {
|
||||
const target = path.join(state().dir, key)
|
||||
await fs.rm(target, { recursive: true, force: true }).catch(() => {})
|
||||
}
|
||||
|
||||
export async function readJSON<T>(key: string) {
|
||||
return Bun.file(path.join(state().dir, key + ".json")).json() as Promise<T>
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import path from "path"
|
||||
import { Tool } from "./tool"
|
||||
import { App } from "../app/app"
|
||||
import DESCRIPTION from "./glob.txt"
|
||||
import { Ripgrep } from "../external/ripgrep"
|
||||
|
||||
export const GlobTool = Tool.define({
|
||||
id: "glob",
|
||||
@@ -24,10 +25,12 @@ export const GlobTool = Tool.define({
|
||||
: path.resolve(app.path.cwd, search)
|
||||
|
||||
const limit = 100
|
||||
const glob = new Bun.Glob(params.pattern)
|
||||
const files = []
|
||||
let truncated = false
|
||||
for await (const file of glob.scan({ cwd: search, dot: true })) {
|
||||
for (const file of await Ripgrep.files({
|
||||
cwd: search,
|
||||
glob: params.pattern,
|
||||
})) {
|
||||
if (files.length >= limit) {
|
||||
truncated = true
|
||||
break
|
||||
|
||||
@@ -19,7 +19,7 @@ export namespace Log {
|
||||
await fs.mkdir(dir, { recursive: true })
|
||||
cleanup(dir)
|
||||
if (options.print) return
|
||||
logpath = path.join(dir, new Date().toISOString().split(".")[0] + ".log")
|
||||
logpath = path.join(dir, new Date().toISOString().split(".")[0].replace(/:/g, "") + ".log")
|
||||
const logfile = Bun.file(logpath)
|
||||
await fs.truncate(logpath).catch(() => {})
|
||||
const writer = logfile.writer()
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
opencode-test
|
||||
@@ -6,11 +6,13 @@ import (
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"log/slog"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea/v2"
|
||||
"github.com/sst/opencode/internal/commands"
|
||||
"github.com/sst/opencode/internal/components/toast"
|
||||
"github.com/sst/opencode/internal/config"
|
||||
"github.com/sst/opencode/internal/theme"
|
||||
"github.com/sst/opencode/internal/util"
|
||||
@@ -47,6 +49,9 @@ type SendMsg struct {
|
||||
type CompletionDialogTriggeredMsg struct {
|
||||
InitialValue string
|
||||
}
|
||||
type OptimisticMessageAddedMsg struct {
|
||||
Message client.MessageInfo
|
||||
}
|
||||
|
||||
func New(
|
||||
ctx context.Context,
|
||||
@@ -297,19 +302,12 @@ func (a *App) SendChatMessage(ctx context.Context, text string, attachments []At
|
||||
if a.Session.Id == "" {
|
||||
session, err := a.CreateSession(ctx)
|
||||
if err != nil {
|
||||
// status.Error(err.Error())
|
||||
return nil
|
||||
return toast.NewErrorToast(err.Error())
|
||||
}
|
||||
a.Session = session
|
||||
cmds = append(cmds, util.CmdHandler(SessionSelectedMsg(session)))
|
||||
}
|
||||
|
||||
// TODO: Handle attachments when API supports them
|
||||
if len(attachments) > 0 {
|
||||
// For now, ignore attachments
|
||||
// return "", fmt.Errorf("attachments not supported yet")
|
||||
}
|
||||
|
||||
part := client.MessagePart{}
|
||||
part.FromMessagePartText(client.MessagePartText{
|
||||
Type: "text",
|
||||
@@ -317,7 +315,26 @@ func (a *App) SendChatMessage(ctx context.Context, text string, attachments []At
|
||||
})
|
||||
parts := []client.MessagePart{part}
|
||||
|
||||
go func() {
|
||||
optimisticMessage := client.MessageInfo{
|
||||
Id: fmt.Sprintf("optimistic-%d", time.Now().UnixNano()),
|
||||
Role: client.User,
|
||||
Parts: parts,
|
||||
Metadata: client.MessageMetadata{
|
||||
SessionID: a.Session.Id,
|
||||
Time: struct {
|
||||
Completed *float32 `json:"completed,omitempty"`
|
||||
Created float32 `json:"created"`
|
||||
}{
|
||||
Created: float32(time.Now().Unix()),
|
||||
},
|
||||
Tool: make(map[string]client.MessageMetadata_Tool_AdditionalProperties),
|
||||
},
|
||||
}
|
||||
|
||||
a.Messages = append(a.Messages, optimisticMessage)
|
||||
cmds = append(cmds, util.CmdHandler(OptimisticMessageAddedMsg{Message: optimisticMessage}))
|
||||
|
||||
cmds = append(cmds, func() tea.Msg {
|
||||
response, err := a.Client.PostSessionChat(ctx, client.PostSessionChatJSONRequestBody{
|
||||
SessionID: a.Session.Id,
|
||||
Parts: parts,
|
||||
@@ -325,14 +342,17 @@ func (a *App) SendChatMessage(ctx context.Context, text string, attachments []At
|
||||
ModelID: a.Model.Id,
|
||||
})
|
||||
if err != nil {
|
||||
slog.Error("Failed to send message", "error", err)
|
||||
// status.Error(err.Error())
|
||||
errormsg := fmt.Sprintf("failed to send message: %v", err)
|
||||
slog.Error(errormsg)
|
||||
return toast.NewErrorToast(errormsg)()
|
||||
}
|
||||
if response != nil && response.StatusCode != 200 {
|
||||
slog.Error("Failed to send message", "error", fmt.Sprintf("failed to send message: %d", response.StatusCode))
|
||||
// status.Error(fmt.Sprintf("failed to send message: %d", response.StatusCode))
|
||||
errormsg := fmt.Sprintf("failed to send message: %d", response.StatusCode)
|
||||
slog.Error(errormsg)
|
||||
return toast.NewErrorToast(errormsg)()
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
})
|
||||
|
||||
// The actual response will come through SSE
|
||||
// For now, just return success
|
||||
@@ -376,6 +396,19 @@ func (a *App) ListSessions(ctx context.Context) ([]client.SessionInfo, error) {
|
||||
return sessions, nil
|
||||
}
|
||||
|
||||
func (a *App) DeleteSession(ctx context.Context, sessionID string) error {
|
||||
resp, err := a.Client.PostSessionDeleteWithResponse(ctx, client.PostSessionDeleteJSONRequestBody{
|
||||
SessionID: sessionID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode() != 200 {
|
||||
return fmt.Errorf("failed to delete session: %d", resp.StatusCode())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) ListMessages(ctx context.Context, sessionId string) ([]client.MessageInfo, error) {
|
||||
resp, err := a.Client.PostSessionMessagesWithResponse(ctx, client.PostSessionMessagesJSONRequestBody{SessionID: sessionId})
|
||||
if err != nil {
|
||||
|
||||
@@ -32,17 +32,19 @@ type EditorComponent interface {
|
||||
Newline() (tea.Model, tea.Cmd)
|
||||
Previous() (tea.Model, tea.Cmd)
|
||||
Next() (tea.Model, tea.Cmd)
|
||||
SetInterruptKeyInDebounce(inDebounce bool)
|
||||
}
|
||||
|
||||
type editorComponent struct {
|
||||
app *app.App
|
||||
width, height int
|
||||
textarea textarea.Model
|
||||
attachments []app.Attachment
|
||||
history []string
|
||||
historyIndex int
|
||||
currentMessage string
|
||||
spinner spinner.Model
|
||||
app *app.App
|
||||
width, height int
|
||||
textarea textarea.Model
|
||||
attachments []app.Attachment
|
||||
history []string
|
||||
historyIndex int
|
||||
currentMessage string
|
||||
spinner spinner.Model
|
||||
interruptKeyInDebounce bool
|
||||
}
|
||||
|
||||
func (m *editorComponent) Init() tea.Cmd {
|
||||
@@ -78,8 +80,15 @@ func (m *editorComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m, tea.Batch(cmds...)
|
||||
} else {
|
||||
existingValue := m.textarea.Value()
|
||||
modifiedValue := strings.Replace(existingValue, msg.SearchString, msg.CompletionValue, 1)
|
||||
m.textarea.SetValue(modifiedValue + " ")
|
||||
|
||||
// Replace the current token (after last space)
|
||||
lastSpaceIndex := strings.LastIndex(existingValue, " ")
|
||||
if lastSpaceIndex == -1 {
|
||||
m.textarea.SetValue(msg.CompletionValue + " ")
|
||||
} else {
|
||||
modifiedValue := existingValue[:lastSpaceIndex+1] + msg.CompletionValue
|
||||
m.textarea.SetValue(modifiedValue + " ")
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
@@ -115,9 +124,14 @@ func (m *editorComponent) Content() string {
|
||||
Background(t.BackgroundElement()).
|
||||
Render(textarea)
|
||||
|
||||
hint := base("enter") + muted(" send ")
|
||||
hint := base(m.getSubmitKeyText()) + muted(" send ")
|
||||
if m.app.IsBusy() {
|
||||
hint = muted("working") + m.spinner.View() + muted(" ") + base("esc") + muted(" interrupt")
|
||||
keyText := m.getInterruptKeyText()
|
||||
if m.interruptKeyInDebounce {
|
||||
hint = muted("working") + m.spinner.View() + muted(" ") + base(keyText+" again") + muted(" interrupt")
|
||||
} else {
|
||||
hint = muted("working") + m.spinner.View() + muted(" ") + base(keyText) + muted(" interrupt")
|
||||
}
|
||||
}
|
||||
|
||||
model := ""
|
||||
@@ -263,6 +277,18 @@ func (m *editorComponent) Next() (tea.Model, tea.Cmd) {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *editorComponent) SetInterruptKeyInDebounce(inDebounce bool) {
|
||||
m.interruptKeyInDebounce = inDebounce
|
||||
}
|
||||
|
||||
func (m *editorComponent) getInterruptKeyText() string {
|
||||
return m.app.Commands[commands.SessionInterruptCommand].Keys()[0]
|
||||
}
|
||||
|
||||
func (m *editorComponent) getSubmitKeyText() string {
|
||||
return m.app.Commands[commands.InputSubmitCommand].Keys()[0]
|
||||
}
|
||||
|
||||
func createTextArea(existing *textarea.Model) textarea.Model {
|
||||
t := theme.CurrentTheme()
|
||||
bgColor := t.BackgroundElement()
|
||||
@@ -311,11 +337,12 @@ func NewEditorComponent(app *app.App) EditorComponent {
|
||||
ta := createTextArea(nil)
|
||||
|
||||
return &editorComponent{
|
||||
app: app,
|
||||
textarea: ta,
|
||||
history: []string{},
|
||||
historyIndex: 0,
|
||||
currentMessage: "",
|
||||
spinner: s,
|
||||
app: app,
|
||||
textarea: ta,
|
||||
history: []string{},
|
||||
historyIndex: 0,
|
||||
currentMessage: "",
|
||||
spinner: s,
|
||||
interruptKeyInDebounce: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,6 @@ import (
|
||||
func toMarkdown(content string, width int, backgroundColor compat.AdaptiveColor) string {
|
||||
r := styles.GetMarkdownRenderer(width, backgroundColor)
|
||||
content = strings.ReplaceAll(content, app.RootPath+"/", "")
|
||||
content = strings.ReplaceAll(content, "<", "\\<")
|
||||
content = strings.ReplaceAll(content, ">", "\\>")
|
||||
rendered, _ := r.Render(content)
|
||||
lines := strings.Split(rendered, "\n")
|
||||
|
||||
@@ -228,7 +226,11 @@ func renderText(message client.MessageInfo, text string, author string) string {
|
||||
textWidth := max(lipgloss.Width(text), lipgloss.Width(info))
|
||||
markdownWidth := min(textWidth, width-padding-4) // -4 for the border and padding
|
||||
if message.Role == client.Assistant {
|
||||
markdownWidth = width - padding - 4
|
||||
markdownWidth = width - padding - 4 - 2
|
||||
}
|
||||
if message.Role == client.User {
|
||||
text = strings.ReplaceAll(text, "<", "\\<")
|
||||
text = strings.ReplaceAll(text, ">", "\\>")
|
||||
}
|
||||
content := toMarkdown(text, markdownWidth, t.BackgroundPanel())
|
||||
content = strings.Join([]string{content, info}, "\n")
|
||||
@@ -251,7 +253,7 @@ func renderText(message client.MessageInfo, text string, author string) string {
|
||||
func renderToolInvocation(
|
||||
toolCall client.MessageToolInvocationToolCall,
|
||||
result *string,
|
||||
metadata client.MessageInfo_Metadata_Tool_AdditionalProperties,
|
||||
metadata client.MessageMetadata_Tool_AdditionalProperties,
|
||||
showDetails bool,
|
||||
isLast bool,
|
||||
contentOnly bool,
|
||||
@@ -464,7 +466,7 @@ func renderToolInvocation(
|
||||
|
||||
if metadata, ok := call["metadata"].(map[string]any); ok {
|
||||
data, _ = json.Marshal(metadata)
|
||||
var toolMetadata client.MessageInfo_Metadata_Tool_AdditionalProperties
|
||||
var toolMetadata client.MessageMetadata_Tool_AdditionalProperties
|
||||
_ = json.Unmarshal(data, &toolMetadata)
|
||||
|
||||
step := renderToolInvocation(
|
||||
|
||||
@@ -58,6 +58,12 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.viewport.GotoBottom()
|
||||
m.tail = true
|
||||
return m, nil
|
||||
case app.OptimisticMessageAddedMsg:
|
||||
m.renderView()
|
||||
if m.tail {
|
||||
m.viewport.GotoBottom()
|
||||
}
|
||||
return m, nil
|
||||
case dialog.ThemeSelectedMsg:
|
||||
m.cache.Clear()
|
||||
return m, m.Reload()
|
||||
@@ -171,7 +177,7 @@ func (m *messagesComponent) renderView() {
|
||||
isLastToolInvocation := slices.Contains(lastToolIndices, i)
|
||||
toolInvocationPart := part.(client.MessagePartToolInvocation)
|
||||
toolCall, _ := toolInvocationPart.ToolInvocation.AsMessageToolInvocationToolCall()
|
||||
metadata := client.MessageInfo_Metadata_Tool_AdditionalProperties{}
|
||||
metadata := client.MessageMetadata_Tool_AdditionalProperties{}
|
||||
if _, ok := message.Metadata.Tool[toolCall.ToolCallId]; ok {
|
||||
metadata = message.Metadata.Tool[toolCall.ToolCallId]
|
||||
}
|
||||
|
||||
@@ -2,12 +2,20 @@ package dialog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"slices"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea/v2"
|
||||
"github.com/charmbracelet/lipgloss/v2"
|
||||
"github.com/muesli/reflow/truncate"
|
||||
"github.com/sst/opencode/internal/app"
|
||||
"github.com/sst/opencode/internal/components/list"
|
||||
"github.com/sst/opencode/internal/components/modal"
|
||||
"github.com/sst/opencode/internal/components/toast"
|
||||
"github.com/sst/opencode/internal/layout"
|
||||
"github.com/sst/opencode/internal/styles"
|
||||
"github.com/sst/opencode/internal/theme"
|
||||
"github.com/sst/opencode/internal/util"
|
||||
"github.com/sst/opencode/pkg/client"
|
||||
)
|
||||
@@ -17,12 +25,65 @@ type SessionDialog interface {
|
||||
layout.Modal
|
||||
}
|
||||
|
||||
// sessionItem is a custom list item for sessions that can show delete confirmation
|
||||
type sessionItem struct {
|
||||
title string
|
||||
isDeleteConfirming bool
|
||||
}
|
||||
|
||||
func (s sessionItem) Render(selected bool, width int) string {
|
||||
t := theme.CurrentTheme()
|
||||
baseStyle := styles.BaseStyle()
|
||||
|
||||
var text string
|
||||
if s.isDeleteConfirming {
|
||||
text = "Press again to confirm delete"
|
||||
} else {
|
||||
text = s.title
|
||||
}
|
||||
|
||||
truncatedStr := truncate.StringWithTail(text, uint(width-1), "...")
|
||||
|
||||
var itemStyle lipgloss.Style
|
||||
if selected {
|
||||
if s.isDeleteConfirming {
|
||||
// Red background for delete confirmation
|
||||
itemStyle = baseStyle.
|
||||
Background(t.Error()).
|
||||
Foreground(t.Background()).
|
||||
Width(width).
|
||||
PaddingLeft(1)
|
||||
} else {
|
||||
// Normal selection
|
||||
itemStyle = baseStyle.
|
||||
Background(t.Primary()).
|
||||
Foreground(t.Background()).
|
||||
Width(width).
|
||||
PaddingLeft(1)
|
||||
}
|
||||
} else {
|
||||
if s.isDeleteConfirming {
|
||||
// Red text for delete confirmation when not selected
|
||||
itemStyle = baseStyle.
|
||||
Foreground(t.Error()).
|
||||
PaddingLeft(1)
|
||||
} else {
|
||||
itemStyle = baseStyle.
|
||||
PaddingLeft(1)
|
||||
}
|
||||
}
|
||||
|
||||
return itemStyle.Render(truncatedStr)
|
||||
}
|
||||
|
||||
type sessionDialog struct {
|
||||
width int
|
||||
height int
|
||||
modal *modal.Modal
|
||||
sessions []client.SessionInfo
|
||||
list list.List[list.StringItem]
|
||||
width int
|
||||
height int
|
||||
modal *modal.Modal
|
||||
sessions []client.SessionInfo
|
||||
list list.List[sessionItem]
|
||||
app *app.App
|
||||
deleteConfirmation int // -1 means no confirmation, >= 0 means confirming deletion of session at this index
|
||||
}
|
||||
|
||||
func (s *sessionDialog) Init() tea.Cmd {
|
||||
@@ -38,6 +99,11 @@ func (s *sessionDialog) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
case tea.KeyPressMsg:
|
||||
switch msg.String() {
|
||||
case "enter":
|
||||
if s.deleteConfirmation >= 0 {
|
||||
s.deleteConfirmation = -1
|
||||
s.updateListItems()
|
||||
return s, nil
|
||||
}
|
||||
if _, idx := s.list.GetSelectedItem(); idx >= 0 && idx < len(s.sessions) {
|
||||
selectedSession := s.sessions[idx]
|
||||
return s, tea.Sequence(
|
||||
@@ -45,17 +111,79 @@ func (s *sessionDialog) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
util.CmdHandler(app.SessionSelectedMsg(&selectedSession)),
|
||||
)
|
||||
}
|
||||
case "x", "delete", "backspace":
|
||||
if _, idx := s.list.GetSelectedItem(); idx >= 0 && idx < len(s.sessions) {
|
||||
if s.deleteConfirmation == idx {
|
||||
// Second press - actually delete the session
|
||||
sessionToDelete := s.sessions[idx]
|
||||
return s, tea.Sequence(
|
||||
func() tea.Msg {
|
||||
s.sessions = slices.Delete(s.sessions, idx, idx+1)
|
||||
s.deleteConfirmation = -1
|
||||
s.updateListItems()
|
||||
return nil
|
||||
},
|
||||
s.deleteSession(sessionToDelete.Id),
|
||||
)
|
||||
} else {
|
||||
// First press - enter delete confirmation mode
|
||||
s.deleteConfirmation = idx
|
||||
s.updateListItems()
|
||||
return s, nil
|
||||
}
|
||||
}
|
||||
case "esc":
|
||||
if s.deleteConfirmation >= 0 {
|
||||
s.deleteConfirmation = -1
|
||||
s.updateListItems()
|
||||
return s, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var cmd tea.Cmd
|
||||
listModel, cmd := s.list.Update(msg)
|
||||
s.list = listModel.(list.List[list.StringItem])
|
||||
s.list = listModel.(list.List[sessionItem])
|
||||
return s, cmd
|
||||
}
|
||||
|
||||
func (s *sessionDialog) Render(background string) string {
|
||||
return s.modal.Render(s.list.View(), background)
|
||||
listView := s.list.View()
|
||||
|
||||
t := theme.CurrentTheme()
|
||||
helpStyle := styles.BaseStyle().PaddingLeft(1).PaddingTop(1)
|
||||
helpText := styles.BaseStyle().Foreground(t.Text()).Render("x/del")
|
||||
helpText = helpText + styles.BaseStyle().Background(t.BackgroundElement()).Foreground(t.TextMuted()).Render(" delete session")
|
||||
helpText = helpStyle.Render(helpText)
|
||||
|
||||
content := strings.Join([]string{listView, helpText}, "\n")
|
||||
|
||||
return s.modal.Render(content, background)
|
||||
}
|
||||
|
||||
func (s *sessionDialog) updateListItems() {
|
||||
_, currentIdx := s.list.GetSelectedItem()
|
||||
|
||||
var items []sessionItem
|
||||
for i, sess := range s.sessions {
|
||||
item := sessionItem{
|
||||
title: sess.Title,
|
||||
isDeleteConfirming: s.deleteConfirmation == i,
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
s.list.SetItems(items)
|
||||
s.list.SetSelectedIndex(currentIdx)
|
||||
}
|
||||
|
||||
func (s *sessionDialog) deleteSession(sessionID string) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
ctx := context.Background()
|
||||
if err := s.app.DeleteSession(ctx, sessionID); err != nil {
|
||||
return toast.NewErrorToast("Failed to delete session: " + err.Error())()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sessionDialog) Close() tea.Cmd {
|
||||
@@ -67,26 +195,32 @@ func NewSessionDialog(app *app.App) SessionDialog {
|
||||
sessions, _ := app.ListSessions(context.Background())
|
||||
|
||||
var filteredSessions []client.SessionInfo
|
||||
var sessionTitles []string
|
||||
var items []sessionItem
|
||||
for _, sess := range sessions {
|
||||
if sess.ParentID != nil {
|
||||
continue
|
||||
}
|
||||
filteredSessions = append(filteredSessions, sess)
|
||||
sessionTitles = append(sessionTitles, sess.Title)
|
||||
items = append(items, sessionItem{
|
||||
title: sess.Title,
|
||||
isDeleteConfirming: false,
|
||||
})
|
||||
}
|
||||
|
||||
list := list.NewStringList(
|
||||
sessionTitles,
|
||||
// Create a generic list component
|
||||
listComponent := list.NewListComponent(
|
||||
items,
|
||||
10, // maxVisibleSessions
|
||||
"No sessions available",
|
||||
true, // useAlphaNumericKeys
|
||||
)
|
||||
list.SetMaxWidth(layout.Current.Container.Width - 12)
|
||||
listComponent.SetMaxWidth(layout.Current.Container.Width - 12)
|
||||
|
||||
return &sessionDialog{
|
||||
sessions: filteredSessions,
|
||||
list: list,
|
||||
sessions: filteredSessions,
|
||||
list: listComponent,
|
||||
app: app,
|
||||
deleteConfirmation: -1,
|
||||
modal: modal.New(
|
||||
modal.WithTitle("Switch Session"),
|
||||
modal.WithMaxWidth(layout.Current.Container.Width-8),
|
||||
|
||||
@@ -111,8 +111,14 @@ func PlaceOverlay(
|
||||
fgLineWidth := ansi.PrintableRuneWidth(fgLine)
|
||||
|
||||
// Extract the styles at the border positions
|
||||
leftStyle := getStyleAtPosition(bgLine, pos)
|
||||
rightStyle := getStyleAtPosition(bgLine, pos+1+fgLineWidth)
|
||||
// We need to get the style just before the border position to preserve background
|
||||
leftStyle := ansiStyle{}
|
||||
if pos > 0 {
|
||||
leftStyle = getStyleAtPosition(bgLine, pos-1)
|
||||
} else {
|
||||
leftStyle = getStyleAtPosition(bgLine, pos)
|
||||
}
|
||||
rightStyle := getStyleAtPosition(bgLine, pos+fgLineWidth)
|
||||
|
||||
// Left border - combine background from original with border foreground
|
||||
leftSeq := combineStyles(leftStyle, options.borderColor)
|
||||
@@ -120,7 +126,9 @@ func PlaceOverlay(
|
||||
b.WriteString(leftSeq)
|
||||
}
|
||||
b.WriteString("┃")
|
||||
b.WriteString("\x1b[0m") // Reset all styles
|
||||
if leftSeq != "" {
|
||||
b.WriteString("\x1b[0m") // Reset all styles only if we applied any
|
||||
}
|
||||
pos++
|
||||
|
||||
// Content
|
||||
@@ -133,7 +141,9 @@ func PlaceOverlay(
|
||||
b.WriteString(rightSeq)
|
||||
}
|
||||
b.WriteString("┃")
|
||||
b.WriteString("\x1b[0m") // Reset all styles
|
||||
if rightSeq != "" {
|
||||
b.WriteString("\x1b[0m") // Reset all styles only if we applied any
|
||||
}
|
||||
pos++
|
||||
} else {
|
||||
// No border, just render the content
|
||||
@@ -188,7 +198,9 @@ func parseANSISequence(seq string) ansiStyle {
|
||||
for i < len(parts) {
|
||||
switch parts[i] {
|
||||
case "0": // Reset
|
||||
style = ansiStyle{}
|
||||
// Mark this as a reset by adding it to attrs
|
||||
style.attrs = append(style.attrs, "0")
|
||||
// Don't clear the style here, let the caller handle it
|
||||
case "1", "2", "3", "4", "5", "6", "7", "8", "9": // Various attributes
|
||||
style.attrs = append(style.attrs, parts[i])
|
||||
case "38": // Foreground color
|
||||
@@ -244,11 +256,9 @@ func combineStyles(bgStyle ansiStyle, fgColor *compat.AdaptiveColor) string {
|
||||
|
||||
// Add foreground color if specified
|
||||
if fgColor != nil {
|
||||
// Use the light color (could be improved to detect terminal background)
|
||||
color := (*fgColor).Light
|
||||
|
||||
// Use RGBA to get color components
|
||||
r, g, b, _ := color.RGBA()
|
||||
// Use the adaptive color which automatically selects based on terminal background
|
||||
// The RGBA method already handles light/dark selection
|
||||
r, g, b, _ := fgColor.RGBA()
|
||||
// RGBA returns 16-bit values, we need 8-bit
|
||||
parts = append(parts, fmt.Sprintf("38;2;%d;%d;%d", r>>8, g>>8, b>>8))
|
||||
}
|
||||
@@ -276,15 +286,21 @@ func getStyleAtPosition(s string, targetPos int) ansiStyle {
|
||||
seq := s[i : i+match[1]]
|
||||
parsedStyle := parseANSISequence(seq)
|
||||
|
||||
// Update current style (merge with existing)
|
||||
if parsedStyle.fgColor != "" {
|
||||
currentStyle.fgColor = parsedStyle.fgColor
|
||||
}
|
||||
if parsedStyle.bgColor != "" {
|
||||
currentStyle.bgColor = parsedStyle.bgColor
|
||||
}
|
||||
if len(parsedStyle.attrs) > 0 {
|
||||
currentStyle.attrs = parsedStyle.attrs
|
||||
// Check if this is a reset sequence
|
||||
if len(parsedStyle.attrs) > 0 && parsedStyle.attrs[0] == "0" {
|
||||
// Reset all styles
|
||||
currentStyle = ansiStyle{}
|
||||
} else {
|
||||
// Update current style (merge with existing)
|
||||
if parsedStyle.fgColor != "" {
|
||||
currentStyle.fgColor = parsedStyle.fgColor
|
||||
}
|
||||
if parsedStyle.bgColor != "" {
|
||||
currentStyle.bgColor = parsedStyle.bgColor
|
||||
}
|
||||
if len(parsedStyle.attrs) > 0 {
|
||||
currentStyle.attrs = parsedStyle.attrs
|
||||
}
|
||||
}
|
||||
|
||||
i += match[1]
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@@ -42,7 +43,7 @@ func LoadThemesFromJSON() error {
|
||||
continue
|
||||
}
|
||||
themeName := strings.TrimSuffix(entry.Name(), ".json")
|
||||
data, err := themesFS.ReadFile(filepath.Join("themes", entry.Name()))
|
||||
data, err := themesFS.ReadFile(path.Join("themes", entry.Name()))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read theme file %s: %w", entry.Name(), err)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/bubbles/v2/key"
|
||||
tea "github.com/charmbracelet/bubbletea/v2"
|
||||
@@ -25,6 +26,19 @@ import (
|
||||
"github.com/sst/opencode/pkg/client"
|
||||
)
|
||||
|
||||
// InterruptDebounceTimeoutMsg is sent when the interrupt key debounce timeout expires
|
||||
type InterruptDebounceTimeoutMsg struct{}
|
||||
|
||||
// InterruptKeyState tracks the state of interrupt key presses for debouncing
|
||||
type InterruptKeyState int
|
||||
|
||||
const (
|
||||
InterruptKeyIdle InterruptKeyState = iota
|
||||
InterruptKeyFirstPress
|
||||
)
|
||||
|
||||
const interruptDebounceTimeout = 1 * time.Second
|
||||
|
||||
type appModel struct {
|
||||
width, height int
|
||||
app *app.App
|
||||
@@ -40,6 +54,7 @@ type appModel struct {
|
||||
leaderBinding *key.Binding
|
||||
isLeaderSequence bool
|
||||
toastManager *toast.ToastManager
|
||||
interruptKeyState InterruptKeyState
|
||||
}
|
||||
|
||||
func (a appModel) Init() tea.Cmd {
|
||||
@@ -171,9 +186,32 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// 6. Check again for commands that don't require leader
|
||||
// 6. Handle interrupt key debounce for session interrupt
|
||||
interruptCommand := a.app.Commands[commands.SessionInterruptCommand]
|
||||
if interruptCommand.Matches(msg, a.isLeaderSequence) && a.app.IsBusy() {
|
||||
switch a.interruptKeyState {
|
||||
case InterruptKeyIdle:
|
||||
// First interrupt key press - start debounce timer
|
||||
a.interruptKeyState = InterruptKeyFirstPress
|
||||
a.editor.SetInterruptKeyInDebounce(true)
|
||||
return a, tea.Tick(interruptDebounceTimeout, func(t time.Time) tea.Msg {
|
||||
return InterruptDebounceTimeoutMsg{}
|
||||
})
|
||||
case InterruptKeyFirstPress:
|
||||
// Second interrupt key press within timeout - actually interrupt
|
||||
a.interruptKeyState = InterruptKeyIdle
|
||||
a.editor.SetInterruptKeyInDebounce(false)
|
||||
return a, util.CmdHandler(commands.ExecuteCommandMsg(interruptCommand))
|
||||
}
|
||||
}
|
||||
|
||||
// 7. Check again for commands that don't require leader (excluding interrupt when busy)
|
||||
matches := a.app.Commands.Matches(msg, a.isLeaderSequence)
|
||||
if len(matches) > 0 {
|
||||
// Skip interrupt key if we're in debounce mode and app is busy
|
||||
if interruptCommand.Matches(msg, a.isLeaderSequence) && a.app.IsBusy() && a.interruptKeyState != InterruptKeyIdle {
|
||||
return a, nil
|
||||
}
|
||||
return a, util.CmdHandler(commands.ExecuteCommandsMsg(matches))
|
||||
}
|
||||
|
||||
@@ -189,7 +227,7 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
updated, cmd := a.messages.Update(msg)
|
||||
a.messages = updated.(chat.MessagesComponent)
|
||||
cmds = append(cmds, cmd)
|
||||
return a, tea.Batch(cmds...)
|
||||
return a, tea.Batch(cmds...)
|
||||
case tea.BackgroundColorMsg:
|
||||
styles.Terminal = &styles.TerminalInfo{
|
||||
BackgroundIsDark: msg.IsDark(),
|
||||
@@ -223,6 +261,12 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
"opencode updated to "+msg.Properties.Version+", restart to apply.",
|
||||
toast.WithTitle("New version installed"),
|
||||
)
|
||||
case client.EventSessionDeleted:
|
||||
if a.app.Session != nil && msg.Properties.Info.Id == a.app.Session.Id {
|
||||
a.app.Session = &client.SessionInfo{}
|
||||
a.app.Messages = []client.MessageInfo{}
|
||||
}
|
||||
return a, toast.NewSuccessToast("Session deleted successfully")
|
||||
case client.EventSessionUpdated:
|
||||
if msg.Properties.Info.Id == a.app.Session.Id {
|
||||
a.app.Session = &msg.Properties.Info
|
||||
@@ -230,12 +274,33 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
case client.EventMessageUpdated:
|
||||
if msg.Properties.Info.Metadata.SessionID == a.app.Session.Id {
|
||||
exists := false
|
||||
for i, m := range a.app.Messages {
|
||||
if m.Id == msg.Properties.Info.Id {
|
||||
a.app.Messages[i] = msg.Properties.Info
|
||||
exists = true
|
||||
optimisticReplaced := false
|
||||
|
||||
// First check if this is replacing an optimistic message
|
||||
if msg.Properties.Info.Role == client.User {
|
||||
// Look for optimistic messages to replace
|
||||
for i, m := range a.app.Messages {
|
||||
if strings.HasPrefix(m.Id, "optimistic-") && m.Role == client.User {
|
||||
// Replace the optimistic message with the real one
|
||||
a.app.Messages[i] = msg.Properties.Info
|
||||
exists = true
|
||||
optimisticReplaced = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If not replacing optimistic, check for existing message with same ID
|
||||
if !optimisticReplaced {
|
||||
for i, m := range a.app.Messages {
|
||||
if m.Id == msg.Properties.Info.Id {
|
||||
a.app.Messages[i] = msg.Properties.Info
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !exists {
|
||||
a.app.Messages = append(a.app.Messages, msg.Properties.Info)
|
||||
}
|
||||
@@ -284,6 +349,10 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
tm, cmd := a.toastManager.Update(msg)
|
||||
a.toastManager = tm
|
||||
cmds = append(cmds, cmd)
|
||||
case InterruptDebounceTimeoutMsg:
|
||||
// Reset interrupt key state after timeout
|
||||
a.interruptKeyState = InterruptKeyIdle
|
||||
a.editor.SetInterruptKeyInDebounce(false)
|
||||
}
|
||||
|
||||
// update status bar
|
||||
@@ -576,6 +645,7 @@ func NewModel(app *app.App) tea.Model {
|
||||
showCompletionDialog: false,
|
||||
editorContainer: editorContainer,
|
||||
toastManager: toast.NewToastManager(),
|
||||
interruptKeyState: InterruptKeyIdle,
|
||||
layout: layout.NewFlexLayout(
|
||||
[]tea.ViewModel{messagesContainer, editorContainer},
|
||||
layout.WithDirection(layout.FlexDirectionVertical),
|
||||
|
||||
@@ -230,6 +230,42 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/session_unshare": {
|
||||
"post": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successfully unshared session",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/session.info"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"operationId": "postSession_unshare",
|
||||
"parameters": [],
|
||||
"description": "Unshare the session",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sessionID": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"sessionID"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/session_messages": {
|
||||
"post": {
|
||||
"responses": {
|
||||
@@ -327,6 +363,42 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/session_delete": {
|
||||
"post": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successfully deleted session",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"operationId": "postSession_delete",
|
||||
"parameters": [],
|
||||
"description": "Delete a session and all its data",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sessionID": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"sessionID"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/session_summarize": {
|
||||
"post": {
|
||||
"responses": {
|
||||
@@ -543,6 +615,9 @@
|
||||
{
|
||||
"$ref": "#/components/schemas/Event.session.updated"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Event.session.deleted"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Event.session.error"
|
||||
}
|
||||
@@ -557,6 +632,7 @@
|
||||
"message.updated": "#/components/schemas/Event.message.updated",
|
||||
"message.part.updated": "#/components/schemas/Event.message.part.updated",
|
||||
"session.updated": "#/components/schemas/Event.session.updated",
|
||||
"session.deleted": "#/components/schemas/Event.session.deleted",
|
||||
"session.error": "#/components/schemas/Event.session.error"
|
||||
}
|
||||
}
|
||||
@@ -734,160 +810,7 @@
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"time": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"created": {
|
||||
"type": "number"
|
||||
},
|
||||
"completed": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"created"
|
||||
]
|
||||
},
|
||||
"error": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ProviderAuthError"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/UnknownError"
|
||||
}
|
||||
],
|
||||
"discriminator": {
|
||||
"propertyName": "name",
|
||||
"mapping": {
|
||||
"ProviderAuthError": "#/components/schemas/ProviderAuthError",
|
||||
"UnknownError": "#/components/schemas/UnknownError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sessionID": {
|
||||
"type": "string"
|
||||
},
|
||||
"tool": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"time": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"start": {
|
||||
"type": "number"
|
||||
},
|
||||
"end": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"start",
|
||||
"end"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"title",
|
||||
"time"
|
||||
],
|
||||
"additionalProperties": {}
|
||||
}
|
||||
},
|
||||
"assistant": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"system": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"modelID": {
|
||||
"type": "string"
|
||||
},
|
||||
"providerID": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"cwd": {
|
||||
"type": "string"
|
||||
},
|
||||
"root": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"cwd",
|
||||
"root"
|
||||
]
|
||||
},
|
||||
"cost": {
|
||||
"type": "number"
|
||||
},
|
||||
"summary": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"tokens": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"input": {
|
||||
"type": "number"
|
||||
},
|
||||
"output": {
|
||||
"type": "number"
|
||||
},
|
||||
"reasoning": {
|
||||
"type": "number"
|
||||
},
|
||||
"cache": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"read": {
|
||||
"type": "number"
|
||||
},
|
||||
"write": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"input",
|
||||
"output",
|
||||
"reasoning",
|
||||
"cache"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"system",
|
||||
"modelID",
|
||||
"providerID",
|
||||
"path",
|
||||
"cost",
|
||||
"tokens"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"time",
|
||||
"sessionID",
|
||||
"tool"
|
||||
]
|
||||
"$ref": "#/components/schemas/Message.Metadata"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1141,6 +1064,162 @@
|
||||
"type"
|
||||
]
|
||||
},
|
||||
"Message.Metadata": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"time": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"created": {
|
||||
"type": "number"
|
||||
},
|
||||
"completed": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"created"
|
||||
]
|
||||
},
|
||||
"error": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ProviderAuthError"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/UnknownError"
|
||||
}
|
||||
],
|
||||
"discriminator": {
|
||||
"propertyName": "name",
|
||||
"mapping": {
|
||||
"ProviderAuthError": "#/components/schemas/ProviderAuthError",
|
||||
"UnknownError": "#/components/schemas/UnknownError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sessionID": {
|
||||
"type": "string"
|
||||
},
|
||||
"tool": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"time": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"start": {
|
||||
"type": "number"
|
||||
},
|
||||
"end": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"start",
|
||||
"end"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"title",
|
||||
"time"
|
||||
],
|
||||
"additionalProperties": {}
|
||||
}
|
||||
},
|
||||
"assistant": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"system": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"modelID": {
|
||||
"type": "string"
|
||||
},
|
||||
"providerID": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"cwd": {
|
||||
"type": "string"
|
||||
},
|
||||
"root": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"cwd",
|
||||
"root"
|
||||
]
|
||||
},
|
||||
"cost": {
|
||||
"type": "number"
|
||||
},
|
||||
"summary": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"tokens": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"input": {
|
||||
"type": "number"
|
||||
},
|
||||
"output": {
|
||||
"type": "number"
|
||||
},
|
||||
"reasoning": {
|
||||
"type": "number"
|
||||
},
|
||||
"cache": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"read": {
|
||||
"type": "number"
|
||||
},
|
||||
"write": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"input",
|
||||
"output",
|
||||
"reasoning",
|
||||
"cache"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"system",
|
||||
"modelID",
|
||||
"providerID",
|
||||
"path",
|
||||
"cost",
|
||||
"tokens"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"time",
|
||||
"sessionID",
|
||||
"tool"
|
||||
]
|
||||
},
|
||||
"ProviderAuthError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1300,6 +1379,30 @@
|
||||
"time"
|
||||
]
|
||||
},
|
||||
"Event.session.deleted": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "session.deleted"
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"info": {
|
||||
"$ref": "#/components/schemas/session.info"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"info"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"properties"
|
||||
]
|
||||
},
|
||||
"Event.session.error": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1461,6 +1564,9 @@
|
||||
"temperature": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"tool_call": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"cost": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1499,6 +1605,10 @@
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"additionalProperties": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1535,7 +1645,8 @@
|
||||
},
|
||||
"description": "MCP (Model Context Protocol) server configurations"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Config.Keybinds": {
|
||||
"type": "object",
|
||||
@@ -1648,7 +1759,8 @@
|
||||
"type": "string",
|
||||
"description": "Exit the application"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Provider.Info": {
|
||||
"type": "object",
|
||||
@@ -1700,6 +1812,9 @@
|
||||
"temperature": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"tool_call": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"cost": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1738,6 +1853,10 @@
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"additionalProperties": {}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1745,9 +1864,11 @@
|
||||
"attachment",
|
||||
"reasoning",
|
||||
"temperature",
|
||||
"tool_call",
|
||||
"cost",
|
||||
"limit",
|
||||
"id"
|
||||
"id",
|
||||
"options"
|
||||
]
|
||||
},
|
||||
"Config.McpLocal": {
|
||||
@@ -1776,7 +1897,8 @@
|
||||
"required": [
|
||||
"type",
|
||||
"command"
|
||||
]
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Config.McpRemote": {
|
||||
"type": "object",
|
||||
@@ -1794,7 +1916,8 @@
|
||||
"required": [
|
||||
"type",
|
||||
"url"
|
||||
]
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Error": {
|
||||
"type": "object",
|
||||
|
||||
@@ -78,9 +78,11 @@ type ConfigInfo struct {
|
||||
Context float32 `json:"context"`
|
||||
Output float32 `json:"output"`
|
||||
} `json:"limit,omitempty"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
Reasoning *bool `json:"reasoning,omitempty"`
|
||||
Temperature *bool `json:"temperature,omitempty"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
Options *map[string]interface{} `json:"options,omitempty"`
|
||||
Reasoning *bool `json:"reasoning,omitempty"`
|
||||
Temperature *bool `json:"temperature,omitempty"`
|
||||
ToolCall *bool `json:"tool_call,omitempty"`
|
||||
} `json:"models"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
Npm *string `json:"npm,omitempty"`
|
||||
@@ -252,6 +254,14 @@ type EventPermissionUpdated struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// EventSessionDeleted defines model for Event.session.deleted.
|
||||
type EventSessionDeleted struct {
|
||||
Properties struct {
|
||||
Info SessionInfo `json:"info"`
|
||||
} `json:"properties"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// EventSessionError defines model for Event.session.error.
|
||||
type EventSessionError struct {
|
||||
Properties struct {
|
||||
@@ -290,47 +300,53 @@ type InstallationInfo struct {
|
||||
|
||||
// MessageInfo defines model for Message.Info.
|
||||
type MessageInfo struct {
|
||||
Id string `json:"id"`
|
||||
Metadata struct {
|
||||
Assistant *struct {
|
||||
Cost float32 `json:"cost"`
|
||||
ModelID string `json:"modelID"`
|
||||
Path struct {
|
||||
Cwd string `json:"cwd"`
|
||||
Root string `json:"root"`
|
||||
} `json:"path"`
|
||||
ProviderID string `json:"providerID"`
|
||||
Summary *bool `json:"summary,omitempty"`
|
||||
System []string `json:"system"`
|
||||
Tokens struct {
|
||||
Cache struct {
|
||||
Read float32 `json:"read"`
|
||||
Write float32 `json:"write"`
|
||||
} `json:"cache"`
|
||||
Input float32 `json:"input"`
|
||||
Output float32 `json:"output"`
|
||||
Reasoning float32 `json:"reasoning"`
|
||||
} `json:"tokens"`
|
||||
} `json:"assistant,omitempty"`
|
||||
Error *MessageInfo_Metadata_Error `json:"error,omitempty"`
|
||||
SessionID string `json:"sessionID"`
|
||||
Time struct {
|
||||
Completed *float32 `json:"completed,omitempty"`
|
||||
Created float32 `json:"created"`
|
||||
} `json:"time"`
|
||||
Tool map[string]MessageInfo_Metadata_Tool_AdditionalProperties `json:"tool"`
|
||||
} `json:"metadata"`
|
||||
Parts []MessagePart `json:"parts"`
|
||||
Role MessageInfoRole `json:"role"`
|
||||
Id string `json:"id"`
|
||||
Metadata MessageMetadata `json:"metadata"`
|
||||
Parts []MessagePart `json:"parts"`
|
||||
Role MessageInfoRole `json:"role"`
|
||||
}
|
||||
|
||||
// MessageInfo_Metadata_Error defines model for MessageInfo.Metadata.Error.
|
||||
type MessageInfo_Metadata_Error struct {
|
||||
// MessageInfoRole defines model for MessageInfo.Role.
|
||||
type MessageInfoRole string
|
||||
|
||||
// MessageMetadata defines model for Message.Metadata.
|
||||
type MessageMetadata struct {
|
||||
Assistant *struct {
|
||||
Cost float32 `json:"cost"`
|
||||
ModelID string `json:"modelID"`
|
||||
Path struct {
|
||||
Cwd string `json:"cwd"`
|
||||
Root string `json:"root"`
|
||||
} `json:"path"`
|
||||
ProviderID string `json:"providerID"`
|
||||
Summary *bool `json:"summary,omitempty"`
|
||||
System []string `json:"system"`
|
||||
Tokens struct {
|
||||
Cache struct {
|
||||
Read float32 `json:"read"`
|
||||
Write float32 `json:"write"`
|
||||
} `json:"cache"`
|
||||
Input float32 `json:"input"`
|
||||
Output float32 `json:"output"`
|
||||
Reasoning float32 `json:"reasoning"`
|
||||
} `json:"tokens"`
|
||||
} `json:"assistant,omitempty"`
|
||||
Error *MessageMetadata_Error `json:"error,omitempty"`
|
||||
SessionID string `json:"sessionID"`
|
||||
Time struct {
|
||||
Completed *float32 `json:"completed,omitempty"`
|
||||
Created float32 `json:"created"`
|
||||
} `json:"time"`
|
||||
Tool map[string]MessageMetadata_Tool_AdditionalProperties `json:"tool"`
|
||||
}
|
||||
|
||||
// MessageMetadata_Error defines model for MessageMetadata.Error.
|
||||
type MessageMetadata_Error struct {
|
||||
union json.RawMessage
|
||||
}
|
||||
|
||||
// MessageInfo_Metadata_Tool_AdditionalProperties defines model for MessageInfo.Metadata.Tool.AdditionalProperties.
|
||||
type MessageInfo_Metadata_Tool_AdditionalProperties struct {
|
||||
// MessageMetadata_Tool_AdditionalProperties defines model for Message.Metadata.tool.AdditionalProperties.
|
||||
type MessageMetadata_Tool_AdditionalProperties struct {
|
||||
Time struct {
|
||||
End float32 `json:"end"`
|
||||
Start float32 `json:"start"`
|
||||
@@ -339,9 +355,6 @@ type MessageInfo_Metadata_Tool_AdditionalProperties struct {
|
||||
AdditionalProperties map[string]interface{} `json:"-"`
|
||||
}
|
||||
|
||||
// MessageInfoRole defines model for MessageInfo.Role.
|
||||
type MessageInfoRole string
|
||||
|
||||
// MessagePart defines model for Message.Part.
|
||||
type MessagePart struct {
|
||||
union json.RawMessage
|
||||
@@ -435,9 +448,11 @@ type ModelInfo struct {
|
||||
Context float32 `json:"context"`
|
||||
Output float32 `json:"output"`
|
||||
} `json:"limit"`
|
||||
Name string `json:"name"`
|
||||
Reasoning bool `json:"reasoning"`
|
||||
Temperature bool `json:"temperature"`
|
||||
Name string `json:"name"`
|
||||
Options map[string]interface{} `json:"options"`
|
||||
Reasoning bool `json:"reasoning"`
|
||||
Temperature bool `json:"temperature"`
|
||||
ToolCall bool `json:"tool_call"`
|
||||
}
|
||||
|
||||
// ProviderInfo defines model for Provider.Info.
|
||||
@@ -511,6 +526,11 @@ type PostSessionChatJSONBody struct {
|
||||
SessionID string `json:"sessionID"`
|
||||
}
|
||||
|
||||
// PostSessionDeleteJSONBody defines parameters for PostSessionDelete.
|
||||
type PostSessionDeleteJSONBody struct {
|
||||
SessionID string `json:"sessionID"`
|
||||
}
|
||||
|
||||
// PostSessionInitializeJSONBody defines parameters for PostSessionInitialize.
|
||||
type PostSessionInitializeJSONBody struct {
|
||||
ModelID string `json:"modelID"`
|
||||
@@ -535,6 +555,11 @@ type PostSessionSummarizeJSONBody struct {
|
||||
SessionID string `json:"sessionID"`
|
||||
}
|
||||
|
||||
// PostSessionUnshareJSONBody defines parameters for PostSessionUnshare.
|
||||
type PostSessionUnshareJSONBody struct {
|
||||
SessionID string `json:"sessionID"`
|
||||
}
|
||||
|
||||
// PostFileSearchJSONRequestBody defines body for PostFileSearch for application/json ContentType.
|
||||
type PostFileSearchJSONRequestBody PostFileSearchJSONBody
|
||||
|
||||
@@ -544,6 +569,9 @@ type PostSessionAbortJSONRequestBody PostSessionAbortJSONBody
|
||||
// PostSessionChatJSONRequestBody defines body for PostSessionChat for application/json ContentType.
|
||||
type PostSessionChatJSONRequestBody PostSessionChatJSONBody
|
||||
|
||||
// PostSessionDeleteJSONRequestBody defines body for PostSessionDelete for application/json ContentType.
|
||||
type PostSessionDeleteJSONRequestBody PostSessionDeleteJSONBody
|
||||
|
||||
// PostSessionInitializeJSONRequestBody defines body for PostSessionInitialize for application/json ContentType.
|
||||
type PostSessionInitializeJSONRequestBody PostSessionInitializeJSONBody
|
||||
|
||||
@@ -556,25 +584,28 @@ type PostSessionShareJSONRequestBody PostSessionShareJSONBody
|
||||
// PostSessionSummarizeJSONRequestBody defines body for PostSessionSummarize for application/json ContentType.
|
||||
type PostSessionSummarizeJSONRequestBody PostSessionSummarizeJSONBody
|
||||
|
||||
// Getter for additional properties for MessageInfo_Metadata_Tool_AdditionalProperties. Returns the specified
|
||||
// PostSessionUnshareJSONRequestBody defines body for PostSessionUnshare for application/json ContentType.
|
||||
type PostSessionUnshareJSONRequestBody PostSessionUnshareJSONBody
|
||||
|
||||
// Getter for additional properties for MessageMetadata_Tool_AdditionalProperties. Returns the specified
|
||||
// element and whether it was found
|
||||
func (a MessageInfo_Metadata_Tool_AdditionalProperties) Get(fieldName string) (value interface{}, found bool) {
|
||||
func (a MessageMetadata_Tool_AdditionalProperties) Get(fieldName string) (value interface{}, found bool) {
|
||||
if a.AdditionalProperties != nil {
|
||||
value, found = a.AdditionalProperties[fieldName]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Setter for additional properties for MessageInfo_Metadata_Tool_AdditionalProperties
|
||||
func (a *MessageInfo_Metadata_Tool_AdditionalProperties) Set(fieldName string, value interface{}) {
|
||||
// Setter for additional properties for MessageMetadata_Tool_AdditionalProperties
|
||||
func (a *MessageMetadata_Tool_AdditionalProperties) Set(fieldName string, value interface{}) {
|
||||
if a.AdditionalProperties == nil {
|
||||
a.AdditionalProperties = make(map[string]interface{})
|
||||
}
|
||||
a.AdditionalProperties[fieldName] = value
|
||||
}
|
||||
|
||||
// Override default JSON handling for MessageInfo_Metadata_Tool_AdditionalProperties to handle AdditionalProperties
|
||||
func (a *MessageInfo_Metadata_Tool_AdditionalProperties) UnmarshalJSON(b []byte) error {
|
||||
// Override default JSON handling for MessageMetadata_Tool_AdditionalProperties to handle AdditionalProperties
|
||||
func (a *MessageMetadata_Tool_AdditionalProperties) UnmarshalJSON(b []byte) error {
|
||||
object := make(map[string]json.RawMessage)
|
||||
err := json.Unmarshal(b, &object)
|
||||
if err != nil {
|
||||
@@ -603,7 +634,7 @@ func (a *MessageInfo_Metadata_Tool_AdditionalProperties) UnmarshalJSON(b []byte)
|
||||
var fieldVal interface{}
|
||||
err := json.Unmarshal(fieldBuf, &fieldVal)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error unmarshalling field %s: %w", fieldName, err)
|
||||
return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err)
|
||||
}
|
||||
a.AdditionalProperties[fieldName] = fieldVal
|
||||
}
|
||||
@@ -611,8 +642,8 @@ func (a *MessageInfo_Metadata_Tool_AdditionalProperties) UnmarshalJSON(b []byte)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Override default JSON handling for MessageInfo_Metadata_Tool_AdditionalProperties to handle AdditionalProperties
|
||||
func (a MessageInfo_Metadata_Tool_AdditionalProperties) MarshalJSON() ([]byte, error) {
|
||||
// Override default JSON handling for MessageMetadata_Tool_AdditionalProperties to handle AdditionalProperties
|
||||
func (a MessageMetadata_Tool_AdditionalProperties) MarshalJSON() ([]byte, error) {
|
||||
var err error
|
||||
object := make(map[string]json.RawMessage)
|
||||
|
||||
@@ -920,6 +951,34 @@ func (t *Event) MergeEventSessionUpdated(v EventSessionUpdated) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// AsEventSessionDeleted returns the union data inside the Event as a EventSessionDeleted
|
||||
func (t Event) AsEventSessionDeleted() (EventSessionDeleted, error) {
|
||||
var body EventSessionDeleted
|
||||
err := json.Unmarshal(t.union, &body)
|
||||
return body, err
|
||||
}
|
||||
|
||||
// FromEventSessionDeleted overwrites any union data inside the Event as the provided EventSessionDeleted
|
||||
func (t *Event) FromEventSessionDeleted(v EventSessionDeleted) error {
|
||||
v.Type = "session.deleted"
|
||||
b, err := json.Marshal(v)
|
||||
t.union = b
|
||||
return err
|
||||
}
|
||||
|
||||
// MergeEventSessionDeleted performs a merge with any union data inside the Event, using the provided EventSessionDeleted
|
||||
func (t *Event) MergeEventSessionDeleted(v EventSessionDeleted) error {
|
||||
v.Type = "session.deleted"
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
merged, err := runtime.JSONMerge(t.union, b)
|
||||
t.union = merged
|
||||
return err
|
||||
}
|
||||
|
||||
// AsEventSessionError returns the union data inside the Event as a EventSessionError
|
||||
func (t Event) AsEventSessionError() (EventSessionError, error) {
|
||||
var body EventSessionError
|
||||
@@ -972,6 +1031,8 @@ func (t Event) ValueByDiscriminator() (interface{}, error) {
|
||||
return t.AsEventMessageUpdated()
|
||||
case "permission.updated":
|
||||
return t.AsEventPermissionUpdated()
|
||||
case "session.deleted":
|
||||
return t.AsEventSessionDeleted()
|
||||
case "session.error":
|
||||
return t.AsEventSessionError()
|
||||
case "session.updated":
|
||||
@@ -1082,23 +1143,23 @@ func (t *EventSessionError_Properties_Error) UnmarshalJSON(b []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// AsProviderAuthError returns the union data inside the MessageInfo_Metadata_Error as a ProviderAuthError
|
||||
func (t MessageInfo_Metadata_Error) AsProviderAuthError() (ProviderAuthError, error) {
|
||||
// AsProviderAuthError returns the union data inside the MessageMetadata_Error as a ProviderAuthError
|
||||
func (t MessageMetadata_Error) AsProviderAuthError() (ProviderAuthError, error) {
|
||||
var body ProviderAuthError
|
||||
err := json.Unmarshal(t.union, &body)
|
||||
return body, err
|
||||
}
|
||||
|
||||
// FromProviderAuthError overwrites any union data inside the MessageInfo_Metadata_Error as the provided ProviderAuthError
|
||||
func (t *MessageInfo_Metadata_Error) FromProviderAuthError(v ProviderAuthError) error {
|
||||
// FromProviderAuthError overwrites any union data inside the MessageMetadata_Error as the provided ProviderAuthError
|
||||
func (t *MessageMetadata_Error) FromProviderAuthError(v ProviderAuthError) error {
|
||||
v.Name = "ProviderAuthError"
|
||||
b, err := json.Marshal(v)
|
||||
t.union = b
|
||||
return err
|
||||
}
|
||||
|
||||
// MergeProviderAuthError performs a merge with any union data inside the MessageInfo_Metadata_Error, using the provided ProviderAuthError
|
||||
func (t *MessageInfo_Metadata_Error) MergeProviderAuthError(v ProviderAuthError) error {
|
||||
// MergeProviderAuthError performs a merge with any union data inside the MessageMetadata_Error, using the provided ProviderAuthError
|
||||
func (t *MessageMetadata_Error) MergeProviderAuthError(v ProviderAuthError) error {
|
||||
v.Name = "ProviderAuthError"
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
@@ -1110,23 +1171,23 @@ func (t *MessageInfo_Metadata_Error) MergeProviderAuthError(v ProviderAuthError)
|
||||
return err
|
||||
}
|
||||
|
||||
// AsUnknownError returns the union data inside the MessageInfo_Metadata_Error as a UnknownError
|
||||
func (t MessageInfo_Metadata_Error) AsUnknownError() (UnknownError, error) {
|
||||
// AsUnknownError returns the union data inside the MessageMetadata_Error as a UnknownError
|
||||
func (t MessageMetadata_Error) AsUnknownError() (UnknownError, error) {
|
||||
var body UnknownError
|
||||
err := json.Unmarshal(t.union, &body)
|
||||
return body, err
|
||||
}
|
||||
|
||||
// FromUnknownError overwrites any union data inside the MessageInfo_Metadata_Error as the provided UnknownError
|
||||
func (t *MessageInfo_Metadata_Error) FromUnknownError(v UnknownError) error {
|
||||
// FromUnknownError overwrites any union data inside the MessageMetadata_Error as the provided UnknownError
|
||||
func (t *MessageMetadata_Error) FromUnknownError(v UnknownError) error {
|
||||
v.Name = "UnknownError"
|
||||
b, err := json.Marshal(v)
|
||||
t.union = b
|
||||
return err
|
||||
}
|
||||
|
||||
// MergeUnknownError performs a merge with any union data inside the MessageInfo_Metadata_Error, using the provided UnknownError
|
||||
func (t *MessageInfo_Metadata_Error) MergeUnknownError(v UnknownError) error {
|
||||
// MergeUnknownError performs a merge with any union data inside the MessageMetadata_Error, using the provided UnknownError
|
||||
func (t *MessageMetadata_Error) MergeUnknownError(v UnknownError) error {
|
||||
v.Name = "UnknownError"
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
@@ -1138,7 +1199,7 @@ func (t *MessageInfo_Metadata_Error) MergeUnknownError(v UnknownError) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (t MessageInfo_Metadata_Error) Discriminator() (string, error) {
|
||||
func (t MessageMetadata_Error) Discriminator() (string, error) {
|
||||
var discriminator struct {
|
||||
Discriminator string `json:"name"`
|
||||
}
|
||||
@@ -1146,7 +1207,7 @@ func (t MessageInfo_Metadata_Error) Discriminator() (string, error) {
|
||||
return discriminator.Discriminator, err
|
||||
}
|
||||
|
||||
func (t MessageInfo_Metadata_Error) ValueByDiscriminator() (interface{}, error) {
|
||||
func (t MessageMetadata_Error) ValueByDiscriminator() (interface{}, error) {
|
||||
discriminator, err := t.Discriminator()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1161,12 +1222,12 @@ func (t MessageInfo_Metadata_Error) ValueByDiscriminator() (interface{}, error)
|
||||
}
|
||||
}
|
||||
|
||||
func (t MessageInfo_Metadata_Error) MarshalJSON() ([]byte, error) {
|
||||
func (t MessageMetadata_Error) MarshalJSON() ([]byte, error) {
|
||||
b, err := t.union.MarshalJSON()
|
||||
return b, err
|
||||
}
|
||||
|
||||
func (t *MessageInfo_Metadata_Error) UnmarshalJSON(b []byte) error {
|
||||
func (t *MessageMetadata_Error) UnmarshalJSON(b []byte) error {
|
||||
err := t.union.UnmarshalJSON(b)
|
||||
return err
|
||||
}
|
||||
@@ -1611,6 +1672,11 @@ type ClientInterface interface {
|
||||
// PostSessionCreate request
|
||||
PostSessionCreate(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
// PostSessionDeleteWithBody request with any body
|
||||
PostSessionDeleteWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
PostSessionDelete(ctx context.Context, body PostSessionDeleteJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
// PostSessionInitializeWithBody request with any body
|
||||
PostSessionInitializeWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
@@ -1633,6 +1699,11 @@ type ClientInterface interface {
|
||||
PostSessionSummarizeWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
PostSessionSummarize(ctx context.Context, body PostSessionSummarizeJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
// PostSessionUnshareWithBody request with any body
|
||||
PostSessionUnshareWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
PostSessionUnshare(ctx context.Context, body PostSessionUnshareJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
}
|
||||
|
||||
func (c *Client) PostAppInfo(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
@@ -1803,6 +1874,30 @@ func (c *Client) PostSessionCreate(ctx context.Context, reqEditors ...RequestEdi
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) PostSessionDeleteWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewPostSessionDeleteRequestWithBody(c.Server, contentType, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) PostSessionDelete(ctx context.Context, body PostSessionDeleteJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewPostSessionDeleteRequest(c.Server, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) PostSessionInitializeWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewPostSessionInitializeRequestWithBody(c.Server, contentType, body)
|
||||
if err != nil {
|
||||
@@ -1911,6 +2006,30 @@ func (c *Client) PostSessionSummarize(ctx context.Context, body PostSessionSumma
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) PostSessionUnshareWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewPostSessionUnshareRequestWithBody(c.Server, contentType, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) PostSessionUnshare(ctx context.Context, body PostSessionUnshareJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewPostSessionUnshareRequest(c.Server, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
// NewPostAppInfoRequest generates requests for PostAppInfo
|
||||
func NewPostAppInfoRequest(server string) (*http.Request, error) {
|
||||
var err error
|
||||
@@ -2247,6 +2366,46 @@ func NewPostSessionCreateRequest(server string) (*http.Request, error) {
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// NewPostSessionDeleteRequest calls the generic PostSessionDelete builder with application/json body
|
||||
func NewPostSessionDeleteRequest(server string, body PostSessionDeleteJSONRequestBody) (*http.Request, error) {
|
||||
var bodyReader io.Reader
|
||||
buf, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bodyReader = bytes.NewReader(buf)
|
||||
return NewPostSessionDeleteRequestWithBody(server, "application/json", bodyReader)
|
||||
}
|
||||
|
||||
// NewPostSessionDeleteRequestWithBody generates requests for PostSessionDelete with any type of body
|
||||
func NewPostSessionDeleteRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) {
|
||||
var err error
|
||||
|
||||
serverURL, err := url.Parse(server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
operationPath := fmt.Sprintf("/session_delete")
|
||||
if operationPath[0] == '/' {
|
||||
operationPath = "." + operationPath
|
||||
}
|
||||
|
||||
queryURL, err := serverURL.Parse(operationPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", queryURL.String(), body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", contentType)
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// NewPostSessionInitializeRequest calls the generic PostSessionInitialize builder with application/json body
|
||||
func NewPostSessionInitializeRequest(server string, body PostSessionInitializeJSONRequestBody) (*http.Request, error) {
|
||||
var bodyReader io.Reader
|
||||
@@ -2434,6 +2593,46 @@ func NewPostSessionSummarizeRequestWithBody(server string, contentType string, b
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// NewPostSessionUnshareRequest calls the generic PostSessionUnshare builder with application/json body
|
||||
func NewPostSessionUnshareRequest(server string, body PostSessionUnshareJSONRequestBody) (*http.Request, error) {
|
||||
var bodyReader io.Reader
|
||||
buf, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bodyReader = bytes.NewReader(buf)
|
||||
return NewPostSessionUnshareRequestWithBody(server, "application/json", bodyReader)
|
||||
}
|
||||
|
||||
// NewPostSessionUnshareRequestWithBody generates requests for PostSessionUnshare with any type of body
|
||||
func NewPostSessionUnshareRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) {
|
||||
var err error
|
||||
|
||||
serverURL, err := url.Parse(server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
operationPath := fmt.Sprintf("/session_unshare")
|
||||
if operationPath[0] == '/' {
|
||||
operationPath = "." + operationPath
|
||||
}
|
||||
|
||||
queryURL, err := serverURL.Parse(operationPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", queryURL.String(), body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", contentType)
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error {
|
||||
for _, r := range c.RequestEditors {
|
||||
if err := r(ctx, req); err != nil {
|
||||
@@ -2516,6 +2715,11 @@ type ClientWithResponsesInterface interface {
|
||||
// PostSessionCreateWithResponse request
|
||||
PostSessionCreateWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*PostSessionCreateResponse, error)
|
||||
|
||||
// PostSessionDeleteWithBodyWithResponse request with any body
|
||||
PostSessionDeleteWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostSessionDeleteResponse, error)
|
||||
|
||||
PostSessionDeleteWithResponse(ctx context.Context, body PostSessionDeleteJSONRequestBody, reqEditors ...RequestEditorFn) (*PostSessionDeleteResponse, error)
|
||||
|
||||
// PostSessionInitializeWithBodyWithResponse request with any body
|
||||
PostSessionInitializeWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostSessionInitializeResponse, error)
|
||||
|
||||
@@ -2538,6 +2742,11 @@ type ClientWithResponsesInterface interface {
|
||||
PostSessionSummarizeWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostSessionSummarizeResponse, error)
|
||||
|
||||
PostSessionSummarizeWithResponse(ctx context.Context, body PostSessionSummarizeJSONRequestBody, reqEditors ...RequestEditorFn) (*PostSessionSummarizeResponse, error)
|
||||
|
||||
// PostSessionUnshareWithBodyWithResponse request with any body
|
||||
PostSessionUnshareWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostSessionUnshareResponse, error)
|
||||
|
||||
PostSessionUnshareWithResponse(ctx context.Context, body PostSessionUnshareJSONRequestBody, reqEditors ...RequestEditorFn) (*PostSessionUnshareResponse, error)
|
||||
}
|
||||
|
||||
type PostAppInfoResponse struct {
|
||||
@@ -2791,6 +3000,28 @@ func (r PostSessionCreateResponse) StatusCode() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
type PostSessionDeleteResponse struct {
|
||||
Body []byte
|
||||
HTTPResponse *http.Response
|
||||
JSON200 *bool
|
||||
}
|
||||
|
||||
// Status returns HTTPResponse.Status
|
||||
func (r PostSessionDeleteResponse) Status() string {
|
||||
if r.HTTPResponse != nil {
|
||||
return r.HTTPResponse.Status
|
||||
}
|
||||
return http.StatusText(0)
|
||||
}
|
||||
|
||||
// StatusCode returns HTTPResponse.StatusCode
|
||||
func (r PostSessionDeleteResponse) StatusCode() int {
|
||||
if r.HTTPResponse != nil {
|
||||
return r.HTTPResponse.StatusCode
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type PostSessionInitializeResponse struct {
|
||||
Body []byte
|
||||
HTTPResponse *http.Response
|
||||
@@ -2901,6 +3132,28 @@ func (r PostSessionSummarizeResponse) StatusCode() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
type PostSessionUnshareResponse struct {
|
||||
Body []byte
|
||||
HTTPResponse *http.Response
|
||||
JSON200 *SessionInfo
|
||||
}
|
||||
|
||||
// Status returns HTTPResponse.Status
|
||||
func (r PostSessionUnshareResponse) Status() string {
|
||||
if r.HTTPResponse != nil {
|
||||
return r.HTTPResponse.Status
|
||||
}
|
||||
return http.StatusText(0)
|
||||
}
|
||||
|
||||
// StatusCode returns HTTPResponse.StatusCode
|
||||
func (r PostSessionUnshareResponse) StatusCode() int {
|
||||
if r.HTTPResponse != nil {
|
||||
return r.HTTPResponse.StatusCode
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// PostAppInfoWithResponse request returning *PostAppInfoResponse
|
||||
func (c *ClientWithResponses) PostAppInfoWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*PostAppInfoResponse, error) {
|
||||
rsp, err := c.PostAppInfo(ctx, reqEditors...)
|
||||
@@ -3024,6 +3277,23 @@ func (c *ClientWithResponses) PostSessionCreateWithResponse(ctx context.Context,
|
||||
return ParsePostSessionCreateResponse(rsp)
|
||||
}
|
||||
|
||||
// PostSessionDeleteWithBodyWithResponse request with arbitrary body returning *PostSessionDeleteResponse
|
||||
func (c *ClientWithResponses) PostSessionDeleteWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostSessionDeleteResponse, error) {
|
||||
rsp, err := c.PostSessionDeleteWithBody(ctx, contentType, body, reqEditors...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ParsePostSessionDeleteResponse(rsp)
|
||||
}
|
||||
|
||||
func (c *ClientWithResponses) PostSessionDeleteWithResponse(ctx context.Context, body PostSessionDeleteJSONRequestBody, reqEditors ...RequestEditorFn) (*PostSessionDeleteResponse, error) {
|
||||
rsp, err := c.PostSessionDelete(ctx, body, reqEditors...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ParsePostSessionDeleteResponse(rsp)
|
||||
}
|
||||
|
||||
// PostSessionInitializeWithBodyWithResponse request with arbitrary body returning *PostSessionInitializeResponse
|
||||
func (c *ClientWithResponses) PostSessionInitializeWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostSessionInitializeResponse, error) {
|
||||
rsp, err := c.PostSessionInitializeWithBody(ctx, contentType, body, reqEditors...)
|
||||
@@ -3101,6 +3371,23 @@ func (c *ClientWithResponses) PostSessionSummarizeWithResponse(ctx context.Conte
|
||||
return ParsePostSessionSummarizeResponse(rsp)
|
||||
}
|
||||
|
||||
// PostSessionUnshareWithBodyWithResponse request with arbitrary body returning *PostSessionUnshareResponse
|
||||
func (c *ClientWithResponses) PostSessionUnshareWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostSessionUnshareResponse, error) {
|
||||
rsp, err := c.PostSessionUnshareWithBody(ctx, contentType, body, reqEditors...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ParsePostSessionUnshareResponse(rsp)
|
||||
}
|
||||
|
||||
func (c *ClientWithResponses) PostSessionUnshareWithResponse(ctx context.Context, body PostSessionUnshareJSONRequestBody, reqEditors ...RequestEditorFn) (*PostSessionUnshareResponse, error) {
|
||||
rsp, err := c.PostSessionUnshare(ctx, body, reqEditors...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ParsePostSessionUnshareResponse(rsp)
|
||||
}
|
||||
|
||||
// ParsePostAppInfoResponse parses an HTTP response from a PostAppInfoWithResponse call
|
||||
func ParsePostAppInfoResponse(rsp *http.Response) (*PostAppInfoResponse, error) {
|
||||
bodyBytes, err := io.ReadAll(rsp.Body)
|
||||
@@ -3402,6 +3689,32 @@ func ParsePostSessionCreateResponse(rsp *http.Response) (*PostSessionCreateRespo
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// ParsePostSessionDeleteResponse parses an HTTP response from a PostSessionDeleteWithResponse call
|
||||
func ParsePostSessionDeleteResponse(rsp *http.Response) (*PostSessionDeleteResponse, error) {
|
||||
bodyBytes, err := io.ReadAll(rsp.Body)
|
||||
defer func() { _ = rsp.Body.Close() }()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := &PostSessionDeleteResponse{
|
||||
Body: bodyBytes,
|
||||
HTTPResponse: rsp,
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
|
||||
var dest bool
|
||||
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.JSON200 = &dest
|
||||
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// ParsePostSessionInitializeResponse parses an HTTP response from a PostSessionInitializeWithResponse call
|
||||
func ParsePostSessionInitializeResponse(rsp *http.Response) (*PostSessionInitializeResponse, error) {
|
||||
bodyBytes, err := io.ReadAll(rsp.Body)
|
||||
@@ -3531,3 +3844,29 @@ func ParsePostSessionSummarizeResponse(rsp *http.Response) (*PostSessionSummariz
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// ParsePostSessionUnshareResponse parses an HTTP response from a PostSessionUnshareWithResponse call
|
||||
func ParsePostSessionUnshareResponse(rsp *http.Response) (*PostSessionUnshareResponse, error) {
|
||||
bodyBytes, err := io.ReadAll(rsp.Body)
|
||||
defer func() { _ = rsp.Body.Close() }()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := &PostSessionUnshareResponse{
|
||||
Body: bodyBytes,
|
||||
HTTPResponse: rsp,
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
|
||||
var dest SessionInfo
|
||||
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.JSON200 = &dest
|
||||
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
@@ -113,29 +113,83 @@ const DiffView: Component<DiffViewProps> = (props) => {
|
||||
|
||||
return (
|
||||
<div class={`${styles.diff} ${props.class ?? ""}`}>
|
||||
<div class={styles.column}>
|
||||
{rows().map((r) => (
|
||||
<CodeBlock
|
||||
code={r.left}
|
||||
lang={props.lang}
|
||||
data-section="cell"
|
||||
data-diff-type={r.type === "removed" || r.type === "modified" ? "removed" : ""}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{rows().map((r) => (
|
||||
<div class={styles.row}>
|
||||
<div class={styles.beforeColumn}>
|
||||
<CodeBlock
|
||||
code={r.left}
|
||||
lang={props.lang}
|
||||
data-section="cell"
|
||||
data-diff-type={r.type === "removed" || r.type === "modified" ? "removed" : ""}
|
||||
data-display-mobile={r.type === "added" && !r.left ? "false" : undefined}
|
||||
/>
|
||||
{(r.type === "added" || r.type === "modified") && r.right !== undefined && (
|
||||
<CodeBlock
|
||||
code={r.right}
|
||||
lang={props.lang}
|
||||
data-section="cell"
|
||||
data-diff-type="added"
|
||||
data-display-mobile="true"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div class={styles.column}>
|
||||
{rows().map((r) => (
|
||||
<CodeBlock
|
||||
code={r.right}
|
||||
lang={props.lang}
|
||||
data-section="cell"
|
||||
data-diff-type={r.type === "added" || r.type === "modified" ? "added" : ""}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div class={styles.afterColumn}>
|
||||
<CodeBlock
|
||||
code={r.right}
|
||||
lang={props.lang}
|
||||
data-section="cell"
|
||||
data-diff-type={r.type === "added" || r.type === "modified" ? "added" : ""}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DiffView
|
||||
|
||||
// String to test diff viewer with
|
||||
const testDiff = `--- combined_before.txt 2025-06-24 16:38:08
|
||||
+++ combined_after.txt 2025-06-24 16:38:12
|
||||
@@ -1,21 +1,25 @@
|
||||
unchanged line
|
||||
-deleted line
|
||||
-old content
|
||||
+added line
|
||||
+new content
|
||||
|
||||
-removed empty line below
|
||||
+added empty line above
|
||||
|
||||
- tab indented
|
||||
-trailing spaces
|
||||
-very long line that will definitely wrap in most editors and cause potential alignment issues when displayed in a two column diff view
|
||||
-unicode content: 🚀 ✨ 中文
|
||||
-mixed content with tabs and spaces
|
||||
+ space indented
|
||||
+no trailing spaces
|
||||
+short line
|
||||
+very long replacement line that will also wrap and test how the diff viewer handles long line additions after short line removals
|
||||
+different unicode: 🎉 💻 日本語
|
||||
+normalized content with consistent spacing
|
||||
+newline to content
|
||||
|
||||
-content to remove
|
||||
-whitespace only:
|
||||
-multiple
|
||||
-consecutive
|
||||
-deletions
|
||||
-single deletion
|
||||
+
|
||||
+single addition
|
||||
+first addition
|
||||
+second addition
|
||||
+third addition
|
||||
line before addition
|
||||
+first added line
|
||||
+
|
||||
+third added line
|
||||
line after addition
|
||||
final unchanged line`
|
||||
|
||||
@@ -463,6 +463,7 @@ function MarkdownPart(props: MarkdownPartProps) {
|
||||
|
||||
interface TerminalPartProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
||||
command: string
|
||||
error?: string
|
||||
result?: string
|
||||
desc?: string
|
||||
expand?: boolean
|
||||
@@ -470,6 +471,7 @@ interface TerminalPartProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
||||
function TerminalPart(props: TerminalPartProps) {
|
||||
const [local, rest] = splitProps(props, [
|
||||
"command",
|
||||
"error",
|
||||
"result",
|
||||
"desc",
|
||||
"expand",
|
||||
@@ -508,12 +510,25 @@ function TerminalPart(props: TerminalPartProps) {
|
||||
</div>
|
||||
<div data-section="content">
|
||||
<CodeBlock lang="bash" code={local.command} />
|
||||
<CodeBlock
|
||||
lang="console"
|
||||
onRendered={checkOverflow}
|
||||
ref={(el) => (preEl = el)}
|
||||
code={local.result || ""}
|
||||
/>
|
||||
<Switch>
|
||||
<Match when={local.error}>
|
||||
<CodeBlock
|
||||
data-section="error"
|
||||
lang="text"
|
||||
onRendered={checkOverflow}
|
||||
ref={(el) => (preEl = el)}
|
||||
code={local.error || ""}
|
||||
/>
|
||||
</Match>
|
||||
<Match when={local.result}>
|
||||
<CodeBlock
|
||||
lang="console"
|
||||
onRendered={checkOverflow}
|
||||
ref={(el) => (preEl = el)}
|
||||
code={local.result || ""}
|
||||
/>
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
{((!local.expand && overflowed()) || expanded()) && (
|
||||
@@ -1601,8 +1616,10 @@ export default function Share(props: {
|
||||
}
|
||||
>
|
||||
{(_part) => {
|
||||
const command = () => toolData()?.args.command
|
||||
const desc = () => toolData()?.args.description
|
||||
const command = () => toolData()?.metadata?.title
|
||||
const desc = () => toolData()?.metadata?.description
|
||||
const result = () => toolData()?.metadata?.stdout
|
||||
const error = () => toolData()?.metadata?.stderr
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -1617,14 +1634,17 @@ export default function Share(props: {
|
||||
<div></div>
|
||||
</div>
|
||||
<div data-section="content">
|
||||
<div data-part-tool-body>
|
||||
<TerminalPart
|
||||
desc={desc()}
|
||||
data-size="sm"
|
||||
command={command()}
|
||||
result={toolData()?.result}
|
||||
/>
|
||||
</div>
|
||||
{command() && (
|
||||
<div data-part-tool-body>
|
||||
<TerminalPart
|
||||
desc={desc()}
|
||||
data-size="sm"
|
||||
command={command()!}
|
||||
result={result()}
|
||||
error={error()}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<ToolFooter
|
||||
time={toolData()?.duration || 0}
|
||||
/>
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
pre {
|
||||
--shiki-dark-bg: var(--sl-color-bg-surface) !important;
|
||||
background-color: var(--sl-color-bg-surface) !important;
|
||||
|
||||
span {
|
||||
white-space: break-spaces;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,39 +1,52 @@
|
||||
.diff {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid var(--sl-color-divider);
|
||||
background-color: var(--sl-color-bg-surface);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.column {
|
||||
.row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.beforeColumn,
|
||||
.afterColumn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
overflow-x: visible;
|
||||
min-width: 0;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-right: 1px solid var(--sl-color-divider);
|
||||
}
|
||||
.beforeColumn {
|
||||
border-right: 1px solid var(--sl-color-divider);
|
||||
}
|
||||
|
||||
& > [data-section="cell"]:first-child {
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
& > [data-section="cell"]:last-child {
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
.diff > .row:first-child [data-section="cell"]:first-child {
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
|
||||
.diff > .row:last-child [data-section="cell"]:last-child {
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
[data-section="cell"] {
|
||||
position: relative;
|
||||
flex: none;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
width: 100%;
|
||||
padding: 0.1875rem 0.5rem 0.1875rem 2.2ch;
|
||||
margin: 0;
|
||||
|
||||
&[data-display-mobile="true"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
pre {
|
||||
--shiki-dark-bg: var(--sl-color-bg-surface) !important;
|
||||
background-color: var(--sl-color-bg-surface) !important;
|
||||
@@ -83,3 +96,27 @@
|
||||
color: var(--sl-color-green-high);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
.row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.afterColumn {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.beforeColumn {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
[data-section="cell"] {
|
||||
&[data-display-mobile="true"] {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&[data-display-mobile="false"] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,16 +37,26 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
border-radius: 0.25rem;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
code {
|
||||
font-weight: 500;
|
||||
|
||||
&::before {
|
||||
content: "`";
|
||||
font-weight: 600;
|
||||
}
|
||||
&::after {
|
||||
content: "`";
|
||||
font-weight: 600;
|
||||
&:not(pre code) {
|
||||
&::before {
|
||||
content: "`";
|
||||
font-weight: 700;
|
||||
}
|
||||
&::after {
|
||||
content: "`";
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -616,6 +616,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
[data-section="error"] {
|
||||
pre {
|
||||
color: var(--sl-color-red) !important;
|
||||
--shiki-dark: var(--sl-color-red) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&[data-expanded="true"] {
|
||||
pre {
|
||||
display: block;
|
||||
|
||||
@@ -89,3 +89,21 @@ You can configure MCP servers you want to use through the `mcp` option.
|
||||
```
|
||||
|
||||
[Learn more here](/docs/mcp-servers).
|
||||
|
||||
---
|
||||
|
||||
### Disabled providers
|
||||
|
||||
You can disable providers that are loaded automatically through the `disabled_providers` option. This is useful when you want to prevent certain providers from being loaded even if their credentials are available.
|
||||
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"disabled_providers": ["openai", "gemini"]
|
||||
}
|
||||
```
|
||||
|
||||
The `disabled_providers` option accepts an array of provider IDs. When a provider is disabled:
|
||||
- It won't be loaded even if environment variables are set
|
||||
- It won't be loaded even if API keys are configured through `opencode auth login`
|
||||
- The provider's models won't appear in the model selection list
|
||||
|
||||
@@ -64,6 +64,10 @@ paru -S opencode-bin
|
||||
|
||||
---
|
||||
|
||||
##### Windows
|
||||
|
||||
Right now the automatic installation methods do not work properly on Windows. However you can grab the binary from the [Releases](https://github.com/sst/opencode/releases).
|
||||
|
||||
## Providers
|
||||
|
||||
We recommend signing up for Claude Pro or Max, running `opencode auth login` and selecting Anthropic. It's the most cost-effective way to use opencode.
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
@echo off
|
||||
|
||||
if not exist ".git" (
|
||||
exit /b 0
|
||||
)
|
||||
|
||||
if not exist ".git\hooks" (
|
||||
mkdir ".git\hooks"
|
||||
)
|
||||
|
||||
(
|
||||
echo #!/bin/sh
|
||||
echo bun run typecheck
|
||||
) > ".git\hooks\pre-push"
|
||||
|
||||
echo ✅ Pre-push hook installed
|
||||
Reference in New Issue
Block a user