mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-10 01:39:36 +00:00
chore: generate
This commit is contained in:
parent
fe0c4f8c74
commit
155e1f20d6
25 changed files with 571 additions and 408 deletions
|
|
@ -115,7 +115,8 @@ export const layer = Layer.effect(
|
|||
if (found) yield* stopProcess(found).pipe(Effect.ignore)
|
||||
|
||||
const entrypoint = compiled ? undefined : process.argv[1]
|
||||
if (!compiled && entrypoint === undefined) return yield* Effect.fail(new Error("Failed to resolve CLI entrypoint"))
|
||||
if (!compiled && entrypoint === undefined)
|
||||
return yield* Effect.fail(new Error("Failed to resolve CLI entrypoint"))
|
||||
yield* Effect.try({
|
||||
try: () => {
|
||||
spawn(process.execPath, [...(entrypoint ? [entrypoint] : []), "serve", "--register"], {
|
||||
|
|
|
|||
|
|
@ -106,10 +106,13 @@ const legacyDefaults: Record<string, unknown> = {
|
|||
"/config": {},
|
||||
}
|
||||
|
||||
const gracefulFetch = Object.assign(async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
const response = await fetch(input, init)
|
||||
if (response.status !== 404) return response
|
||||
const fallback = legacyDefaults[new URL(input instanceof Request ? input.url : input).pathname]
|
||||
if (fallback === undefined) return response
|
||||
return Response.json(fallback)
|
||||
}, { preconnect: fetch.preconnect })
|
||||
const gracefulFetch = Object.assign(
|
||||
async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
const response = await fetch(input, init)
|
||||
if (response.status !== 404) return response
|
||||
const fallback = legacyDefaults[new URL(input instanceof Request ? input.url : input).pathname]
|
||||
if (fallback === undefined) return response
|
||||
return Response.json(fallback)
|
||||
},
|
||||
{ preconnect: fetch.preconnect },
|
||||
)
|
||||
|
|
|
|||
|
|
@ -100,9 +100,7 @@ function normalizePack(pack: TuiAttentionSoundPack): RegisteredSoundPack | undef
|
|||
sounds: Object.fromEntries(
|
||||
Object.entries(pack.sounds).filter(
|
||||
(item): item is [TuiAttentionSoundName, string] =>
|
||||
Schema.is(AttentionSoundName)(item[0]) &&
|
||||
typeof item[1] === "string" &&
|
||||
item[1].trim().length > 0,
|
||||
Schema.is(AttentionSoundName)(item[0]) && typeof item[1] === "string" && item[1].trim().length > 0,
|
||||
),
|
||||
),
|
||||
}
|
||||
|
|
@ -200,9 +198,7 @@ export function createTuiAttention(input: {
|
|||
const requestedSound = typeof request.sound === "object" ? request.sound : undefined
|
||||
const soundSkip = volume === undefined ? undefined : focusSkip(requestedSound?.when ?? "always", focus)
|
||||
const soundName =
|
||||
requestedSound?.name && Schema.is(AttentionSoundName)(requestedSound.name)
|
||||
? requestedSound.name
|
||||
: "default"
|
||||
requestedSound?.name && Schema.is(AttentionSoundName)(requestedSound.name) ? requestedSound.name : "default"
|
||||
const sound = volume === undefined || soundSkip ? false : await playSound(soundName, volume)
|
||||
|
||||
if (!notification && !sound) {
|
||||
|
|
|
|||
|
|
@ -86,13 +86,15 @@ export function discoverEditorConnection(directory: string) {
|
|||
: []
|
||||
const score = Math.max(0, ...folders.map(contains))
|
||||
if (!score) return []
|
||||
return [{
|
||||
url: `ws://127.0.0.1:${port}`,
|
||||
authToken: typeof value.authToken === "string" ? value.authToken : undefined,
|
||||
source: `lock:${port}`,
|
||||
score,
|
||||
mtime: statSync(file).mtimeMs,
|
||||
}]
|
||||
return [
|
||||
{
|
||||
url: `ws://127.0.0.1:${port}`,
|
||||
authToken: typeof value.authToken === "string" ? value.authToken : undefined,
|
||||
source: `lock:${port}`,
|
||||
score,
|
||||
mtime: statSync(file).mtimeMs,
|
||||
},
|
||||
]
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
|
|
@ -110,11 +112,13 @@ function resolveZedDbPath() {
|
|||
path.join(os.homedir(), "Library", "Application Support", "Zed", "db", "0-stable", "db.sqlite"),
|
||||
path.join(os.homedir(), ".local", "share", "zed", "db", "0-stable", "db.sqlite"),
|
||||
].filter((item): item is string => Boolean(item))
|
||||
return candidates.find((item) => {
|
||||
try {
|
||||
return statSync(item).isFile()
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}) ?? ""
|
||||
return (
|
||||
candidates.find((item) => {
|
||||
try {
|
||||
return statSync(item).isFile()
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}) ?? ""
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,10 @@ import { HttpEffect, HttpRouter, HttpServerRequest, HttpServerResponse } from "e
|
|||
import { HttpApiError, HttpApiMiddleware } from "effect/unstable/httpapi"
|
||||
import { hasPtyConnectTicketURL } from "@/server/shared/pty-ticket"
|
||||
import { isPublicUIPath } from "@/server/shared/public-ui"
|
||||
export { Authorization as ServerAuthorization, authorizationLayer as serverAuthorizationLayer } from "@opencode-ai/server/middleware/authorization"
|
||||
export {
|
||||
Authorization as ServerAuthorization,
|
||||
authorizationLayer as serverAuthorizationLayer,
|
||||
} from "@opencode-ai/server/middleware/authorization"
|
||||
|
||||
const AUTH_TOKEN_QUERY = "auth_token"
|
||||
const UNAUTHORIZED = 401
|
||||
|
|
|
|||
|
|
@ -3,12 +3,7 @@ import { mkdir, symlink } from "node:fs/promises"
|
|||
import os from "node:os"
|
||||
import path from "node:path"
|
||||
import { afterEach, expect, spyOn, test } from "bun:test"
|
||||
import {
|
||||
isZedTerminal,
|
||||
offsetToPosition,
|
||||
resolveZedDbPath,
|
||||
resolveZedSelection,
|
||||
} from "../../../src/cli/tui/editor-zed"
|
||||
import { isZedTerminal, offsetToPosition, resolveZedDbPath, resolveZedSelection } from "../../../src/cli/tui/editor-zed"
|
||||
import { tmpdir } from "../../fixture/fixture"
|
||||
|
||||
const originalZedTerm = process.env.ZED_TERM
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,15 +1,14 @@
|
|||
import { Schema } from "effect"
|
||||
import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
|
||||
export const HealthGroup = HttpApiGroup.make("server.health")
|
||||
.add(
|
||||
HttpApiEndpoint.get("health.get", "/api/health", {
|
||||
success: Schema.Struct({ healthy: Schema.Literal(true) }),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "v2.health.get",
|
||||
summary: "Check server health",
|
||||
description: "Check whether the API server is ready to accept requests.",
|
||||
}),
|
||||
),
|
||||
)
|
||||
export const HealthGroup = HttpApiGroup.make("server.health").add(
|
||||
HttpApiEndpoint.get("health.get", "/api/health", {
|
||||
success: Schema.Struct({ healthy: Schema.Literal(true) }),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "v2.health.get",
|
||||
summary: "Check server health",
|
||||
description: "Check whether the API server is ready to accept requests.",
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -83,6 +83,4 @@ export const PermissionGroup = HttpApiGroup.make("server.permission")
|
|||
}),
|
||||
),
|
||||
)
|
||||
.annotateMerge(
|
||||
OpenApi.annotations({ title: "permissions", description: "Experimental permission routes." }),
|
||||
)
|
||||
.annotateMerge(OpenApi.annotations({ title: "permissions", description: "Experimental permission routes." }))
|
||||
|
|
|
|||
|
|
@ -33,8 +33,7 @@ export const ProviderGroup = HttpApiGroup.make("server.provider")
|
|||
OpenApi.annotations({
|
||||
identifier: "v2.provider.get",
|
||||
summary: "Get provider",
|
||||
description:
|
||||
"Retrieve a single AI provider so clients can inspect its availability and endpoint settings.",
|
||||
description: "Retrieve a single AI provider so clients can inspect its availability and endpoint settings.",
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -43,7 +43,11 @@ export const PermissionHandler = HttpApiBuilder.group(Api, "server.permission",
|
|||
"permission.saved.list",
|
||||
Effect.fn(function* (ctx) {
|
||||
const location = yield* Location.Service
|
||||
return { data: yield* (yield* PermissionSaved.Service).list({ projectID: ctx.query.projectID ?? location.project.id }) }
|
||||
return {
|
||||
data: yield* (yield* PermissionSaved.Service).list({
|
||||
projectID: ctx.query.projectID ?? location.project.id,
|
||||
}),
|
||||
}
|
||||
}),
|
||||
)
|
||||
.handle(
|
||||
|
|
|
|||
|
|
@ -7,12 +7,9 @@ import { HttpApiMiddleware } from "effect/unstable/httpapi"
|
|||
const AUTH_TOKEN_QUERY = "auth_token"
|
||||
const WWW_AUTHENTICATE = 'Basic realm="Secure Area"'
|
||||
|
||||
export class Authorization extends HttpApiMiddleware.Service<Authorization>()(
|
||||
"@opencode/HttpApiAuthorization",
|
||||
{
|
||||
error: UnauthorizedError,
|
||||
},
|
||||
) {}
|
||||
export class Authorization extends HttpApiMiddleware.Service<Authorization>()("@opencode/HttpApiAuthorization", {
|
||||
error: UnauthorizedError,
|
||||
}) {}
|
||||
|
||||
function emptyCredential() {
|
||||
return { username: "", password: Redacted.make("") }
|
||||
|
|
|
|||
|
|
@ -480,30 +480,33 @@ function App(props: { onSnapshot?: () => Promise<string[]>; pluginHost: TuiPlugi
|
|||
const attention = props.host.attention({ renderer, config: tuiConfig, kv })
|
||||
const platform = useTuiPlatform()
|
||||
|
||||
const api = createTuiApi(createTuiApiAdapters({
|
||||
version: build.version,
|
||||
tuiConfig,
|
||||
dialog,
|
||||
keymap,
|
||||
kv,
|
||||
route,
|
||||
routes: pluginRuntime.routes,
|
||||
event,
|
||||
sdk,
|
||||
sync,
|
||||
theme: themeState,
|
||||
toast,
|
||||
renderer,
|
||||
attention,
|
||||
Slot: pluginRuntime.Slot,
|
||||
}))
|
||||
const api = createTuiApi(
|
||||
createTuiApiAdapters({
|
||||
version: build.version,
|
||||
tuiConfig,
|
||||
dialog,
|
||||
keymap,
|
||||
kv,
|
||||
route,
|
||||
routes: pluginRuntime.routes,
|
||||
event,
|
||||
sdk,
|
||||
sync,
|
||||
theme: themeState,
|
||||
toast,
|
||||
renderer,
|
||||
attention,
|
||||
Slot: pluginRuntime.Slot,
|
||||
}),
|
||||
)
|
||||
const [ready, setReady] = createSignal(false)
|
||||
props.pluginHost.start({
|
||||
api,
|
||||
config: tuiConfig,
|
||||
runtime: pluginRuntime,
|
||||
dispose: () => attention.dispose(),
|
||||
})
|
||||
props.pluginHost
|
||||
.start({
|
||||
api,
|
||||
config: tuiConfig,
|
||||
runtime: pluginRuntime,
|
||||
dispose: () => attention.dispose(),
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to load TUI plugins", error)
|
||||
})
|
||||
|
|
@ -529,7 +532,8 @@ function App(props: { onSnapshot?: () => Promise<string[]>; pluginHost: TuiPlugi
|
|||
renderer.console.onCopySelection = async (text: string) => {
|
||||
if (!text || text.length === 0) return
|
||||
|
||||
await platform.clipboard?.write?.(text)
|
||||
await platform.clipboard
|
||||
?.write?.(text)
|
||||
.then(() => toast.show({ message: "Copied to clipboard", variant: "info" }))
|
||||
.catch(toast.error)
|
||||
|
||||
|
|
@ -691,7 +695,8 @@ function App(props: { onSnapshot?: () => Promise<string[]>; pluginHost: TuiPlugi
|
|||
run: async () => {
|
||||
const workspace = currentWorktreeWorkspace()
|
||||
if (!workspace?.directory) return
|
||||
await platform.clipboard?.write?.(workspace.directory)
|
||||
await platform.clipboard
|
||||
?.write?.(workspace.directory)
|
||||
.then(() => toast.show({ message: "Copied worktree path", variant: "info" }))
|
||||
.catch(toast.error)
|
||||
dialog.clear()
|
||||
|
|
|
|||
|
|
@ -16,10 +16,7 @@ export const AttentionSoundName = Schema.Literals([
|
|||
export type AttentionSoundName = Schema.Schema.Type<typeof AttentionSoundName>
|
||||
|
||||
export const PluginOptions = Schema.Record(Schema.String, Schema.Unknown)
|
||||
export const PluginSpec = Schema.Union([
|
||||
Schema.String,
|
||||
Schema.mutable(Schema.Tuple([Schema.String, PluginOptions])),
|
||||
])
|
||||
export const PluginSpec = Schema.Union([Schema.String, Schema.mutable(Schema.Tuple([Schema.String, PluginOptions]))])
|
||||
|
||||
export const LeaderTimeoutDefault = 2000
|
||||
export const LeaderTimeout = Schema.Int.check(Schema.isGreaterThan(0)).annotate({
|
||||
|
|
|
|||
|
|
@ -55,7 +55,11 @@ export type SyncDependencies = {
|
|||
logger: { error(message: string, extra?: Record<string, unknown>): void }
|
||||
}
|
||||
|
||||
export const { context: SyncContext, use: useSync, provider: SyncProvider } = createSimpleContext({
|
||||
export const {
|
||||
context: SyncContext,
|
||||
use: useSync,
|
||||
provider: SyncProvider,
|
||||
} = createSimpleContext({
|
||||
name: "Sync",
|
||||
init: (dependencies: SyncDependencies) => {
|
||||
const environment = useTuiEnvironment()
|
||||
|
|
|
|||
|
|
@ -96,14 +96,15 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
|
|||
})
|
||||
|
||||
function syncCustomThemes() {
|
||||
return (platform?.themes?.discover() ?? Promise.resolve({})).then((themes) => {
|
||||
setCustomThemes(
|
||||
Object.entries(themes).reduce<Record<string, ThemeJson>>((result, [name, theme]) => {
|
||||
if (isTheme(theme)) result[name] = theme
|
||||
return result
|
||||
}, {}),
|
||||
)
|
||||
})
|
||||
return (platform?.themes?.discover() ?? Promise.resolve({}))
|
||||
.then((themes) => {
|
||||
setCustomThemes(
|
||||
Object.entries(themes).reduce<Record<string, ThemeJson>>((result, [name, theme]) => {
|
||||
if (isTheme(theme)) result[name] = theme
|
||||
return result
|
||||
}, {}),
|
||||
)
|
||||
})
|
||||
.catch(() => setStore("active", "opencode"))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -136,10 +136,12 @@ export function Tips(props: { api: TuiPluginApi; connected?: boolean }) {
|
|||
}
|
||||
const tip = createMemo(() => {
|
||||
if (props.connected === false) return NO_MODELS_TIP
|
||||
const tips = [...TIPS, environment.capabilities.terminalSuspend ? TERMINAL_SUSPEND_TIP : INPUT_UNDO_TIP].flatMap((item) => {
|
||||
const value = typeof item === "string" ? item : item(shortcuts)
|
||||
return value ? [value] : []
|
||||
})
|
||||
const tips = [...TIPS, environment.capabilities.terminalSuspend ? TERMINAL_SUSPEND_TIP : INPUT_UNDO_TIP].flatMap(
|
||||
(item) => {
|
||||
const value = typeof item === "string" ? item : item(shortcuts)
|
||||
return value ? [value] : []
|
||||
},
|
||||
)
|
||||
return tips[Math.floor(tipOffset * tips.length)] ?? NO_MODELS_TIP
|
||||
}, NO_MODELS_TIP)
|
||||
// Solid can expose a memo's initial value while a pure computation is pending.
|
||||
|
|
|
|||
|
|
@ -211,11 +211,7 @@ export function formatKeyBindings(bindings: Parameters<typeof formatCommandBindi
|
|||
return formatCommandBindingsExtra(bindings, formatOptions(config))
|
||||
}
|
||||
|
||||
export function registerOpencodeKeymap(
|
||||
keymap: OpenTuiKeymap,
|
||||
renderer: CliRenderer,
|
||||
config: ResolvedKeymapConfig,
|
||||
) {
|
||||
export function registerOpencodeKeymap(keymap: OpenTuiKeymap, renderer: CliRenderer, config: ResolvedKeymapConfig) {
|
||||
const modeStack = createOpencodeModeStack(keymap)
|
||||
const offCommaBindings = registerCommaBindings(keymap)
|
||||
const offAliasExpander = registerKeyAliases(keymap)
|
||||
|
|
|
|||
|
|
@ -5,10 +5,7 @@ export function stripPromptPartIDs<Part extends { id: string; messageID: string;
|
|||
return rest
|
||||
}
|
||||
|
||||
export function expandPastedTextPlaceholders(
|
||||
text: string,
|
||||
parts: readonly unknown[],
|
||||
) {
|
||||
export function expandPastedTextPlaceholders(text: string, parts: readonly unknown[]) {
|
||||
return parts.reduce<string>((result, part) => {
|
||||
if (!isPastedTextPart(part)) return result
|
||||
return result.replace(part.source.text.value, part.text)
|
||||
|
|
|
|||
|
|
@ -460,7 +460,8 @@ export function Session() {
|
|||
},
|
||||
run: async () => {
|
||||
const copy = (url: string) =>
|
||||
platform.clipboard?.write?.(url)
|
||||
platform.clipboard
|
||||
?.write?.(url)
|
||||
.then(() => toast.show({ message: "Share URL copied to clipboard!", variant: "success" }))
|
||||
.catch(() => toast.show({ message: "Failed to copy URL to clipboard", variant: "error" }))
|
||||
const url = session()?.share?.url
|
||||
|
|
@ -895,7 +896,8 @@ export function Session() {
|
|||
return
|
||||
}
|
||||
|
||||
platform.clipboard?.write?.(text)
|
||||
platform.clipboard
|
||||
?.write?.(text)
|
||||
.then(() => toast.show({ message: "Message copied to clipboard!", variant: "success" }))
|
||||
.catch(() => toast.show({ message: "Failed to copy to clipboard", variant: "error" }))
|
||||
dialog.clear()
|
||||
|
|
|
|||
|
|
@ -30,7 +30,8 @@ export function cliErrorMessage(input: unknown): string | undefined {
|
|||
}
|
||||
|
||||
const provider = configData(input, "ProviderInitError")
|
||||
if (provider) return `Failed to initialize provider "${field(provider, "providerID")}". Check credentials and configuration.`
|
||||
if (provider)
|
||||
return `Failed to initialize provider "${field(provider, "providerID")}". Check credentials and configuration.`
|
||||
|
||||
const json = configData(input, "ConfigJsonError")
|
||||
if (json) {
|
||||
|
|
|
|||
|
|
@ -20,14 +20,7 @@ test("defines package-owned plugin specs and attention sound names", () => {
|
|||
expect(decodePlugin("example-plugin")).toBe("example-plugin")
|
||||
expect(decodePlugin(["example-plugin", { enabled: true }])).toEqual(["example-plugin", { enabled: true }])
|
||||
expect(() => decodePlugin(["example-plugin"])).toThrow()
|
||||
expect(AttentionSoundName.literals).toEqual([
|
||||
"default",
|
||||
"question",
|
||||
"permission",
|
||||
"error",
|
||||
"done",
|
||||
"subagent_done",
|
||||
])
|
||||
expect(AttentionSoundName.literals).toEqual(["default", "question", "permission", "error", "done", "subagent_done"])
|
||||
})
|
||||
|
||||
test("validates config constraints", () => {
|
||||
|
|
|
|||
|
|
@ -43,8 +43,19 @@ export function createFetch(override?: FetchHandler) {
|
|||
const overridden = await override?.(url)
|
||||
if (overridden) return overridden
|
||||
|
||||
if (["/agent", "/command", "/experimental/workspace", "/experimental/workspace/status", "/formatter", "/lsp"].includes(url.pathname)) return json([])
|
||||
if (["/config", "/experimental/resource", "/mcp", "/provider/auth", "/session/status"].includes(url.pathname)) return json({})
|
||||
if (
|
||||
[
|
||||
"/agent",
|
||||
"/command",
|
||||
"/experimental/workspace",
|
||||
"/experimental/workspace/status",
|
||||
"/formatter",
|
||||
"/lsp",
|
||||
].includes(url.pathname)
|
||||
)
|
||||
return json([])
|
||||
if (["/config", "/experimental/resource", "/mcp", "/provider/auth", "/session/status"].includes(url.pathname))
|
||||
return json({})
|
||||
if (url.pathname === "/config/providers") return json({ providers: {}, default: {} })
|
||||
if (url.pathname === "/experimental/console") return json({ consoleManagedProviders: [], switchableOrgCount: 0 })
|
||||
if (url.pathname === "/path") return json({ home: "", state: "", config: "", worktree, directory })
|
||||
|
|
|
|||
|
|
@ -5,12 +5,7 @@ import { testRender, useRenderer } from "@opentui/solid"
|
|||
import { expect, test } from "bun:test"
|
||||
import { onCleanup } from "solid-js"
|
||||
import { TuiKeybind } from "../src/config/keybind"
|
||||
import {
|
||||
getOpencodeModeStack,
|
||||
OPENCODE_BASE_MODE,
|
||||
OpencodeKeymapProvider,
|
||||
registerOpencodeKeymap,
|
||||
} from "../src/keymap"
|
||||
import { getOpencodeModeStack, OPENCODE_BASE_MODE, OpencodeKeymapProvider, registerOpencodeKeymap } from "../src/keymap"
|
||||
|
||||
function createResolvedKeymapConfig(input: TuiKeybind.KeybindOverrides = {}) {
|
||||
const keybinds = TuiKeybind.parse(input)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,5 @@
|
|||
import { describe, expect, test } from "bun:test"
|
||||
import {
|
||||
formatAssistantHeader,
|
||||
formatMessage,
|
||||
formatPart,
|
||||
formatTranscript,
|
||||
} from "../../src/util/transcript"
|
||||
import { formatAssistantHeader, formatMessage, formatPart, formatTranscript } from "../../src/util/transcript"
|
||||
import type { AssistantMessage, Part, Provider, UserMessage } from "@opencode-ai/sdk/v2"
|
||||
|
||||
const providers: Provider[] = [
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue