refactor(core): move v1 schemas into core (#30473)
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import type { ContentBlock, ContentChunk, ResourceLink, Role } from "@agentclientprotocol/sdk"
|
||||
import path from "node:path"
|
||||
import { pathToFileURL } from "node:url"
|
||||
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||
|
||||
export type PromptPart = SessionLegacy.TextPartInput | SessionLegacy.FilePartInput
|
||||
export type PromptPart = SessionV1.TextPartInput | SessionV1.FilePartInput
|
||||
|
||||
export type ReplayPart =
|
||||
| {
|
||||
@@ -141,7 +141,7 @@ function uriToFilePart(
|
||||
uri: string,
|
||||
mime: string,
|
||||
filename?: string,
|
||||
): SessionLegacy.FilePartInput | SessionLegacy.TextPartInput {
|
||||
): SessionV1.FilePartInput | SessionV1.TextPartInput {
|
||||
try {
|
||||
if (uri.startsWith("file://")) {
|
||||
return {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PermissionLegacy } from "@opencode-ai/core/permission/legacy"
|
||||
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
|
||||
import { Config } from "@/config/config"
|
||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import { Provider } from "@/provider/provider"
|
||||
@@ -35,7 +35,7 @@ export const Info = Schema.Struct({
|
||||
topP: Schema.optional(Schema.Finite),
|
||||
temperature: Schema.optional(Schema.Finite),
|
||||
color: Schema.optional(Schema.String),
|
||||
permission: PermissionLegacy.Ruleset,
|
||||
permission: PermissionV1.Ruleset,
|
||||
model: Schema.optional(
|
||||
Schema.Struct({
|
||||
modelID: ProviderV2.ModelID,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PermissionLegacy } from "@opencode-ai/core/permission/legacy"
|
||||
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
|
||||
import type { Permission } from "../permission"
|
||||
import type { Agent } from "./agent"
|
||||
|
||||
@@ -16,10 +16,10 @@ import type { Agent } from "./agent"
|
||||
* doesn't already permit them.
|
||||
*/
|
||||
export function deriveSubagentSessionPermission(input: {
|
||||
parentSessionPermission: PermissionLegacy.Ruleset
|
||||
parentSessionPermission: PermissionV1.Ruleset
|
||||
parentAgent: Agent.Info | undefined
|
||||
subagent: Agent.Info
|
||||
}): PermissionLegacy.Ruleset {
|
||||
}): PermissionV1.Ruleset {
|
||||
const canTask = input.subagent.permission.some((rule) => rule.permission === "task")
|
||||
const canTodo = input.subagent.permission.some((rule) => rule.permission === "todowrite")
|
||||
const parentAgentDenies =
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PermissionLegacy } from "@opencode-ai/core/permission/legacy"
|
||||
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
|
||||
import { EOL } from "os"
|
||||
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||
import { basename } from "path"
|
||||
import { Cause, Effect } from "effect"
|
||||
import { Agent } from "../../../agent/agent"
|
||||
@@ -150,7 +150,7 @@ const createToolContext = Effect.fn("Cli.debug.agent.createToolContext")(functio
|
||||
)
|
||||
})
|
||||
const now = Date.now()
|
||||
const message: SessionLegacy.Assistant = {
|
||||
const message: SessionV1.Assistant = {
|
||||
id: messageID,
|
||||
sessionID: session.id,
|
||||
role: "assistant",
|
||||
@@ -179,12 +179,12 @@ const createToolContext = Effect.fn("Cli.debug.agent.createToolContext")(functio
|
||||
abort: new AbortController().signal,
|
||||
messages: [],
|
||||
metadata: () => Effect.void,
|
||||
ask(req: Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">) {
|
||||
ask(req: Omit<PermissionV1.Request, "id" | "sessionID" | "tool">) {
|
||||
return Effect.sync(() => {
|
||||
for (const pattern of req.patterns) {
|
||||
const rule = Permission.evaluate(req.permission, pattern, ruleset)
|
||||
if (rule.action === "deny") {
|
||||
throw new PermissionLegacy.DeniedError({ ruleset })
|
||||
throw new PermissionV1.DeniedError({ ruleset })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Session } from "@/session/session"
|
||||
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||
import { MessageV2 } from "../../session/message-v2"
|
||||
import { SessionID } from "../../session/schema"
|
||||
import { effectCmd, fail } from "../effect-cmd"
|
||||
@@ -32,7 +32,7 @@ function diff(kind: string, diffs: { file?: string; patch?: string }[] | undefin
|
||||
}))
|
||||
}
|
||||
|
||||
function source(part: SessionLegacy.FilePart) {
|
||||
function source(part: SessionV1.FilePart) {
|
||||
if (!part.source) return part.source
|
||||
if (part.source.type === "symbol") {
|
||||
return {
|
||||
@@ -57,7 +57,7 @@ function source(part: SessionLegacy.FilePart) {
|
||||
}
|
||||
}
|
||||
|
||||
function filepart(part: SessionLegacy.FilePart): SessionLegacy.FilePart {
|
||||
function filepart(part: SessionV1.FilePart): SessionV1.FilePart {
|
||||
return {
|
||||
...part,
|
||||
url: redact("file-url", part.id, part.url),
|
||||
@@ -66,7 +66,7 @@ function filepart(part: SessionLegacy.FilePart): SessionLegacy.FilePart {
|
||||
}
|
||||
}
|
||||
|
||||
function part(part: SessionLegacy.Part): SessionLegacy.Part {
|
||||
function part(part: SessionV1.Part): SessionV1.Part {
|
||||
switch (part.type) {
|
||||
case "text":
|
||||
return {
|
||||
@@ -160,7 +160,7 @@ function part(part: SessionLegacy.Part): SessionLegacy.Part {
|
||||
|
||||
const partFn = part
|
||||
|
||||
function sanitize(data: { info: Session.Info; messages: SessionLegacy.WithParts[] }) {
|
||||
function sanitize(data: { info: Session.Info; messages: SessionV1.WithParts[] }) {
|
||||
return {
|
||||
info: {
|
||||
...data.info,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import type { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||
|
||||
export { parseGitHubRemote } from "@/util/repository"
|
||||
|
||||
@@ -7,7 +7,7 @@ export { parseGitHubRemote } from "@/util/repository"
|
||||
* Returns null for non-text responses (signals summary needed).
|
||||
* Throws only for truly empty responses.
|
||||
*/
|
||||
export function extractResponseText(parts: SessionLegacy.Part[]): string | null {
|
||||
export function extractResponseText(parts: SessionV1.Part[]): string | null {
|
||||
const textPart = parts.findLast((p) => p.type === "text")
|
||||
if (textPart) return textPart.text
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Session as SDKSession, Message, Part } from "@opencode-ai/sdk/v2"
|
||||
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||
import { Session } from "@/session/session"
|
||||
import { MessageV2 } from "../../session/message-v2"
|
||||
import { CliError, effectCmd } from "../effect-cmd"
|
||||
@@ -13,8 +13,8 @@ import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { Effect, Schema } from "effect"
|
||||
import type { InstanceContext } from "@/project/instance-context"
|
||||
|
||||
const decodeMessageInfo = Schema.decodeUnknownSync(SessionLegacy.Info)
|
||||
const decodePart = Schema.decodeUnknownSync(SessionLegacy.Part)
|
||||
const decodeMessageInfo = Schema.decodeUnknownSync(SessionV1.Info)
|
||||
const decodePart = Schema.decodeUnknownSync(SessionV1.Part)
|
||||
|
||||
/** Discriminated union returned by the ShareNext API (GET /api/shares/:id/data) */
|
||||
export type ShareData =
|
||||
@@ -188,7 +188,7 @@ const runImport = Effect.fn("Cli.import.body")(function* (file: string, ctx: Ins
|
||||
.pipe(Effect.orDie)
|
||||
|
||||
for (const msg of exportData.messages) {
|
||||
const msgInfo = decodeMessageInfo(msg.info) as SessionLegacy.Info
|
||||
const msgInfo = decodeMessageInfo(msg.info) as SessionV1.Info
|
||||
const { id, sessionID: _, ...msgData } = msgInfo
|
||||
yield* db
|
||||
.insert(MessageTable)
|
||||
@@ -203,7 +203,7 @@ const runImport = Effect.fn("Cli.import.body")(function* (file: string, ctx: Ins
|
||||
.pipe(Effect.orDie)
|
||||
|
||||
for (const part of msg.parts) {
|
||||
const partInfo = decodePart(part) as SessionLegacy.Part
|
||||
const partInfo = decodePart(part) as SessionV1.Part
|
||||
const { id: partId, sessionID: _s, messageID, ...partData } = partInfo
|
||||
yield* db
|
||||
.insert(PartTable)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { cmd } from "./cmd"
|
||||
import { ConfigV1 } from "@opencode-ai/core/v1/config/config"
|
||||
import { effectCmd } from "../effect-cmd"
|
||||
import { Cause } from "effect"
|
||||
import { Client } from "@modelcontextprotocol/sdk/client/index.js"
|
||||
@@ -10,7 +11,7 @@ import { MCP } from "../../mcp"
|
||||
import { McpAuth } from "../../mcp/auth"
|
||||
import { McpOAuthProvider } from "../../mcp/oauth-provider"
|
||||
import { Config } from "@/config/config"
|
||||
import { ConfigMCP } from "../../config/mcp"
|
||||
import { ConfigMCPV1 } from "@opencode-ai/core/v1/config/mcp"
|
||||
import { InstanceRef } from "@/effect/instance-ref"
|
||||
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||
import path from "path"
|
||||
@@ -43,9 +44,9 @@ function getAuthStatusText(status: MCP.AuthStatus): string {
|
||||
}
|
||||
}
|
||||
|
||||
type McpEntry = NonNullable<Config.Info["mcp"]>[string]
|
||||
type McpEntry = NonNullable<ConfigV1.Info["mcp"]>[string]
|
||||
|
||||
type McpConfigured = ConfigMCP.Info
|
||||
type McpConfigured = ConfigMCPV1.Info
|
||||
function isMcpConfigured(config: McpEntry): config is McpConfigured {
|
||||
return typeof config === "object" && config !== null && "type" in config
|
||||
}
|
||||
@@ -55,11 +56,11 @@ function isMcpRemote(config: McpEntry): config is McpRemote {
|
||||
return isMcpConfigured(config) && config.type === "remote"
|
||||
}
|
||||
|
||||
function configuredServers(config: Config.Info) {
|
||||
function configuredServers(config: ConfigV1.Info) {
|
||||
return Object.entries(config.mcp ?? {}).filter((entry): entry is [string, McpConfigured] => isMcpConfigured(entry[1]))
|
||||
}
|
||||
|
||||
function oauthServers(config: Config.Info) {
|
||||
function oauthServers(config: ConfigV1.Info) {
|
||||
return configuredServers(config).filter(
|
||||
(entry): entry is [string, McpRemote] => isMcpRemote(entry[1]) && entry[1].oauth !== false,
|
||||
)
|
||||
@@ -418,7 +419,7 @@ async function resolveConfigPath(baseDir: string, global = false) {
|
||||
return candidates[0]
|
||||
}
|
||||
|
||||
async function addMcpToConfig(name: string, mcpConfig: ConfigMCP.Info, configPath: string) {
|
||||
async function addMcpToConfig(name: string, mcpConfig: ConfigMCPV1.Info, configPath: string) {
|
||||
let text = "{}"
|
||||
if (await Filesystem.exists(configPath)) {
|
||||
text = await Filesystem.readText(configPath)
|
||||
@@ -507,7 +508,7 @@ export const McpAddCommand = effectCmd({
|
||||
})
|
||||
if (prompts.isCancel(command)) throw new UI.CancelledError()
|
||||
|
||||
const mcpConfig: ConfigMCP.Info = {
|
||||
const mcpConfig: ConfigMCPV1.Info = {
|
||||
type: "local",
|
||||
command: command.split(" "),
|
||||
}
|
||||
@@ -537,7 +538,7 @@ export const McpAddCommand = effectCmd({
|
||||
})
|
||||
if (prompts.isCancel(useOAuth)) throw new UI.CancelledError()
|
||||
|
||||
let mcpConfig: ConfigMCP.Info
|
||||
let mcpConfig: ConfigMCPV1.Info
|
||||
|
||||
if (useOAuth) {
|
||||
const hasClientId = await prompts.confirm({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { PermissionLegacy } from "@opencode-ai/core/permission/legacy"
|
||||
import type { PermissionV1 } from "@opencode-ai/core/v1/permission"
|
||||
// CLI entry point for `opencode run`.
|
||||
//
|
||||
// Handles three modes:
|
||||
@@ -362,7 +362,7 @@ export const RunCommand = effectCmd({
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const rules: PermissionLegacy.Ruleset = args.interactive
|
||||
const rules: PermissionV1.Ruleset = args.interactive
|
||||
? []
|
||||
: [
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ConfigPlugin } from "@/config/plugin"
|
||||
import { ConfigPluginV1 } from "@opencode-ai/core/v1/config/plugin"
|
||||
import { TuiKeybind } from "./keybind"
|
||||
import { Schema } from "effect"
|
||||
import { isRecord } from "@/util/record"
|
||||
@@ -74,7 +74,7 @@ export const TuiInfo = Schema.Struct({
|
||||
$schema: Schema.optional(Schema.String),
|
||||
theme: Schema.optional(Schema.String),
|
||||
keybinds: Schema.optional(TuiKeybind.KeybindOverrides),
|
||||
plugin: Schema.optional(Schema.Array(ConfigPlugin.Spec)),
|
||||
plugin: Schema.optional(Schema.Array(ConfigPluginV1.Spec)),
|
||||
plugin_enabled: Schema.optional(Schema.Record(Schema.String, Schema.Boolean)),
|
||||
leader_timeout: Schema.optional(KeymapLeaderTimeout),
|
||||
attention: Schema.optional(Attention),
|
||||
|
||||
@@ -29,7 +29,7 @@ import { useExit } from "./exit"
|
||||
import { useArgs } from "./args"
|
||||
import { batch, onMount } from "solid-js"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { emptyConsoleState, type ConsoleState } from "@/config/console-state"
|
||||
import { emptyConsoleState, type ConsoleState } from "@opencode-ai/core/v1/config/console-state"
|
||||
import path from "path"
|
||||
import { useKV } from "./kv"
|
||||
import { aggregateFailures } from "./aggregate-failures"
|
||||
|
||||
@@ -39,6 +39,7 @@ import { internalTuiPlugins, type InternalTuiPlugin } from "./internal"
|
||||
import { setupSlots, Slot as View } from "./slots"
|
||||
import type { HostPluginApi, HostSlots } from "./slots"
|
||||
import { ConfigPlugin } from "@/config/plugin"
|
||||
import { ConfigPluginV1 } from "@opencode-ai/core/v1/config/plugin"
|
||||
import { createCommandShim } from "./command-shim"
|
||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
import { Effect } from "effect"
|
||||
@@ -46,7 +47,7 @@ import { Effect } from "effect"
|
||||
ensureRuntimePluginSupport({ additional: keymapRuntimeModules })
|
||||
|
||||
type PluginLoad = {
|
||||
options: ConfigPlugin.Options | undefined
|
||||
options: ConfigPluginV1.Options | undefined
|
||||
spec: string
|
||||
target: string
|
||||
retry: boolean
|
||||
@@ -995,7 +996,7 @@ async function installPluginBySpec(
|
||||
const tui = manifest.targets.find((item) => item.kind === "tui")
|
||||
if (tui) {
|
||||
const file = patch.items.find((item) => item.kind === "tui")?.file
|
||||
const next = tui.opts ? ([spec, tui.opts] as ConfigPlugin.Spec) : spec
|
||||
const next = tui.opts ? ([spec, tui.opts] as ConfigPluginV1.Spec) : spec
|
||||
state.pending.set(spec, {
|
||||
spec: next,
|
||||
scope: global ? "global" : "local",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Argv, InferredOptionTypes } from "yargs"
|
||||
import { ConfigV1 } from "@opencode-ai/core/v1/config/config"
|
||||
import type { Config } from "@/config/config"
|
||||
import { Effect } from "effect"
|
||||
|
||||
@@ -42,7 +43,7 @@ export const resolveNetworkOptions = Effect.fn("Cli.resolveNetworkOptions")(func
|
||||
return resolveNetworkOptionsNoConfig(args, config)
|
||||
})
|
||||
|
||||
export function resolveNetworkOptionsNoConfig(args: NetworkOptions, config?: Config.Info) {
|
||||
export function resolveNetworkOptionsNoConfig(args: NetworkOptions, config?: ConfigV1.Info) {
|
||||
const portExplicitlySet = process.argv.includes("--port")
|
||||
const hostnameExplicitlySet = process.argv.includes("--hostname")
|
||||
const mdnsExplicitlySet = process.argv.includes("--mdns")
|
||||
|
||||
@@ -1,110 +1,18 @@
|
||||
export * as ConfigAgent from "./agent"
|
||||
|
||||
import path from "path"
|
||||
import { Exit, Schema, SchemaGetter } from "effect"
|
||||
import { PositiveInt } from "@opencode-ai/core/schema"
|
||||
import { Exit, Schema } from "effect"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { Glob } from "@opencode-ai/core/util/glob"
|
||||
import { ConfigAgentV1 } from "@opencode-ai/core/v1/config/agent"
|
||||
import { configEntryNameFromPath } from "./entry-name"
|
||||
import * as ConfigMarkdown from "./markdown"
|
||||
import { ConfigModelID } from "./model-id"
|
||||
import { ConfigParse } from "./parse"
|
||||
import { ConfigPermission } from "./permission"
|
||||
|
||||
const log = Log.create({ service: "config" })
|
||||
|
||||
const Color = Schema.Union([
|
||||
Schema.String.check(Schema.isPattern(/^#[0-9a-fA-F]{6}$/)),
|
||||
Schema.Literals(["primary", "secondary", "accent", "success", "warning", "error", "info"]),
|
||||
])
|
||||
|
||||
const AgentSchema = Schema.StructWithRest(
|
||||
Schema.Struct({
|
||||
model: Schema.optional(ConfigModelID),
|
||||
variant: Schema.optional(Schema.String).annotate({
|
||||
description: "Default model variant for this agent (applies only when using the agent's configured model).",
|
||||
}),
|
||||
temperature: Schema.optional(Schema.Finite),
|
||||
top_p: Schema.optional(Schema.Finite),
|
||||
prompt: Schema.optional(Schema.String),
|
||||
tools: Schema.optional(Schema.Record(Schema.String, Schema.Boolean)).annotate({
|
||||
description: "@deprecated Use 'permission' field instead",
|
||||
}),
|
||||
disable: Schema.optional(Schema.Boolean),
|
||||
description: Schema.optional(Schema.String).annotate({ description: "Description of when to use the agent" }),
|
||||
mode: Schema.optional(Schema.Literals(["subagent", "primary", "all"])),
|
||||
hidden: Schema.optional(Schema.Boolean).annotate({
|
||||
description: "Hide this subagent from the @ autocomplete menu (default: false, only applies to mode: subagent)",
|
||||
}),
|
||||
options: Schema.optional(Schema.Record(Schema.String, Schema.Any)),
|
||||
color: Schema.optional(Color).annotate({
|
||||
description: "Hex color code (e.g., #FF5733) or theme color (e.g., primary)",
|
||||
}),
|
||||
steps: Schema.optional(PositiveInt).annotate({
|
||||
description: "Maximum number of agentic iterations before forcing text-only response",
|
||||
}),
|
||||
maxSteps: Schema.optional(PositiveInt).annotate({ description: "@deprecated Use 'steps' field instead." }),
|
||||
permission: Schema.optional(ConfigPermission.Info),
|
||||
}),
|
||||
[Schema.Record(Schema.String, Schema.Any)],
|
||||
)
|
||||
|
||||
const KNOWN_KEYS = new Set([
|
||||
"name",
|
||||
"model",
|
||||
"variant",
|
||||
"prompt",
|
||||
"description",
|
||||
"temperature",
|
||||
"top_p",
|
||||
"mode",
|
||||
"hidden",
|
||||
"color",
|
||||
"steps",
|
||||
"maxSteps",
|
||||
"options",
|
||||
"permission",
|
||||
"disable",
|
||||
"tools",
|
||||
])
|
||||
|
||||
// Post-parse normalisation:
|
||||
// - Promote any unknown-but-present keys into `options` so they survive the
|
||||
// round-trip in a well-known field.
|
||||
// - Translate the deprecated `tools: { name: boolean }` map into the new
|
||||
// `permission` shape (write-adjacent tools collapse into `permission.edit`).
|
||||
// - Coalesce `steps ?? maxSteps` so downstream can ignore the deprecated alias.
|
||||
const normalize = (agent: Schema.Schema.Type<typeof AgentSchema>): Schema.Schema.Type<typeof AgentSchema> => {
|
||||
const options: Record<string, unknown> = { ...agent.options }
|
||||
for (const [key, value] of Object.entries(agent)) {
|
||||
if (!KNOWN_KEYS.has(key)) options[key] = value
|
||||
}
|
||||
|
||||
const permission: ConfigPermission.Info = {}
|
||||
for (const [tool, enabled] of Object.entries(agent.tools ?? {})) {
|
||||
const action = enabled ? "allow" : "deny"
|
||||
if (tool === "write" || tool === "edit" || tool === "patch") {
|
||||
permission.edit = action
|
||||
continue
|
||||
}
|
||||
permission[tool] = action
|
||||
}
|
||||
globalThis.Object.assign(permission, agent.permission)
|
||||
|
||||
const steps = agent.steps ?? agent.maxSteps
|
||||
return { ...agent, options, permission, ...(steps !== undefined ? { steps } : {}) }
|
||||
}
|
||||
|
||||
export const Info = AgentSchema.pipe(
|
||||
Schema.decodeTo(AgentSchema, {
|
||||
decode: SchemaGetter.transform(normalize),
|
||||
encode: SchemaGetter.passthrough({ strict: false }),
|
||||
}),
|
||||
).annotate({ identifier: "AgentConfig" })
|
||||
export type Info = Schema.Schema.Type<typeof Info>
|
||||
|
||||
export async function load(dir: string) {
|
||||
const result: Record<string, Info> = {}
|
||||
const result: Record<string, ConfigAgentV1.Info> = {}
|
||||
for (const item of await Glob.scan("{agent,agents}/**/*.md", {
|
||||
cwd: dir,
|
||||
absolute: true,
|
||||
@@ -124,13 +32,13 @@ export async function load(dir: string) {
|
||||
...md.data,
|
||||
prompt: md.content.trim(),
|
||||
}
|
||||
result[config.name] = ConfigParse.schema(Info, config, item)
|
||||
result[config.name] = ConfigParse.schema(ConfigAgentV1.Info, config, item)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export async function loadMode(dir: string) {
|
||||
const result: Record<string, Info> = {}
|
||||
const result: Record<string, ConfigAgentV1.Info> = {}
|
||||
for (const item of await Glob.scan("{mode,modes}/*.md", {
|
||||
cwd: dir,
|
||||
absolute: true,
|
||||
@@ -148,7 +56,7 @@ export async function loadMode(dir: string) {
|
||||
...md.data,
|
||||
prompt: md.content.trim(),
|
||||
}
|
||||
const parsed = Schema.decodeUnknownExit(Info)(config, { errors: "all", propertyOrder: "original" })
|
||||
const parsed = Schema.decodeUnknownExit(ConfigAgentV1.Info)(config, { errors: "all", propertyOrder: "original" })
|
||||
if (Exit.isSuccess(parsed)) {
|
||||
result[config.name] = {
|
||||
...parsed.value,
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
export * as ConfigAttachment from "./attachment"
|
||||
|
||||
import { Schema } from "effect"
|
||||
import { PositiveInt } from "@opencode-ai/core/schema"
|
||||
|
||||
export const Image = Schema.Struct({
|
||||
auto_resize: Schema.optional(Schema.Boolean).annotate({
|
||||
description: "Resize images before sending them to the model when they exceed configured limits (default: true)",
|
||||
}),
|
||||
max_width: Schema.optional(PositiveInt).annotate({
|
||||
description: "Maximum image width before resizing or rejecting the attachment (default: 2000)",
|
||||
}),
|
||||
max_height: Schema.optional(PositiveInt).annotate({
|
||||
description: "Maximum image height before resizing or rejecting the attachment (default: 2000)",
|
||||
}),
|
||||
max_base64_bytes: Schema.optional(PositiveInt).annotate({
|
||||
description: "Maximum base64 payload bytes for an image attachment (default: 5242880)",
|
||||
}),
|
||||
}).annotate({ identifier: "ImageAttachmentConfig" })
|
||||
export type Image = Schema.Schema.Type<typeof Image>
|
||||
|
||||
export const Info = Schema.Struct({
|
||||
image: Schema.optional(Image).annotate({ description: "Image attachment configuration" }),
|
||||
}).annotate({ identifier: "AttachmentConfig" })
|
||||
export type Info = Schema.Schema.Type<typeof Info>
|
||||
@@ -4,27 +4,17 @@ import path from "path"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { Cause, Exit, Schema } from "effect"
|
||||
import { Glob } from "@opencode-ai/core/util/glob"
|
||||
import { ConfigCommandV1 } from "@opencode-ai/core/v1/config/command"
|
||||
import { configEntryNameFromPath } from "./entry-name"
|
||||
import { InvalidError } from "./error"
|
||||
import { InvalidError } from "@opencode-ai/core/v1/config/error"
|
||||
import * as ConfigMarkdown from "./markdown"
|
||||
import { ConfigModelID } from "./model-id"
|
||||
|
||||
const log = Log.create({ service: "config" })
|
||||
|
||||
export const Info = Schema.Struct({
|
||||
template: Schema.String,
|
||||
description: Schema.optional(Schema.String),
|
||||
agent: Schema.optional(Schema.String),
|
||||
model: Schema.optional(ConfigModelID),
|
||||
subtask: Schema.optional(Schema.Boolean),
|
||||
})
|
||||
|
||||
export type Info = Schema.Schema.Type<typeof Info>
|
||||
|
||||
const decodeInfo = Schema.decodeUnknownExit(Info)
|
||||
const decodeInfo = Schema.decodeUnknownExit(ConfigCommandV1.Info)
|
||||
|
||||
export async function load(dir: string) {
|
||||
const result: Record<string, Info> = {}
|
||||
const result: Record<string, ConfigCommandV1.Info> = {}
|
||||
for (const item of await Glob.scan("{command,commands}/**/*.md", {
|
||||
cwd: dir,
|
||||
absolute: true,
|
||||
|
||||
@@ -6,7 +6,6 @@ import os from "os"
|
||||
import { mergeDeep } from "remeda"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import fsNode from "fs/promises"
|
||||
import { NamedError } from "@opencode-ai/core/util/error"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { Auth } from "../auth"
|
||||
import { Env } from "../env"
|
||||
@@ -15,35 +14,25 @@ import { InstallationLocal, InstallationVersion } from "@opencode-ai/core/instal
|
||||
import { existsSync } from "fs"
|
||||
import { Account } from "@/account/account"
|
||||
import { isRecord } from "@/util/record"
|
||||
import type { ConsoleState } from "./console-state"
|
||||
import type { ConsoleState } from "@opencode-ai/core/v1/config/console-state"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import { Context, Duration, Effect, Exit, Fiber, Layer, Option, Schema } from "effect"
|
||||
import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
|
||||
import { EffectFlock } from "@opencode-ai/core/util/effect-flock"
|
||||
import { containsPath, type InstanceContext } from "../project/instance-context"
|
||||
import { NonNegativeInt, PositiveInt, type DeepMutable } from "@opencode-ai/core/schema"
|
||||
import { ConfigV1 } from "@opencode-ai/core/v1/config/config"
|
||||
import { ConfigPermissionV1 } from "@opencode-ai/core/v1/config/permission"
|
||||
import { ConfigPluginV1 } from "@opencode-ai/core/v1/config/plugin"
|
||||
import { ConfigAgent } from "./agent"
|
||||
import { ConfigAttachment } from "./attachment"
|
||||
import { ConfigCommand } from "./command"
|
||||
import { ConfigFormatter } from "./formatter"
|
||||
import { ConfigLayout } from "./layout"
|
||||
import { ConfigLSP } from "./lsp"
|
||||
import { ConfigManaged } from "./managed"
|
||||
import { ConfigMCP } from "./mcp"
|
||||
import { ConfigModelID } from "./model-id"
|
||||
import { ConfigParse } from "./parse"
|
||||
import { ConfigPaths } from "./paths"
|
||||
import { ConfigPermission } from "./permission"
|
||||
import { ConfigPlugin } from "./plugin"
|
||||
import { ConfigProvider } from "./provider"
|
||||
import { ConfigReference } from "./reference"
|
||||
import { ConfigServer } from "./server"
|
||||
import { ConfigSkills } from "./skills"
|
||||
import { ConfigVariable } from "./variable"
|
||||
import { Npm } from "@opencode-ai/core/npm"
|
||||
import { withTransientReadRetry } from "@/util/effect-http-client"
|
||||
import { ConfigExperimental } from "@opencode-ai/core/config/experimental"
|
||||
|
||||
const log = Log.create({ service: "config" })
|
||||
|
||||
@@ -110,12 +99,7 @@ async function substituteWellKnownRemoteConfig(input: {
|
||||
return { url, headers }
|
||||
}
|
||||
|
||||
const WellKnownConfig = Schema.Struct({
|
||||
config: Schema.optional(Schema.Json),
|
||||
remote_config: Schema.optional(Schema.Json),
|
||||
})
|
||||
|
||||
async function resolveLoadedPlugins<T extends { plugin?: ConfigPlugin.Spec[] }>(config: T, filepath: string) {
|
||||
async function resolveLoadedPlugins<T extends { plugin?: ConfigPluginV1.Spec[] }>(config: T, filepath: string) {
|
||||
if (!config.plugin) return config
|
||||
for (let i = 0; i < config.plugin.length; i++) {
|
||||
// Normalize path-like plugin specs while we still know which config file declared them.
|
||||
@@ -125,193 +109,7 @@ async function resolveLoadedPlugins<T extends { plugin?: ConfigPlugin.Spec[] }>(
|
||||
return config
|
||||
}
|
||||
|
||||
export type Layout = ConfigLayout.Layout
|
||||
|
||||
const LogLevelRef = Schema.Literals(["DEBUG", "INFO", "WARN", "ERROR"]).annotate({
|
||||
identifier: "LogLevel",
|
||||
description: "Log level",
|
||||
})
|
||||
|
||||
export const Info = Schema.Struct({
|
||||
$schema: Schema.optional(Schema.String).annotate({
|
||||
description: "JSON schema reference for configuration validation",
|
||||
}),
|
||||
shell: Schema.optional(Schema.String).annotate({
|
||||
description: "Default shell to use for terminal and bash tool",
|
||||
}),
|
||||
logLevel: Schema.optional(LogLevelRef).annotate({ description: "Log level" }),
|
||||
server: Schema.optional(ConfigServer.Server).annotate({
|
||||
description: "Server configuration for opencode serve and web commands",
|
||||
}),
|
||||
command: Schema.optional(Schema.Record(Schema.String, ConfigCommand.Info)).annotate({
|
||||
description: "Command configuration, see https://opencode.ai/docs/commands",
|
||||
}),
|
||||
skills: Schema.optional(ConfigSkills.Info).annotate({ description: "Additional skill folder paths" }),
|
||||
reference: Schema.optional(ConfigReference.Info).annotate({
|
||||
description: "Named git or local directory references that can be mentioned as @alias or @alias/path",
|
||||
}),
|
||||
watcher: Schema.optional(
|
||||
Schema.Struct({
|
||||
ignore: Schema.optional(Schema.mutable(Schema.Array(Schema.String))),
|
||||
}),
|
||||
),
|
||||
snapshot: Schema.optional(Schema.Boolean).annotate({
|
||||
description:
|
||||
"Enable or disable snapshot tracking. When false, filesystem snapshots are not recorded and undoing or reverting will not undo/redo file changes. Defaults to true.",
|
||||
}),
|
||||
// User-facing plugin config is stored as Specs; provenance gets attached later while configs are merged.
|
||||
plugin: Schema.optional(Schema.mutable(Schema.Array(ConfigPlugin.Spec))),
|
||||
share: Schema.optional(Schema.Literals(["manual", "auto", "disabled"])).annotate({
|
||||
description:
|
||||
"Control sharing behavior:'manual' allows manual sharing via commands, 'auto' enables automatic sharing, 'disabled' disables all sharing",
|
||||
}),
|
||||
autoshare: Schema.optional(Schema.Boolean).annotate({
|
||||
description: "@deprecated Use 'share' field instead. Share newly created sessions automatically",
|
||||
}),
|
||||
autoupdate: Schema.optional(Schema.Union([Schema.Boolean, Schema.Literal("notify")])).annotate({
|
||||
description:
|
||||
"Automatically update to the latest version. Set to true to auto-update, false to disable, or 'notify' to show update notifications",
|
||||
}),
|
||||
disabled_providers: Schema.optional(Schema.mutable(Schema.Array(Schema.String))).annotate({
|
||||
description: "Disable providers that are loaded automatically",
|
||||
}),
|
||||
enabled_providers: Schema.optional(Schema.mutable(Schema.Array(Schema.String))).annotate({
|
||||
description: "When set, ONLY these providers will be enabled. All other providers will be ignored",
|
||||
}),
|
||||
model: Schema.optional(ConfigModelID).annotate({
|
||||
description: "Model to use in the format of provider/model, eg anthropic/claude-2",
|
||||
}),
|
||||
small_model: Schema.optional(ConfigModelID).annotate({
|
||||
description: "Small model to use for tasks like title generation in the format of provider/model",
|
||||
}),
|
||||
default_agent: Schema.optional(Schema.String).annotate({
|
||||
description:
|
||||
"Default agent to use when none is specified. Must be a primary agent. Falls back to 'build' if not set or if the specified agent is invalid.",
|
||||
}),
|
||||
username: Schema.optional(Schema.String).annotate({
|
||||
description: "Custom username to display in conversations instead of system username",
|
||||
}),
|
||||
mode: Schema.optional(
|
||||
Schema.StructWithRest(
|
||||
Schema.Struct({
|
||||
build: Schema.optional(ConfigAgent.Info),
|
||||
plan: Schema.optional(ConfigAgent.Info),
|
||||
}),
|
||||
[Schema.Record(Schema.String, ConfigAgent.Info)],
|
||||
),
|
||||
).annotate({ description: "@deprecated Use `agent` field instead." }),
|
||||
agent: Schema.optional(
|
||||
Schema.StructWithRest(
|
||||
Schema.Struct({
|
||||
// primary
|
||||
plan: Schema.optional(ConfigAgent.Info),
|
||||
build: Schema.optional(ConfigAgent.Info),
|
||||
// subagent
|
||||
general: Schema.optional(ConfigAgent.Info),
|
||||
explore: Schema.optional(ConfigAgent.Info),
|
||||
// specialized
|
||||
title: Schema.optional(ConfigAgent.Info),
|
||||
summary: Schema.optional(ConfigAgent.Info),
|
||||
compaction: Schema.optional(ConfigAgent.Info),
|
||||
}),
|
||||
[Schema.Record(Schema.String, ConfigAgent.Info)],
|
||||
),
|
||||
).annotate({ description: "Agent configuration, see https://opencode.ai/docs/agents" }),
|
||||
provider: Schema.optional(Schema.Record(Schema.String, ConfigProvider.Info)).annotate({
|
||||
description: "Custom provider configurations and model overrides",
|
||||
}),
|
||||
mcp: Schema.optional(
|
||||
Schema.Record(
|
||||
Schema.String,
|
||||
Schema.Union([
|
||||
ConfigMCP.Info,
|
||||
// Matches the legacy `{ enabled: false }` form used to disable a server.
|
||||
Schema.Struct({ enabled: Schema.Boolean }),
|
||||
]),
|
||||
),
|
||||
).annotate({ description: "MCP (Model Context Protocol) server configurations" }),
|
||||
formatter: Schema.optional(ConfigFormatter.Info).annotate({
|
||||
description:
|
||||
"Enable or configure formatters. Omit or set to false to disable, true to enable built-ins, or an object to enable built-ins with overrides.",
|
||||
}),
|
||||
lsp: Schema.optional(ConfigLSP.Info).annotate({
|
||||
description:
|
||||
"Enable or configure LSP servers. Omit or set to false to disable, true to enable built-ins, or an object to enable built-ins with overrides.",
|
||||
}),
|
||||
instructions: Schema.optional(Schema.mutable(Schema.Array(Schema.String))).annotate({
|
||||
description: "Additional instruction files or patterns to include",
|
||||
}),
|
||||
layout: Schema.optional(ConfigLayout.Layout).annotate({ description: "@deprecated Always uses stretch layout." }),
|
||||
permission: Schema.optional(ConfigPermission.Info),
|
||||
tools: Schema.optional(Schema.Record(Schema.String, Schema.Boolean)),
|
||||
attachment: Schema.optional(ConfigAttachment.Info).annotate({
|
||||
description: "Attachment processing configuration, including image size limits and resizing behavior",
|
||||
}),
|
||||
enterprise: Schema.optional(
|
||||
Schema.Struct({
|
||||
url: Schema.optional(Schema.String).annotate({ description: "Enterprise URL" }),
|
||||
}),
|
||||
),
|
||||
tool_output: Schema.optional(
|
||||
Schema.Struct({
|
||||
max_lines: Schema.optional(PositiveInt).annotate({
|
||||
description: "Maximum lines of tool output before it is truncated and saved to disk (default: 2000)",
|
||||
}),
|
||||
max_bytes: Schema.optional(PositiveInt).annotate({
|
||||
description: "Maximum bytes of tool output before it is truncated and saved to disk (default: 51200)",
|
||||
}),
|
||||
}),
|
||||
).annotate({
|
||||
description:
|
||||
"Thresholds for truncating tool output. When output exceeds either limit, the full text is written to the truncation directory and a preview is returned.",
|
||||
}),
|
||||
compaction: Schema.optional(
|
||||
Schema.Struct({
|
||||
auto: Schema.optional(Schema.Boolean).annotate({
|
||||
description: "Enable automatic compaction when context is full (default: true)",
|
||||
}),
|
||||
prune: Schema.optional(Schema.Boolean).annotate({
|
||||
description: "Enable pruning of old tool outputs (default: true)",
|
||||
}),
|
||||
tail_turns: Schema.optional(NonNegativeInt).annotate({
|
||||
description:
|
||||
"Number of recent user turns, including their following assistant/tool responses, to keep verbatim during compaction (default: 2)",
|
||||
}),
|
||||
preserve_recent_tokens: Schema.optional(NonNegativeInt).annotate({
|
||||
description: "Maximum number of tokens from recent turns to preserve verbatim after compaction",
|
||||
}),
|
||||
reserved: Schema.optional(NonNegativeInt).annotate({
|
||||
description: "Token buffer for compaction. Leaves enough window to avoid overflow during compaction.",
|
||||
}),
|
||||
}),
|
||||
),
|
||||
experimental: Schema.optional(
|
||||
Schema.Struct({
|
||||
disable_paste_summary: Schema.optional(Schema.Boolean),
|
||||
batch_tool: Schema.optional(Schema.Boolean).annotate({ description: "Enable the batch tool" }),
|
||||
openTelemetry: Schema.optional(Schema.Boolean).annotate({
|
||||
description: "Enable OpenTelemetry spans for AI SDK calls (using the 'experimental_telemetry' flag)",
|
||||
}),
|
||||
primary_tools: Schema.optional(Schema.mutable(Schema.Array(Schema.String))).annotate({
|
||||
description: "Tools that should only be available to primary agents.",
|
||||
}),
|
||||
continue_loop_on_deny: Schema.optional(Schema.Boolean).annotate({
|
||||
description: "Continue the agent loop when a tool call is denied",
|
||||
}),
|
||||
mcp_timeout: Schema.optional(PositiveInt).annotate({
|
||||
description: "Timeout in milliseconds for model context protocol (MCP) requests",
|
||||
}),
|
||||
policies: Schema.optional(Schema.mutable(Schema.Array(ConfigExperimental.Policy))).annotate({
|
||||
description: "Policy statements applied to supported resources, such as provider access",
|
||||
}),
|
||||
}),
|
||||
),
|
||||
}).annotate({ identifier: "Config" })
|
||||
|
||||
// Uses the shared `DeepMutable` from `@opencode-ai/core/schema`. See the definition
|
||||
// there for why the local variant is needed over `Types.DeepMutable` from
|
||||
// effect-smol (the upstream version collapses `unknown` to `{}`).
|
||||
export type Info = DeepMutable<Schema.Schema.Type<typeof Info>> & {
|
||||
type Info = ConfigV1.Info & {
|
||||
// plugin_origins is derived state, not a persisted config field. It keeps each winning plugin spec together
|
||||
// with the file and scope it came from so later runtime code can make location-sensitive decisions.
|
||||
plugin_origins?: ConfigPlugin.Origin[]
|
||||
@@ -375,12 +173,6 @@ function writableGlobal(info: Info) {
|
||||
return next
|
||||
}
|
||||
|
||||
export const ConfigDirectoryTypoError = NamedError.create("ConfigDirectoryTypoError", {
|
||||
path: Schema.String,
|
||||
dir: Schema.String,
|
||||
suggestion: Schema.String,
|
||||
})
|
||||
|
||||
export const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
@@ -424,7 +216,7 @@ export const layer = Layer.effect(
|
||||
),
|
||||
)
|
||||
const parsed = ConfigParse.jsonc(expanded, source)
|
||||
const data = ConfigParse.schema(Info, normalizeLoadedConfig(parsed, source), source)
|
||||
const data = ConfigParse.schema(ConfigV1.Info, normalizeLoadedConfig(parsed, source), source)
|
||||
if (!("path" in options)) return data
|
||||
|
||||
yield* Effect.promise(() => resolveLoadedPlugins(data, options.path))
|
||||
@@ -530,7 +322,7 @@ export const layer = Layer.effect(
|
||||
source: string,
|
||||
// mergePluginOrigins receives raw Specs from one config source, before provenance for this merge step
|
||||
// is attached.
|
||||
list: ConfigPlugin.Spec[] | undefined,
|
||||
list: ConfigPluginV1.Spec[] | undefined,
|
||||
// Scope can be inferred from the source path, but some callers already know whether the config should
|
||||
// behave as global or local and can pass that explicitly.
|
||||
kind?: ConfigPlugin.Scope,
|
||||
@@ -558,7 +350,7 @@ export const layer = Layer.effect(
|
||||
authEnv[value.key] = value.token
|
||||
const wellknownURL = `${url}/.well-known/opencode`
|
||||
log.debug("fetching remote config", { url: wellknownURL })
|
||||
const wellknown = yield* fetchRemoteJson(wellknownURL, undefined, WellKnownConfig)
|
||||
const wellknown = yield* fetchRemoteJson(wellknownURL, undefined, ConfigV1.WellKnown)
|
||||
const remote = yield* Effect.promise(() =>
|
||||
substituteWellKnownRemoteConfig({
|
||||
value: wellknown.remote_config,
|
||||
@@ -753,9 +545,9 @@ export const layer = Layer.effect(
|
||||
}
|
||||
|
||||
if (result.tools) {
|
||||
const perms: Record<string, ConfigPermission.Action> = {}
|
||||
const perms: Record<string, ConfigPermissionV1.Action> = {}
|
||||
for (const [tool, enabled] of Object.entries(result.tools)) {
|
||||
const action: ConfigPermission.Action = enabled ? "allow" : "deny"
|
||||
const action: ConfigPermissionV1.Action = enabled ? "allow" : "deny"
|
||||
if (tool === "write" || tool === "edit" || tool === "patch") {
|
||||
perms.edit = action
|
||||
continue
|
||||
@@ -844,7 +636,7 @@ export const layer = Layer.effect(
|
||||
let next: Info
|
||||
let changed: boolean
|
||||
if (!file.endsWith(".jsonc")) {
|
||||
const existing = ConfigParse.schema(Info, ConfigParse.jsonc(before, file), file)
|
||||
const existing = ConfigParse.schema(ConfigV1.Info, ConfigParse.jsonc(before, file), file)
|
||||
const merged = mergeDeep(writable(existing), patch)
|
||||
const serialized = JSON.stringify(merged, null, 2)
|
||||
changed = serialized !== before
|
||||
@@ -852,7 +644,7 @@ export const layer = Layer.effect(
|
||||
next = merged
|
||||
} else {
|
||||
const updated = patchJsonc(before, patch)
|
||||
next = ConfigParse.schema(Info, ConfigParse.jsonc(updated, file), file)
|
||||
next = ConfigParse.schema(ConfigV1.Info, ConfigParse.jsonc(updated, file), file)
|
||||
changed = updated !== before
|
||||
if (changed) yield* fs.writeFileString(file, updated).pipe(Effect.orDie)
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { Schema } from "effect"
|
||||
import { NonNegativeInt } from "@opencode-ai/core/schema"
|
||||
|
||||
export class ConsoleState extends Schema.Class<ConsoleState>("ConsoleState")({
|
||||
consoleManagedProviders: Schema.mutable(Schema.Array(Schema.String)),
|
||||
activeOrgName: Schema.optional(Schema.String),
|
||||
switchableOrgCount: NonNegativeInt,
|
||||
}) {}
|
||||
|
||||
export const emptyConsoleState: ConsoleState = ConsoleState.make({
|
||||
consoleManagedProviders: [],
|
||||
activeOrgName: undefined,
|
||||
switchableOrgCount: 0,
|
||||
})
|
||||
@@ -1,23 +0,0 @@
|
||||
export * as ConfigError from "./error"
|
||||
|
||||
import { NamedError } from "@opencode-ai/core/util/error"
|
||||
import { Schema } from "effect"
|
||||
|
||||
const Issue = Schema.StructWithRest(
|
||||
Schema.Struct({
|
||||
message: Schema.String,
|
||||
path: Schema.Array(Schema.String),
|
||||
}),
|
||||
[Schema.Record(Schema.String, Schema.Unknown)],
|
||||
)
|
||||
|
||||
export const JsonError = NamedError.create("ConfigJsonError", {
|
||||
path: Schema.String,
|
||||
message: Schema.optional(Schema.String),
|
||||
})
|
||||
|
||||
export const InvalidError = NamedError.create("ConfigInvalidError", {
|
||||
path: Schema.String,
|
||||
issues: Schema.optional(Schema.Array(Issue)),
|
||||
message: Schema.optional(Schema.String),
|
||||
})
|
||||
@@ -1,13 +0,0 @@
|
||||
export * as ConfigFormatter from "./formatter"
|
||||
|
||||
import { Schema } from "effect"
|
||||
|
||||
export const Entry = Schema.Struct({
|
||||
disabled: Schema.optional(Schema.Boolean),
|
||||
command: Schema.optional(Schema.mutable(Schema.Array(Schema.String))),
|
||||
environment: Schema.optional(Schema.Record(Schema.String, Schema.String)),
|
||||
extensions: Schema.optional(Schema.mutable(Schema.Array(Schema.String))),
|
||||
})
|
||||
|
||||
export const Info = Schema.Union([Schema.Boolean, Schema.Record(Schema.String, Entry)])
|
||||
export type Info = Schema.Schema.Type<typeof Info>
|
||||
@@ -1,6 +0,0 @@
|
||||
import { Schema } from "effect"
|
||||
|
||||
export const Layout = Schema.Literals(["auto", "stretch"]).annotate({ identifier: "LayoutConfig" })
|
||||
export type Layout = Schema.Schema.Type<typeof Layout>
|
||||
|
||||
export * as ConfigLayout from "./layout"
|
||||
@@ -1,43 +0,0 @@
|
||||
export * as ConfigLSP from "./lsp"
|
||||
|
||||
import { Schema } from "effect"
|
||||
import * as LSPServer from "../lsp/server"
|
||||
|
||||
export const Disabled = Schema.Struct({
|
||||
disabled: Schema.Literal(true),
|
||||
}).pipe((schema) => schema)
|
||||
|
||||
export const Entry = Schema.Union([
|
||||
Disabled,
|
||||
Schema.Struct({
|
||||
command: Schema.mutable(Schema.Array(Schema.String)),
|
||||
extensions: Schema.optional(Schema.mutable(Schema.Array(Schema.String))),
|
||||
disabled: Schema.optional(Schema.Boolean),
|
||||
env: Schema.optional(Schema.Record(Schema.String, Schema.String)),
|
||||
initialization: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
|
||||
}),
|
||||
]).pipe((schema) => schema)
|
||||
|
||||
/**
|
||||
* For custom (non-builtin) LSP server entries, `extensions` is required so the
|
||||
* client knows which files the server should attach to. Builtin server IDs and
|
||||
* explicitly disabled entries are exempt.
|
||||
*/
|
||||
export const requiresExtensionsForCustomServers = Schema.makeFilter<
|
||||
boolean | Record<string, Schema.Schema.Type<typeof Entry>>
|
||||
>((data) => {
|
||||
if (typeof data === "boolean") return undefined
|
||||
const serverIds = new Set(Object.values(LSPServer).map((server) => server.id))
|
||||
const ok = Object.entries(data).every(([id, config]) => {
|
||||
if ("disabled" in config && config.disabled) return true
|
||||
if (serverIds.has(id)) return true
|
||||
return "extensions" in config && Boolean(config.extensions)
|
||||
})
|
||||
return ok ? undefined : "For custom LSP servers, 'extensions' array is required."
|
||||
})
|
||||
|
||||
export const Info = Schema.Union([Schema.Boolean, Schema.Record(Schema.String, Entry)])
|
||||
.check(requiresExtensionsForCustomServers)
|
||||
.pipe((schema) => schema)
|
||||
|
||||
export type Info = Schema.Schema.Type<typeof Info>
|
||||
@@ -1,7 +1,6 @@
|
||||
import { NamedError } from "@opencode-ai/core/util/error"
|
||||
import matter from "gray-matter"
|
||||
import { Schema } from "effect"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import { FrontmatterError } from "@opencode-ai/core/v1/config/error"
|
||||
|
||||
export const FILE_REGEX = /(?<![\w`])@(\.?[^\s`,.]*(?:\.[^\s`,.]+)*)/g
|
||||
export const SHELL_REGEX = /!`([^`]+)`/g
|
||||
@@ -88,9 +87,4 @@ export async function parse(filePath: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export const FrontmatterError = NamedError.create("ConfigFrontmatterError", {
|
||||
path: Schema.String,
|
||||
message: Schema.String,
|
||||
})
|
||||
|
||||
export * as ConfigMarkdown from "./markdown"
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import { Schema } from "effect"
|
||||
import { PositiveInt } from "@opencode-ai/core/schema"
|
||||
|
||||
export const Local = Schema.Struct({
|
||||
type: Schema.Literal("local").annotate({ description: "Type of MCP server connection" }),
|
||||
command: Schema.mutable(Schema.Array(Schema.String)).annotate({
|
||||
description: "Command and arguments to run the MCP server",
|
||||
}),
|
||||
environment: Schema.optional(Schema.Record(Schema.String, Schema.String)).annotate({
|
||||
description: "Environment variables to set when running the MCP server",
|
||||
}),
|
||||
enabled: Schema.optional(Schema.Boolean).annotate({
|
||||
description: "Enable or disable the MCP server on startup",
|
||||
}),
|
||||
timeout: Schema.optional(PositiveInt).annotate({
|
||||
description: "Timeout in ms for MCP server requests. Defaults to 5000 (5 seconds) if not specified.",
|
||||
}),
|
||||
}).annotate({ identifier: "McpLocalConfig" })
|
||||
export type Local = Schema.Schema.Type<typeof Local>
|
||||
|
||||
export const OAuth = Schema.Struct({
|
||||
clientId: Schema.optional(Schema.String).annotate({
|
||||
description: "OAuth client ID. If not provided, dynamic client registration (RFC 7591) will be attempted.",
|
||||
}),
|
||||
clientSecret: Schema.optional(Schema.String).annotate({
|
||||
description: "OAuth client secret (if required by the authorization server)",
|
||||
}),
|
||||
scope: Schema.optional(Schema.String).annotate({ description: "OAuth scopes to request during authorization" }),
|
||||
callbackPort: Schema.optional(Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 65535 }))).annotate({
|
||||
description:
|
||||
"Port for the local OAuth callback server (default: 19876). Shorthand for redirectUri when only the port needs changing. Ignored if redirectUri is set.",
|
||||
}),
|
||||
redirectUri: Schema.optional(Schema.String).annotate({
|
||||
description: "OAuth redirect URI (default: http://127.0.0.1:19876/mcp/oauth/callback).",
|
||||
}),
|
||||
}).annotate({ identifier: "McpOAuthConfig" })
|
||||
export type OAuth = Schema.Schema.Type<typeof OAuth>
|
||||
|
||||
export const Remote = Schema.Struct({
|
||||
type: Schema.Literal("remote").annotate({ description: "Type of MCP server connection" }),
|
||||
url: Schema.String.annotate({ description: "URL of the remote MCP server" }),
|
||||
enabled: Schema.optional(Schema.Boolean).annotate({
|
||||
description: "Enable or disable the MCP server on startup",
|
||||
}),
|
||||
headers: Schema.optional(Schema.Record(Schema.String, Schema.String)).annotate({
|
||||
description: "Headers to send with the request",
|
||||
}),
|
||||
oauth: Schema.optional(Schema.Union([OAuth, Schema.Literal(false)])).annotate({
|
||||
description: "OAuth authentication configuration for the MCP server. Set to false to disable OAuth auto-detection.",
|
||||
}),
|
||||
timeout: Schema.optional(PositiveInt).annotate({
|
||||
description: "Timeout in ms for MCP server requests. Defaults to 5000 (5 seconds) if not specified.",
|
||||
}),
|
||||
}).annotate({ identifier: "McpRemoteConfig" })
|
||||
export type Remote = Schema.Schema.Type<typeof Remote>
|
||||
|
||||
export const Info = Schema.Union([Local, Remote]).annotate({ discriminator: "type" })
|
||||
export type Info = Schema.Schema.Type<typeof Info>
|
||||
|
||||
export * as ConfigMCP from "./mcp"
|
||||
@@ -1,5 +0,0 @@
|
||||
import { Schema } from "effect"
|
||||
|
||||
export const ConfigModelID = Schema.String
|
||||
|
||||
export type ConfigModelID = Schema.Schema.Type<typeof ConfigModelID>
|
||||
@@ -3,7 +3,7 @@ export * as ConfigParse from "./parse"
|
||||
import { type ParseError as JsoncParseError, parse as parseJsoncImpl, printParseErrorCode } from "jsonc-parser"
|
||||
import { Cause, Exit, Schema as EffectSchema, SchemaIssue } from "effect"
|
||||
import type { DeepMutable } from "@opencode-ai/core/schema"
|
||||
import { InvalidError, JsonError } from "./error"
|
||||
import { InvalidError, JsonError } from "@opencode-ai/core/v1/config/error"
|
||||
|
||||
export function jsonc(text: string, filepath: string): unknown {
|
||||
const errors: JsoncParseError[] = []
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
export * as ConfigPermission from "./permission"
|
||||
import { Schema, SchemaGetter } from "effect"
|
||||
|
||||
export const Action = Schema.Literals(["ask", "allow", "deny"]).annotate({ identifier: "PermissionActionConfig" })
|
||||
export type Action = Schema.Schema.Type<typeof Action>
|
||||
|
||||
export const Object = Schema.Record(Schema.String, Action).annotate({ identifier: "PermissionObjectConfig" })
|
||||
export type Object = Schema.Schema.Type<typeof Object>
|
||||
|
||||
export const Rule = Schema.Union([Action, Object]).annotate({ identifier: "PermissionRuleConfig" })
|
||||
export type Rule = Schema.Schema.Type<typeof Rule>
|
||||
|
||||
// Known permission keys get explicit types in the Effect schema for generated
|
||||
// docs/types. Runtime config parsing uses Effect's `propertyOrder: "original"`
|
||||
// parse option so user key order is preserved for permission precedence.
|
||||
const InputObject = Schema.StructWithRest(
|
||||
Schema.Struct({
|
||||
read: Schema.optional(Rule),
|
||||
edit: Schema.optional(Rule),
|
||||
glob: Schema.optional(Rule),
|
||||
grep: Schema.optional(Rule),
|
||||
list: Schema.optional(Rule),
|
||||
bash: Schema.optional(Rule),
|
||||
task: Schema.optional(Rule),
|
||||
external_directory: Schema.optional(Rule),
|
||||
todowrite: Schema.optional(Action),
|
||||
question: Schema.optional(Action),
|
||||
webfetch: Schema.optional(Action),
|
||||
websearch: Schema.optional(Action),
|
||||
lsp: Schema.optional(Rule),
|
||||
doom_loop: Schema.optional(Action),
|
||||
skill: Schema.optional(Rule),
|
||||
}),
|
||||
[Schema.Record(Schema.String, Rule)],
|
||||
)
|
||||
|
||||
// Input the user writes in config: either a single Action (shorthand for "*")
|
||||
// or an object of per-target rules.
|
||||
const InputSchema = Schema.Union([Action, InputObject])
|
||||
|
||||
// Normalise the Action shorthand into `{ "*": action }`. Object inputs pass
|
||||
// through untouched.
|
||||
const normalizeInput = (input: Schema.Schema.Type<typeof InputSchema>): Schema.Schema.Type<typeof InputObject> =>
|
||||
typeof input === "string" ? { "*": input } : input
|
||||
|
||||
export const Info = InputSchema.pipe(
|
||||
Schema.decodeTo(InputObject, {
|
||||
decode: SchemaGetter.transform(normalizeInput),
|
||||
// Not perfectly invertible (we lose whether the user originally typed an
|
||||
// Action shorthand), but the object form is always a valid representation
|
||||
// of the same rules.
|
||||
encode: SchemaGetter.passthrough({ strict: false }),
|
||||
}),
|
||||
).annotate({ identifier: "PermissionConfig" })
|
||||
type _Info = Schema.Schema.Type<typeof InputObject>
|
||||
export type Info = { -readonly [K in keyof _Info]: _Info[K] }
|
||||
@@ -1,30 +1,22 @@
|
||||
import { Glob } from "@opencode-ai/core/util/glob"
|
||||
import { Schema } from "effect"
|
||||
import { ConfigPluginV1 } from "@opencode-ai/core/v1/config/plugin"
|
||||
import { pathToFileURL } from "url"
|
||||
import { isPathPluginSpec, parsePluginSpecifier, resolvePathPluginTarget } from "@/plugin/shared"
|
||||
import path from "path"
|
||||
|
||||
export const Options = Schema.Record(Schema.String, Schema.Unknown)
|
||||
export type Options = Schema.Schema.Type<typeof Options>
|
||||
|
||||
// Spec is the user-config value: either just a plugin identifier, or the identifier plus inline options.
|
||||
// It answers "what should we load?" but says nothing about where that value came from.
|
||||
export const Spec = Schema.Union([Schema.String, Schema.mutable(Schema.Tuple([Schema.String, Options]))])
|
||||
export type Spec = Schema.Schema.Type<typeof Spec>
|
||||
|
||||
export type Scope = "global" | "local"
|
||||
|
||||
// Origin keeps the original config provenance attached to a spec.
|
||||
// After multiple config files are merged, callers still need to know which file declared the plugin
|
||||
// and whether it should behave like a global or project-local plugin.
|
||||
export type Origin = {
|
||||
spec: Spec
|
||||
spec: ConfigPluginV1.Spec
|
||||
source: string
|
||||
scope: Scope
|
||||
}
|
||||
|
||||
export async function load(dir: string) {
|
||||
const plugins: Spec[] = []
|
||||
const plugins: ConfigPluginV1.Spec[] = []
|
||||
|
||||
for (const item of await Glob.scan("{plugin,plugins}/*.{ts,js}", {
|
||||
cwd: dir,
|
||||
@@ -37,17 +29,17 @@ export async function load(dir: string) {
|
||||
return plugins
|
||||
}
|
||||
|
||||
export function pluginSpecifier(plugin: Spec): string {
|
||||
export function pluginSpecifier(plugin: ConfigPluginV1.Spec): string {
|
||||
return Array.isArray(plugin) ? plugin[0] : plugin
|
||||
}
|
||||
|
||||
export function pluginOptions(plugin: Spec): Options | undefined {
|
||||
export function pluginOptions(plugin: ConfigPluginV1.Spec): ConfigPluginV1.Options | undefined {
|
||||
return Array.isArray(plugin) ? plugin[1] : undefined
|
||||
}
|
||||
|
||||
// Path-like specs are resolved relative to the config file that declared them so merges later on do not
|
||||
// accidentally reinterpret `./plugin.ts` relative to some other directory.
|
||||
export async function resolvePluginSpec(plugin: Spec, configFilepath: string): Promise<Spec> {
|
||||
export async function resolvePluginSpec(plugin: ConfigPluginV1.Spec, configFilepath: string): Promise<ConfigPluginV1.Spec> {
|
||||
const spec = pluginSpecifier(plugin)
|
||||
if (!isPathPluginSpec(spec)) return plugin
|
||||
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
import { Schema } from "effect"
|
||||
import { PositiveInt } from "@opencode-ai/core/schema"
|
||||
import { ModelStatus } from "@/provider/model-status"
|
||||
|
||||
export const Model = Schema.Struct({
|
||||
id: Schema.optional(Schema.String),
|
||||
name: Schema.optional(Schema.String),
|
||||
family: Schema.optional(Schema.String),
|
||||
release_date: Schema.optional(Schema.String),
|
||||
attachment: Schema.optional(Schema.Boolean),
|
||||
reasoning: Schema.optional(Schema.Boolean),
|
||||
temperature: Schema.optional(Schema.Boolean),
|
||||
tool_call: Schema.optional(Schema.Boolean),
|
||||
interleaved: Schema.optional(
|
||||
Schema.Union([
|
||||
Schema.Literal(true),
|
||||
Schema.Struct({
|
||||
field: Schema.Literals(["reasoning_content", "reasoning_details"]),
|
||||
}),
|
||||
]),
|
||||
),
|
||||
cost: Schema.optional(
|
||||
Schema.Struct({
|
||||
input: Schema.Finite,
|
||||
output: Schema.Finite,
|
||||
cache_read: Schema.optional(Schema.Finite),
|
||||
cache_write: Schema.optional(Schema.Finite),
|
||||
context_over_200k: Schema.optional(
|
||||
Schema.Struct({
|
||||
input: Schema.Finite,
|
||||
output: Schema.Finite,
|
||||
cache_read: Schema.optional(Schema.Finite),
|
||||
cache_write: Schema.optional(Schema.Finite),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
),
|
||||
limit: Schema.optional(
|
||||
Schema.Struct({
|
||||
context: Schema.Finite,
|
||||
input: Schema.optional(Schema.Finite),
|
||||
output: Schema.Finite,
|
||||
}),
|
||||
),
|
||||
modalities: Schema.optional(
|
||||
Schema.Struct({
|
||||
input: Schema.optional(Schema.mutable(Schema.Array(Schema.Literals(["text", "audio", "image", "video", "pdf"])))),
|
||||
output: Schema.optional(
|
||||
Schema.mutable(Schema.Array(Schema.Literals(["text", "audio", "image", "video", "pdf"]))),
|
||||
),
|
||||
}),
|
||||
),
|
||||
experimental: Schema.optional(Schema.Boolean),
|
||||
status: Schema.optional(ModelStatus),
|
||||
provider: Schema.optional(
|
||||
Schema.Struct({ npm: Schema.optional(Schema.String), api: Schema.optional(Schema.String) }),
|
||||
),
|
||||
options: Schema.optional(Schema.Record(Schema.String, Schema.Any)),
|
||||
headers: Schema.optional(Schema.Record(Schema.String, Schema.String)),
|
||||
variants: Schema.optional(
|
||||
Schema.Record(
|
||||
Schema.String,
|
||||
Schema.StructWithRest(
|
||||
Schema.Struct({
|
||||
disabled: Schema.optional(Schema.Boolean).annotate({ description: "Disable this variant for the model" }),
|
||||
}),
|
||||
[Schema.Record(Schema.String, Schema.Any)],
|
||||
),
|
||||
).annotate({ description: "Variant-specific configuration" }),
|
||||
),
|
||||
})
|
||||
|
||||
export const Info = Schema.Struct({
|
||||
api: Schema.optional(Schema.String),
|
||||
name: Schema.optional(Schema.String),
|
||||
env: Schema.optional(Schema.mutable(Schema.Array(Schema.String))),
|
||||
id: Schema.optional(Schema.String),
|
||||
npm: Schema.optional(Schema.String),
|
||||
whitelist: Schema.optional(Schema.mutable(Schema.Array(Schema.String))),
|
||||
blacklist: Schema.optional(Schema.mutable(Schema.Array(Schema.String))),
|
||||
options: Schema.optional(
|
||||
Schema.StructWithRest(
|
||||
Schema.Struct({
|
||||
apiKey: Schema.optional(Schema.String),
|
||||
baseURL: Schema.optional(Schema.String),
|
||||
enterpriseUrl: Schema.optional(Schema.String).annotate({
|
||||
description: "GitHub Enterprise URL for copilot authentication",
|
||||
}),
|
||||
setCacheKey: Schema.optional(Schema.Boolean).annotate({
|
||||
description: "Enable promptCacheKey for this provider (default false)",
|
||||
}),
|
||||
timeout: Schema.optional(
|
||||
Schema.Union([PositiveInt, Schema.Literal(false)]).annotate({
|
||||
description: "Timeout in milliseconds for full requests to this provider. Set to false to disable timeout.",
|
||||
}),
|
||||
).annotate({
|
||||
description: "Timeout in milliseconds for full requests to this provider. Set to false to disable timeout.",
|
||||
}),
|
||||
headerTimeout: Schema.optional(
|
||||
Schema.Union([PositiveInt, Schema.Literal(false)]).annotate({
|
||||
description:
|
||||
"Timeout in milliseconds to wait for response headers. Provider integrations may set defaults. Set to false to disable timeout.",
|
||||
}),
|
||||
).annotate({
|
||||
description:
|
||||
"Timeout in milliseconds to wait for response headers. Provider integrations may set defaults. Set to false to disable timeout.",
|
||||
}),
|
||||
chunkTimeout: Schema.optional(PositiveInt).annotate({
|
||||
description:
|
||||
"Timeout in milliseconds between streamed SSE chunks for this provider. If no chunk arrives within this window, the request is aborted.",
|
||||
}),
|
||||
}),
|
||||
[Schema.Record(Schema.String, Schema.Any)],
|
||||
),
|
||||
),
|
||||
models: Schema.optional(Schema.Record(Schema.String, Model)),
|
||||
}).annotate({ identifier: "ProviderConfig" })
|
||||
export type Info = Schema.Schema.Type<typeof Info>
|
||||
|
||||
export * as ConfigProvider from "./provider"
|
||||
@@ -1,27 +1,6 @@
|
||||
export * as ConfigReference from "./reference"
|
||||
|
||||
import { Schema } from "effect"
|
||||
|
||||
const Git = Schema.Struct({
|
||||
repository: Schema.String.annotate({
|
||||
description: "Git repository URL, host/path reference, or GitHub owner/repo shorthand",
|
||||
}),
|
||||
branch: Schema.optional(Schema.String).annotate({
|
||||
description: "Branch or ref to clone and inspect",
|
||||
}),
|
||||
})
|
||||
|
||||
const Local = Schema.Struct({
|
||||
path: Schema.String.annotate({
|
||||
description: "Absolute path, ~/ path, or workspace-relative path to a local reference directory",
|
||||
}),
|
||||
})
|
||||
|
||||
export const Entry = Schema.Union([Schema.String, Git, Local]).annotate({ identifier: "ReferenceConfigEntry" })
|
||||
export type Entry = Schema.Schema.Type<typeof Entry>
|
||||
|
||||
export const Info = Schema.Record(Schema.String, Entry).annotate({ identifier: "ReferenceConfig" })
|
||||
export type Info = Schema.Schema.Type<typeof Info>
|
||||
import { ConfigReferenceV1 } from "@opencode-ai/core/v1/config/reference"
|
||||
|
||||
export type NormalizedEntry =
|
||||
| {
|
||||
@@ -47,7 +26,7 @@ export function validateAlias(name: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeEntry(entry: Entry): NormalizedEntry {
|
||||
export function normalizeEntry(entry: ConfigReferenceV1.Entry): NormalizedEntry {
|
||||
if (typeof entry === "string") {
|
||||
if (entry.startsWith(".") || entry.startsWith("/") || entry.startsWith("~")) {
|
||||
return { kind: "local", path: entry }
|
||||
@@ -59,7 +38,7 @@ export function normalizeEntry(entry: Entry): NormalizedEntry {
|
||||
return { kind: "git", repository: entry.repository, branch: entry.branch }
|
||||
}
|
||||
|
||||
export function normalize(info: Info): NormalizedInfo {
|
||||
export function normalize(info: ConfigReferenceV1.Info): NormalizedInfo {
|
||||
return Object.fromEntries(
|
||||
Object.entries(info).map(([name, entry]) => {
|
||||
const aliasError = validateAlias(name)
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { Schema } from "effect"
|
||||
import { PositiveInt } from "@opencode-ai/core/schema"
|
||||
|
||||
export const Server = Schema.Struct({
|
||||
port: Schema.optional(PositiveInt).annotate({
|
||||
description: "Port to listen on",
|
||||
}),
|
||||
hostname: Schema.optional(Schema.String).annotate({ description: "Hostname to listen on" }),
|
||||
mdns: Schema.optional(Schema.Boolean).annotate({ description: "Enable mDNS service discovery" }),
|
||||
mdnsDomain: Schema.optional(Schema.String).annotate({
|
||||
description: "Custom domain name for mDNS service (default: opencode.local)",
|
||||
}),
|
||||
cors: Schema.optional(Schema.mutable(Schema.Array(Schema.String))).annotate({
|
||||
description: "Additional domains to allow for CORS",
|
||||
}),
|
||||
}).annotate({ identifier: "ServerConfig" })
|
||||
export type Server = Schema.Schema.Type<typeof Server>
|
||||
|
||||
export * as ConfigServer from "./server"
|
||||
@@ -1,14 +0,0 @@
|
||||
import { Schema } from "effect"
|
||||
|
||||
export const Info = Schema.Struct({
|
||||
paths: Schema.optional(Schema.Array(Schema.String)).annotate({
|
||||
description: "Additional paths to skill folders",
|
||||
}),
|
||||
urls: Schema.optional(Schema.Array(Schema.String)).annotate({
|
||||
description: "URLs to fetch skills from (e.g., https://example.com/.well-known/skills/)",
|
||||
}),
|
||||
})
|
||||
|
||||
export type Info = Schema.Schema.Type<typeof Info>
|
||||
|
||||
export * as ConfigSkills from "./skills"
|
||||
@@ -3,7 +3,7 @@ export * as ConfigVariable from "./variable"
|
||||
import path from "path"
|
||||
import os from "os"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import { InvalidError } from "./error"
|
||||
import { InvalidError } from "@opencode-ai/core/v1/config/error"
|
||||
|
||||
type ParseSource =
|
||||
| {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Config } from "@/config/config"
|
||||
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||
import type { MessageV2 } from "@/session/message-v2"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import photonWasm from "@silvia-odwyer/photon-node/photon_rs_bg.wasm" with { type: "file" }
|
||||
@@ -53,7 +53,7 @@ export class SizeError extends Schema.TaggedErrorClass<SizeError>()("ImageSizeEr
|
||||
export type Error = ResizerUnavailableError | InvalidDataUrlError | DecodeError | SizeError
|
||||
|
||||
export interface Interface {
|
||||
readonly normalize: (input: SessionLegacy.FilePart) => Effect.Effect<SessionLegacy.FilePart, Error>
|
||||
readonly normalize: (input: SessionV1.FilePart) => Effect.Effect<SessionV1.FilePart, Error>
|
||||
}
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/Image") {}
|
||||
@@ -74,7 +74,7 @@ export const layer = Layer.effect(
|
||||
),
|
||||
)
|
||||
|
||||
const normalize = Effect.fn("Image.normalize")(function* (input: SessionLegacy.FilePart) {
|
||||
const normalize = Effect.fn("Image.normalize")(function* (input: SessionV1.FilePart) {
|
||||
const image = (yield* config.get()).attachment?.image
|
||||
const info = {
|
||||
autoResize: image?.auto_resize ?? AUTO_RESIZE,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { dynamicTool, type Tool, jsonSchema, type JSONSchema7 } from "ai"
|
||||
import { ConfigV1 } from "@opencode-ai/core/v1/config/config"
|
||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import { Client } from "@modelcontextprotocol/sdk/client/index.js"
|
||||
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
|
||||
@@ -13,7 +14,7 @@ import {
|
||||
ToolListChangedNotificationSchema,
|
||||
} from "@modelcontextprotocol/sdk/types.js"
|
||||
import { Config } from "@/config/config"
|
||||
import { ConfigMCP } from "../config/mcp"
|
||||
import { ConfigMCPV1 } from "@opencode-ai/core/v1/config/mcp"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { NamedError } from "@opencode-ai/core/util/error"
|
||||
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||
@@ -106,9 +107,9 @@ const pendingOAuthTransports = new Map<string, TransportWithAuth>()
|
||||
// Prompt cache types
|
||||
type PromptInfo = Awaited<ReturnType<MCPClient["listPrompts"]>>["prompts"][number]
|
||||
type ResourceInfo = Awaited<ReturnType<MCPClient["listResources"]>>["resources"][number]
|
||||
type McpEntry = NonNullable<Config.Info["mcp"]>[string]
|
||||
type McpEntry = NonNullable<ConfigV1.Info["mcp"]>[string]
|
||||
|
||||
function isMcpConfigured(entry: McpEntry): entry is ConfigMCP.Info {
|
||||
function isMcpConfigured(entry: McpEntry): entry is ConfigMCPV1.Info {
|
||||
return typeof entry === "object" && entry !== null && "type" in entry
|
||||
}
|
||||
|
||||
@@ -234,7 +235,7 @@ interface AuthResult {
|
||||
// --- Effect Service ---
|
||||
|
||||
interface State {
|
||||
config: Record<string, ConfigMCP.Info>
|
||||
config: Record<string, ConfigMCPV1.Info>
|
||||
status: Record<string, Status>
|
||||
clients: Record<string, MCPClient>
|
||||
defs: Record<string, MCPToolDef[]>
|
||||
@@ -246,7 +247,7 @@ export interface Interface {
|
||||
readonly tools: () => Effect.Effect<Record<string, Tool>>
|
||||
readonly prompts: () => Effect.Effect<Record<string, PromptInfo & { client: string }>>
|
||||
readonly resources: () => Effect.Effect<Record<string, ResourceInfo & { client: string }>>
|
||||
readonly add: (name: string, mcp: ConfigMCP.Info) => Effect.Effect<{ status: Record<string, Status> | Status }>
|
||||
readonly add: (name: string, mcp: ConfigMCPV1.Info) => Effect.Effect<{ status: Record<string, Status> | Status }>
|
||||
readonly connect: (name: string) => Effect.Effect<void, NotFoundError>
|
||||
readonly disconnect: (name: string) => Effect.Effect<void, NotFoundError>
|
||||
readonly getPrompt: (
|
||||
@@ -304,7 +305,7 @@ export const layer = Layer.effect(
|
||||
|
||||
const connectRemote = Effect.fn("MCP.connectRemote")(function* (
|
||||
key: string,
|
||||
mcp: ConfigMCP.Info & { type: "remote" },
|
||||
mcp: ConfigMCPV1.Info & { type: "remote" },
|
||||
) {
|
||||
const oauthDisabled = mcp.oauth === false
|
||||
const oauthConfig = typeof mcp.oauth === "object" ? mcp.oauth : undefined
|
||||
@@ -421,7 +422,7 @@ export const layer = Layer.effect(
|
||||
|
||||
const connectLocal = Effect.fn("MCP.connectLocal")(function* (
|
||||
key: string,
|
||||
mcp: ConfigMCP.Info & { type: "local" },
|
||||
mcp: ConfigMCPV1.Info & { type: "local" },
|
||||
) {
|
||||
const [cmd, ...args] = mcp.command
|
||||
const cwd = yield* InstanceState.directory
|
||||
@@ -454,7 +455,7 @@ export const layer = Layer.effect(
|
||||
)
|
||||
})
|
||||
|
||||
const create = Effect.fn("MCP.create")(function* (key: string, mcp: ConfigMCP.Info) {
|
||||
const create = Effect.fn("MCP.create")(function* (key: string, mcp: ConfigMCPV1.Info) {
|
||||
if (mcp.enabled === false) {
|
||||
log.info("mcp server disabled", { key })
|
||||
return DISABLED_RESULT
|
||||
@@ -464,8 +465,8 @@ export const layer = Layer.effect(
|
||||
|
||||
const { client: mcpClient, status } =
|
||||
mcp.type === "remote"
|
||||
? yield* connectRemote(key, mcp as ConfigMCP.Info & { type: "remote" })
|
||||
: yield* connectLocal(key, mcp as ConfigMCP.Info & { type: "local" })
|
||||
? yield* connectRemote(key, mcp as ConfigMCPV1.Info & { type: "remote" })
|
||||
: yield* connectLocal(key, mcp as ConfigMCPV1.Info & { type: "local" })
|
||||
|
||||
if (!mcpClient) {
|
||||
return { status } satisfies CreateResult
|
||||
@@ -633,7 +634,7 @@ export const layer = Layer.effect(
|
||||
return s.clients
|
||||
})
|
||||
|
||||
const createAndStore = Effect.fn("MCP.createAndStore")(function* (name: string, mcp: ConfigMCP.Info) {
|
||||
const createAndStore = Effect.fn("MCP.createAndStore")(function* (name: string, mcp: ConfigMCPV1.Info) {
|
||||
const s = yield* InstanceState.get(state)
|
||||
const result = yield* create(name, mcp)
|
||||
|
||||
@@ -647,7 +648,7 @@ export const layer = Layer.effect(
|
||||
return yield* storeClient(s, name, result.mcpClient, result.defs!, mcp.timeout)
|
||||
})
|
||||
|
||||
const add = Effect.fn("MCP.add")(function* (name: string, mcp: ConfigMCP.Info) {
|
||||
const add = Effect.fn("MCP.add")(function* (name: string, mcp: ConfigMCPV1.Info) {
|
||||
const s = yield* InstanceState.get(state)
|
||||
s.config[name] = mcp
|
||||
yield* createAndStore(name, mcp)
|
||||
|
||||
@@ -1,48 +1,48 @@
|
||||
import { ConfigPermission } from "@/config/permission"
|
||||
import { ConfigPermissionV1 } from "@opencode-ai/core/v1/config/permission"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { Wildcard } from "@opencode-ai/core/util/wildcard"
|
||||
import { Deferred, Effect, Layer, Context } from "effect"
|
||||
import os from "os"
|
||||
import { PermissionLegacy } from "@opencode-ai/core/permission/legacy"
|
||||
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
|
||||
const log = Log.create({ service: "permission" })
|
||||
|
||||
export const Event = {
|
||||
Asked: EventV2.define({ type: "permission.asked", schema: PermissionLegacy.Request.fields }),
|
||||
Asked: EventV2.define({ type: "permission.asked", schema: PermissionV1.Request.fields }),
|
||||
Replied: EventV2.define({
|
||||
type: "permission.replied",
|
||||
schema: {
|
||||
sessionID: PermissionLegacy.Request.fields.sessionID,
|
||||
requestID: PermissionLegacy.ID,
|
||||
reply: PermissionLegacy.Reply,
|
||||
sessionID: PermissionV1.Request.fields.sessionID,
|
||||
requestID: PermissionV1.ID,
|
||||
reply: PermissionV1.Reply,
|
||||
},
|
||||
}),
|
||||
}
|
||||
|
||||
export interface Interface {
|
||||
readonly ask: (input: PermissionLegacy.AskInput) => Effect.Effect<void, PermissionLegacy.Error>
|
||||
readonly reply: (input: PermissionLegacy.ReplyInput) => Effect.Effect<void, PermissionLegacy.NotFoundError>
|
||||
readonly list: () => Effect.Effect<ReadonlyArray<PermissionLegacy.Request>>
|
||||
readonly ask: (input: PermissionV1.AskInput) => Effect.Effect<void, PermissionV1.Error>
|
||||
readonly reply: (input: PermissionV1.ReplyInput) => Effect.Effect<void, PermissionV1.NotFoundError>
|
||||
readonly list: () => Effect.Effect<ReadonlyArray<PermissionV1.Request>>
|
||||
}
|
||||
|
||||
interface PendingEntry {
|
||||
info: PermissionLegacy.Request
|
||||
deferred: Deferred.Deferred<void, PermissionLegacy.RejectedError | PermissionLegacy.CorrectedError>
|
||||
info: PermissionV1.Request
|
||||
deferred: Deferred.Deferred<void, PermissionV1.RejectedError | PermissionV1.CorrectedError>
|
||||
}
|
||||
|
||||
interface State {
|
||||
pending: Map<PermissionLegacy.ID, PendingEntry>
|
||||
approved: PermissionLegacy.Rule[]
|
||||
pending: Map<PermissionV1.ID, PendingEntry>
|
||||
approved: PermissionV1.Rule[]
|
||||
}
|
||||
|
||||
export function evaluate(
|
||||
permission: string,
|
||||
pattern: string,
|
||||
...rulesets: PermissionLegacy.Ruleset[]
|
||||
): PermissionLegacy.Rule {
|
||||
...rulesets: PermissionV1.Ruleset[]
|
||||
): PermissionV1.Rule {
|
||||
return (
|
||||
rulesets
|
||||
.flat()
|
||||
@@ -64,14 +64,14 @@ export const layer = Layer.effect(
|
||||
Effect.fn("Permission.state")(function* (ctx) {
|
||||
void ctx
|
||||
const state = {
|
||||
pending: new Map<PermissionLegacy.ID, PendingEntry>(),
|
||||
pending: new Map<PermissionV1.ID, PendingEntry>(),
|
||||
approved: [],
|
||||
}
|
||||
|
||||
yield* Effect.addFinalizer(() =>
|
||||
Effect.gen(function* () {
|
||||
for (const item of state.pending.values()) {
|
||||
yield* Deferred.fail(item.deferred, new PermissionLegacy.RejectedError())
|
||||
yield* Deferred.fail(item.deferred, new PermissionV1.RejectedError())
|
||||
}
|
||||
state.pending.clear()
|
||||
}),
|
||||
@@ -81,7 +81,7 @@ export const layer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
const ask = Effect.fn("Permission.ask")(function* (input: PermissionLegacy.AskInput) {
|
||||
const ask = Effect.fn("Permission.ask")(function* (input: PermissionV1.AskInput) {
|
||||
const { approved, pending } = yield* InstanceState.get(state)
|
||||
const { ruleset, ...request } = input
|
||||
let needsAsk = false
|
||||
@@ -90,7 +90,7 @@ export const layer = Layer.effect(
|
||||
const rule = evaluate(request.permission, pattern, ruleset, approved)
|
||||
log.info("evaluated", { permission: request.permission, pattern, action: rule })
|
||||
if (rule.action === "deny") {
|
||||
return yield* new PermissionLegacy.DeniedError({
|
||||
return yield* new PermissionV1.DeniedError({
|
||||
ruleset: ruleset.filter((rule) => Wildcard.match(request.permission, rule.permission)),
|
||||
})
|
||||
}
|
||||
@@ -100,8 +100,8 @@ export const layer = Layer.effect(
|
||||
|
||||
if (!needsAsk) return
|
||||
|
||||
const id = request.id ?? PermissionLegacy.ID.ascending()
|
||||
const info: PermissionLegacy.Request = {
|
||||
const id = request.id ?? PermissionV1.ID.ascending()
|
||||
const info: PermissionV1.Request = {
|
||||
id,
|
||||
sessionID: request.sessionID,
|
||||
permission: request.permission,
|
||||
@@ -112,7 +112,7 @@ export const layer = Layer.effect(
|
||||
}
|
||||
log.info("asking", { id, permission: info.permission, patterns: info.patterns })
|
||||
|
||||
const deferred = yield* Deferred.make<void, PermissionLegacy.RejectedError | PermissionLegacy.CorrectedError>()
|
||||
const deferred = yield* Deferred.make<void, PermissionV1.RejectedError | PermissionV1.CorrectedError>()
|
||||
pending.set(id, { info, deferred })
|
||||
yield* events.publish(Event.Asked, info)
|
||||
return yield* Effect.ensuring(
|
||||
@@ -123,10 +123,10 @@ export const layer = Layer.effect(
|
||||
)
|
||||
})
|
||||
|
||||
const reply = Effect.fn("Permission.reply")(function* (input: PermissionLegacy.ReplyInput) {
|
||||
const reply = Effect.fn("Permission.reply")(function* (input: PermissionV1.ReplyInput) {
|
||||
const { approved, pending } = yield* InstanceState.get(state)
|
||||
const existing = pending.get(input.requestID)
|
||||
if (!existing) return yield* new PermissionLegacy.NotFoundError({ requestID: input.requestID })
|
||||
if (!existing) return yield* new PermissionV1.NotFoundError({ requestID: input.requestID })
|
||||
|
||||
pending.delete(input.requestID)
|
||||
yield* events.publish(Event.Replied, {
|
||||
@@ -139,8 +139,8 @@ export const layer = Layer.effect(
|
||||
yield* Deferred.fail(
|
||||
existing.deferred,
|
||||
input.message
|
||||
? new PermissionLegacy.CorrectedError({ feedback: input.message })
|
||||
: new PermissionLegacy.RejectedError(),
|
||||
? new PermissionV1.CorrectedError({ feedback: input.message })
|
||||
: new PermissionV1.RejectedError(),
|
||||
)
|
||||
|
||||
for (const [id, item] of pending.entries()) {
|
||||
@@ -151,7 +151,7 @@ export const layer = Layer.effect(
|
||||
requestID: item.info.id,
|
||||
reply: "reject",
|
||||
})
|
||||
yield* Deferred.fail(item.deferred, new PermissionLegacy.RejectedError())
|
||||
yield* Deferred.fail(item.deferred, new PermissionV1.RejectedError())
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -200,8 +200,8 @@ function expand(pattern: string): string {
|
||||
return pattern
|
||||
}
|
||||
|
||||
export function fromConfig(permission: ConfigPermission.Info) {
|
||||
const ruleset: PermissionLegacy.Rule[] = []
|
||||
export function fromConfig(permission: ConfigPermissionV1.Info) {
|
||||
const ruleset: PermissionV1.Rule[] = []
|
||||
for (const [key, value] of Object.entries(permission)) {
|
||||
if (typeof value === "string") {
|
||||
ruleset.push({ permission: key, action: value, pattern: "*" })
|
||||
@@ -214,11 +214,11 @@ export function fromConfig(permission: ConfigPermission.Info) {
|
||||
return ruleset
|
||||
}
|
||||
|
||||
export function merge(...rulesets: PermissionLegacy.Ruleset[]): PermissionLegacy.Rule[] {
|
||||
export function merge(...rulesets: PermissionV1.Ruleset[]): PermissionV1.Rule[] {
|
||||
return rulesets.flat()
|
||||
}
|
||||
|
||||
export function disabled(tools: string[], ruleset: PermissionLegacy.Ruleset): Set<string> {
|
||||
export function disabled(tools: string[], ruleset: PermissionV1.Ruleset): Set<string> {
|
||||
const edits = ["edit", "write", "apply_patch"]
|
||||
return new Set(
|
||||
tools.filter((tool) => {
|
||||
|
||||
@@ -9,13 +9,14 @@ import {
|
||||
type PluginSource,
|
||||
} from "./shared"
|
||||
import { ConfigPlugin } from "@/config/plugin"
|
||||
import { ConfigPluginV1 } from "@opencode-ai/core/v1/config/plugin"
|
||||
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||
|
||||
export namespace PluginLoader {
|
||||
// A normalized plugin declaration derived from config before any filesystem or npm work happens.
|
||||
export type Plan = {
|
||||
spec: string
|
||||
options: ConfigPlugin.Options | undefined
|
||||
options: ConfigPluginV1.Options | undefined
|
||||
deprecated: boolean
|
||||
}
|
||||
|
||||
@@ -73,7 +74,7 @@ export namespace PluginLoader {
|
||||
}
|
||||
|
||||
// Normalize a config item into the loader's internal representation.
|
||||
function plan(item: ConfigPlugin.Spec): Plan {
|
||||
function plan(item: ConfigPluginV1.Spec): Plan {
|
||||
const spec = ConfigPlugin.pluginSpecifier(item)
|
||||
return { spec, options: ConfigPlugin.pluginOptions(item), deprecated: isDeprecatedPlugin(spec) }
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import os from "os"
|
||||
import { ConfigV1 } from "@opencode-ai/core/v1/config/config"
|
||||
import fuzzysort from "fuzzysort"
|
||||
import { Config } from "@/config/config"
|
||||
import { mapValues, mergeDeep, omit, pickBy, sortBy } from "remeda"
|
||||
@@ -142,7 +143,7 @@ type CustomLoader = (provider: Info) => Effect.Effect<{
|
||||
|
||||
type CustomDep = {
|
||||
auth: (id: string) => Effect.Effect<Auth.Info | undefined>
|
||||
config: () => Effect.Effect<Config.Info>
|
||||
config: () => Effect.Effect<ConfigV1.Info>
|
||||
env: () => Effect.Effect<Record<string, string | undefined>>
|
||||
get: (key: string) => Effect.Effect<string | undefined>
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Config } from "@/config/config"
|
||||
import { ConfigV1 } from "@opencode-ai/core/v1/config/config"
|
||||
import { Provider } from "@/provider/provider"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { Authorization } from "../middleware/authorization"
|
||||
@@ -14,7 +15,7 @@ export const ConfigApi = HttpApi.make("config")
|
||||
.add(
|
||||
HttpApiEndpoint.get("get", root, {
|
||||
query: WorkspaceRoutingQuery,
|
||||
success: described(Config.Info, "Get config info"),
|
||||
success: described(ConfigV1.Info, "Get config info"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "config.get",
|
||||
@@ -24,8 +25,8 @@ export const ConfigApi = HttpApi.make("config")
|
||||
),
|
||||
HttpApiEndpoint.patch("update", root, {
|
||||
query: WorkspaceRoutingQuery,
|
||||
payload: Config.Info,
|
||||
success: described(Config.Info, "Successfully updated config"),
|
||||
payload: ConfigV1.Info,
|
||||
success: described(ConfigV1.Info, "Successfully updated config"),
|
||||
error: HttpApiError.BadRequest,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Config } from "@/config/config"
|
||||
import { ConfigV1 } from "@opencode-ai/core/v1/config/config"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { InstanceDisposed } from "@/server/event"
|
||||
import "@opencode-ai/core/account"
|
||||
@@ -90,7 +91,7 @@ export const GlobalApi = HttpApi.make("global").add(
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.get("configGet", GlobalPaths.config, {
|
||||
success: described(Config.Info, "Get global config info"),
|
||||
success: described(ConfigV1.Info, "Get global config info"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "global.config.get",
|
||||
@@ -99,8 +100,8 @@ export const GlobalApi = HttpApi.make("global").add(
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.patch("configUpdate", GlobalPaths.config, {
|
||||
payload: Config.Info,
|
||||
success: described(Config.Info, "Successfully updated global config"),
|
||||
payload: ConfigV1.Info,
|
||||
success: described(ConfigV1.Info, "Successfully updated global config"),
|
||||
error: HttpApiError.BadRequest,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { MCP } from "@/mcp"
|
||||
import { ConfigMCP } from "@/config/mcp"
|
||||
import { ConfigMCPV1 } from "@opencode-ai/core/v1/config/mcp"
|
||||
import { Schema } from "effect"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { McpServerNotFoundError } from "../errors"
|
||||
@@ -10,7 +10,7 @@ import { described } from "./metadata"
|
||||
|
||||
export const AddPayload = Schema.Struct({
|
||||
name: Schema.String,
|
||||
config: ConfigMCP.Info,
|
||||
config: ConfigMCPV1.Info,
|
||||
})
|
||||
|
||||
export const StatusMap = Schema.Record(Schema.String, MCP.Status)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PermissionLegacy } from "@opencode-ai/core/permission/legacy"
|
||||
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
|
||||
import { Permission } from "@/permission"
|
||||
import { Schema } from "effect"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
@@ -10,7 +10,7 @@ import { described } from "./metadata"
|
||||
|
||||
const root = "/permission"
|
||||
const ReplyPayload = Schema.Struct({
|
||||
reply: PermissionLegacy.Reply,
|
||||
reply: PermissionV1.Reply,
|
||||
message: Schema.optional(Schema.String),
|
||||
})
|
||||
|
||||
@@ -20,7 +20,7 @@ export const PermissionApi = HttpApi.make("permission")
|
||||
.add(
|
||||
HttpApiEndpoint.get("list", root, {
|
||||
query: WorkspaceRoutingQuery,
|
||||
success: described(Schema.Array(PermissionLegacy.Request), "List of pending permissions"),
|
||||
success: described(Schema.Array(PermissionV1.Request), "List of pending permissions"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "permission.list",
|
||||
@@ -29,7 +29,7 @@ export const PermissionApi = HttpApi.make("permission")
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.post("reply", `${root}/:requestID/reply`, {
|
||||
params: { requestID: PermissionLegacy.ID },
|
||||
params: { requestID: PermissionV1.ID },
|
||||
query: WorkspaceRoutingQuery,
|
||||
payload: ReplyPayload,
|
||||
success: described(Schema.Boolean, "Permission processed successfully"),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PermissionLegacy } from "@opencode-ai/core/permission/legacy"
|
||||
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
|
||||
import { Permission } from "@/permission"
|
||||
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||
|
||||
import { Session } from "@/session/session"
|
||||
import { MessageV2 } from "@/session/message-v2"
|
||||
@@ -48,7 +48,7 @@ export const StatusMap = Schema.Record(Schema.String, SessionStatus.Info)
|
||||
export const UpdatePayload = Schema.Struct({
|
||||
title: Schema.optional(Schema.String),
|
||||
metadata: Schema.optional(Session.Metadata),
|
||||
permission: Schema.optional(PermissionLegacy.Ruleset),
|
||||
permission: Schema.optional(PermissionV1.Ruleset),
|
||||
time: Schema.optional(
|
||||
Schema.Struct({
|
||||
archived: Schema.optional(Session.ArchivedTimestamp),
|
||||
@@ -71,7 +71,7 @@ export const CommandPayload = Schema.Struct(Struct.omit(SessionPrompt.CommandInp
|
||||
export const ShellPayload = Schema.Struct(Struct.omit(SessionPrompt.ShellInput.fields, ["sessionID"]))
|
||||
export const RevertPayload = Schema.Struct(Struct.omit(SessionRevert.RevertInput.fields, ["sessionID"]))
|
||||
export const PermissionResponsePayload = Schema.Struct({
|
||||
response: PermissionLegacy.Reply,
|
||||
response: PermissionV1.Reply,
|
||||
})
|
||||
|
||||
export const SessionPaths = {
|
||||
@@ -178,7 +178,7 @@ export const SessionApi = HttpApi.make("session")
|
||||
HttpApiEndpoint.get("messages", SessionPaths.messages, {
|
||||
params: { sessionID: SessionID },
|
||||
query: MessagesQuery,
|
||||
success: described(Schema.Array(SessionLegacy.WithParts), "List of messages"),
|
||||
success: described(Schema.Array(SessionV1.WithParts), "List of messages"),
|
||||
error: [HttpApiError.BadRequest, ApiNotFoundError],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
@@ -190,7 +190,7 @@ export const SessionApi = HttpApi.make("session")
|
||||
HttpApiEndpoint.get("message", SessionPaths.message, {
|
||||
params: { sessionID: SessionID, messageID: MessageID },
|
||||
query: WorkspaceRoutingQuery,
|
||||
success: described(SessionLegacy.WithParts, "Message"),
|
||||
success: described(SessionV1.WithParts, "Message"),
|
||||
error: [HttpApiError.BadRequest, ApiNotFoundError],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
@@ -316,7 +316,7 @@ export const SessionApi = HttpApi.make("session")
|
||||
params: { sessionID: SessionID },
|
||||
query: WorkspaceRoutingQuery,
|
||||
payload: PromptPayload,
|
||||
success: described(SessionLegacy.WithParts, "Created message"),
|
||||
success: described(SessionV1.WithParts, "Created message"),
|
||||
error: [HttpApiError.BadRequest, ApiNotFoundError],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
@@ -343,7 +343,7 @@ export const SessionApi = HttpApi.make("session")
|
||||
params: { sessionID: SessionID },
|
||||
query: WorkspaceRoutingQuery,
|
||||
payload: CommandPayload,
|
||||
success: described(SessionLegacy.WithParts, "Created message"),
|
||||
success: described(SessionV1.WithParts, "Created message"),
|
||||
error: [HttpApiError.BadRequest, ApiNotFoundError],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
@@ -356,7 +356,7 @@ export const SessionApi = HttpApi.make("session")
|
||||
params: { sessionID: SessionID },
|
||||
query: WorkspaceRoutingQuery,
|
||||
payload: ShellPayload,
|
||||
success: described(SessionLegacy.WithParts, "Created message"),
|
||||
success: described(SessionV1.WithParts, "Created message"),
|
||||
error: [HttpApiError.BadRequest, ApiNotFoundError, SessionBusyError],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
@@ -392,7 +392,7 @@ export const SessionApi = HttpApi.make("session")
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.post("permissionRespond", SessionPaths.permissions, {
|
||||
params: { sessionID: SessionID, permissionID: PermissionLegacy.ID },
|
||||
params: { sessionID: SessionID, permissionID: PermissionV1.ID },
|
||||
query: WorkspaceRoutingQuery,
|
||||
payload: PermissionResponsePayload,
|
||||
success: described(Schema.Boolean, "Permission processed successfully"),
|
||||
@@ -432,8 +432,8 @@ export const SessionApi = HttpApi.make("session")
|
||||
HttpApiEndpoint.patch("updatePart", SessionPaths.updatePart, {
|
||||
params: { sessionID: SessionID, messageID: MessageID, partID: PartID },
|
||||
query: WorkspaceRoutingQuery,
|
||||
payload: SessionLegacy.Part,
|
||||
success: described(SessionLegacy.Part, "Successfully updated part"),
|
||||
payload: SessionV1.Part,
|
||||
success: described(SessionV1.Part, "Successfully updated part"),
|
||||
error: [HttpApiError.BadRequest, ApiNotFoundError],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PermissionLegacy } from "@opencode-ai/core/permission/legacy"
|
||||
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
|
||||
import { Permission } from "@/permission"
|
||||
import { Effect } from "effect"
|
||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
||||
@@ -14,8 +14,8 @@ export const permissionHandlers = HttpApiBuilder.group(InstanceHttpApi, "permiss
|
||||
})
|
||||
|
||||
const reply = Effect.fn("PermissionHttpApi.reply")(function* (ctx: {
|
||||
params: { requestID: PermissionLegacy.ID }
|
||||
payload: PermissionLegacy.ReplyBody
|
||||
params: { requestID: PermissionV1.ID }
|
||||
payload: PermissionV1.ReplyBody
|
||||
}) {
|
||||
yield* svc
|
||||
.reply({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PermissionLegacy } from "@opencode-ai/core/permission/legacy"
|
||||
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
|
||||
import { Agent } from "@/agent/agent"
|
||||
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { Command } from "@/command"
|
||||
import { Permission } from "@/permission"
|
||||
@@ -360,7 +360,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
|
||||
})
|
||||
|
||||
const permissionRespond = Effect.fn("SessionHttpApi.permissionRespond")(function* (ctx: {
|
||||
params: { sessionID: SessionID; permissionID: PermissionLegacy.ID }
|
||||
params: { sessionID: SessionID; permissionID: PermissionV1.ID }
|
||||
payload: typeof PermissionResponsePayload.Type
|
||||
}) {
|
||||
yield* requireSession(ctx.params.sessionID)
|
||||
@@ -396,10 +396,10 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
|
||||
|
||||
const updatePart = Effect.fn("SessionHttpApi.updatePart")(function* (ctx: {
|
||||
params: { sessionID: SessionID; messageID: MessageID; partID: PartID }
|
||||
payload: typeof SessionLegacy.Part.Type
|
||||
payload: typeof SessionV1.Part.Type
|
||||
}) {
|
||||
yield* requireSession(ctx.params.sessionID)
|
||||
const payload = ctx.payload as SessionLegacy.Part
|
||||
const payload = ctx.payload as SessionV1.Part
|
||||
if (
|
||||
payload.id !== ctx.params.partID ||
|
||||
payload.messageID !== ctx.params.messageID ||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||
import { ConfigV1 } from "@opencode-ai/core/v1/config/config"
|
||||
import { Session } from "./session"
|
||||
import { SessionID, MessageID, PartID } from "./schema"
|
||||
import { Provider } from "@/provider/provider"
|
||||
@@ -93,9 +94,9 @@ type CompletedCompaction = {
|
||||
summary: string | undefined
|
||||
}
|
||||
|
||||
function summaryText(message: SessionLegacy.WithParts) {
|
||||
function summaryText(message: SessionV1.WithParts) {
|
||||
const text = message.parts
|
||||
.filter((part): part is SessionLegacy.TextPart => part.type === "text")
|
||||
.filter((part): part is SessionV1.TextPart => part.type === "text")
|
||||
.map((part) => part.text.trim())
|
||||
.filter(Boolean)
|
||||
.join("\n\n")
|
||||
@@ -103,7 +104,7 @@ function summaryText(message: SessionLegacy.WithParts) {
|
||||
return text || undefined
|
||||
}
|
||||
|
||||
function completedCompactions(messages: SessionLegacy.WithParts[]) {
|
||||
function completedCompactions(messages: SessionV1.WithParts[]) {
|
||||
const users = new Map<MessageID, number>()
|
||||
for (let i = 0; i < messages.length; i++) {
|
||||
const msg = messages[i]
|
||||
@@ -134,14 +135,14 @@ function buildPrompt(input: { previousSummary?: string; context: string[] }) {
|
||||
return [anchor, SUMMARY_TEMPLATE, ...input.context].join("\n\n")
|
||||
}
|
||||
|
||||
function preserveRecentBudget(input: { cfg: Config.Info; model: Provider.Model }) {
|
||||
function preserveRecentBudget(input: { cfg: ConfigV1.Info; model: Provider.Model }) {
|
||||
return (
|
||||
input.cfg.compaction?.preserve_recent_tokens ??
|
||||
Math.min(MAX_PRESERVE_RECENT_TOKENS, Math.max(MIN_PRESERVE_RECENT_TOKENS, Math.floor(usable(input) * 0.25)))
|
||||
)
|
||||
}
|
||||
|
||||
function turns(messages: SessionLegacy.WithParts[]) {
|
||||
function turns(messages: SessionV1.WithParts[]) {
|
||||
const result: Turn[] = []
|
||||
for (let i = 0; i < messages.length; i++) {
|
||||
const msg = messages[i]
|
||||
@@ -160,11 +161,11 @@ function turns(messages: SessionLegacy.WithParts[]) {
|
||||
}
|
||||
|
||||
function splitTurn(input: {
|
||||
messages: SessionLegacy.WithParts[]
|
||||
messages: SessionV1.WithParts[]
|
||||
turn: Turn
|
||||
model: Provider.Model
|
||||
budget: number
|
||||
estimate: (input: { messages: SessionLegacy.WithParts[]; model: Provider.Model }) => Effect.Effect<number>
|
||||
estimate: (input: { messages: SessionV1.WithParts[]; model: Provider.Model }) => Effect.Effect<number>
|
||||
}) {
|
||||
return Effect.gen(function* () {
|
||||
if (input.budget <= 0) return undefined
|
||||
@@ -186,13 +187,13 @@ function splitTurn(input: {
|
||||
|
||||
export interface Interface {
|
||||
readonly isOverflow: (input: {
|
||||
tokens: SessionLegacy.Assistant["tokens"]
|
||||
tokens: SessionV1.Assistant["tokens"]
|
||||
model: Provider.Model
|
||||
}) => Effect.Effect<boolean>
|
||||
readonly prune: (input: { sessionID: SessionID }) => Effect.Effect<void>
|
||||
readonly process: (input: {
|
||||
parentID: MessageID
|
||||
messages: SessionLegacy.WithParts[]
|
||||
messages: SessionV1.WithParts[]
|
||||
sessionID: SessionID
|
||||
auto: boolean
|
||||
overflow?: boolean
|
||||
@@ -223,7 +224,7 @@ export const layer = Layer.effect(
|
||||
const flags = yield* RuntimeFlags.Service
|
||||
|
||||
const isOverflow = Effect.fn("SessionCompaction.isOverflow")(function* (input: {
|
||||
tokens: SessionLegacy.Assistant["tokens"]
|
||||
tokens: SessionV1.Assistant["tokens"]
|
||||
model: Provider.Model
|
||||
}) {
|
||||
return overflow({
|
||||
@@ -235,7 +236,7 @@ export const layer = Layer.effect(
|
||||
})
|
||||
|
||||
const estimate = Effect.fn("SessionCompaction.estimate")(function* (input: {
|
||||
messages: SessionLegacy.WithParts[]
|
||||
messages: SessionV1.WithParts[]
|
||||
model: Provider.Model
|
||||
}) {
|
||||
const msgs = yield* MessageV2.toModelMessagesEffect(input.messages, input.model)
|
||||
@@ -243,8 +244,8 @@ export const layer = Layer.effect(
|
||||
})
|
||||
|
||||
const select = Effect.fn("SessionCompaction.select")(function* (input: {
|
||||
messages: SessionLegacy.WithParts[]
|
||||
cfg: Config.Info
|
||||
messages: SessionV1.WithParts[]
|
||||
cfg: ConfigV1.Info
|
||||
model: Provider.Model
|
||||
}) {
|
||||
const limit = input.cfg.compaction?.tail_turns ?? DEFAULT_TAIL_TURNS
|
||||
@@ -307,7 +308,7 @@ export const layer = Layer.effect(
|
||||
|
||||
let total = 0
|
||||
let pruned = 0
|
||||
const toPrune: SessionLegacy.ToolPart[] = []
|
||||
const toPrune: SessionV1.ToolPart[] = []
|
||||
let turns = 0
|
||||
|
||||
loop: for (let msgIndex = msgs.length - 1; msgIndex >= 0; msgIndex--) {
|
||||
@@ -343,7 +344,7 @@ export const layer = Layer.effect(
|
||||
|
||||
const processCompaction = Effect.fn("SessionCompaction.process")(function* (input: {
|
||||
parentID: MessageID
|
||||
messages: SessionLegacy.WithParts[]
|
||||
messages: SessionV1.WithParts[]
|
||||
sessionID: SessionID
|
||||
auto: boolean
|
||||
overflow?: boolean
|
||||
@@ -354,14 +355,14 @@ export const layer = Layer.effect(
|
||||
}
|
||||
const userMessage = parent.info
|
||||
const compactionPart = parent.parts.find(
|
||||
(part): part is SessionLegacy.CompactionPart => part.type === "compaction",
|
||||
(part): part is SessionV1.CompactionPart => part.type === "compaction",
|
||||
)
|
||||
|
||||
let messages = input.messages
|
||||
let replay:
|
||||
| {
|
||||
info: SessionLegacy.User
|
||||
parts: SessionLegacy.Part[]
|
||||
info: SessionV1.User
|
||||
parts: SessionV1.Part[]
|
||||
}
|
||||
| undefined
|
||||
if (input.overflow) {
|
||||
@@ -410,7 +411,7 @@ export const layer = Layer.effect(
|
||||
toolOutputMaxChars: TOOL_OUTPUT_MAX_CHARS,
|
||||
})
|
||||
const ctx = yield* InstanceState.context
|
||||
const msg: SessionLegacy.Assistant = {
|
||||
const msg: SessionV1.Assistant = {
|
||||
id: MessageID.ascending(),
|
||||
role: "assistant",
|
||||
parentID: input.parentID,
|
||||
@@ -459,7 +460,7 @@ export const layer = Layer.effect(
|
||||
})
|
||||
|
||||
if (result === "compact") {
|
||||
processor.message.error = new SessionLegacy.ContextOverflowError({
|
||||
processor.message.error = new SessionV1.ContextOverflowError({
|
||||
message: replay
|
||||
? "Conversation history too large to compact - exceeds model context limit"
|
||||
: "Session too large to compact - context exceeds model limit even after stripping media",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import path from "path"
|
||||
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||
import { Effect, Layer, Context } from "effect"
|
||||
import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http"
|
||||
import { Config } from "@/config/config"
|
||||
@@ -12,7 +12,7 @@ import { Global } from "@opencode-ai/core/global"
|
||||
import type { MessageV2 } from "./message-v2"
|
||||
import type { MessageID } from "./schema"
|
||||
|
||||
function extract(messages: SessionLegacy.WithParts[]) {
|
||||
function extract(messages: SessionV1.WithParts[]) {
|
||||
const paths = new Set<string>()
|
||||
for (const msg of messages) {
|
||||
for (const part of msg.parts) {
|
||||
@@ -35,7 +35,7 @@ export interface Interface {
|
||||
readonly system: () => Effect.Effect<string[], FSUtil.Error>
|
||||
readonly find: (dir: string) => Effect.Effect<string | undefined, FSUtil.Error>
|
||||
readonly resolve: (
|
||||
messages: SessionLegacy.WithParts[],
|
||||
messages: SessionV1.WithParts[],
|
||||
filepath: string,
|
||||
messageID: MessageID,
|
||||
) => Effect.Effect<{ filepath: string; content: string }[], FSUtil.Error>
|
||||
@@ -175,7 +175,7 @@ export const layer: Layer.Layer<
|
||||
})
|
||||
|
||||
const resolve = Effect.fn("Instruction.resolve")(function* (
|
||||
messages: SessionLegacy.WithParts[],
|
||||
messages: SessionV1.WithParts[],
|
||||
filepath: string,
|
||||
messageID: MessageID,
|
||||
) {
|
||||
@@ -230,7 +230,7 @@ export const defaultLayer = layer.pipe(
|
||||
Layer.provide(RuntimeFlags.defaultLayer),
|
||||
)
|
||||
|
||||
export function loaded(messages: SessionLegacy.WithParts[]) {
|
||||
export function loaded(messages: SessionV1.WithParts[]) {
|
||||
return extract(messages)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PermissionLegacy } from "@opencode-ai/core/permission/legacy"
|
||||
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
|
||||
import { Provider } from "@/provider/provider"
|
||||
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import { Log } from "@opencode-ai/core/util/log"
|
||||
import { Context, Effect, Layer } from "effect"
|
||||
@@ -33,12 +33,12 @@ const log = Log.create({ service: "llm" })
|
||||
export const OUTPUT_TOKEN_MAX = ProviderTransform.OUTPUT_TOKEN_MAX
|
||||
|
||||
export type StreamInput = {
|
||||
user: SessionLegacy.User
|
||||
user: SessionV1.User
|
||||
sessionID: string
|
||||
parentSessionID?: string
|
||||
model: Provider.Model
|
||||
agent: Agent.Info
|
||||
permission?: PermissionLegacy.Ruleset
|
||||
permission?: PermissionV1.Ruleset
|
||||
system: string[]
|
||||
messages: ModelMessage[]
|
||||
small?: boolean
|
||||
@@ -165,7 +165,7 @@ const live: Layer.Layer<
|
||||
return { approved: true }
|
||||
}
|
||||
|
||||
const id = PermissionLegacy.ID.ascending()
|
||||
const id = PermissionV1.ID.ascending()
|
||||
let unsub: EventV2.Unsubscribe | undefined
|
||||
try {
|
||||
unsub = await bridge.promise(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PermissionLegacy } from "@opencode-ai/core/permission/legacy"
|
||||
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
|
||||
import type { Auth } from "@/auth"
|
||||
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||
import type { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import { Permission } from "@/permission"
|
||||
@@ -18,12 +18,12 @@ import { mergeDeep } from "remeda"
|
||||
const USER_AGENT = `opencode/${InstallationVersion}`
|
||||
|
||||
type PrepareInput = {
|
||||
readonly user: SessionLegacy.User
|
||||
readonly user: SessionV1.User
|
||||
readonly sessionID: string
|
||||
readonly parentSessionID?: string
|
||||
readonly model: Provider.Model
|
||||
readonly agent: Agent.Info
|
||||
readonly permission?: PermissionLegacy.Ruleset
|
||||
readonly permission?: PermissionV1.Ruleset
|
||||
readonly system: string[]
|
||||
readonly messages: ModelMessage[]
|
||||
readonly small?: boolean
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { SessionID, MessageID, PartID } from "./schema"
|
||||
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import {
|
||||
APIError,
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
User,
|
||||
WithParts,
|
||||
type ToolPart,
|
||||
} from "@opencode-ai/core/session/legacy"
|
||||
} from "@opencode-ai/core/v1/session"
|
||||
|
||||
import { NamedError } from "@opencode-ai/core/util/error"
|
||||
import { APICallError, convertToModelMessages, LoadAPIKeyError, type ModelMessage, type UIMessage } from "ai"
|
||||
@@ -56,9 +56,9 @@ function truncateToolOutput(text: string, maxChars?: number) {
|
||||
}
|
||||
|
||||
export const Event = {
|
||||
Updated: SessionLegacy.Event.MessageUpdated,
|
||||
Removed: SessionLegacy.Event.MessageRemoved,
|
||||
PartUpdated: SessionLegacy.Event.PartUpdated,
|
||||
Updated: SessionV1.Event.MessageUpdated,
|
||||
Removed: SessionV1.Event.MessageRemoved,
|
||||
PartUpdated: SessionV1.Event.PartUpdated,
|
||||
PartDelta: EventV2.define({
|
||||
type: "message.part.delta",
|
||||
schema: {
|
||||
@@ -69,7 +69,7 @@ export const Event = {
|
||||
delta: Schema.String,
|
||||
},
|
||||
}),
|
||||
PartRemoved: SessionLegacy.Event.PartRemoved,
|
||||
PartRemoved: SessionV1.Event.PartRemoved,
|
||||
}
|
||||
|
||||
const Cursor = Schema.Struct({
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import type { Config } from "@/config/config"
|
||||
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import { ConfigV1 } from "@opencode-ai/core/v1/config/config"
|
||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||
import type { Provider } from "@/provider/provider"
|
||||
import { ProviderTransform } from "@/provider/transform"
|
||||
import type { MessageV2 } from "./message-v2"
|
||||
|
||||
const COMPACTION_BUFFER = 20_000
|
||||
|
||||
export function usable(input: { cfg: Config.Info; model: Provider.Model; outputTokenMax?: number }) {
|
||||
export function usable(input: { cfg: ConfigV1.Info; model: Provider.Model; outputTokenMax?: number }) {
|
||||
const context = input.model.limit.context
|
||||
if (context === 0) return 0
|
||||
|
||||
@@ -19,8 +20,8 @@ export function usable(input: { cfg: Config.Info; model: Provider.Model; outputT
|
||||
}
|
||||
|
||||
export function isOverflow(input: {
|
||||
cfg: Config.Info
|
||||
tokens: SessionLegacy.Assistant["tokens"]
|
||||
cfg: ConfigV1.Info
|
||||
tokens: SessionV1.Assistant["tokens"]
|
||||
model: Provider.Model
|
||||
outputTokenMax?: number
|
||||
}) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PermissionLegacy } from "@opencode-ai/core/permission/legacy"
|
||||
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
|
||||
import { Image } from "@/image/image"
|
||||
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||
import { Cause, Deferred, Effect, Exit, Layer, Context, Scope, Schema } from "effect"
|
||||
import * as Stream from "effect/Stream"
|
||||
import { Agent } from "@/agent/agent"
|
||||
@@ -37,25 +37,25 @@ const log = Log.create({ service: "session.processor" })
|
||||
export type Result = "compact" | "stop" | "continue"
|
||||
|
||||
export interface Handle {
|
||||
readonly message: SessionLegacy.Assistant
|
||||
readonly message: SessionV1.Assistant
|
||||
readonly updateToolCall: (
|
||||
toolCallID: string,
|
||||
update: (part: SessionLegacy.ToolPart) => SessionLegacy.ToolPart,
|
||||
) => Effect.Effect<SessionLegacy.ToolPart | undefined>
|
||||
update: (part: SessionV1.ToolPart) => SessionV1.ToolPart,
|
||||
) => Effect.Effect<SessionV1.ToolPart | undefined>
|
||||
readonly completeToolCall: (
|
||||
toolCallID: string,
|
||||
output: {
|
||||
title: string
|
||||
metadata: Record<string, any>
|
||||
output: string
|
||||
attachments?: SessionLegacy.FilePart[]
|
||||
attachments?: SessionV1.FilePart[]
|
||||
},
|
||||
) => Effect.Effect<void>
|
||||
readonly process: (streamInput: LLM.StreamInput) => Effect.Effect<Result>
|
||||
}
|
||||
|
||||
type Input = {
|
||||
assistantMessage: SessionLegacy.Assistant
|
||||
assistantMessage: SessionV1.Assistant
|
||||
sessionID: SessionID
|
||||
model: Provider.Model
|
||||
}
|
||||
@@ -65,9 +65,9 @@ export interface Interface {
|
||||
}
|
||||
|
||||
type ToolCall = {
|
||||
partID: SessionLegacy.ToolPart["id"]
|
||||
messageID: SessionLegacy.ToolPart["messageID"]
|
||||
sessionID: SessionLegacy.ToolPart["sessionID"]
|
||||
partID: SessionV1.ToolPart["id"]
|
||||
messageID: SessionV1.ToolPart["messageID"]
|
||||
sessionID: SessionV1.ToolPart["sessionID"]
|
||||
done: Deferred.Deferred<void>
|
||||
inputEnded: boolean
|
||||
}
|
||||
@@ -78,8 +78,8 @@ interface ProcessorContext extends Input {
|
||||
snapshot: string | undefined
|
||||
blocked: boolean
|
||||
needsCompaction: boolean
|
||||
currentText: SessionLegacy.TextPart | undefined
|
||||
reasoningMap: Record<string, SessionLegacy.ReasoningPart>
|
||||
currentText: SessionV1.TextPart | undefined
|
||||
reasoningMap: Record<string, SessionV1.ReasoningPart>
|
||||
}
|
||||
|
||||
type StreamEvent = LLMEvent
|
||||
@@ -153,7 +153,7 @@ export const layer = Layer.effect(
|
||||
|
||||
const updateToolCall = Effect.fn("SessionProcessor.updateToolCall")(function* (
|
||||
toolCallID: string,
|
||||
update: (part: SessionLegacy.ToolPart) => SessionLegacy.ToolPart,
|
||||
update: (part: SessionV1.ToolPart) => SessionV1.ToolPart,
|
||||
) {
|
||||
const match = yield* readToolCall(toolCallID)
|
||||
if (!match) return undefined
|
||||
@@ -173,7 +173,7 @@ export const layer = Layer.effect(
|
||||
title: string
|
||||
metadata: Record<string, any>
|
||||
output: string
|
||||
attachments?: SessionLegacy.FilePart[]
|
||||
attachments?: SessionV1.FilePart[]
|
||||
},
|
||||
) {
|
||||
const match = yield* readToolCall(toolCallID)
|
||||
@@ -205,7 +205,7 @@ export const layer = Layer.effect(
|
||||
time: { start: match.part.state.time.start, end: Date.now() },
|
||||
},
|
||||
})
|
||||
if (error instanceof PermissionLegacy.RejectedError || error instanceof Question.RejectedError) {
|
||||
if (error instanceof PermissionV1.RejectedError || error instanceof Question.RejectedError) {
|
||||
ctx.blocked = ctx.shouldBreak
|
||||
}
|
||||
yield* settleToolCall(toolCallID)
|
||||
@@ -268,7 +268,7 @@ export const layer = Layer.effect(
|
||||
callID: input.id,
|
||||
state: { status: "pending", input: {}, raw: "" },
|
||||
metadata: input.providerExecuted ? { providerExecuted: true } : undefined,
|
||||
} satisfies SessionLegacy.ToolPart)
|
||||
} satisfies SessionV1.ToolPart)
|
||||
ctx.toolcalls[input.id] = {
|
||||
done: yield* Deferred.make<void>(),
|
||||
partID: part.id,
|
||||
@@ -279,11 +279,11 @@ export const layer = Layer.effect(
|
||||
return { call: ctx.toolcalls[input.id], part }
|
||||
})
|
||||
|
||||
const isFilePart = (value: unknown): value is SessionLegacy.FilePart => Schema.is(SessionLegacy.FilePart)(value)
|
||||
const isFilePart = (value: unknown): value is SessionV1.FilePart => Schema.is(SessionV1.FilePart)(value)
|
||||
|
||||
const toolResultOutput = (
|
||||
value: Extract<StreamEvent, { type: "tool-result" }>,
|
||||
): { title: string; metadata: Record<string, any>; output: string; attachments?: SessionLegacy.FilePart[] } => {
|
||||
): { title: string; metadata: Record<string, any>; output: string; attachments?: SessionV1.FilePart[] } => {
|
||||
if (isRecord(value.result.value) && typeof value.result.value.output === "string") {
|
||||
return {
|
||||
title: typeof value.result.value.title === "string" ? value.result.value.title : value.name,
|
||||
@@ -463,7 +463,7 @@ export const layer = Layer.effect(
|
||||
),
|
||||
Effect.exit,
|
||||
)
|
||||
: Effect.succeed(Exit.succeed<SessionLegacy.FilePart>(attachment)),
|
||||
: Effect.succeed(Exit.succeed<SessionV1.FilePart>(attachment)),
|
||||
)
|
||||
const omitted = normalized.filter(Exit.isFailure).length
|
||||
const attachments = normalized.filter(Exit.isSuccess).map((item) => item.value)
|
||||
@@ -486,7 +486,7 @@ export const layer = Layer.effect(
|
||||
type: "text",
|
||||
text: output.output,
|
||||
},
|
||||
...(output.attachments?.map((item: SessionLegacy.FilePart) => ({
|
||||
...(output.attachments?.map((item: SessionV1.FilePart) => ({
|
||||
type: "file" as const,
|
||||
uri: item.url,
|
||||
mime: item.mime,
|
||||
@@ -753,7 +753,7 @@ export const layer = Layer.effect(
|
||||
const halt = Effect.fn("SessionProcessor.halt")(function* (e: unknown) {
|
||||
slog.error("process", { error: errorMessage(e), stack: e instanceof Error ? e.stack : undefined })
|
||||
const error = parse(e)
|
||||
if (SessionLegacy.ContextOverflowError.isInstance(error)) {
|
||||
if (SessionV1.ContextOverflowError.isInstance(error)) {
|
||||
ctx.needsCompaction = true
|
||||
yield* events.publish(Session.Event.Error, { sessionID: ctx.sessionID, error })
|
||||
return
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PermissionLegacy } from "@opencode-ai/core/permission/legacy"
|
||||
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
|
||||
import path from "path"
|
||||
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||
import os from "os"
|
||||
import { SessionID, MessageID, PartID } from "./schema"
|
||||
import { MessageV2 } from "./message-v2"
|
||||
@@ -66,8 +66,8 @@ import { LLMEvent } from "@opencode-ai/llm"
|
||||
// @ts-ignore
|
||||
globalThis.AI_SDK_LOG_WARNINGS = false
|
||||
|
||||
const decodeMessageInfo = Schema.decodeUnknownExit(SessionLegacy.Info)
|
||||
const decodeMessagePart = Schema.decodeUnknownExit(SessionLegacy.Part)
|
||||
const decodeMessageInfo = Schema.decodeUnknownExit(SessionV1.Info)
|
||||
const decodeMessagePart = Schema.decodeUnknownExit(SessionV1.Part)
|
||||
|
||||
const STRUCTURED_OUTPUT_DESCRIPTION = `Use this tool to return your final response in the requested structured format.
|
||||
|
||||
@@ -82,7 +82,7 @@ const STRUCTURED_OUTPUT_SYSTEM_PROMPT = `IMPORTANT: The user has requested struc
|
||||
const log = Log.create({ service: "session.prompt" })
|
||||
const elog = EffectLogger.create({ service: "session.prompt" })
|
||||
|
||||
function isOrphanedInterruptedTool(part: SessionLegacy.ToolPart) {
|
||||
function isOrphanedInterruptedTool(part: SessionV1.ToolPart) {
|
||||
// cleanup() marks abandoned tool_use blocks this way after retries/aborts.
|
||||
// They are not pending work and must not trigger an assistant-prefill request.
|
||||
return part.state.status === "error" && part.state.metadata?.interrupted === true
|
||||
@@ -90,10 +90,10 @@ function isOrphanedInterruptedTool(part: SessionLegacy.ToolPart) {
|
||||
|
||||
export interface Interface {
|
||||
readonly cancel: (sessionID: SessionID) => Effect.Effect<void>
|
||||
readonly prompt: (input: PromptInput) => Effect.Effect<SessionLegacy.WithParts, Image.Error>
|
||||
readonly loop: (input: LoopInput) => Effect.Effect<SessionLegacy.WithParts>
|
||||
readonly shell: (input: ShellInput) => Effect.Effect<SessionLegacy.WithParts, Session.BusyError>
|
||||
readonly command: (input: CommandInput) => Effect.Effect<SessionLegacy.WithParts, Image.Error>
|
||||
readonly prompt: (input: PromptInput) => Effect.Effect<SessionV1.WithParts, Image.Error>
|
||||
readonly loop: (input: LoopInput) => Effect.Effect<SessionV1.WithParts>
|
||||
readonly shell: (input: ShellInput) => Effect.Effect<SessionV1.WithParts, Session.BusyError>
|
||||
readonly command: (input: CommandInput) => Effect.Effect<SessionV1.WithParts, Image.Error>
|
||||
readonly resolvePromptParts: (template: string) => Effect.Effect<PromptInput["parts"]>
|
||||
}
|
||||
|
||||
@@ -239,14 +239,14 @@ export const layer = Layer.effect(
|
||||
|
||||
const title = Effect.fn("SessionPrompt.ensureTitle")(function* (input: {
|
||||
session: Session.Info
|
||||
history: SessionLegacy.WithParts[]
|
||||
history: SessionV1.WithParts[]
|
||||
providerID: ProviderV2.ID
|
||||
modelID: ProviderV2.ModelID
|
||||
}) {
|
||||
if (input.session.parentID) return
|
||||
if (!Session.isDefaultTitle(input.session.title)) return
|
||||
|
||||
const real = (m: SessionLegacy.WithParts) =>
|
||||
const real = (m: SessionV1.WithParts) =>
|
||||
m.info.role === "user" && !m.parts.every((p) => "synthetic" in p && p.synthetic)
|
||||
const idx = input.history.findIndex(real)
|
||||
if (idx === -1) return
|
||||
@@ -257,7 +257,7 @@ export const layer = Layer.effect(
|
||||
if (!firstUser || firstUser.info.role !== "user") return
|
||||
const firstInfo = firstUser.info
|
||||
|
||||
const subtasks = firstUser.parts.filter((p): p is SessionLegacy.SubtaskPart => p.type === "subtask")
|
||||
const subtasks = firstUser.parts.filter((p): p is SessionV1.SubtaskPart => p.type === "subtask")
|
||||
const onlySubtasks = subtasks.length > 0 && firstUser.parts.every((p) => p.type === "subtask")
|
||||
|
||||
const ag = yield* agents.get("title")
|
||||
@@ -300,19 +300,19 @@ export const layer = Layer.effect(
|
||||
})
|
||||
|
||||
const handleSubtask = Effect.fn("SessionPrompt.handleSubtask")(function* (input: {
|
||||
task: SessionLegacy.SubtaskPart
|
||||
task: SessionV1.SubtaskPart
|
||||
model: Provider.Model
|
||||
lastUser: SessionLegacy.User
|
||||
lastUser: SessionV1.User
|
||||
sessionID: SessionID
|
||||
session: Session.Info
|
||||
msgs: SessionLegacy.WithParts[]
|
||||
msgs: SessionV1.WithParts[]
|
||||
}) {
|
||||
const { task, model, lastUser, sessionID, session, msgs } = input
|
||||
const ctx = yield* InstanceState.context
|
||||
const promptOps = yield* ops()
|
||||
const { task: taskTool } = yield* registry.named()
|
||||
const taskModel = task.model ? yield* getModel(task.model.providerID, task.model.modelID, sessionID) : model
|
||||
const assistantMessage: SessionLegacy.Assistant = yield* sessions.updateMessage({
|
||||
const assistantMessage: SessionV1.Assistant = yield* sessions.updateMessage({
|
||||
id: MessageID.ascending(),
|
||||
role: "assistant",
|
||||
parentID: lastUser.id,
|
||||
@@ -327,7 +327,7 @@ export const layer = Layer.effect(
|
||||
providerID: taskModel.providerID,
|
||||
time: { created: Date.now() },
|
||||
})
|
||||
let part: SessionLegacy.ToolPart = yield* sessions.updatePart({
|
||||
let part: SessionV1.ToolPart = yield* sessions.updatePart({
|
||||
id: PartID.ascending(),
|
||||
messageID: assistantMessage.id,
|
||||
sessionID: assistantMessage.sessionID,
|
||||
@@ -383,7 +383,7 @@ export const layer = Layer.effect(
|
||||
...part,
|
||||
type: "tool",
|
||||
state: { ...part.state, ...val },
|
||||
} satisfies SessionLegacy.ToolPart)
|
||||
} satisfies SessionV1.ToolPart)
|
||||
}),
|
||||
ask: (req: any) =>
|
||||
permission
|
||||
@@ -417,7 +417,7 @@ export const layer = Layer.effect(
|
||||
metadata: part.state.metadata,
|
||||
input: part.state.input,
|
||||
},
|
||||
} satisfies SessionLegacy.ToolPart)
|
||||
} satisfies SessionV1.ToolPart)
|
||||
}
|
||||
}),
|
||||
),
|
||||
@@ -452,7 +452,7 @@ export const layer = Layer.effect(
|
||||
attachments,
|
||||
time: { ...part.state.time, end: Date.now() },
|
||||
},
|
||||
} satisfies SessionLegacy.ToolPart)
|
||||
} satisfies SessionV1.ToolPart)
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
@@ -468,12 +468,12 @@ export const layer = Layer.effect(
|
||||
metadata: part.state.status === "pending" ? undefined : part.state.metadata,
|
||||
input: part.state.input,
|
||||
},
|
||||
} satisfies SessionLegacy.ToolPart)
|
||||
} satisfies SessionV1.ToolPart)
|
||||
}
|
||||
|
||||
if (!task.command) return
|
||||
|
||||
const summaryUserMsg: SessionLegacy.User = {
|
||||
const summaryUserMsg: SessionV1.User = {
|
||||
id: MessageID.ascending(),
|
||||
sessionID,
|
||||
role: "user",
|
||||
@@ -489,7 +489,7 @@ export const layer = Layer.effect(
|
||||
type: "text",
|
||||
text: "Summarize the task tool output above and continue with your task.",
|
||||
synthetic: true,
|
||||
} satisfies SessionLegacy.TextPart)
|
||||
} satisfies SessionV1.TextPart)
|
||||
})
|
||||
|
||||
const shellImpl = Effect.fn("SessionPrompt.shellImpl")(function* (input: ShellInput, ready?: Latch.Latch) {
|
||||
@@ -511,7 +511,7 @@ export const layer = Layer.effect(
|
||||
throw error
|
||||
}
|
||||
const model = input.model ?? agent.model ?? (yield* currentModel(input.sessionID))
|
||||
const userMsg: SessionLegacy.User = {
|
||||
const userMsg: SessionV1.User = {
|
||||
id: input.messageID ?? MessageID.ascending(),
|
||||
sessionID: input.sessionID,
|
||||
time: { created: Date.now() },
|
||||
@@ -520,7 +520,7 @@ export const layer = Layer.effect(
|
||||
model: { providerID: model.providerID, modelID: model.modelID },
|
||||
}
|
||||
yield* sessions.updateMessage(userMsg)
|
||||
const userPart: SessionLegacy.Part = {
|
||||
const userPart: SessionV1.Part = {
|
||||
type: "text",
|
||||
id: PartID.ascending(),
|
||||
messageID: userMsg.id,
|
||||
@@ -530,7 +530,7 @@ export const layer = Layer.effect(
|
||||
}
|
||||
yield* sessions.updatePart(userPart)
|
||||
|
||||
const msg: SessionLegacy.Assistant = {
|
||||
const msg: SessionV1.Assistant = {
|
||||
id: MessageID.ascending(),
|
||||
sessionID: input.sessionID,
|
||||
parentID: userMsg.id,
|
||||
@@ -546,7 +546,7 @@ export const layer = Layer.effect(
|
||||
}
|
||||
yield* sessions.updateMessage(msg)
|
||||
const started = Date.now()
|
||||
const part: SessionLegacy.ToolPart = {
|
||||
const part: SessionV1.ToolPart = {
|
||||
type: "tool",
|
||||
id: PartID.ascending(),
|
||||
messageID: msg.id,
|
||||
@@ -719,7 +719,7 @@ export const layer = Layer.effect(
|
||||
: undefined
|
||||
const variant = input.variant ?? (ag.variant && full?.variants?.[ag.variant] ? ag.variant : undefined)
|
||||
|
||||
const info: SessionLegacy.User = {
|
||||
const info: SessionV1.User = {
|
||||
id: input.messageID ?? MessageID.ascending(),
|
||||
role: "user",
|
||||
sessionID: input.sessionID,
|
||||
@@ -760,8 +760,8 @@ export const layer = Layer.effect(
|
||||
|
||||
yield* Effect.addFinalizer(() => instruction.clear(info.id))
|
||||
|
||||
type Draft<T> = T extends SessionLegacy.Part ? Omit<T, "id"> & { id?: string } : never
|
||||
const assign = (part: Draft<SessionLegacy.Part>): SessionLegacy.Part => ({
|
||||
type Draft<T> = T extends SessionV1.Part ? Omit<T, "id"> & { id?: string } : never
|
||||
const assign = (part: Draft<SessionV1.Part>): SessionV1.Part => ({
|
||||
...part,
|
||||
id: part.id ? PartID.make(part.id) : PartID.ascending(),
|
||||
})
|
||||
@@ -790,14 +790,14 @@ export const layer = Layer.effect(
|
||||
})
|
||||
})
|
||||
|
||||
const resolvePart: (part: PromptInput["parts"][number]) => Effect.Effect<Draft<SessionLegacy.Part>[]> = Effect.fn(
|
||||
const resolvePart: (part: PromptInput["parts"][number]) => Effect.Effect<Draft<SessionV1.Part>[]> = Effect.fn(
|
||||
"SessionPrompt.resolveUserPart",
|
||||
)(function* (part) {
|
||||
if (part.type === "file") {
|
||||
if (part.source?.type === "resource") {
|
||||
const { clientName, uri } = part.source
|
||||
log.info("mcp resource", { clientName, uri, mime: part.mime })
|
||||
const pieces: Draft<SessionLegacy.Part>[] = [
|
||||
const pieces: Draft<SessionV1.Part>[] = [
|
||||
{
|
||||
messageID: info.id,
|
||||
sessionID: input.sessionID,
|
||||
@@ -917,7 +917,7 @@ export const layer = Layer.effect(
|
||||
if (end) limit = end - (offset - 1)
|
||||
}
|
||||
const args = { filePath: filepath, offset, limit }
|
||||
const pieces: Draft<SessionLegacy.Part>[] = [
|
||||
const pieces: Draft<SessionV1.Part>[] = [
|
||||
...(referenceContext
|
||||
? [{ ...referenceContext, messageID: info.id, sessionID: input.sessionID }]
|
||||
: []),
|
||||
@@ -1213,7 +1213,7 @@ export const layer = Layer.effect(
|
||||
return { info, parts }
|
||||
}, Effect.scoped)
|
||||
|
||||
const prompt: (input: PromptInput) => Effect.Effect<SessionLegacy.WithParts, Image.Error> = Effect.fn(
|
||||
const prompt: (input: PromptInput) => Effect.Effect<SessionV1.WithParts, Image.Error> = Effect.fn(
|
||||
"SessionPrompt.prompt",
|
||||
)(function* (input: PromptInput) {
|
||||
const session = yield* sessions.get(input.sessionID).pipe(Effect.orDie)
|
||||
@@ -1221,7 +1221,7 @@ export const layer = Layer.effect(
|
||||
const message = yield* createUserMessage(input)
|
||||
yield* sessions.touch(input.sessionID)
|
||||
|
||||
const permissions: PermissionLegacy.Rule[] = []
|
||||
const permissions: PermissionV1.Rule[] = []
|
||||
for (const [t, enabled] of Object.entries(input.tools ?? {})) {
|
||||
permissions.push({ permission: t, action: enabled ? "allow" : "deny", pattern: "*" })
|
||||
}
|
||||
@@ -1242,7 +1242,7 @@ export const layer = Layer.effect(
|
||||
throw new Error("Impossible")
|
||||
})
|
||||
|
||||
const runLoop: (sessionID: SessionID) => Effect.Effect<SessionLegacy.WithParts> = Effect.fn("SessionPrompt.run")(
|
||||
const runLoop: (sessionID: SessionID) => Effect.Effect<SessionV1.WithParts> = Effect.fn("SessionPrompt.run")(
|
||||
function* (sessionID: SessionID) {
|
||||
const ctx = yield* InstanceState.context
|
||||
const slog = elog.with({ sessionID })
|
||||
@@ -1280,7 +1280,7 @@ export const layer = Layer.effect(
|
||||
lastUser.id < lastAssistant.id
|
||||
) {
|
||||
const orphan = lastAssistantMsg?.parts.find(
|
||||
(part): part is SessionLegacy.ToolPart => part.type === "tool" && isOrphanedInterruptedTool(part),
|
||||
(part): part is SessionV1.ToolPart => part.type === "tool" && isOrphanedInterruptedTool(part),
|
||||
)
|
||||
if (orphan) {
|
||||
yield* slog.warn("loop exit with orphaned interrupted tool", {
|
||||
@@ -1347,7 +1347,7 @@ export const layer = Layer.effect(
|
||||
Effect.provideService(Session.Service, sessions),
|
||||
)
|
||||
|
||||
const msg: SessionLegacy.Assistant = {
|
||||
const msg: SessionV1.Assistant = {
|
||||
id: MessageID.ascending(),
|
||||
parentID: lastUser.id,
|
||||
role: "assistant",
|
||||
@@ -1467,7 +1467,7 @@ export const layer = Layer.effect(
|
||||
const finished = handle.message.finish && !["tool-calls", "unknown"].includes(handle.message.finish)
|
||||
if (finished && !handle.message.error) {
|
||||
if (format.type === "json_schema") {
|
||||
handle.message.error = new SessionLegacy.StructuredOutputError({
|
||||
handle.message.error = new SessionV1.StructuredOutputError({
|
||||
message: "Model did not produce structured output",
|
||||
retries: 0,
|
||||
}).toObject()
|
||||
@@ -1500,13 +1500,13 @@ export const layer = Layer.effect(
|
||||
},
|
||||
)
|
||||
|
||||
const loop: (input: LoopInput) => Effect.Effect<SessionLegacy.WithParts> = Effect.fn("SessionPrompt.loop")(
|
||||
const loop: (input: LoopInput) => Effect.Effect<SessionV1.WithParts> = Effect.fn("SessionPrompt.loop")(
|
||||
function* (input: LoopInput) {
|
||||
return yield* state.ensureRunning(input.sessionID, lastAssistant(input.sessionID), runLoop(input.sessionID))
|
||||
},
|
||||
)
|
||||
|
||||
const shell: (input: ShellInput) => Effect.Effect<SessionLegacy.WithParts, Session.BusyError> = Effect.fn(
|
||||
const shell: (input: ShellInput) => Effect.Effect<SessionV1.WithParts, Session.BusyError> = Effect.fn(
|
||||
"SessionPrompt.shell",
|
||||
)(function* (input: ShellInput) {
|
||||
const ready = yield* Latch.make()
|
||||
@@ -1691,15 +1691,15 @@ export const PromptInput = Schema.Struct({
|
||||
description:
|
||||
"@deprecated tools and permissions have been merged, you can set permissions on the session itself now",
|
||||
}),
|
||||
format: Schema.optional(SessionLegacy.Format),
|
||||
format: Schema.optional(SessionV1.Format),
|
||||
system: Schema.optional(Schema.String),
|
||||
variant: Schema.optional(Schema.String),
|
||||
parts: Schema.Array(
|
||||
Schema.Union([
|
||||
SessionLegacy.TextPartInput,
|
||||
SessionLegacy.FilePartInput,
|
||||
SessionLegacy.AgentPartInput,
|
||||
SessionLegacy.SubtaskPartInput,
|
||||
SessionV1.TextPartInput,
|
||||
SessionV1.FilePartInput,
|
||||
SessionV1.AgentPartInput,
|
||||
SessionV1.SubtaskPartInput,
|
||||
]).annotate({ discriminator: "type" }),
|
||||
),
|
||||
})
|
||||
@@ -1738,7 +1738,7 @@ export const CommandInput = Schema.Struct({
|
||||
mime: Schema.String,
|
||||
filename: Schema.optional(Schema.String),
|
||||
url: Schema.String,
|
||||
source: Schema.optional(SessionLegacy.FilePartSource),
|
||||
source: Schema.optional(SessionV1.FilePartSource),
|
||||
}),
|
||||
]).annotate({ discriminator: "type" }),
|
||||
),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Option, Schema } from "effect"
|
||||
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||
import { MessageV2 } from "../message-v2"
|
||||
import { Reference } from "@/reference/reference"
|
||||
|
||||
@@ -34,7 +34,7 @@ export function referenceTextPart(input: {
|
||||
target?: string
|
||||
targetPath?: string
|
||||
problem?: string
|
||||
}): SessionLegacy.TextPartInput {
|
||||
}): SessionV1.TextPartInput {
|
||||
const metadata: ReferencePromptMetadata = {
|
||||
name: input.reference.name,
|
||||
kind: input.reference.kind,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import path from "path"
|
||||
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||
import { Effect } from "effect"
|
||||
import { Agent } from "@/agent/agent"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
@@ -13,7 +13,7 @@ import BUILD_SWITCH from "./prompt/build-switch.txt"
|
||||
import PLAN_MODE from "./prompt/plan-mode.txt"
|
||||
|
||||
export const apply = Effect.fn("SessionReminders.apply")(function* (input: {
|
||||
messages: SessionLegacy.WithParts[]
|
||||
messages: SessionV1.WithParts[]
|
||||
agent: Agent.Info
|
||||
session: Session.Info
|
||||
}) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { NamedError } from "@opencode-ai/core/util/error"
|
||||
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||
import { Cause, Clock, Duration, Effect, Schedule } from "effect"
|
||||
import { MessageV2 } from "./message-v2"
|
||||
import { iife } from "@/util/iife"
|
||||
@@ -32,7 +32,7 @@ function cap(ms: number) {
|
||||
return Math.min(ms, RETRY_MAX_DELAY)
|
||||
}
|
||||
|
||||
export function delay(attempt: number, error?: SessionLegacy.APIError) {
|
||||
export function delay(attempt: number, error?: SessionV1.APIError) {
|
||||
if (error) {
|
||||
const headers = error.data.responseHeaders
|
||||
if (headers) {
|
||||
@@ -67,8 +67,8 @@ export function delay(attempt: number, error?: SessionLegacy.APIError) {
|
||||
|
||||
export function retryable(error: Err, provider: string) {
|
||||
// context overflow errors should not be retried
|
||||
if (SessionLegacy.ContextOverflowError.isInstance(error)) return undefined
|
||||
if (SessionLegacy.APIError.isInstance(error)) {
|
||||
if (SessionV1.ContextOverflowError.isInstance(error)) return undefined
|
||||
if (SessionV1.APIError.isInstance(error)) {
|
||||
const status = error.data.statusCode
|
||||
// 5xx errors are transient server failures and should always be retried,
|
||||
// even when the provider SDK doesn't explicitly mark them as retryable.
|
||||
@@ -184,7 +184,7 @@ export function policy(opts: {
|
||||
const retry = retryable(error, opts.provider)
|
||||
if (!retry) return Cause.done(meta.attempt)
|
||||
return Effect.gen(function* () {
|
||||
const wait = delay(meta.attempt, SessionLegacy.APIError.isInstance(error) ? error : undefined)
|
||||
const wait = delay(meta.attempt, SessionV1.APIError.isInstance(error) ? error : undefined)
|
||||
const now = yield* Clock.currentTimeMillis
|
||||
yield* opts.set({
|
||||
attempt: meta.attempt,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Effect, Layer, Context, Schema } from "effect"
|
||||
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { Snapshot } from "../snapshot"
|
||||
import { Storage } from "@/storage/storage"
|
||||
@@ -40,7 +40,7 @@ export const layer = Layer.effect(
|
||||
const revert = Effect.fn("SessionRevert.revert")(function* (input: RevertInput) {
|
||||
yield* state.assertNotBusy(input.sessionID)
|
||||
const all = yield* sessions.messages({ sessionID: input.sessionID }).pipe(Effect.orDie)
|
||||
let lastUser: SessionLegacy.User | undefined
|
||||
let lastUser: SessionV1.User | undefined
|
||||
const session = yield* sessions.get(input.sessionID).pipe(Effect.orDie)
|
||||
|
||||
let rev: Session.Info["revert"]
|
||||
@@ -104,8 +104,8 @@ export const layer = Layer.effect(
|
||||
const sessionID = session.id
|
||||
const msgs = yield* sessions.messages({ sessionID }).pipe(Effect.orDie)
|
||||
const messageID = session.revert.messageID
|
||||
const remove = [] as SessionLegacy.WithParts[]
|
||||
let target: SessionLegacy.WithParts | undefined
|
||||
const remove = [] as SessionV1.WithParts[]
|
||||
let target: SessionV1.WithParts | undefined
|
||||
for (const msg of msgs) {
|
||||
if (msg.info.id < messageID) continue
|
||||
if (msg.info.id > messageID) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||
import { Runner } from "@/effect/runner"
|
||||
import { BackgroundJob } from "@/background/job"
|
||||
import { Effect, Latch, Layer, Scope, Context } from "effect"
|
||||
@@ -13,15 +13,15 @@ export interface Interface {
|
||||
readonly cancel: (sessionID: SessionID) => Effect.Effect<void>
|
||||
readonly ensureRunning: (
|
||||
sessionID: SessionID,
|
||||
onInterrupt: Effect.Effect<SessionLegacy.WithParts>,
|
||||
work: Effect.Effect<SessionLegacy.WithParts>,
|
||||
) => Effect.Effect<SessionLegacy.WithParts>
|
||||
onInterrupt: Effect.Effect<SessionV1.WithParts>,
|
||||
work: Effect.Effect<SessionV1.WithParts>,
|
||||
) => Effect.Effect<SessionV1.WithParts>
|
||||
readonly startShell: (
|
||||
sessionID: SessionID,
|
||||
onInterrupt: Effect.Effect<SessionLegacy.WithParts>,
|
||||
work: Effect.Effect<SessionLegacy.WithParts>,
|
||||
onInterrupt: Effect.Effect<SessionV1.WithParts>,
|
||||
work: Effect.Effect<SessionV1.WithParts>,
|
||||
ready?: Latch.Latch,
|
||||
) => Effect.Effect<SessionLegacy.WithParts, Session.BusyError>
|
||||
) => Effect.Effect<SessionV1.WithParts, Session.BusyError>
|
||||
}
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/SessionRunState") {}
|
||||
@@ -35,7 +35,7 @@ export const layer = Layer.effect(
|
||||
const state = yield* InstanceState.make(
|
||||
Effect.fn("SessionRunState.state")(function* () {
|
||||
const scope = yield* Scope.Scope
|
||||
const runners = new Map<SessionID, Runner.Runner<SessionLegacy.WithParts>>()
|
||||
const runners = new Map<SessionID, Runner.Runner<SessionV1.WithParts>>()
|
||||
yield* Effect.addFinalizer(
|
||||
Effect.fnUntraced(function* () {
|
||||
yield* Effect.forEach(runners.values(), (runner) => runner.cancel, {
|
||||
@@ -51,12 +51,12 @@ export const layer = Layer.effect(
|
||||
|
||||
const runner = Effect.fn("SessionRunState.runner")(function* (
|
||||
sessionID: SessionID,
|
||||
onInterrupt: Effect.Effect<SessionLegacy.WithParts>,
|
||||
onInterrupt: Effect.Effect<SessionV1.WithParts>,
|
||||
) {
|
||||
const data = yield* InstanceState.get(state)
|
||||
const existing = data.runners.get(sessionID)
|
||||
if (existing) return existing
|
||||
const next = Runner.make<SessionLegacy.WithParts>(data.scope, {
|
||||
const next = Runner.make<SessionV1.WithParts>(data.scope, {
|
||||
onIdle: Effect.gen(function* () {
|
||||
data.runners.delete(sessionID)
|
||||
yield* status.set(sessionID, { type: "idle" })
|
||||
@@ -87,16 +87,16 @@ export const layer = Layer.effect(
|
||||
|
||||
const ensureRunning = Effect.fn("SessionRunState.ensureRunning")(function* (
|
||||
sessionID: SessionID,
|
||||
onInterrupt: Effect.Effect<SessionLegacy.WithParts>,
|
||||
work: Effect.Effect<SessionLegacy.WithParts>,
|
||||
onInterrupt: Effect.Effect<SessionV1.WithParts>,
|
||||
work: Effect.Effect<SessionV1.WithParts>,
|
||||
) {
|
||||
return yield* (yield* runner(sessionID, onInterrupt)).ensureRunning(work)
|
||||
})
|
||||
|
||||
const startShell = Effect.fn("SessionRunState.startShell")(function* (
|
||||
sessionID: SessionID,
|
||||
onInterrupt: Effect.Effect<SessionLegacy.WithParts>,
|
||||
work: Effect.Effect<SessionLegacy.WithParts>,
|
||||
onInterrupt: Effect.Effect<SessionV1.WithParts>,
|
||||
work: Effect.Effect<SessionV1.WithParts>,
|
||||
ready?: Latch.Latch,
|
||||
) {
|
||||
return yield* (yield* runner(sessionID, onInterrupt))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PermissionLegacy } from "@opencode-ai/core/permission/legacy"
|
||||
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
|
||||
import { Slug } from "@opencode-ai/core/util/slug"
|
||||
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import path from "path"
|
||||
import { BackgroundJob } from "@/background/job"
|
||||
@@ -234,7 +234,7 @@ export const Info = Schema.Struct({
|
||||
version: Schema.String,
|
||||
metadata: optionalOmitUndefined(Metadata),
|
||||
time: Time,
|
||||
permission: optionalOmitUndefined(PermissionLegacy.Ruleset),
|
||||
permission: optionalOmitUndefined(PermissionV1.Ruleset),
|
||||
revert: optionalOmitUndefined(Revert),
|
||||
}).annotate({ identifier: "Session" })
|
||||
export type Info = Types.DeepMutable<Schema.Schema.Type<typeof Info>>
|
||||
@@ -259,7 +259,7 @@ export const CreateInput = Schema.optional(
|
||||
agent: Schema.optional(Schema.String),
|
||||
model: Schema.optional(Model),
|
||||
metadata: Schema.optional(Metadata),
|
||||
permission: Schema.optional(PermissionLegacy.Ruleset),
|
||||
permission: Schema.optional(PermissionV1.Ruleset),
|
||||
workspaceID: Schema.optional(WorkspaceV2.ID),
|
||||
}),
|
||||
)
|
||||
@@ -283,7 +283,7 @@ export const SetMetadataInput = Schema.Struct({
|
||||
})
|
||||
export const SetPermissionInput = Schema.Struct({
|
||||
sessionID: SessionID,
|
||||
permission: PermissionLegacy.Ruleset,
|
||||
permission: PermissionV1.Ruleset,
|
||||
})
|
||||
export const SetRevertInput = Schema.Struct({
|
||||
sessionID: SessionID,
|
||||
@@ -349,7 +349,7 @@ const UpdatedInfo = Schema.Struct({
|
||||
version: Schema.optional(Schema.NullOr(Schema.String)),
|
||||
metadata: Schema.optional(Schema.NullOr(Metadata)),
|
||||
time: Schema.optional(UpdatedTime),
|
||||
permission: Schema.optional(Schema.NullOr(PermissionLegacy.Ruleset)),
|
||||
permission: Schema.optional(Schema.NullOr(PermissionV1.Ruleset)),
|
||||
revert: Schema.optional(Schema.NullOr(Revert)),
|
||||
})
|
||||
|
||||
@@ -359,9 +359,9 @@ const UpdatedEventSchema = Schema.Struct({
|
||||
})
|
||||
|
||||
export const Event = {
|
||||
Created: SessionLegacy.Event.Created,
|
||||
Updated: SessionLegacy.Event.Updated,
|
||||
Deleted: SessionLegacy.Event.Deleted,
|
||||
Created: SessionV1.Event.Created,
|
||||
Updated: SessionV1.Event.Updated,
|
||||
Deleted: SessionV1.Event.Deleted,
|
||||
Diff: EventV2.define({
|
||||
type: "session.diff",
|
||||
schema: {
|
||||
@@ -373,9 +373,9 @@ export const Event = {
|
||||
type: "session.error",
|
||||
schema: {
|
||||
sessionID: Schema.optional(SessionID),
|
||||
// Reuses SessionLegacy.Assistant.fields.error (already Schema.optional) so
|
||||
// Reuses SessionV1.Assistant.fields.error (already Schema.optional) so
|
||||
// the derived schema keeps the same discriminated-union shape on the event stream.
|
||||
error: SessionLegacy.Assistant.fields.error,
|
||||
error: SessionV1.Assistant.fields.error,
|
||||
},
|
||||
}),
|
||||
}
|
||||
@@ -473,7 +473,7 @@ export interface Interface {
|
||||
agent?: string
|
||||
model?: Schema.Schema.Type<typeof Model>
|
||||
metadata?: typeof Metadata.Type
|
||||
permission?: PermissionLegacy.Ruleset
|
||||
permission?: PermissionV1.Ruleset
|
||||
workspaceID?: WorkspaceV2.ID
|
||||
}) => Effect.Effect<Info>
|
||||
readonly fork: (input: { sessionID: SessionID; messageID?: MessageID }) => Effect.Effect<Info, NotFound>
|
||||
@@ -482,7 +482,7 @@ export interface Interface {
|
||||
readonly setTitle: (input: { sessionID: SessionID; title: string }) => Effect.Effect<void>
|
||||
readonly setArchived: (input: { sessionID: SessionID; time?: number }) => Effect.Effect<void>
|
||||
readonly setMetadata: (input: typeof SetMetadataInput.Type) => Effect.Effect<void>
|
||||
readonly setPermission: (input: { sessionID: SessionID; permission: PermissionLegacy.Ruleset }) => Effect.Effect<void>
|
||||
readonly setPermission: (input: { sessionID: SessionID; permission: PermissionV1.Ruleset }) => Effect.Effect<void>
|
||||
readonly setRevert: (input: {
|
||||
sessionID: SessionID
|
||||
revert: Info["revert"]
|
||||
@@ -496,18 +496,18 @@ export interface Interface {
|
||||
readonly messages: (input: {
|
||||
sessionID: SessionID
|
||||
limit?: number
|
||||
}) => Effect.Effect<SessionLegacy.WithParts[], NotFound>
|
||||
}) => Effect.Effect<SessionV1.WithParts[], NotFound>
|
||||
readonly children: (parentID: SessionID) => Effect.Effect<Info[]>
|
||||
readonly remove: (sessionID: SessionID) => Effect.Effect<void, NotFound>
|
||||
readonly updateMessage: <T extends SessionLegacy.Info>(msg: T) => Effect.Effect<T>
|
||||
readonly updateMessage: <T extends SessionV1.Info>(msg: T) => Effect.Effect<T>
|
||||
readonly removeMessage: (input: { sessionID: SessionID; messageID: MessageID }) => Effect.Effect<MessageID>
|
||||
readonly removePart: (input: { sessionID: SessionID; messageID: MessageID; partID: PartID }) => Effect.Effect<PartID>
|
||||
readonly getPart: (input: {
|
||||
sessionID: SessionID
|
||||
messageID: MessageID
|
||||
partID: PartID
|
||||
}) => Effect.Effect<SessionLegacy.Part | undefined>
|
||||
readonly updatePart: <T extends SessionLegacy.Part>(part: T) => Effect.Effect<T>
|
||||
}) => Effect.Effect<SessionV1.Part | undefined>
|
||||
readonly updatePart: <T extends SessionV1.Part>(part: T) => Effect.Effect<T>
|
||||
readonly updatePartDelta: (input: {
|
||||
sessionID: SessionID
|
||||
messageID: MessageID
|
||||
@@ -518,8 +518,8 @@ export interface Interface {
|
||||
/** Finds the first message matching the predicate, searching newest-first. */
|
||||
readonly findMessage: (
|
||||
sessionID: SessionID,
|
||||
predicate: (msg: SessionLegacy.WithParts) => boolean,
|
||||
) => Effect.Effect<Option.Option<SessionLegacy.WithParts>, NotFound>
|
||||
predicate: (msg: SessionV1.WithParts) => boolean,
|
||||
) => Effect.Effect<Option.Option<SessionV1.WithParts>, NotFound>
|
||||
}
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/Session") {}
|
||||
@@ -571,7 +571,7 @@ export const layer: Layer.Layer<
|
||||
directory: string
|
||||
path?: string
|
||||
metadata?: typeof Metadata.Type
|
||||
permission?: PermissionLegacy.Ruleset
|
||||
permission?: PermissionV1.Ruleset
|
||||
}) {
|
||||
const ctx = yield* InstanceState.context
|
||||
const result: Info = {
|
||||
@@ -598,7 +598,7 @@ export const layer: Layer.Layer<
|
||||
log.info("created", result)
|
||||
|
||||
yield* events.publish(
|
||||
SessionLegacy.Event.Created,
|
||||
SessionV1.Event.Created,
|
||||
{ sessionID: result.id, info: result },
|
||||
{ location: eventLocation(result) },
|
||||
)
|
||||
@@ -689,7 +689,7 @@ export const layer: Layer.Layer<
|
||||
}
|
||||
|
||||
yield* events.publish(
|
||||
SessionLegacy.Event.Deleted,
|
||||
SessionV1.Event.Deleted,
|
||||
{ sessionID, info: session },
|
||||
{ location: eventLocation(session) },
|
||||
)
|
||||
@@ -699,18 +699,18 @@ export const layer: Layer.Layer<
|
||||
}
|
||||
})
|
||||
|
||||
const updateMessage = <T extends SessionLegacy.Info>(msg: T): Effect.Effect<T> =>
|
||||
const updateMessage = <T extends SessionV1.Info>(msg: T): Effect.Effect<T> =>
|
||||
Effect.gen(function* () {
|
||||
const location = yield* locationForSession(msg.sessionID)
|
||||
yield* events.publish(SessionLegacy.Event.MessageUpdated, { sessionID: msg.sessionID, info: msg }, { location })
|
||||
yield* events.publish(SessionV1.Event.MessageUpdated, { sessionID: msg.sessionID, info: msg }, { location })
|
||||
return msg
|
||||
}).pipe(Effect.withSpan("Session.updateMessage"))
|
||||
|
||||
const updatePart = <T extends SessionLegacy.Part>(part: T): Effect.Effect<T> =>
|
||||
const updatePart = <T extends SessionV1.Part>(part: T): Effect.Effect<T> =>
|
||||
Effect.gen(function* () {
|
||||
const location = yield* locationForSession(part.sessionID)
|
||||
yield* events.publish(
|
||||
SessionLegacy.Event.PartUpdated,
|
||||
SessionV1.Event.PartUpdated,
|
||||
{
|
||||
sessionID: part.sessionID,
|
||||
part: structuredClone(part),
|
||||
@@ -740,7 +740,7 @@ export const layer: Layer.Layer<
|
||||
id: row.id,
|
||||
sessionID: row.session_id,
|
||||
messageID: row.message_id,
|
||||
} as SessionLegacy.Part
|
||||
} as SessionV1.Part
|
||||
})
|
||||
|
||||
const create = Effect.fn("Session.create")(function* (input?: {
|
||||
@@ -749,7 +749,7 @@ export const layer: Layer.Layer<
|
||||
agent?: string
|
||||
model?: Schema.Schema.Type<typeof Model>
|
||||
metadata?: typeof Metadata.Type
|
||||
permission?: PermissionLegacy.Ruleset
|
||||
permission?: PermissionV1.Ruleset
|
||||
workspaceID?: WorkspaceV2.ID
|
||||
}) {
|
||||
const ctx = yield* InstanceState.context
|
||||
@@ -795,7 +795,7 @@ export const layer: Layer.Layer<
|
||||
})
|
||||
|
||||
for (const part of msg.parts) {
|
||||
const p: SessionLegacy.Part = {
|
||||
const p: SessionV1.Part = {
|
||||
...part,
|
||||
id: PartID.ascending(),
|
||||
messageID: cloned.id,
|
||||
@@ -822,7 +822,7 @@ export const layer: Layer.Layer<
|
||||
revert: info.revert === null ? undefined : (info.revert ?? current.revert),
|
||||
permission: info.permission === null ? undefined : (info.permission ?? current.permission),
|
||||
} as Info
|
||||
yield* events.publish(SessionLegacy.Event.Updated, { sessionID, info: next }, { location: eventLocation(next) })
|
||||
yield* events.publish(SessionV1.Event.Updated, { sessionID, info: next }, { location: eventLocation(next) })
|
||||
})
|
||||
|
||||
const touch = Effect.fn("Session.touch")(function* (sessionID: SessionID) {
|
||||
@@ -843,7 +843,7 @@ export const layer: Layer.Layer<
|
||||
|
||||
const setPermission = Effect.fn("Session.setPermission")(function* (input: {
|
||||
sessionID: SessionID
|
||||
permission: PermissionLegacy.Ruleset
|
||||
permission: PermissionV1.Ruleset
|
||||
}) {
|
||||
yield* patch(input.sessionID, { permission: [...input.permission], time: { updated: Date.now() } }).pipe(
|
||||
Effect.orDie,
|
||||
@@ -899,7 +899,7 @@ export const layer: Layer.Layer<
|
||||
}
|
||||
|
||||
const size = 50
|
||||
const result = [] as SessionLegacy.WithParts[]
|
||||
const result = [] as SessionV1.WithParts[]
|
||||
let before: string | undefined
|
||||
while (true) {
|
||||
const page = yield* MessageV2.page({ sessionID: input.sessionID, limit: size, before }).pipe(
|
||||
@@ -922,7 +922,7 @@ export const layer: Layer.Layer<
|
||||
}) {
|
||||
const location = yield* locationForSession(input.sessionID)
|
||||
yield* events.publish(
|
||||
SessionLegacy.Event.MessageRemoved,
|
||||
SessionV1.Event.MessageRemoved,
|
||||
{
|
||||
sessionID: input.sessionID,
|
||||
messageID: input.messageID,
|
||||
@@ -939,7 +939,7 @@ export const layer: Layer.Layer<
|
||||
}) {
|
||||
const location = yield* locationForSession(input.sessionID)
|
||||
yield* events.publish(
|
||||
SessionLegacy.Event.PartRemoved,
|
||||
SessionV1.Event.PartRemoved,
|
||||
{
|
||||
sessionID: input.sessionID,
|
||||
messageID: input.messageID,
|
||||
@@ -976,7 +976,7 @@ export const layer: Layer.Layer<
|
||||
if (!page.more || !page.cursor) break
|
||||
before = page.cursor
|
||||
}
|
||||
return Option.none<SessionLegacy.WithParts>()
|
||||
return Option.none<SessionV1.WithParts>()
|
||||
})
|
||||
|
||||
return Service.of({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Effect, Layer, Context, Schema } from "effect"
|
||||
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { Snapshot } from "@/snapshot"
|
||||
import { Session } from "./session"
|
||||
@@ -65,7 +65,7 @@ function unquoteGitPath(input: string) {
|
||||
export interface Interface {
|
||||
readonly summarize: (input: { sessionID: SessionID; messageID: MessageID }) => Effect.Effect<void>
|
||||
readonly diff: (input: { sessionID: SessionID; messageID?: MessageID }) => Effect.Effect<Snapshot.FileDiff[]>
|
||||
readonly computeDiff: (input: { messages: SessionLegacy.WithParts[] }) => Effect.Effect<Snapshot.FileDiff[]>
|
||||
readonly computeDiff: (input: { messages: SessionV1.WithParts[] }) => Effect.Effect<Snapshot.FileDiff[]>
|
||||
}
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/SessionSummary") {}
|
||||
@@ -79,7 +79,7 @@ export const layer = Layer.effect(
|
||||
const config = yield* Config.Service
|
||||
|
||||
const computeDiff = Effect.fn("SessionSummary.computeDiff")(function* (input: {
|
||||
messages: SessionLegacy.WithParts[]
|
||||
messages: SessionV1.WithParts[]
|
||||
}) {
|
||||
let from: string | undefined
|
||||
let to: string | undefined
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Agent } from "@/agent/agent"
|
||||
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||
import { Provider } from "@/provider/provider"
|
||||
import { ProviderTransform } from "@/provider/transform"
|
||||
import { MCP } from "@/mcp"
|
||||
@@ -29,7 +29,7 @@ export const resolve = Effect.fn("SessionTools.resolve")(function* (input: {
|
||||
session: Session.Info
|
||||
processor: Pick<SessionProcessor.Handle, "message" | "updateToolCall" | "completeToolCall">
|
||||
bypassAgentCheck: boolean
|
||||
messages: SessionLegacy.WithParts[]
|
||||
messages: SessionV1.WithParts[]
|
||||
promptOps: TaskPromptOps
|
||||
}) {
|
||||
using _ = log.time("resolveTools")
|
||||
@@ -153,7 +153,7 @@ export const resolve = Effect.fn("SessionTools.resolve")(function* (input: {
|
||||
)
|
||||
|
||||
const textParts: string[] = []
|
||||
const attachments: Omit<SessionLegacy.FilePart, "id" | "sessionID" | "messageID">[] = []
|
||||
const attachments: Omit<SessionV1.FilePart, "id" | "sessionID" | "messageID">[] = []
|
||||
for (const contentItem of result.content) {
|
||||
if (contentItem.type === "text") textParts.push(contentItem.text)
|
||||
else if (contentItem.type === "image") {
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Global } from "@opencode-ai/core/global"
|
||||
import { Permission } from "@/permission"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { Config } from "@/config/config"
|
||||
import { FrontmatterError } from "@opencode-ai/core/v1/config/error"
|
||||
import { ConfigMarkdown } from "@/config/markdown"
|
||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
import { Glob } from "@opencode-ai/core/util/glob"
|
||||
@@ -108,7 +109,7 @@ const add = Effect.fnUntraced(function* (state: State, match: string, events: Ev
|
||||
}).pipe(
|
||||
Effect.catch(
|
||||
Effect.fnUntraced(function* (err) {
|
||||
const message = ConfigMarkdown.FrontmatterError.isInstance(err)
|
||||
const message = FrontmatterError.isInstance(err)
|
||||
? err.data.message
|
||||
: `Failed to parse skill ${match}`
|
||||
const { Session } = yield* Effect.promise(() => import("@/session/session"))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import path from "path"
|
||||
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||
import { Effect, Schema } from "effect"
|
||||
import * as Tool from "./tool"
|
||||
import { Question } from "../question"
|
||||
@@ -50,7 +50,7 @@ export const PlanExitTool = Tool.define(
|
||||
const model =
|
||||
lastUser?.info.role === "user" && lastUser.info.model ? lastUser.info.model : yield* provider.defaultModel()
|
||||
|
||||
const msg: SessionLegacy.User = {
|
||||
const msg: SessionV1.User = {
|
||||
id: MessageID.ascending(),
|
||||
sessionID: ctx.sessionID,
|
||||
role: "user",
|
||||
@@ -66,7 +66,7 @@ export const PlanExitTool = Tool.define(
|
||||
type: "text",
|
||||
text: `The plan at ${plan} has been approved, you can now edit files. Execute the plan`,
|
||||
synthetic: true,
|
||||
} satisfies SessionLegacy.TextPart)
|
||||
} satisfies SessionV1.TextPart)
|
||||
|
||||
return {
|
||||
title: "Switching to build agent",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as Tool from "./tool"
|
||||
import DESCRIPTION from "./task.txt"
|
||||
import { ToolJsonSchema } from "./json-schema"
|
||||
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||
import { BackgroundJob } from "@/background/job"
|
||||
import { Session } from "@/session/session"
|
||||
import { SessionID, MessageID } from "../session/schema"
|
||||
@@ -18,7 +18,7 @@ import { Database } from "@opencode-ai/core/database/database"
|
||||
export interface TaskPromptOps {
|
||||
cancel(sessionID: SessionID): Effect.Effect<void>
|
||||
resolvePromptParts(template: string): Effect.Effect<SessionPrompt.PromptInput["parts"]>
|
||||
prompt(input: SessionPrompt.PromptInput): Effect.Effect<SessionLegacy.WithParts>
|
||||
prompt(input: SessionPrompt.PromptInput): Effect.Effect<SessionV1.WithParts>
|
||||
}
|
||||
|
||||
const id = "task"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PermissionLegacy } from "@opencode-ai/core/permission/legacy"
|
||||
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
|
||||
import { Effect, Schema } from "effect"
|
||||
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||
import type { JSONSchema7 } from "@ai-sdk/provider"
|
||||
import type { MessageV2 } from "../session/message-v2"
|
||||
import type { Permission } from "../permission"
|
||||
@@ -40,16 +40,16 @@ export type Context<M extends Metadata = Metadata> = {
|
||||
abort: AbortSignal
|
||||
callID?: string
|
||||
extra?: { [key: string]: unknown }
|
||||
messages: SessionLegacy.WithParts[]
|
||||
messages: SessionV1.WithParts[]
|
||||
metadata(input: { title?: string; metadata?: M }): Effect.Effect<void>
|
||||
ask(input: Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">): Effect.Effect<void>
|
||||
ask(input: Omit<PermissionV1.Request, "id" | "sessionID" | "tool">): Effect.Effect<void>
|
||||
}
|
||||
|
||||
export interface ExecuteResult<M extends Metadata = Metadata> {
|
||||
title: string
|
||||
metadata: M
|
||||
output: string
|
||||
attachments?: Omit<SessionLegacy.FilePart, "id" | "sessionID" | "messageID">[]
|
||||
attachments?: Omit<SessionV1.FilePart, "id" | "sessionID" | "messageID">[]
|
||||
}
|
||||
|
||||
export interface Def<
|
||||
|
||||
Reference in New Issue
Block a user