mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-15 17:08:21 -04:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 70b493af15 | |||
| 81f6e06681 | |||
| da68e2865e | |||
| f86e1cc1ff |
@@ -55,7 +55,7 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) {
|
|||||||
const path = url.pathname
|
const path = url.pathname
|
||||||
if (path === "/global/event" || path === "/event") return sse(route, config.events?.(), config.eventRetry)
|
if (path === "/global/event" || path === "/event") return sse(route, config.events?.(), config.eventRetry)
|
||||||
if (path === "/global/health") return json(route, { healthy: true })
|
if (path === "/global/health") return json(route, { healthy: true })
|
||||||
if (path === "/experimental/capabilities") return json(route, { backgroundSubagents: false })
|
if (path === "/experimental/capabilities") return json(route, { backgroundSubagents: true })
|
||||||
if (path === "/permission")
|
if (path === "/permission")
|
||||||
return json(route, typeof config.permissions === "function" ? config.permissions() : (config.permissions ?? []))
|
return json(route, typeof config.permissions === "function" ? config.permissions() : (config.permissions ?? []))
|
||||||
if (path === "/question")
|
if (path === "/question")
|
||||||
|
|||||||
@@ -84,7 +84,6 @@ type RunFooterOptions = {
|
|||||||
theme: RunTheme
|
theme: RunTheme
|
||||||
keymap: Keymap<Renderable, KeyEvent>
|
keymap: Keymap<Renderable, KeyEvent>
|
||||||
tuiConfig: RunTuiConfig
|
tuiConfig: RunTuiConfig
|
||||||
backgroundSubagents: boolean
|
|
||||||
diffStyle: RunDiffStyle
|
diffStyle: RunDiffStyle
|
||||||
onPermissionReply: (input: PermissionReply) => void | Promise<void>
|
onPermissionReply: (input: PermissionReply) => void | Promise<void>
|
||||||
onQuestionReply: (input: QuestionReply) => void | Promise<void>
|
onQuestionReply: (input: QuestionReply) => void | Promise<void>
|
||||||
@@ -326,7 +325,6 @@ export class RunFooter implements FooterApi {
|
|||||||
theme: footer.theme,
|
theme: footer.theme,
|
||||||
diffStyle: options.diffStyle,
|
diffStyle: options.diffStyle,
|
||||||
tuiConfig: options.tuiConfig,
|
tuiConfig: options.tuiConfig,
|
||||||
backgroundSubagents: options.backgroundSubagents,
|
|
||||||
history: footer.history,
|
history: footer.history,
|
||||||
agent: options.agentLabel,
|
agent: options.agentLabel,
|
||||||
onSubmit: footer.handlePrompt,
|
onSubmit: footer.handlePrompt,
|
||||||
|
|||||||
@@ -89,7 +89,6 @@ type RunFooterViewProps = {
|
|||||||
theme: () => RunTheme
|
theme: () => RunTheme
|
||||||
diffStyle?: RunDiffStyle
|
diffStyle?: RunDiffStyle
|
||||||
tuiConfig: RunTuiConfig
|
tuiConfig: RunTuiConfig
|
||||||
backgroundSubagents: boolean
|
|
||||||
history?: () => RunPrompt[]
|
history?: () => RunPrompt[]
|
||||||
agent: string
|
agent: string
|
||||||
onSubmit: (input: RunPrompt) => boolean
|
onSubmit: (input: RunPrompt) => boolean
|
||||||
@@ -169,9 +168,7 @@ export function RunFooterView(props: RunFooterViewProps) {
|
|||||||
|
|
||||||
return tabs().findIndex((item) => item.sessionID === sessionID) + 1
|
return tabs().findIndex((item) => item.sessionID === sessionID) + 1
|
||||||
})
|
})
|
||||||
const foregroundSubagents = createMemo(
|
const foregroundSubagents = createMemo(() => activeTabs().some((item) => !item.background))
|
||||||
() => props.backgroundSubagents && activeTabs().some((item) => !item.background),
|
|
||||||
)
|
|
||||||
const model = createMemo(() => {
|
const model = createMemo(() => {
|
||||||
const current = props.currentModel()
|
const current = props.currentModel()
|
||||||
return current ? modelInfo(props.providers(), current) : { model: props.state().model, provider: undefined }
|
return current ? modelInfo(props.providers(), current) : { model: props.state().model, provider: undefined }
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { NodeFileSystem } from "@effect/platform-node"
|
import { NodeFileSystem } from "@effect/platform-node"
|
||||||
import { OpenCode, type OpenCodeClient } from "@opencode-ai/client/promise"
|
import { OpenCode, type OpenCodeClient } from "@opencode-ai/client/promise"
|
||||||
import { truthy } from "@opencode-ai/core/flag/flag"
|
|
||||||
import { Global } from "@opencode-ai/core/global"
|
import { Global } from "@opencode-ai/core/global"
|
||||||
import { Effect } from "effect"
|
import { Effect } from "effect"
|
||||||
import path from "node:path"
|
import path from "node:path"
|
||||||
@@ -84,9 +83,6 @@ export async function runMini(input: MiniCommandInput) {
|
|||||||
files: [],
|
files: [],
|
||||||
initialInput,
|
initialInput,
|
||||||
thinking: true,
|
thinking: true,
|
||||||
backgroundSubagents:
|
|
||||||
truthy("OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS") ||
|
|
||||||
(process.env.OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS === undefined && truthy("OPENCODE_EXPERIMENTAL")),
|
|
||||||
replay: input.replay ?? true,
|
replay: input.replay ?? true,
|
||||||
replayLimit: input.replayLimit,
|
replayLimit: input.replayLimit,
|
||||||
demo: input.demo,
|
demo: input.demo,
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ export type LifecycleInput = {
|
|||||||
model: RunInput["model"]
|
model: RunInput["model"]
|
||||||
variant: string | undefined
|
variant: string | undefined
|
||||||
tuiConfig: RunTuiConfig | Promise<RunTuiConfig>
|
tuiConfig: RunTuiConfig | Promise<RunTuiConfig>
|
||||||
backgroundSubagents: boolean
|
|
||||||
onPermissionReply: (input: PermissionReply) => void | Promise<void>
|
onPermissionReply: (input: PermissionReply) => void | Promise<void>
|
||||||
onQuestionReply: (input: QuestionReply) => void | Promise<void>
|
onQuestionReply: (input: QuestionReply) => void | Promise<void>
|
||||||
onQuestionReject: (input: QuestionReject) => void | Promise<void>
|
onQuestionReject: (input: QuestionReject) => void | Promise<void>
|
||||||
@@ -236,7 +235,6 @@ export async function createRuntimeLifecycle(input: LifecycleInput): Promise<Lif
|
|||||||
wrote,
|
wrote,
|
||||||
keymap,
|
keymap,
|
||||||
tuiConfig,
|
tuiConfig,
|
||||||
backgroundSubagents: input.backgroundSubagents,
|
|
||||||
diffStyle: tuiConfig.diff_style ?? "auto",
|
diffStyle: tuiConfig.diff_style ?? "auto",
|
||||||
onPermissionReply: input.onPermissionReply,
|
onPermissionReply: input.onPermissionReply,
|
||||||
onQuestionReply: input.onQuestionReply,
|
onQuestionReply: input.onQuestionReply,
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ type RunRuntimeInput = {
|
|||||||
files: RunInput["files"]
|
files: RunInput["files"]
|
||||||
initialInput?: string
|
initialInput?: string
|
||||||
thinking: boolean
|
thinking: boolean
|
||||||
backgroundSubagents: boolean
|
|
||||||
replay?: boolean
|
replay?: boolean
|
||||||
replayLimit?: number
|
replayLimit?: number
|
||||||
demo?: RunInput["demo"]
|
demo?: RunInput["demo"]
|
||||||
@@ -75,7 +74,6 @@ type RunDeferredInput = {
|
|||||||
files: RunInput["files"]
|
files: RunInput["files"]
|
||||||
initialInput?: string
|
initialInput?: string
|
||||||
thinking: boolean
|
thinking: boolean
|
||||||
backgroundSubagents: boolean
|
|
||||||
replay?: boolean
|
replay?: boolean
|
||||||
replayLimit?: number
|
replayLimit?: number
|
||||||
demo?: RunInput["demo"]
|
demo?: RunInput["demo"]
|
||||||
@@ -270,7 +268,6 @@ async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDep
|
|||||||
model: state.model,
|
model: state.model,
|
||||||
variant: state.activeVariant,
|
variant: state.activeVariant,
|
||||||
tuiConfig: tuiConfigTask,
|
tuiConfig: tuiConfigTask,
|
||||||
backgroundSubagents: input.backgroundSubagents,
|
|
||||||
onPermissionReply: async (next) => {
|
onPermissionReply: async (next) => {
|
||||||
if (state.demo?.permission(next)) {
|
if (state.demo?.permission(next)) {
|
||||||
return
|
return
|
||||||
@@ -876,7 +873,6 @@ export async function runInteractiveDeferredMode(input: RunDeferredInput, deps?:
|
|||||||
files: input.files,
|
files: input.files,
|
||||||
initialInput: input.initialInput,
|
initialInput: input.initialInput,
|
||||||
thinking: input.thinking,
|
thinking: input.thinking,
|
||||||
backgroundSubagents: input.backgroundSubagents,
|
|
||||||
replay: input.replay,
|
replay: input.replay,
|
||||||
replayLimit: input.replayLimit,
|
replayLimit: input.replayLimit,
|
||||||
demo: input.demo,
|
demo: input.demo,
|
||||||
@@ -928,7 +924,6 @@ export async function runInteractiveMode(
|
|||||||
files: input.files,
|
files: input.files,
|
||||||
initialInput: input.initialInput,
|
initialInput: input.initialInput,
|
||||||
thinking: input.thinking,
|
thinking: input.thinking,
|
||||||
backgroundSubagents: input.backgroundSubagents,
|
|
||||||
replay: input.replay,
|
replay: input.replay,
|
||||||
replayLimit: input.replayLimit,
|
replayLimit: input.replayLimit,
|
||||||
demo: input.demo,
|
demo: input.demo,
|
||||||
|
|||||||
@@ -135,7 +135,6 @@ export type RunInput = {
|
|||||||
files: RunFilePart[]
|
files: RunFilePart[]
|
||||||
initialInput?: string
|
initialInput?: string
|
||||||
thinking: boolean
|
thinking: boolean
|
||||||
backgroundSubagents: boolean
|
|
||||||
demo?: boolean
|
demo?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -111,158 +111,167 @@ export type Endpoint4_8Input = {
|
|||||||
export type Endpoint4_8Output = EffectValue<ReturnType<RawClient["server.session"]["session.rename"]>>
|
export type Endpoint4_8Output = EffectValue<ReturnType<RawClient["server.session"]["session.rename"]>>
|
||||||
export type SessionRenameOperation<E = never> = (input: Endpoint4_8Input) => Effect.Effect<Endpoint4_8Output, E>
|
export type SessionRenameOperation<E = never> = (input: Endpoint4_8Input) => Effect.Effect<Endpoint4_8Output, E>
|
||||||
|
|
||||||
type Endpoint4_9Request = Parameters<RawClient["server.session"]["session.prompt"]>[0]
|
type Endpoint4_9Request = Parameters<RawClient["server.session"]["session.move"]>[0]
|
||||||
export type Endpoint4_9Input = {
|
export type Endpoint4_9Input = {
|
||||||
readonly sessionID: Endpoint4_9Request["params"]["sessionID"]
|
readonly sessionID: Endpoint4_9Request["params"]["sessionID"]
|
||||||
readonly id?: Endpoint4_9Request["payload"]["id"]
|
readonly destination: Endpoint4_9Request["payload"]["destination"]
|
||||||
readonly prompt: Endpoint4_9Request["payload"]["prompt"]
|
readonly moveChanges?: Endpoint4_9Request["payload"]["moveChanges"]
|
||||||
readonly delivery?: Endpoint4_9Request["payload"]["delivery"]
|
|
||||||
readonly resume?: Endpoint4_9Request["payload"]["resume"]
|
|
||||||
}
|
}
|
||||||
export type Endpoint4_9Output = EffectValue<ReturnType<RawClient["server.session"]["session.prompt"]>>["data"]
|
export type Endpoint4_9Output = EffectValue<ReturnType<RawClient["server.session"]["session.move"]>>
|
||||||
export type SessionPromptOperation<E = never> = (input: Endpoint4_9Input) => Effect.Effect<Endpoint4_9Output, E>
|
export type SessionMoveOperation<E = never> = (input: Endpoint4_9Input) => Effect.Effect<Endpoint4_9Output, E>
|
||||||
|
|
||||||
type Endpoint4_10Request = Parameters<RawClient["server.session"]["session.command"]>[0]
|
type Endpoint4_10Request = Parameters<RawClient["server.session"]["session.prompt"]>[0]
|
||||||
export type Endpoint4_10Input = {
|
export type Endpoint4_10Input = {
|
||||||
readonly sessionID: Endpoint4_10Request["params"]["sessionID"]
|
readonly sessionID: Endpoint4_10Request["params"]["sessionID"]
|
||||||
readonly id?: Endpoint4_10Request["payload"]["id"]
|
readonly id?: Endpoint4_10Request["payload"]["id"]
|
||||||
readonly command: Endpoint4_10Request["payload"]["command"]
|
readonly prompt: Endpoint4_10Request["payload"]["prompt"]
|
||||||
readonly arguments?: Endpoint4_10Request["payload"]["arguments"]
|
|
||||||
readonly agent?: Endpoint4_10Request["payload"]["agent"]
|
|
||||||
readonly model?: Endpoint4_10Request["payload"]["model"]
|
|
||||||
readonly files?: Endpoint4_10Request["payload"]["files"]
|
|
||||||
readonly agents?: Endpoint4_10Request["payload"]["agents"]
|
|
||||||
readonly delivery?: Endpoint4_10Request["payload"]["delivery"]
|
readonly delivery?: Endpoint4_10Request["payload"]["delivery"]
|
||||||
readonly resume?: Endpoint4_10Request["payload"]["resume"]
|
readonly resume?: Endpoint4_10Request["payload"]["resume"]
|
||||||
}
|
}
|
||||||
export type Endpoint4_10Output = EffectValue<ReturnType<RawClient["server.session"]["session.command"]>>["data"]
|
export type Endpoint4_10Output = EffectValue<ReturnType<RawClient["server.session"]["session.prompt"]>>["data"]
|
||||||
export type SessionCommandOperation<E = never> = (input: Endpoint4_10Input) => Effect.Effect<Endpoint4_10Output, E>
|
export type SessionPromptOperation<E = never> = (input: Endpoint4_10Input) => Effect.Effect<Endpoint4_10Output, E>
|
||||||
|
|
||||||
type Endpoint4_11Request = Parameters<RawClient["server.session"]["session.skill"]>[0]
|
type Endpoint4_11Request = Parameters<RawClient["server.session"]["session.command"]>[0]
|
||||||
export type Endpoint4_11Input = {
|
export type Endpoint4_11Input = {
|
||||||
readonly sessionID: Endpoint4_11Request["params"]["sessionID"]
|
readonly sessionID: Endpoint4_11Request["params"]["sessionID"]
|
||||||
readonly id?: Endpoint4_11Request["payload"]["id"]
|
readonly id?: Endpoint4_11Request["payload"]["id"]
|
||||||
readonly skill: Endpoint4_11Request["payload"]["skill"]
|
readonly command: Endpoint4_11Request["payload"]["command"]
|
||||||
|
readonly arguments?: Endpoint4_11Request["payload"]["arguments"]
|
||||||
|
readonly agent?: Endpoint4_11Request["payload"]["agent"]
|
||||||
|
readonly model?: Endpoint4_11Request["payload"]["model"]
|
||||||
|
readonly files?: Endpoint4_11Request["payload"]["files"]
|
||||||
|
readonly agents?: Endpoint4_11Request["payload"]["agents"]
|
||||||
|
readonly delivery?: Endpoint4_11Request["payload"]["delivery"]
|
||||||
readonly resume?: Endpoint4_11Request["payload"]["resume"]
|
readonly resume?: Endpoint4_11Request["payload"]["resume"]
|
||||||
}
|
}
|
||||||
export type Endpoint4_11Output = EffectValue<ReturnType<RawClient["server.session"]["session.skill"]>>
|
export type Endpoint4_11Output = EffectValue<ReturnType<RawClient["server.session"]["session.command"]>>["data"]
|
||||||
export type SessionSkillOperation<E = never> = (input: Endpoint4_11Input) => Effect.Effect<Endpoint4_11Output, E>
|
export type SessionCommandOperation<E = never> = (input: Endpoint4_11Input) => Effect.Effect<Endpoint4_11Output, E>
|
||||||
|
|
||||||
type Endpoint4_12Request = Parameters<RawClient["server.session"]["session.synthetic"]>[0]
|
type Endpoint4_12Request = Parameters<RawClient["server.session"]["session.skill"]>[0]
|
||||||
export type Endpoint4_12Input = {
|
export type Endpoint4_12Input = {
|
||||||
readonly sessionID: Endpoint4_12Request["params"]["sessionID"]
|
readonly sessionID: Endpoint4_12Request["params"]["sessionID"]
|
||||||
readonly text: Endpoint4_12Request["payload"]["text"]
|
readonly id?: Endpoint4_12Request["payload"]["id"]
|
||||||
readonly description?: Endpoint4_12Request["payload"]["description"]
|
readonly skill: Endpoint4_12Request["payload"]["skill"]
|
||||||
readonly metadata?: Endpoint4_12Request["payload"]["metadata"]
|
|
||||||
readonly resume?: Endpoint4_12Request["payload"]["resume"]
|
readonly resume?: Endpoint4_12Request["payload"]["resume"]
|
||||||
}
|
}
|
||||||
export type Endpoint4_12Output = EffectValue<ReturnType<RawClient["server.session"]["session.synthetic"]>>
|
export type Endpoint4_12Output = EffectValue<ReturnType<RawClient["server.session"]["session.skill"]>>
|
||||||
export type SessionSyntheticOperation<E = never> = (input: Endpoint4_12Input) => Effect.Effect<Endpoint4_12Output, E>
|
export type SessionSkillOperation<E = never> = (input: Endpoint4_12Input) => Effect.Effect<Endpoint4_12Output, E>
|
||||||
|
|
||||||
type Endpoint4_13Request = Parameters<RawClient["server.session"]["session.shell"]>[0]
|
type Endpoint4_13Request = Parameters<RawClient["server.session"]["session.synthetic"]>[0]
|
||||||
export type Endpoint4_13Input = {
|
export type Endpoint4_13Input = {
|
||||||
readonly sessionID: Endpoint4_13Request["params"]["sessionID"]
|
readonly sessionID: Endpoint4_13Request["params"]["sessionID"]
|
||||||
readonly id?: Endpoint4_13Request["payload"]["id"]
|
readonly text: Endpoint4_13Request["payload"]["text"]
|
||||||
readonly command: Endpoint4_13Request["payload"]["command"]
|
readonly description?: Endpoint4_13Request["payload"]["description"]
|
||||||
|
readonly metadata?: Endpoint4_13Request["payload"]["metadata"]
|
||||||
|
readonly resume?: Endpoint4_13Request["payload"]["resume"]
|
||||||
}
|
}
|
||||||
export type Endpoint4_13Output = EffectValue<ReturnType<RawClient["server.session"]["session.shell"]>>
|
export type Endpoint4_13Output = EffectValue<ReturnType<RawClient["server.session"]["session.synthetic"]>>
|
||||||
export type SessionShellOperation<E = never> = (input: Endpoint4_13Input) => Effect.Effect<Endpoint4_13Output, E>
|
export type SessionSyntheticOperation<E = never> = (input: Endpoint4_13Input) => Effect.Effect<Endpoint4_13Output, E>
|
||||||
|
|
||||||
type Endpoint4_14Request = Parameters<RawClient["server.session"]["session.compact"]>[0]
|
type Endpoint4_14Request = Parameters<RawClient["server.session"]["session.shell"]>[0]
|
||||||
export type Endpoint4_14Input = {
|
export type Endpoint4_14Input = {
|
||||||
readonly sessionID: Endpoint4_14Request["params"]["sessionID"]
|
readonly sessionID: Endpoint4_14Request["params"]["sessionID"]
|
||||||
readonly id?: Endpoint4_14Request["payload"]["id"]
|
readonly id?: Endpoint4_14Request["payload"]["id"]
|
||||||
|
readonly command: Endpoint4_14Request["payload"]["command"]
|
||||||
}
|
}
|
||||||
export type Endpoint4_14Output = EffectValue<ReturnType<RawClient["server.session"]["session.compact"]>>["data"]
|
export type Endpoint4_14Output = EffectValue<ReturnType<RawClient["server.session"]["session.shell"]>>
|
||||||
export type SessionCompactOperation<E = never> = (input: Endpoint4_14Input) => Effect.Effect<Endpoint4_14Output, E>
|
export type SessionShellOperation<E = never> = (input: Endpoint4_14Input) => Effect.Effect<Endpoint4_14Output, E>
|
||||||
|
|
||||||
type Endpoint4_15Request = Parameters<RawClient["server.session"]["session.wait"]>[0]
|
type Endpoint4_15Request = Parameters<RawClient["server.session"]["session.compact"]>[0]
|
||||||
export type Endpoint4_15Input = { readonly sessionID: Endpoint4_15Request["params"]["sessionID"] }
|
export type Endpoint4_15Input = {
|
||||||
export type Endpoint4_15Output = EffectValue<ReturnType<RawClient["server.session"]["session.wait"]>>
|
readonly sessionID: Endpoint4_15Request["params"]["sessionID"]
|
||||||
export type SessionWaitOperation<E = never> = (input: Endpoint4_15Input) => Effect.Effect<Endpoint4_15Output, E>
|
readonly id?: Endpoint4_15Request["payload"]["id"]
|
||||||
|
|
||||||
type Endpoint4_16Request = Parameters<RawClient["server.session"]["session.revert.stage"]>[0]
|
|
||||||
export type Endpoint4_16Input = {
|
|
||||||
readonly sessionID: Endpoint4_16Request["params"]["sessionID"]
|
|
||||||
readonly messageID: Endpoint4_16Request["payload"]["messageID"]
|
|
||||||
readonly files?: Endpoint4_16Request["payload"]["files"]
|
|
||||||
}
|
}
|
||||||
export type Endpoint4_16Output = EffectValue<ReturnType<RawClient["server.session"]["session.revert.stage"]>>["data"]
|
export type Endpoint4_15Output = EffectValue<ReturnType<RawClient["server.session"]["session.compact"]>>["data"]
|
||||||
export type SessionRevertStageOperation<E = never> = (input: Endpoint4_16Input) => Effect.Effect<Endpoint4_16Output, E>
|
export type SessionCompactOperation<E = never> = (input: Endpoint4_15Input) => Effect.Effect<Endpoint4_15Output, E>
|
||||||
|
|
||||||
type Endpoint4_17Request = Parameters<RawClient["server.session"]["session.revert.clear"]>[0]
|
type Endpoint4_16Request = Parameters<RawClient["server.session"]["session.wait"]>[0]
|
||||||
export type Endpoint4_17Input = { readonly sessionID: Endpoint4_17Request["params"]["sessionID"] }
|
export type Endpoint4_16Input = { readonly sessionID: Endpoint4_16Request["params"]["sessionID"] }
|
||||||
export type Endpoint4_17Output = EffectValue<ReturnType<RawClient["server.session"]["session.revert.clear"]>>
|
export type Endpoint4_16Output = EffectValue<ReturnType<RawClient["server.session"]["session.wait"]>>
|
||||||
export type SessionRevertClearOperation<E = never> = (input: Endpoint4_17Input) => Effect.Effect<Endpoint4_17Output, E>
|
export type SessionWaitOperation<E = never> = (input: Endpoint4_16Input) => Effect.Effect<Endpoint4_16Output, E>
|
||||||
|
|
||||||
type Endpoint4_18Request = Parameters<RawClient["server.session"]["session.revert.commit"]>[0]
|
type Endpoint4_17Request = Parameters<RawClient["server.session"]["session.revert.stage"]>[0]
|
||||||
|
export type Endpoint4_17Input = {
|
||||||
|
readonly sessionID: Endpoint4_17Request["params"]["sessionID"]
|
||||||
|
readonly messageID: Endpoint4_17Request["payload"]["messageID"]
|
||||||
|
readonly files?: Endpoint4_17Request["payload"]["files"]
|
||||||
|
}
|
||||||
|
export type Endpoint4_17Output = EffectValue<ReturnType<RawClient["server.session"]["session.revert.stage"]>>["data"]
|
||||||
|
export type SessionRevertStageOperation<E = never> = (input: Endpoint4_17Input) => Effect.Effect<Endpoint4_17Output, E>
|
||||||
|
|
||||||
|
type Endpoint4_18Request = Parameters<RawClient["server.session"]["session.revert.clear"]>[0]
|
||||||
export type Endpoint4_18Input = { readonly sessionID: Endpoint4_18Request["params"]["sessionID"] }
|
export type Endpoint4_18Input = { readonly sessionID: Endpoint4_18Request["params"]["sessionID"] }
|
||||||
export type Endpoint4_18Output = EffectValue<ReturnType<RawClient["server.session"]["session.revert.commit"]>>
|
export type Endpoint4_18Output = EffectValue<ReturnType<RawClient["server.session"]["session.revert.clear"]>>
|
||||||
export type SessionRevertCommitOperation<E = never> = (input: Endpoint4_18Input) => Effect.Effect<Endpoint4_18Output, E>
|
export type SessionRevertClearOperation<E = never> = (input: Endpoint4_18Input) => Effect.Effect<Endpoint4_18Output, E>
|
||||||
|
|
||||||
type Endpoint4_19Request = Parameters<RawClient["server.session"]["session.context"]>[0]
|
type Endpoint4_19Request = Parameters<RawClient["server.session"]["session.revert.commit"]>[0]
|
||||||
export type Endpoint4_19Input = { readonly sessionID: Endpoint4_19Request["params"]["sessionID"] }
|
export type Endpoint4_19Input = { readonly sessionID: Endpoint4_19Request["params"]["sessionID"] }
|
||||||
export type Endpoint4_19Output = EffectValue<ReturnType<RawClient["server.session"]["session.context"]>>["data"]
|
export type Endpoint4_19Output = EffectValue<ReturnType<RawClient["server.session"]["session.revert.commit"]>>
|
||||||
export type SessionContextOperation<E = never> = (input: Endpoint4_19Input) => Effect.Effect<Endpoint4_19Output, E>
|
export type SessionRevertCommitOperation<E = never> = (input: Endpoint4_19Input) => Effect.Effect<Endpoint4_19Output, E>
|
||||||
|
|
||||||
type Endpoint4_20Request = Parameters<RawClient["server.session"]["session.instructions.entry.list"]>[0]
|
type Endpoint4_20Request = Parameters<RawClient["server.session"]["session.context"]>[0]
|
||||||
export type Endpoint4_20Input = { readonly sessionID: Endpoint4_20Request["params"]["sessionID"] }
|
export type Endpoint4_20Input = { readonly sessionID: Endpoint4_20Request["params"]["sessionID"] }
|
||||||
export type Endpoint4_20Output = EffectValue<
|
export type Endpoint4_20Output = EffectValue<ReturnType<RawClient["server.session"]["session.context"]>>["data"]
|
||||||
|
export type SessionContextOperation<E = never> = (input: Endpoint4_20Input) => Effect.Effect<Endpoint4_20Output, E>
|
||||||
|
|
||||||
|
type Endpoint4_21Request = Parameters<RawClient["server.session"]["session.instructions.entry.list"]>[0]
|
||||||
|
export type Endpoint4_21Input = { readonly sessionID: Endpoint4_21Request["params"]["sessionID"] }
|
||||||
|
export type Endpoint4_21Output = EffectValue<
|
||||||
ReturnType<RawClient["server.session"]["session.instructions.entry.list"]>
|
ReturnType<RawClient["server.session"]["session.instructions.entry.list"]>
|
||||||
>["data"]
|
>["data"]
|
||||||
export type SessionInstructionsEntryListOperation<E = never> = (
|
export type SessionInstructionsEntryListOperation<E = never> = (
|
||||||
input: Endpoint4_20Input,
|
|
||||||
) => Effect.Effect<Endpoint4_20Output, E>
|
|
||||||
|
|
||||||
type Endpoint4_21Request = Parameters<RawClient["server.session"]["session.instructions.entry.put"]>[0]
|
|
||||||
export type Endpoint4_21Input = {
|
|
||||||
readonly sessionID: Endpoint4_21Request["params"]["sessionID"]
|
|
||||||
readonly key: Endpoint4_21Request["params"]["key"]
|
|
||||||
readonly value: Endpoint4_21Request["payload"]["value"]
|
|
||||||
}
|
|
||||||
export type Endpoint4_21Output = EffectValue<ReturnType<RawClient["server.session"]["session.instructions.entry.put"]>>
|
|
||||||
export type SessionInstructionsEntryPutOperation<E = never> = (
|
|
||||||
input: Endpoint4_21Input,
|
input: Endpoint4_21Input,
|
||||||
) => Effect.Effect<Endpoint4_21Output, E>
|
) => Effect.Effect<Endpoint4_21Output, E>
|
||||||
|
|
||||||
type Endpoint4_22Request = Parameters<RawClient["server.session"]["session.instructions.entry.remove"]>[0]
|
type Endpoint4_22Request = Parameters<RawClient["server.session"]["session.instructions.entry.put"]>[0]
|
||||||
export type Endpoint4_22Input = {
|
export type Endpoint4_22Input = {
|
||||||
readonly sessionID: Endpoint4_22Request["params"]["sessionID"]
|
readonly sessionID: Endpoint4_22Request["params"]["sessionID"]
|
||||||
readonly key: Endpoint4_22Request["params"]["key"]
|
readonly key: Endpoint4_22Request["params"]["key"]
|
||||||
|
readonly value: Endpoint4_22Request["payload"]["value"]
|
||||||
}
|
}
|
||||||
export type Endpoint4_22Output = EffectValue<
|
export type Endpoint4_22Output = EffectValue<ReturnType<RawClient["server.session"]["session.instructions.entry.put"]>>
|
||||||
ReturnType<RawClient["server.session"]["session.instructions.entry.remove"]>
|
export type SessionInstructionsEntryPutOperation<E = never> = (
|
||||||
>
|
|
||||||
export type SessionInstructionsEntryRemoveOperation<E = never> = (
|
|
||||||
input: Endpoint4_22Input,
|
input: Endpoint4_22Input,
|
||||||
) => Effect.Effect<Endpoint4_22Output, E>
|
) => Effect.Effect<Endpoint4_22Output, E>
|
||||||
|
|
||||||
type Endpoint4_23Request = Parameters<RawClient["server.session"]["session.log"]>[0]
|
type Endpoint4_23Request = Parameters<RawClient["server.session"]["session.instructions.entry.remove"]>[0]
|
||||||
export type Endpoint4_23Input = {
|
export type Endpoint4_23Input = {
|
||||||
readonly sessionID: Endpoint4_23Request["params"]["sessionID"]
|
readonly sessionID: Endpoint4_23Request["params"]["sessionID"]
|
||||||
readonly after?: Endpoint4_23Request["query"]["after"]
|
readonly key: Endpoint4_23Request["params"]["key"]
|
||||||
readonly follow?: Endpoint4_23Request["query"]["follow"]
|
|
||||||
}
|
}
|
||||||
export type Endpoint4_23Output = StreamValue<EffectValue<ReturnType<RawClient["server.session"]["session.log"]>>>
|
export type Endpoint4_23Output = EffectValue<
|
||||||
export type SessionLogOperation<E = never> = (input: Endpoint4_23Input) => Stream.Stream<Endpoint4_23Output, E>
|
ReturnType<RawClient["server.session"]["session.instructions.entry.remove"]>
|
||||||
|
>
|
||||||
|
export type SessionInstructionsEntryRemoveOperation<E = never> = (
|
||||||
|
input: Endpoint4_23Input,
|
||||||
|
) => Effect.Effect<Endpoint4_23Output, E>
|
||||||
|
|
||||||
type Endpoint4_24Request = Parameters<RawClient["server.session"]["session.interrupt"]>[0]
|
type Endpoint4_24Request = Parameters<RawClient["server.session"]["session.log"]>[0]
|
||||||
export type Endpoint4_24Input = { readonly sessionID: Endpoint4_24Request["params"]["sessionID"] }
|
export type Endpoint4_24Input = {
|
||||||
export type Endpoint4_24Output = EffectValue<ReturnType<RawClient["server.session"]["session.interrupt"]>>
|
readonly sessionID: Endpoint4_24Request["params"]["sessionID"]
|
||||||
export type SessionInterruptOperation<E = never> = (input: Endpoint4_24Input) => Effect.Effect<Endpoint4_24Output, E>
|
readonly after?: Endpoint4_24Request["query"]["after"]
|
||||||
|
readonly follow?: Endpoint4_24Request["query"]["follow"]
|
||||||
|
}
|
||||||
|
export type Endpoint4_24Output = StreamValue<EffectValue<ReturnType<RawClient["server.session"]["session.log"]>>>
|
||||||
|
export type SessionLogOperation<E = never> = (input: Endpoint4_24Input) => Stream.Stream<Endpoint4_24Output, E>
|
||||||
|
|
||||||
type Endpoint4_25Request = Parameters<RawClient["server.session"]["session.background"]>[0]
|
type Endpoint4_25Request = Parameters<RawClient["server.session"]["session.interrupt"]>[0]
|
||||||
export type Endpoint4_25Input = { readonly sessionID: Endpoint4_25Request["params"]["sessionID"] }
|
export type Endpoint4_25Input = { readonly sessionID: Endpoint4_25Request["params"]["sessionID"] }
|
||||||
export type Endpoint4_25Output = EffectValue<ReturnType<RawClient["server.session"]["session.background"]>>
|
export type Endpoint4_25Output = EffectValue<ReturnType<RawClient["server.session"]["session.interrupt"]>>
|
||||||
export type SessionBackgroundOperation<E = never> = (input: Endpoint4_25Input) => Effect.Effect<Endpoint4_25Output, E>
|
export type SessionInterruptOperation<E = never> = (input: Endpoint4_25Input) => Effect.Effect<Endpoint4_25Output, E>
|
||||||
|
|
||||||
type Endpoint4_26Request = Parameters<RawClient["server.session"]["session.message"]>[0]
|
type Endpoint4_26Request = Parameters<RawClient["server.session"]["session.background"]>[0]
|
||||||
export type Endpoint4_26Input = {
|
export type Endpoint4_26Input = { readonly sessionID: Endpoint4_26Request["params"]["sessionID"] }
|
||||||
readonly sessionID: Endpoint4_26Request["params"]["sessionID"]
|
export type Endpoint4_26Output = EffectValue<ReturnType<RawClient["server.session"]["session.background"]>>
|
||||||
readonly messageID: Endpoint4_26Request["params"]["messageID"]
|
export type SessionBackgroundOperation<E = never> = (input: Endpoint4_26Input) => Effect.Effect<Endpoint4_26Output, E>
|
||||||
|
|
||||||
|
type Endpoint4_27Request = Parameters<RawClient["server.session"]["session.message"]>[0]
|
||||||
|
export type Endpoint4_27Input = {
|
||||||
|
readonly sessionID: Endpoint4_27Request["params"]["sessionID"]
|
||||||
|
readonly messageID: Endpoint4_27Request["params"]["messageID"]
|
||||||
}
|
}
|
||||||
export type Endpoint4_26Output = EffectValue<ReturnType<RawClient["server.session"]["session.message"]>>["data"]
|
export type Endpoint4_27Output = EffectValue<ReturnType<RawClient["server.session"]["session.message"]>>["data"]
|
||||||
export type SessionMessageOperation<E = never> = (input: Endpoint4_26Input) => Effect.Effect<Endpoint4_26Output, E>
|
export type SessionMessageOperation<E = never> = (input: Endpoint4_27Input) => Effect.Effect<Endpoint4_27Output, E>
|
||||||
|
|
||||||
export interface SessionApi<E = never> {
|
export interface SessionApi<E = never> {
|
||||||
readonly list: SessionListOperation<E>
|
readonly list: SessionListOperation<E>
|
||||||
@@ -274,6 +283,7 @@ export interface SessionApi<E = never> {
|
|||||||
readonly switchAgent: SessionSwitchAgentOperation<E>
|
readonly switchAgent: SessionSwitchAgentOperation<E>
|
||||||
readonly switchModel: SessionSwitchModelOperation<E>
|
readonly switchModel: SessionSwitchModelOperation<E>
|
||||||
readonly rename: SessionRenameOperation<E>
|
readonly rename: SessionRenameOperation<E>
|
||||||
|
readonly move: SessionMoveOperation<E>
|
||||||
readonly prompt: SessionPromptOperation<E>
|
readonly prompt: SessionPromptOperation<E>
|
||||||
readonly command: SessionCommandOperation<E>
|
readonly command: SessionCommandOperation<E>
|
||||||
readonly skill: SessionSkillOperation<E>
|
readonly skill: SessionSkillOperation<E>
|
||||||
|
|||||||
@@ -141,15 +141,27 @@ const Endpoint4_8 = (raw: RawClient["server.session"]) => (input: Endpoint4_8Inp
|
|||||||
Effect.mapError(mapClientError),
|
Effect.mapError(mapClientError),
|
||||||
)
|
)
|
||||||
|
|
||||||
type Endpoint4_9Request = Parameters<RawClient["server.session"]["session.prompt"]>[0]
|
type Endpoint4_9Request = Parameters<RawClient["server.session"]["session.move"]>[0]
|
||||||
type Endpoint4_9Input = {
|
type Endpoint4_9Input = {
|
||||||
readonly sessionID: Endpoint4_9Request["params"]["sessionID"]
|
readonly sessionID: Endpoint4_9Request["params"]["sessionID"]
|
||||||
readonly id?: Endpoint4_9Request["payload"]["id"]
|
readonly destination: Endpoint4_9Request["payload"]["destination"]
|
||||||
readonly prompt: Endpoint4_9Request["payload"]["prompt"]
|
readonly moveChanges?: Endpoint4_9Request["payload"]["moveChanges"]
|
||||||
readonly delivery?: Endpoint4_9Request["payload"]["delivery"]
|
|
||||||
readonly resume?: Endpoint4_9Request["payload"]["resume"]
|
|
||||||
}
|
}
|
||||||
const Endpoint4_9 = (raw: RawClient["server.session"]) => (input: Endpoint4_9Input) =>
|
const Endpoint4_9 = (raw: RawClient["server.session"]) => (input: Endpoint4_9Input) =>
|
||||||
|
raw["session.move"]({
|
||||||
|
params: { sessionID: input["sessionID"] },
|
||||||
|
payload: { destination: input["destination"], moveChanges: input["moveChanges"] },
|
||||||
|
}).pipe(Effect.mapError(mapClientError))
|
||||||
|
|
||||||
|
type Endpoint4_10Request = Parameters<RawClient["server.session"]["session.prompt"]>[0]
|
||||||
|
type Endpoint4_10Input = {
|
||||||
|
readonly sessionID: Endpoint4_10Request["params"]["sessionID"]
|
||||||
|
readonly id?: Endpoint4_10Request["payload"]["id"]
|
||||||
|
readonly prompt: Endpoint4_10Request["payload"]["prompt"]
|
||||||
|
readonly delivery?: Endpoint4_10Request["payload"]["delivery"]
|
||||||
|
readonly resume?: Endpoint4_10Request["payload"]["resume"]
|
||||||
|
}
|
||||||
|
const Endpoint4_10 = (raw: RawClient["server.session"]) => (input: Endpoint4_10Input) =>
|
||||||
raw["session.prompt"]({
|
raw["session.prompt"]({
|
||||||
params: { sessionID: input["sessionID"] },
|
params: { sessionID: input["sessionID"] },
|
||||||
payload: { id: input["id"], prompt: input["prompt"], delivery: input["delivery"], resume: input["resume"] },
|
payload: { id: input["id"], prompt: input["prompt"], delivery: input["delivery"], resume: input["resume"] },
|
||||||
@@ -158,20 +170,20 @@ const Endpoint4_9 = (raw: RawClient["server.session"]) => (input: Endpoint4_9Inp
|
|||||||
Effect.map((value) => value.data),
|
Effect.map((value) => value.data),
|
||||||
)
|
)
|
||||||
|
|
||||||
type Endpoint4_10Request = Parameters<RawClient["server.session"]["session.command"]>[0]
|
type Endpoint4_11Request = Parameters<RawClient["server.session"]["session.command"]>[0]
|
||||||
type Endpoint4_10Input = {
|
type Endpoint4_11Input = {
|
||||||
readonly sessionID: Endpoint4_10Request["params"]["sessionID"]
|
readonly sessionID: Endpoint4_11Request["params"]["sessionID"]
|
||||||
readonly id?: Endpoint4_10Request["payload"]["id"]
|
readonly id?: Endpoint4_11Request["payload"]["id"]
|
||||||
readonly command: Endpoint4_10Request["payload"]["command"]
|
readonly command: Endpoint4_11Request["payload"]["command"]
|
||||||
readonly arguments?: Endpoint4_10Request["payload"]["arguments"]
|
readonly arguments?: Endpoint4_11Request["payload"]["arguments"]
|
||||||
readonly agent?: Endpoint4_10Request["payload"]["agent"]
|
readonly agent?: Endpoint4_11Request["payload"]["agent"]
|
||||||
readonly model?: Endpoint4_10Request["payload"]["model"]
|
readonly model?: Endpoint4_11Request["payload"]["model"]
|
||||||
readonly files?: Endpoint4_10Request["payload"]["files"]
|
readonly files?: Endpoint4_11Request["payload"]["files"]
|
||||||
readonly agents?: Endpoint4_10Request["payload"]["agents"]
|
readonly agents?: Endpoint4_11Request["payload"]["agents"]
|
||||||
readonly delivery?: Endpoint4_10Request["payload"]["delivery"]
|
readonly delivery?: Endpoint4_11Request["payload"]["delivery"]
|
||||||
readonly resume?: Endpoint4_10Request["payload"]["resume"]
|
readonly resume?: Endpoint4_11Request["payload"]["resume"]
|
||||||
}
|
}
|
||||||
const Endpoint4_10 = (raw: RawClient["server.session"]) => (input: Endpoint4_10Input) =>
|
const Endpoint4_11 = (raw: RawClient["server.session"]) => (input: Endpoint4_11Input) =>
|
||||||
raw["session.command"]({
|
raw["session.command"]({
|
||||||
params: { sessionID: input["sessionID"] },
|
params: { sessionID: input["sessionID"] },
|
||||||
payload: {
|
payload: {
|
||||||
@@ -190,28 +202,28 @@ const Endpoint4_10 = (raw: RawClient["server.session"]) => (input: Endpoint4_10I
|
|||||||
Effect.map((value) => value.data),
|
Effect.map((value) => value.data),
|
||||||
)
|
)
|
||||||
|
|
||||||
type Endpoint4_11Request = Parameters<RawClient["server.session"]["session.skill"]>[0]
|
type Endpoint4_12Request = Parameters<RawClient["server.session"]["session.skill"]>[0]
|
||||||
type Endpoint4_11Input = {
|
type Endpoint4_12Input = {
|
||||||
readonly sessionID: Endpoint4_11Request["params"]["sessionID"]
|
readonly sessionID: Endpoint4_12Request["params"]["sessionID"]
|
||||||
readonly id?: Endpoint4_11Request["payload"]["id"]
|
readonly id?: Endpoint4_12Request["payload"]["id"]
|
||||||
readonly skill: Endpoint4_11Request["payload"]["skill"]
|
readonly skill: Endpoint4_12Request["payload"]["skill"]
|
||||||
readonly resume?: Endpoint4_11Request["payload"]["resume"]
|
readonly resume?: Endpoint4_12Request["payload"]["resume"]
|
||||||
}
|
}
|
||||||
const Endpoint4_11 = (raw: RawClient["server.session"]) => (input: Endpoint4_11Input) =>
|
const Endpoint4_12 = (raw: RawClient["server.session"]) => (input: Endpoint4_12Input) =>
|
||||||
raw["session.skill"]({
|
raw["session.skill"]({
|
||||||
params: { sessionID: input["sessionID"] },
|
params: { sessionID: input["sessionID"] },
|
||||||
payload: { id: input["id"], skill: input["skill"], resume: input["resume"] },
|
payload: { id: input["id"], skill: input["skill"], resume: input["resume"] },
|
||||||
}).pipe(Effect.mapError(mapClientError))
|
}).pipe(Effect.mapError(mapClientError))
|
||||||
|
|
||||||
type Endpoint4_12Request = Parameters<RawClient["server.session"]["session.synthetic"]>[0]
|
type Endpoint4_13Request = Parameters<RawClient["server.session"]["session.synthetic"]>[0]
|
||||||
type Endpoint4_12Input = {
|
type Endpoint4_13Input = {
|
||||||
readonly sessionID: Endpoint4_12Request["params"]["sessionID"]
|
readonly sessionID: Endpoint4_13Request["params"]["sessionID"]
|
||||||
readonly text: Endpoint4_12Request["payload"]["text"]
|
readonly text: Endpoint4_13Request["payload"]["text"]
|
||||||
readonly description?: Endpoint4_12Request["payload"]["description"]
|
readonly description?: Endpoint4_13Request["payload"]["description"]
|
||||||
readonly metadata?: Endpoint4_12Request["payload"]["metadata"]
|
readonly metadata?: Endpoint4_13Request["payload"]["metadata"]
|
||||||
readonly resume?: Endpoint4_12Request["payload"]["resume"]
|
readonly resume?: Endpoint4_13Request["payload"]["resume"]
|
||||||
}
|
}
|
||||||
const Endpoint4_12 = (raw: RawClient["server.session"]) => (input: Endpoint4_12Input) =>
|
const Endpoint4_13 = (raw: RawClient["server.session"]) => (input: Endpoint4_13Input) =>
|
||||||
raw["session.synthetic"]({
|
raw["session.synthetic"]({
|
||||||
params: { sessionID: input["sessionID"] },
|
params: { sessionID: input["sessionID"] },
|
||||||
payload: {
|
payload: {
|
||||||
@@ -222,41 +234,41 @@ const Endpoint4_12 = (raw: RawClient["server.session"]) => (input: Endpoint4_12I
|
|||||||
},
|
},
|
||||||
}).pipe(Effect.mapError(mapClientError))
|
}).pipe(Effect.mapError(mapClientError))
|
||||||
|
|
||||||
type Endpoint4_13Request = Parameters<RawClient["server.session"]["session.shell"]>[0]
|
type Endpoint4_14Request = Parameters<RawClient["server.session"]["session.shell"]>[0]
|
||||||
type Endpoint4_13Input = {
|
type Endpoint4_14Input = {
|
||||||
readonly sessionID: Endpoint4_13Request["params"]["sessionID"]
|
readonly sessionID: Endpoint4_14Request["params"]["sessionID"]
|
||||||
readonly id?: Endpoint4_13Request["payload"]["id"]
|
readonly id?: Endpoint4_14Request["payload"]["id"]
|
||||||
readonly command: Endpoint4_13Request["payload"]["command"]
|
readonly command: Endpoint4_14Request["payload"]["command"]
|
||||||
}
|
}
|
||||||
const Endpoint4_13 = (raw: RawClient["server.session"]) => (input: Endpoint4_13Input) =>
|
const Endpoint4_14 = (raw: RawClient["server.session"]) => (input: Endpoint4_14Input) =>
|
||||||
raw["session.shell"]({
|
raw["session.shell"]({
|
||||||
params: { sessionID: input["sessionID"] },
|
params: { sessionID: input["sessionID"] },
|
||||||
payload: { id: input["id"], command: input["command"] },
|
payload: { id: input["id"], command: input["command"] },
|
||||||
}).pipe(Effect.mapError(mapClientError))
|
}).pipe(Effect.mapError(mapClientError))
|
||||||
|
|
||||||
type Endpoint4_14Request = Parameters<RawClient["server.session"]["session.compact"]>[0]
|
type Endpoint4_15Request = Parameters<RawClient["server.session"]["session.compact"]>[0]
|
||||||
type Endpoint4_14Input = {
|
type Endpoint4_15Input = {
|
||||||
readonly sessionID: Endpoint4_14Request["params"]["sessionID"]
|
readonly sessionID: Endpoint4_15Request["params"]["sessionID"]
|
||||||
readonly id?: Endpoint4_14Request["payload"]["id"]
|
readonly id?: Endpoint4_15Request["payload"]["id"]
|
||||||
}
|
}
|
||||||
const Endpoint4_14 = (raw: RawClient["server.session"]) => (input: Endpoint4_14Input) =>
|
const Endpoint4_15 = (raw: RawClient["server.session"]) => (input: Endpoint4_15Input) =>
|
||||||
raw["session.compact"]({ params: { sessionID: input["sessionID"] }, payload: { id: input["id"] } }).pipe(
|
raw["session.compact"]({ params: { sessionID: input["sessionID"] }, payload: { id: input["id"] } }).pipe(
|
||||||
Effect.mapError(mapClientError),
|
Effect.mapError(mapClientError),
|
||||||
Effect.map((value) => value.data),
|
Effect.map((value) => value.data),
|
||||||
)
|
)
|
||||||
|
|
||||||
type Endpoint4_15Request = Parameters<RawClient["server.session"]["session.wait"]>[0]
|
type Endpoint4_16Request = Parameters<RawClient["server.session"]["session.wait"]>[0]
|
||||||
type Endpoint4_15Input = { readonly sessionID: Endpoint4_15Request["params"]["sessionID"] }
|
type Endpoint4_16Input = { readonly sessionID: Endpoint4_16Request["params"]["sessionID"] }
|
||||||
const Endpoint4_15 = (raw: RawClient["server.session"]) => (input: Endpoint4_15Input) =>
|
const Endpoint4_16 = (raw: RawClient["server.session"]) => (input: Endpoint4_16Input) =>
|
||||||
raw["session.wait"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
|
raw["session.wait"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
|
||||||
|
|
||||||
type Endpoint4_16Request = Parameters<RawClient["server.session"]["session.revert.stage"]>[0]
|
type Endpoint4_17Request = Parameters<RawClient["server.session"]["session.revert.stage"]>[0]
|
||||||
type Endpoint4_16Input = {
|
type Endpoint4_17Input = {
|
||||||
readonly sessionID: Endpoint4_16Request["params"]["sessionID"]
|
readonly sessionID: Endpoint4_17Request["params"]["sessionID"]
|
||||||
readonly messageID: Endpoint4_16Request["payload"]["messageID"]
|
readonly messageID: Endpoint4_17Request["payload"]["messageID"]
|
||||||
readonly files?: Endpoint4_16Request["payload"]["files"]
|
readonly files?: Endpoint4_17Request["payload"]["files"]
|
||||||
}
|
}
|
||||||
const Endpoint4_16 = (raw: RawClient["server.session"]) => (input: Endpoint4_16Input) =>
|
const Endpoint4_17 = (raw: RawClient["server.session"]) => (input: Endpoint4_17Input) =>
|
||||||
raw["session.revert.stage"]({
|
raw["session.revert.stage"]({
|
||||||
params: { sessionID: input["sessionID"] },
|
params: { sessionID: input["sessionID"] },
|
||||||
payload: { messageID: input["messageID"], files: input["files"] },
|
payload: { messageID: input["messageID"], files: input["files"] },
|
||||||
@@ -265,61 +277,61 @@ const Endpoint4_16 = (raw: RawClient["server.session"]) => (input: Endpoint4_16I
|
|||||||
Effect.map((value) => value.data),
|
Effect.map((value) => value.data),
|
||||||
)
|
)
|
||||||
|
|
||||||
type Endpoint4_17Request = Parameters<RawClient["server.session"]["session.revert.clear"]>[0]
|
type Endpoint4_18Request = Parameters<RawClient["server.session"]["session.revert.clear"]>[0]
|
||||||
type Endpoint4_17Input = { readonly sessionID: Endpoint4_17Request["params"]["sessionID"] }
|
|
||||||
const Endpoint4_17 = (raw: RawClient["server.session"]) => (input: Endpoint4_17Input) =>
|
|
||||||
raw["session.revert.clear"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
|
|
||||||
|
|
||||||
type Endpoint4_18Request = Parameters<RawClient["server.session"]["session.revert.commit"]>[0]
|
|
||||||
type Endpoint4_18Input = { readonly sessionID: Endpoint4_18Request["params"]["sessionID"] }
|
type Endpoint4_18Input = { readonly sessionID: Endpoint4_18Request["params"]["sessionID"] }
|
||||||
const Endpoint4_18 = (raw: RawClient["server.session"]) => (input: Endpoint4_18Input) =>
|
const Endpoint4_18 = (raw: RawClient["server.session"]) => (input: Endpoint4_18Input) =>
|
||||||
raw["session.revert.commit"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
|
raw["session.revert.clear"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
|
||||||
|
|
||||||
type Endpoint4_19Request = Parameters<RawClient["server.session"]["session.context"]>[0]
|
type Endpoint4_19Request = Parameters<RawClient["server.session"]["session.revert.commit"]>[0]
|
||||||
type Endpoint4_19Input = { readonly sessionID: Endpoint4_19Request["params"]["sessionID"] }
|
type Endpoint4_19Input = { readonly sessionID: Endpoint4_19Request["params"]["sessionID"] }
|
||||||
const Endpoint4_19 = (raw: RawClient["server.session"]) => (input: Endpoint4_19Input) =>
|
const Endpoint4_19 = (raw: RawClient["server.session"]) => (input: Endpoint4_19Input) =>
|
||||||
|
raw["session.revert.commit"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
|
||||||
|
|
||||||
|
type Endpoint4_20Request = Parameters<RawClient["server.session"]["session.context"]>[0]
|
||||||
|
type Endpoint4_20Input = { readonly sessionID: Endpoint4_20Request["params"]["sessionID"] }
|
||||||
|
const Endpoint4_20 = (raw: RawClient["server.session"]) => (input: Endpoint4_20Input) =>
|
||||||
raw["session.context"]({ params: { sessionID: input["sessionID"] } }).pipe(
|
raw["session.context"]({ params: { sessionID: input["sessionID"] } }).pipe(
|
||||||
Effect.mapError(mapClientError),
|
Effect.mapError(mapClientError),
|
||||||
Effect.map((value) => value.data),
|
Effect.map((value) => value.data),
|
||||||
)
|
)
|
||||||
|
|
||||||
type Endpoint4_20Request = Parameters<RawClient["server.session"]["session.instructions.entry.list"]>[0]
|
type Endpoint4_21Request = Parameters<RawClient["server.session"]["session.instructions.entry.list"]>[0]
|
||||||
type Endpoint4_20Input = { readonly sessionID: Endpoint4_20Request["params"]["sessionID"] }
|
type Endpoint4_21Input = { readonly sessionID: Endpoint4_21Request["params"]["sessionID"] }
|
||||||
const Endpoint4_20 = (raw: RawClient["server.session"]) => (input: Endpoint4_20Input) =>
|
const Endpoint4_21 = (raw: RawClient["server.session"]) => (input: Endpoint4_21Input) =>
|
||||||
raw["session.instructions.entry.list"]({ params: { sessionID: input["sessionID"] } }).pipe(
|
raw["session.instructions.entry.list"]({ params: { sessionID: input["sessionID"] } }).pipe(
|
||||||
Effect.mapError(mapClientError),
|
Effect.mapError(mapClientError),
|
||||||
Effect.map((value) => value.data),
|
Effect.map((value) => value.data),
|
||||||
)
|
)
|
||||||
|
|
||||||
type Endpoint4_21Request = Parameters<RawClient["server.session"]["session.instructions.entry.put"]>[0]
|
type Endpoint4_22Request = Parameters<RawClient["server.session"]["session.instructions.entry.put"]>[0]
|
||||||
type Endpoint4_21Input = {
|
type Endpoint4_22Input = {
|
||||||
readonly sessionID: Endpoint4_21Request["params"]["sessionID"]
|
readonly sessionID: Endpoint4_22Request["params"]["sessionID"]
|
||||||
readonly key: Endpoint4_21Request["params"]["key"]
|
readonly key: Endpoint4_22Request["params"]["key"]
|
||||||
readonly value: Endpoint4_21Request["payload"]["value"]
|
readonly value: Endpoint4_22Request["payload"]["value"]
|
||||||
}
|
}
|
||||||
const Endpoint4_21 = (raw: RawClient["server.session"]) => (input: Endpoint4_21Input) =>
|
const Endpoint4_22 = (raw: RawClient["server.session"]) => (input: Endpoint4_22Input) =>
|
||||||
raw["session.instructions.entry.put"]({
|
raw["session.instructions.entry.put"]({
|
||||||
params: { sessionID: input["sessionID"], key: input["key"] },
|
params: { sessionID: input["sessionID"], key: input["key"] },
|
||||||
payload: { value: input["value"] },
|
payload: { value: input["value"] },
|
||||||
}).pipe(Effect.mapError(mapClientError))
|
}).pipe(Effect.mapError(mapClientError))
|
||||||
|
|
||||||
type Endpoint4_22Request = Parameters<RawClient["server.session"]["session.instructions.entry.remove"]>[0]
|
type Endpoint4_23Request = Parameters<RawClient["server.session"]["session.instructions.entry.remove"]>[0]
|
||||||
type Endpoint4_22Input = {
|
type Endpoint4_23Input = {
|
||||||
readonly sessionID: Endpoint4_22Request["params"]["sessionID"]
|
readonly sessionID: Endpoint4_23Request["params"]["sessionID"]
|
||||||
readonly key: Endpoint4_22Request["params"]["key"]
|
readonly key: Endpoint4_23Request["params"]["key"]
|
||||||
}
|
}
|
||||||
const Endpoint4_22 = (raw: RawClient["server.session"]) => (input: Endpoint4_22Input) =>
|
const Endpoint4_23 = (raw: RawClient["server.session"]) => (input: Endpoint4_23Input) =>
|
||||||
raw["session.instructions.entry.remove"]({ params: { sessionID: input["sessionID"], key: input["key"] } }).pipe(
|
raw["session.instructions.entry.remove"]({ params: { sessionID: input["sessionID"], key: input["key"] } }).pipe(
|
||||||
Effect.mapError(mapClientError),
|
Effect.mapError(mapClientError),
|
||||||
)
|
)
|
||||||
|
|
||||||
type Endpoint4_23Request = Parameters<RawClient["server.session"]["session.log"]>[0]
|
type Endpoint4_24Request = Parameters<RawClient["server.session"]["session.log"]>[0]
|
||||||
type Endpoint4_23Input = {
|
type Endpoint4_24Input = {
|
||||||
readonly sessionID: Endpoint4_23Request["params"]["sessionID"]
|
readonly sessionID: Endpoint4_24Request["params"]["sessionID"]
|
||||||
readonly after?: Endpoint4_23Request["query"]["after"]
|
readonly after?: Endpoint4_24Request["query"]["after"]
|
||||||
readonly follow?: Endpoint4_23Request["query"]["follow"]
|
readonly follow?: Endpoint4_24Request["query"]["follow"]
|
||||||
}
|
}
|
||||||
const Endpoint4_23 = (raw: RawClient["server.session"]) => (input: Endpoint4_23Input) =>
|
const Endpoint4_24 = (raw: RawClient["server.session"]) => (input: Endpoint4_24Input) =>
|
||||||
Stream.unwrap(
|
Stream.unwrap(
|
||||||
raw["session.log"]({
|
raw["session.log"]({
|
||||||
params: { sessionID: input["sessionID"] },
|
params: { sessionID: input["sessionID"] },
|
||||||
@@ -330,22 +342,22 @@ const Endpoint4_23 = (raw: RawClient["server.session"]) => (input: Endpoint4_23I
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
type Endpoint4_24Request = Parameters<RawClient["server.session"]["session.interrupt"]>[0]
|
type Endpoint4_25Request = Parameters<RawClient["server.session"]["session.interrupt"]>[0]
|
||||||
type Endpoint4_24Input = { readonly sessionID: Endpoint4_24Request["params"]["sessionID"] }
|
|
||||||
const Endpoint4_24 = (raw: RawClient["server.session"]) => (input: Endpoint4_24Input) =>
|
|
||||||
raw["session.interrupt"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
|
|
||||||
|
|
||||||
type Endpoint4_25Request = Parameters<RawClient["server.session"]["session.background"]>[0]
|
|
||||||
type Endpoint4_25Input = { readonly sessionID: Endpoint4_25Request["params"]["sessionID"] }
|
type Endpoint4_25Input = { readonly sessionID: Endpoint4_25Request["params"]["sessionID"] }
|
||||||
const Endpoint4_25 = (raw: RawClient["server.session"]) => (input: Endpoint4_25Input) =>
|
const Endpoint4_25 = (raw: RawClient["server.session"]) => (input: Endpoint4_25Input) =>
|
||||||
|
raw["session.interrupt"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
|
||||||
|
|
||||||
|
type Endpoint4_26Request = Parameters<RawClient["server.session"]["session.background"]>[0]
|
||||||
|
type Endpoint4_26Input = { readonly sessionID: Endpoint4_26Request["params"]["sessionID"] }
|
||||||
|
const Endpoint4_26 = (raw: RawClient["server.session"]) => (input: Endpoint4_26Input) =>
|
||||||
raw["session.background"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
|
raw["session.background"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
|
||||||
|
|
||||||
type Endpoint4_26Request = Parameters<RawClient["server.session"]["session.message"]>[0]
|
type Endpoint4_27Request = Parameters<RawClient["server.session"]["session.message"]>[0]
|
||||||
type Endpoint4_26Input = {
|
type Endpoint4_27Input = {
|
||||||
readonly sessionID: Endpoint4_26Request["params"]["sessionID"]
|
readonly sessionID: Endpoint4_27Request["params"]["sessionID"]
|
||||||
readonly messageID: Endpoint4_26Request["params"]["messageID"]
|
readonly messageID: Endpoint4_27Request["params"]["messageID"]
|
||||||
}
|
}
|
||||||
const Endpoint4_26 = (raw: RawClient["server.session"]) => (input: Endpoint4_26Input) =>
|
const Endpoint4_27 = (raw: RawClient["server.session"]) => (input: Endpoint4_27Input) =>
|
||||||
raw["session.message"]({ params: { sessionID: input["sessionID"], messageID: input["messageID"] } }).pipe(
|
raw["session.message"]({ params: { sessionID: input["sessionID"], messageID: input["messageID"] } }).pipe(
|
||||||
Effect.mapError(mapClientError),
|
Effect.mapError(mapClientError),
|
||||||
Effect.map((value) => value.data),
|
Effect.map((value) => value.data),
|
||||||
@@ -361,22 +373,23 @@ const adaptGroup4 = (raw: RawClient["server.session"]) => ({
|
|||||||
switchAgent: Endpoint4_6(raw),
|
switchAgent: Endpoint4_6(raw),
|
||||||
switchModel: Endpoint4_7(raw),
|
switchModel: Endpoint4_7(raw),
|
||||||
rename: Endpoint4_8(raw),
|
rename: Endpoint4_8(raw),
|
||||||
prompt: Endpoint4_9(raw),
|
move: Endpoint4_9(raw),
|
||||||
command: Endpoint4_10(raw),
|
prompt: Endpoint4_10(raw),
|
||||||
skill: Endpoint4_11(raw),
|
command: Endpoint4_11(raw),
|
||||||
synthetic: Endpoint4_12(raw),
|
skill: Endpoint4_12(raw),
|
||||||
shell: Endpoint4_13(raw),
|
synthetic: Endpoint4_13(raw),
|
||||||
compact: Endpoint4_14(raw),
|
shell: Endpoint4_14(raw),
|
||||||
wait: Endpoint4_15(raw),
|
compact: Endpoint4_15(raw),
|
||||||
revertStage: Endpoint4_16(raw),
|
wait: Endpoint4_16(raw),
|
||||||
revertClear: Endpoint4_17(raw),
|
revertStage: Endpoint4_17(raw),
|
||||||
revertCommit: Endpoint4_18(raw),
|
revertClear: Endpoint4_18(raw),
|
||||||
context: Endpoint4_19(raw),
|
revertCommit: Endpoint4_19(raw),
|
||||||
instructions: { entry: { list: Endpoint4_20(raw), put: Endpoint4_21(raw), remove: Endpoint4_22(raw) } },
|
context: Endpoint4_20(raw),
|
||||||
log: Endpoint4_23(raw),
|
instructions: { entry: { list: Endpoint4_21(raw), put: Endpoint4_22(raw), remove: Endpoint4_23(raw) } },
|
||||||
interrupt: Endpoint4_24(raw),
|
log: Endpoint4_24(raw),
|
||||||
background: Endpoint4_25(raw),
|
interrupt: Endpoint4_25(raw),
|
||||||
message: Endpoint4_26(raw),
|
background: Endpoint4_26(raw),
|
||||||
|
message: Endpoint4_27(raw),
|
||||||
})
|
})
|
||||||
|
|
||||||
type Endpoint5_0Request = Parameters<RawClient["server.message"]["session.messages"]>[0]
|
type Endpoint5_0Request = Parameters<RawClient["server.message"]["session.messages"]>[0]
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ import type {
|
|||||||
SessionSwitchModelOutput,
|
SessionSwitchModelOutput,
|
||||||
SessionRenameInput,
|
SessionRenameInput,
|
||||||
SessionRenameOutput,
|
SessionRenameOutput,
|
||||||
|
SessionMoveInput,
|
||||||
|
SessionMoveOutput,
|
||||||
SessionPromptInput,
|
SessionPromptInput,
|
||||||
SessionPromptOutput,
|
SessionPromptOutput,
|
||||||
SessionCommandInput,
|
SessionCommandInput,
|
||||||
@@ -489,6 +491,18 @@ export function make(options: ClientOptions) {
|
|||||||
},
|
},
|
||||||
requestOptions,
|
requestOptions,
|
||||||
),
|
),
|
||||||
|
move: (input: SessionMoveInput, requestOptions?: RequestOptions) =>
|
||||||
|
request<SessionMoveOutput>(
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
path: `/api/session/${encodeURIComponent(input.sessionID)}/move`,
|
||||||
|
body: { destination: input["destination"], moveChanges: input["moveChanges"] },
|
||||||
|
successStatus: 204,
|
||||||
|
declaredStatuses: [404, 400, 401],
|
||||||
|
empty: true,
|
||||||
|
},
|
||||||
|
requestOptions,
|
||||||
|
),
|
||||||
prompt: (input: SessionPromptInput, requestOptions?: RequestOptions) =>
|
prompt: (input: SessionPromptInput, requestOptions?: RequestOptions) =>
|
||||||
request<{ readonly data: SessionPromptOutput }>(
|
request<{ readonly data: SessionPromptOutput }>(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -526,6 +526,20 @@ export type SessionRenameInput = {
|
|||||||
|
|
||||||
export type SessionRenameOutput = void
|
export type SessionRenameOutput = void
|
||||||
|
|
||||||
|
export type SessionMoveInput = {
|
||||||
|
readonly sessionID: { readonly sessionID: string }["sessionID"]
|
||||||
|
readonly destination: {
|
||||||
|
readonly destination: { readonly directory: string }
|
||||||
|
readonly moveChanges?: boolean | undefined
|
||||||
|
}["destination"]
|
||||||
|
readonly moveChanges?: {
|
||||||
|
readonly destination: { readonly directory: string }
|
||||||
|
readonly moveChanges?: boolean | undefined
|
||||||
|
}["moveChanges"]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SessionMoveOutput = void
|
||||||
|
|
||||||
export type SessionPromptInput = {
|
export type SessionPromptInput = {
|
||||||
readonly sessionID: { readonly sessionID: string }["sessionID"]
|
readonly sessionID: { readonly sessionID: string }["sessionID"]
|
||||||
readonly id?: {
|
readonly id?: {
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
export * as CodeModeV2 from "./code-mode"
|
||||||
|
|
||||||
|
import type { CodeMode } from "@opencode-ai/plugin/v2/effect"
|
||||||
|
import { Context, Effect, Layer, Scope } from "effect"
|
||||||
|
import { makeLocationNode } from "./effect/app-node"
|
||||||
|
import { Flag } from "./flag/flag"
|
||||||
|
import { PermissionV2 } from "./permission"
|
||||||
|
import { ExecuteTool } from "./code-mode/execute"
|
||||||
|
import { permission, RegistrationError, type AnyTool } from "./tool/tool"
|
||||||
|
import { Wildcard } from "./util/wildcard"
|
||||||
|
|
||||||
|
type Registration = {
|
||||||
|
readonly identity: object
|
||||||
|
readonly tool: AnyTool
|
||||||
|
readonly name: string
|
||||||
|
readonly path: readonly [string, ...string[]]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MaterializeInput {
|
||||||
|
readonly permissions?: PermissionV2.Ruleset
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Interface {
|
||||||
|
readonly enabled: boolean
|
||||||
|
readonly register: (
|
||||||
|
source: (draft: CodeMode.Draft) => void,
|
||||||
|
) => Effect.Effect<void, RegistrationError, Scope.Scope>
|
||||||
|
readonly materialize: (input: MaterializeInput) => Effect.Effect<AnyTool | undefined>
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/CodeMode") {}
|
||||||
|
|
||||||
|
const layer = Layer.effect(
|
||||||
|
Service,
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const enabled = Flag.CODEMODE_ENABLED
|
||||||
|
const local = new Map<string, Array<{ readonly token: object; readonly registration: Registration }>>()
|
||||||
|
|
||||||
|
return Service.of({
|
||||||
|
enabled,
|
||||||
|
register: Effect.fn("CodeMode.register")(function* (source) {
|
||||||
|
const pending: Array<{ readonly path: readonly [string, ...string[]]; readonly tool: AnyTool }> = []
|
||||||
|
yield* Effect.sync(() =>
|
||||||
|
source({
|
||||||
|
add: (path, tool) => pending.push({ path, tool }),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
if (pending.length === 0) return
|
||||||
|
|
||||||
|
const entries = pending.map((entry) => ({
|
||||||
|
...entry,
|
||||||
|
key: entry.path.join("\0"),
|
||||||
|
name: entry.path.join("_"),
|
||||||
|
}))
|
||||||
|
const invalid = entries.find((entry) => entry.path.some((segment) => segment.length === 0))
|
||||||
|
if (invalid)
|
||||||
|
return yield* new RegistrationError({
|
||||||
|
name: invalid.path.join("."),
|
||||||
|
message: "Code Mode paths cannot contain empty segments",
|
||||||
|
})
|
||||||
|
const keys = new Set<string>()
|
||||||
|
const duplicate = entries.find((entry) => {
|
||||||
|
if (keys.has(entry.key)) return true
|
||||||
|
keys.add(entry.key)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
if (duplicate)
|
||||||
|
return yield* new RegistrationError({
|
||||||
|
name: duplicate.path.join("."),
|
||||||
|
message: `Duplicate Code Mode path: ${duplicate.path.join(".")}`,
|
||||||
|
})
|
||||||
|
|
||||||
|
yield* Effect.uninterruptible(
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const token = {}
|
||||||
|
for (const entry of entries)
|
||||||
|
local.set(entry.key, [
|
||||||
|
...(local.get(entry.key) ?? []),
|
||||||
|
{
|
||||||
|
token,
|
||||||
|
registration: {
|
||||||
|
identity: {},
|
||||||
|
tool: entry.tool,
|
||||||
|
name: entry.name,
|
||||||
|
path: entry.path,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
yield* Effect.addFinalizer(() =>
|
||||||
|
Effect.sync(() => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
const registrations = local.get(entry.key)?.filter((item) => item.token !== token) ?? []
|
||||||
|
if (registrations.length > 0) local.set(entry.key, registrations)
|
||||||
|
else local.delete(entry.key)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
materialize: Effect.fn("CodeMode.materialize")(function* (input) {
|
||||||
|
if (!enabled || whollyDisabled("execute", input.permissions ?? [])) return undefined
|
||||||
|
const registrations = new Map<string, Registration>()
|
||||||
|
for (const [key, entries] of local) {
|
||||||
|
const registration = entries.at(-1)?.registration
|
||||||
|
if (
|
||||||
|
registration &&
|
||||||
|
!whollyDisabled(permission(registration.tool, registration.name), input.permissions ?? [])
|
||||||
|
)
|
||||||
|
registrations.set(key, registration)
|
||||||
|
}
|
||||||
|
if (registrations.size === 0) return undefined
|
||||||
|
return ExecuteTool.create({
|
||||||
|
registrations,
|
||||||
|
current: (key) => local.get(key)?.at(-1)?.registration,
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
function whollyDisabled(action: string, rules: PermissionV2.Ruleset) {
|
||||||
|
const rule = rules.findLast((rule) => Wildcard.match(action, rule.action))
|
||||||
|
return rule?.resource === "*" && rule.effect === "deny"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const node = makeLocationNode({ service: Service, layer, deps: [] })
|
||||||
@@ -3,7 +3,7 @@ export * as ExecuteTool from "./execute"
|
|||||||
import { CodeMode, Tool, toolError } from "@opencode-ai/codemode"
|
import { CodeMode, Tool, toolError } from "@opencode-ai/codemode"
|
||||||
import { ToolOutput } from "@opencode-ai/llm"
|
import { ToolOutput } from "@opencode-ai/llm"
|
||||||
import { Effect, Ref, Schema } from "effect"
|
import { Effect, Ref, Schema } from "effect"
|
||||||
import { definition, make, settle, type AnyTool } from "./tool"
|
import { definition, make, settle, type AnyTool } from "../tool/tool"
|
||||||
|
|
||||||
const ExecuteFile = Schema.Struct({
|
const ExecuteFile = Schema.Struct({
|
||||||
data: Schema.String,
|
data: Schema.String,
|
||||||
@@ -40,7 +40,11 @@ export interface Registration {
|
|||||||
readonly identity: object
|
readonly identity: object
|
||||||
readonly tool: AnyTool
|
readonly tool: AnyTool
|
||||||
readonly name: string
|
readonly name: string
|
||||||
readonly group?: string
|
readonly path: readonly [string, ...string[]]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CodeModeTools {
|
||||||
|
[name: string]: Tool.Definition<never> | CodeModeTools
|
||||||
}
|
}
|
||||||
|
|
||||||
export const create = (options: {
|
export const create = (options: {
|
||||||
@@ -51,33 +55,16 @@ export const create = (options: {
|
|||||||
invoke: (name: string, registration: Registration, input: unknown) => Effect.Effect<unknown, unknown>,
|
invoke: (name: string, registration: Registration, input: unknown) => Effect.Effect<unknown, unknown>,
|
||||||
hooks?: CodeMode.ToolCallHooks,
|
hooks?: CodeMode.ToolCallHooks,
|
||||||
) => {
|
) => {
|
||||||
const tools: Record<string, Tool.Definition<never> | Record<string, Tool.Definition<never>>> = {}
|
const tools: CodeModeTools = Object.create(null)
|
||||||
for (const [name, registration] of options.registrations) {
|
for (const [key, registration] of options.registrations) {
|
||||||
const child = definition(name, registration.tool)
|
const child = definition(registration.name, registration.tool)
|
||||||
const value = Tool.make({
|
const value = Tool.make({
|
||||||
description: child.description,
|
description: child.description,
|
||||||
input: child.inputSchema,
|
input: child.inputSchema,
|
||||||
output: child.outputSchema,
|
output: child.outputSchema,
|
||||||
run: (input) => invoke(name, registration, input),
|
run: (input) => invoke(key, registration, input),
|
||||||
})
|
})
|
||||||
if (registration.group === undefined) {
|
addTool(tools, registration.path, value)
|
||||||
const path = registration.name
|
|
||||||
if (Object.hasOwn(tools, path)) throw new TypeError(`Deferred tool namespace conflict: ${path}`)
|
|
||||||
tools[path] = value
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const path = registration.name
|
|
||||||
const namespace = registration.group
|
|
||||||
const group = tools[namespace]
|
|
||||||
if (group && Tool.isDefinition(group)) throw new TypeError(`Deferred tool namespace conflict: ${namespace}`)
|
|
||||||
if (group) {
|
|
||||||
if (Object.hasOwn(group, path)) throw new TypeError(`Deferred tool namespace conflict: ${namespace}.${path}`)
|
|
||||||
group[path] = value
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const entries: Record<string, Tool.Definition<never>> = {}
|
|
||||||
entries[path] = value
|
|
||||||
tools[namespace] = entries
|
|
||||||
}
|
}
|
||||||
return CodeMode.make<typeof tools>({ tools, ...hooks })
|
return CodeMode.make<typeof tools>({ tools, ...hooks })
|
||||||
}
|
}
|
||||||
@@ -112,15 +99,15 @@ export const create = (options: {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
const result = yield* runtime(
|
const result = yield* runtime(
|
||||||
(name, registration, input) =>
|
(key, registration, input) =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const index = yield* Ref.getAndUpdate(callIndex, (index) => index + 1)
|
const index = yield* Ref.getAndUpdate(callIndex, (index) => index + 1)
|
||||||
const current = options.current(name)
|
const current = options.current(key)
|
||||||
if (!current || current.identity !== registration.identity)
|
if (!current || current.identity !== registration.identity)
|
||||||
return yield* Effect.fail(toolError(`Stale tool call: ${name}`))
|
return yield* Effect.fail(toolError(`Stale tool call: ${registration.path.join(".")}`))
|
||||||
const output = yield* settle(
|
const output = yield* settle(
|
||||||
current.tool,
|
current.tool,
|
||||||
{ type: "tool-call", id: context.toolCallID, name, input },
|
{ type: "tool-call", id: context.toolCallID, name: registration.name, input },
|
||||||
{
|
{
|
||||||
sessionID: context.sessionID,
|
sessionID: context.sessionID,
|
||||||
agent: context.agent,
|
agent: context.agent,
|
||||||
@@ -163,6 +150,21 @@ export const create = (options: {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addTool(tools: CodeModeTools, path: readonly string[], value: Tool.Definition<never>) {
|
||||||
|
const [name, ...rest] = path
|
||||||
|
if (name === undefined) return
|
||||||
|
if (rest.length === 0) {
|
||||||
|
if (Object.hasOwn(tools, name)) throw new TypeError(`Code Mode namespace conflict: ${path.join(".")}`)
|
||||||
|
tools[name] = value
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const current = tools[name]
|
||||||
|
if (Tool.isDefinition(current)) throw new TypeError(`Code Mode namespace conflict: ${path.join(".")}`)
|
||||||
|
const child: CodeModeTools = current ?? Object.create(null)
|
||||||
|
tools[name] = child
|
||||||
|
addTool(child, rest, value)
|
||||||
|
}
|
||||||
|
|
||||||
function displayInput(input: unknown): Record<string, unknown> | undefined {
|
function displayInput(input: unknown): Record<string, unknown> | undefined {
|
||||||
if (input === null || input === undefined) return
|
if (input === null || input === undefined) return
|
||||||
if (typeof input !== "object" || Array.isArray(input)) return { input }
|
if (typeof input !== "object" || Array.isArray(input)) return { input }
|
||||||
@@ -2,6 +2,7 @@ import { Effect, Layer, LayerMap } from "effect"
|
|||||||
import { AgentV2 } from "./agent"
|
import { AgentV2 } from "./agent"
|
||||||
import { AISDK } from "./aisdk"
|
import { AISDK } from "./aisdk"
|
||||||
import { Catalog } from "./catalog"
|
import { Catalog } from "./catalog"
|
||||||
|
import { CodeModeV2 } from "./code-mode"
|
||||||
import { CommandV2 } from "./command"
|
import { CommandV2 } from "./command"
|
||||||
import { Config } from "./config"
|
import { Config } from "./config"
|
||||||
import { LayerNode } from "./effect/layer-node"
|
import { LayerNode } from "./effect/layer-node"
|
||||||
@@ -119,6 +120,7 @@ const locationServiceNodes = [
|
|||||||
MCP.node,
|
MCP.node,
|
||||||
PermissionV2.node,
|
PermissionV2.node,
|
||||||
ToolOutputStore.node,
|
ToolOutputStore.node,
|
||||||
|
CodeModeV2.node,
|
||||||
ToolRegistry.node,
|
ToolRegistry.node,
|
||||||
ToolRegistry.toolsNode,
|
ToolRegistry.toolsNode,
|
||||||
Image.node,
|
Image.node,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export * as McpGuidance from "./guidance"
|
export * as McpGuidance from "./guidance"
|
||||||
|
|
||||||
import { makeLocationNode } from "../effect/app-node"
|
import { makeLocationNode } from "../effect/app-node"
|
||||||
import { Flag } from "../flag/flag"
|
import { CodeModeV2 } from "../code-mode"
|
||||||
import { Context, Effect, Layer, Schema } from "effect"
|
import { Context, Effect, Layer, Schema } from "effect"
|
||||||
import { AgentV2 } from "../agent"
|
import { AgentV2 } from "../agent"
|
||||||
import { PermissionV2 } from "../permission"
|
import { PermissionV2 } from "../permission"
|
||||||
@@ -15,10 +15,10 @@ const Summary = Schema.Struct({
|
|||||||
})
|
})
|
||||||
type Summary = typeof Summary.Type
|
type Summary = typeof Summary.Type
|
||||||
|
|
||||||
const entries = (servers: ReadonlyArray<Summary>) =>
|
const entries = (servers: ReadonlyArray<Summary>, codeMode: boolean) =>
|
||||||
servers.flatMap((server) => [
|
servers.flatMap((server) => [
|
||||||
` <server name="${server.server}">`,
|
` <server name="${server.server}">`,
|
||||||
...(Flag.CODEMODE_ENABLED
|
...(codeMode
|
||||||
? [
|
? [
|
||||||
` Use tools from this server through \`execute\` under \`tools[${JSON.stringify(McpTool.group(server.server))}]\`.`,
|
` Use tools from this server through \`execute\` under \`tools[${JSON.stringify(McpTool.group(server.server))}]\`.`,
|
||||||
]
|
]
|
||||||
@@ -27,10 +27,10 @@ const entries = (servers: ReadonlyArray<Summary>) =>
|
|||||||
" </server>",
|
" </server>",
|
||||||
])
|
])
|
||||||
|
|
||||||
const render = (servers: ReadonlyArray<Summary>) =>
|
const render = (servers: ReadonlyArray<Summary>, codeMode: boolean) =>
|
||||||
["<mcp_instructions>", ...entries(servers), "</mcp_instructions>"].join("\n")
|
["<mcp_instructions>", ...entries(servers, codeMode), "</mcp_instructions>"].join("\n")
|
||||||
|
|
||||||
const update = (previous: ReadonlyArray<Summary>, current: ReadonlyArray<Summary>) => {
|
const update = (previous: ReadonlyArray<Summary>, current: ReadonlyArray<Summary>, codeMode: boolean) => {
|
||||||
const diff = Instructions.diffByKey(
|
const diff = Instructions.diffByKey(
|
||||||
previous,
|
previous,
|
||||||
current,
|
current,
|
||||||
@@ -41,12 +41,15 @@ const update = (previous: ReadonlyArray<Summary>, current: ReadonlyArray<Summary
|
|||||||
if (diff.changed.length > 0 || (diff.added.length === 0 && diff.removed.length === 0))
|
if (diff.changed.length > 0 || (diff.added.length === 0 && diff.removed.length === 0))
|
||||||
return [
|
return [
|
||||||
"The available MCP server instructions have changed. This list supersedes the previous one.",
|
"The available MCP server instructions have changed. This list supersedes the previous one.",
|
||||||
render(current),
|
render(current, codeMode),
|
||||||
].join("\n")
|
].join("\n")
|
||||||
return [
|
return [
|
||||||
...(diff.added.length === 0
|
...(diff.added.length === 0
|
||||||
? []
|
? []
|
||||||
: ["New MCP server instructions are available in addition to those previously listed:", ...entries(diff.added)]),
|
: [
|
||||||
|
"New MCP server instructions are available in addition to those previously listed:",
|
||||||
|
...entries(diff.added, codeMode),
|
||||||
|
]),
|
||||||
...(diff.removed.length === 0
|
...(diff.removed.length === 0
|
||||||
? []
|
? []
|
||||||
: [
|
: [
|
||||||
@@ -64,13 +67,14 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/v2
|
|||||||
export const layer = Layer.effect(
|
export const layer = Layer.effect(
|
||||||
Service,
|
Service,
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
|
const codeMode = yield* CodeModeV2.Service
|
||||||
const mcp = yield* MCP.Service
|
const mcp = yield* MCP.Service
|
||||||
|
|
||||||
return Service.of({
|
return Service.of({
|
||||||
load: Effect.fn("McpGuidance.load")(function* (selection) {
|
load: Effect.fn("McpGuidance.load")(function* (selection) {
|
||||||
const agent = selection.info
|
const agent = selection.info
|
||||||
if (!agent) return Instructions.empty
|
if (!agent) return Instructions.empty
|
||||||
if (Flag.CODEMODE_ENABLED && PermissionV2.evaluate("execute", "*", agent.permissions).effect === "deny")
|
if (codeMode.enabled && PermissionV2.evaluate("execute", "*", agent.permissions).effect === "deny")
|
||||||
return Instructions.empty
|
return Instructions.empty
|
||||||
const [instructions, tools] = yield* Effect.all([mcp.instructions(), mcp.tools()], {
|
const [instructions, tools] = yield* Effect.all([mcp.instructions(), mcp.tools()], {
|
||||||
concurrency: "unbounded",
|
concurrency: "unbounded",
|
||||||
@@ -80,7 +84,7 @@ export const layer = Layer.effect(
|
|||||||
.filter((item) => {
|
.filter((item) => {
|
||||||
const owned = tools.filter((tool) => tool.server === item.server)
|
const owned = tools.filter((tool) => tool.server === item.server)
|
||||||
return (
|
return (
|
||||||
(!Flag.CODEMODE_ENABLED && owned.length === 0) ||
|
(!codeMode.enabled && owned.length === 0) ||
|
||||||
owned.some(
|
owned.some(
|
||||||
(tool) =>
|
(tool) =>
|
||||||
PermissionV2.evaluate(McpTool.name(tool.server, tool.name), "*", agent.permissions).effect !== "deny",
|
PermissionV2.evaluate(McpTool.name(tool.server, tool.name), "*", agent.permissions).effect !== "deny",
|
||||||
@@ -93,8 +97,8 @@ export const layer = Layer.effect(
|
|||||||
key: Instructions.Key.make("core/mcp-guidance"),
|
key: Instructions.Key.make("core/mcp-guidance"),
|
||||||
codec: Schema.toCodecJson(Schema.Array(Summary)),
|
codec: Schema.toCodecJson(Schema.Array(Summary)),
|
||||||
load: Effect.succeed(visible),
|
load: Effect.succeed(visible),
|
||||||
baseline: render,
|
baseline: (servers) => render(servers, codeMode.enabled),
|
||||||
update,
|
update: (previous, current) => update(previous, current, codeMode.enabled),
|
||||||
removed: () => "MCP server instructions are no longer available.",
|
removed: () => "MCP server instructions are no longer available.",
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
@@ -102,4 +106,4 @@ export const layer = Layer.effect(
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
export const node = makeLocationNode({ service: Service, layer, deps: [MCP.node] })
|
export const node = makeLocationNode({ service: Service, layer, deps: [CodeModeV2.node, MCP.node] })
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { Context, Effect, Exit, Layer, Scope, Semaphore } from "effect"
|
|||||||
import { AgentV2 } from "./agent"
|
import { AgentV2 } from "./agent"
|
||||||
import { AISDK } from "./aisdk"
|
import { AISDK } from "./aisdk"
|
||||||
import { Catalog } from "./catalog"
|
import { Catalog } from "./catalog"
|
||||||
|
import { CodeModeV2 } from "./code-mode"
|
||||||
import { CommandV2 } from "./command"
|
import { CommandV2 } from "./command"
|
||||||
import { EventV2 } from "./event"
|
import { EventV2 } from "./event"
|
||||||
import { Integration } from "./integration"
|
import { Integration } from "./integration"
|
||||||
@@ -124,6 +125,7 @@ export const node = makeLocationNode({
|
|||||||
AgentV2.node,
|
AgentV2.node,
|
||||||
AISDK.node,
|
AISDK.node,
|
||||||
Catalog.node,
|
Catalog.node,
|
||||||
|
CodeModeV2.node,
|
||||||
CommandV2.node,
|
CommandV2.node,
|
||||||
Integration.node,
|
Integration.node,
|
||||||
Location.node,
|
Location.node,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { Effect, Schema, Stream } from "effect"
|
|||||||
import { AgentV2 } from "../agent"
|
import { AgentV2 } from "../agent"
|
||||||
import { AISDK } from "../aisdk"
|
import { AISDK } from "../aisdk"
|
||||||
import { Catalog } from "../catalog"
|
import { Catalog } from "../catalog"
|
||||||
|
import { CodeModeV2 } from "../code-mode"
|
||||||
import { CommandV2 } from "../command"
|
import { CommandV2 } from "../command"
|
||||||
import { Credential } from "../credential"
|
import { Credential } from "../credential"
|
||||||
import { EventV2 } from "../event"
|
import { EventV2 } from "../event"
|
||||||
@@ -28,6 +29,7 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
|
|||||||
const agents = yield* AgentV2.Service
|
const agents = yield* AgentV2.Service
|
||||||
const aisdk = yield* AISDK.Service
|
const aisdk = yield* AISDK.Service
|
||||||
const catalog = yield* Catalog.Service
|
const catalog = yield* Catalog.Service
|
||||||
|
const codeMode = yield* CodeModeV2.Service
|
||||||
const commands = yield* CommandV2.Service
|
const commands = yield* CommandV2.Service
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
const integration = yield* Integration.Service
|
const integration = yield* Integration.Service
|
||||||
@@ -158,6 +160,9 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
|
|||||||
callback(draft)
|
callback(draft)
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
codemode: {
|
||||||
|
register: codeMode.register,
|
||||||
|
},
|
||||||
event: {
|
event: {
|
||||||
subscribe: () => events.subscribe().pipe(Stream.filter(EventManifest.isServer)),
|
subscribe: () => events.subscribe().pipe(Stream.filter(EventManifest.isServer)),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { McpEvent } from "@opencode-ai/schema/mcp-event"
|
|||||||
import { Effect, Exit, type JsonSchema, Layer, Scope, Semaphore, Stream } from "effect"
|
import { Effect, Exit, type JsonSchema, Layer, Scope, Semaphore, Stream } from "effect"
|
||||||
import { makeLocationNode } from "../effect/app-node"
|
import { makeLocationNode } from "../effect/app-node"
|
||||||
import { EventV2 } from "../event"
|
import { EventV2 } from "../event"
|
||||||
import { Flag } from "../flag/flag"
|
import { CodeModeV2 } from "../code-mode"
|
||||||
import { MCP } from "../mcp"
|
import { MCP } from "../mcp"
|
||||||
import { PermissionV2 } from "../permission"
|
import { PermissionV2 } from "../permission"
|
||||||
import { Tool } from "./tool"
|
import { Tool } from "./tool"
|
||||||
@@ -21,6 +21,7 @@ export const name = (server: string, tool: string) => `${group(server)}_${tool.r
|
|||||||
export const layer = Layer.effectDiscard(
|
export const layer = Layer.effectDiscard(
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const mcp = yield* MCP.Service
|
const mcp = yield* MCP.Service
|
||||||
|
const codeMode = yield* CodeModeV2.Service
|
||||||
const tools = yield* Tools.Service
|
const tools = yield* Tools.Service
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
const permission = yield* PermissionV2.Service
|
const permission = yield* PermissionV2.Service
|
||||||
@@ -105,13 +106,14 @@ export const layer = Layer.effectDiscard(
|
|||||||
groups.set(tool.server, group)
|
groups.set(tool.server, group)
|
||||||
}
|
}
|
||||||
const next = yield* Scope.fork(scope)
|
const next = yield* Scope.fork(scope)
|
||||||
yield* Effect.forEach(
|
const register = codeMode.enabled
|
||||||
groups,
|
? codeMode.register((draft) => {
|
||||||
([group, record]) => tools.register(record, { group, deferred: Flag.CODEMODE_ENABLED }),
|
for (const [server, record] of groups)
|
||||||
{
|
for (const [tool, value] of Object.entries(record))
|
||||||
discard: true,
|
draft.add([group(server), tool.replace(/[^a-zA-Z0-9_-]/g, "_")], value)
|
||||||
},
|
})
|
||||||
).pipe(Scope.provide(next), Effect.orDie)
|
: Effect.forEach(groups, ([group, record]) => tools.register(record, { group }), { discard: true })
|
||||||
|
yield* register.pipe(Scope.provide(next), Effect.orDie)
|
||||||
if (current) yield* Scope.close(current, Exit.void)
|
if (current) yield* Scope.close(current, Exit.void)
|
||||||
current = next
|
current = next
|
||||||
}),
|
}),
|
||||||
@@ -128,5 +130,5 @@ export const layer = Layer.effectDiscard(
|
|||||||
export const node = makeLocationNode({
|
export const node = makeLocationNode({
|
||||||
name: "mcp-tools",
|
name: "mcp-tools",
|
||||||
layer,
|
layer,
|
||||||
deps: [ToolRegistry.toolsNode, MCP.node, EventV2.node, PermissionV2.node],
|
deps: [CodeModeV2.node, ToolRegistry.toolsNode, MCP.node, EventV2.node, PermissionV2.node],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,13 +3,12 @@ export * as ToolRegistry from "./registry"
|
|||||||
import { ToolOutput, type ToolCall, type ToolDefinition, type ToolResultValue } from "@opencode-ai/llm"
|
import { ToolOutput, type ToolCall, type ToolDefinition, type ToolResultValue } from "@opencode-ai/llm"
|
||||||
import { Context, Effect, Layer, Scope } from "effect"
|
import { Context, Effect, Layer, Scope } from "effect"
|
||||||
import type { AgentV2 } from "../agent"
|
import type { AgentV2 } from "../agent"
|
||||||
import { Flag } from "../flag/flag"
|
import { CodeModeV2 } from "../code-mode"
|
||||||
import { PermissionV2 } from "../permission"
|
import { PermissionV2 } from "../permission"
|
||||||
import { SessionMessage } from "../session/message"
|
import { SessionMessage } from "../session/message"
|
||||||
import { SessionSchema } from "../session/schema"
|
import { SessionSchema } from "../session/schema"
|
||||||
import { ToolOutputStore } from "../tool-output-store"
|
import { ToolOutputStore } from "../tool-output-store"
|
||||||
import { Wildcard } from "../util/wildcard"
|
import { Wildcard } from "../util/wildcard"
|
||||||
import { ExecuteTool } from "./execute"
|
|
||||||
import { definition, permission, registrationEntries, RegistrationError, settle, type AnyTool } from "./tool"
|
import { definition, permission, registrationEntries, RegistrationError, settle, type AnyTool } from "./tool"
|
||||||
import { Tools } from "./tools"
|
import { Tools } from "./tools"
|
||||||
import { ToolHooks } from "./hooks"
|
import { ToolHooks } from "./hooks"
|
||||||
@@ -55,14 +54,12 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/v2
|
|||||||
const registryLayer = Layer.effect(
|
const registryLayer = Layer.effect(
|
||||||
Service,
|
Service,
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
|
const codeMode = yield* CodeModeV2.Service
|
||||||
const resources = yield* ToolOutputStore.Service
|
const resources = yield* ToolOutputStore.Service
|
||||||
const toolHooks = yield* ToolHooks.Service
|
const toolHooks = yield* ToolHooks.Service
|
||||||
type Registration = {
|
type Registration = {
|
||||||
readonly identity: object
|
readonly identity: object
|
||||||
readonly tool: AnyTool
|
readonly tool: AnyTool
|
||||||
readonly name: string
|
|
||||||
readonly group?: string
|
|
||||||
readonly deferred: boolean
|
|
||||||
}
|
}
|
||||||
const local = new Map<string, Array<{ readonly token: object; readonly registration: Registration }>>()
|
const local = new Map<string, Array<{ readonly token: object; readonly registration: Registration }>>()
|
||||||
|
|
||||||
@@ -150,7 +147,7 @@ const registryLayer = Layer.effect(
|
|||||||
register: Effect.fn("ToolRegistry.register")(function* (tools, options) {
|
register: Effect.fn("ToolRegistry.register")(function* (tools, options) {
|
||||||
const entries = registrationEntries(tools, options?.group)
|
const entries = registrationEntries(tools, options?.group)
|
||||||
if (entries.length === 0) return
|
if (entries.length === 0) return
|
||||||
const reserved = options?.deferred ? undefined : entries.find((entry) => entry.key === "execute")
|
const reserved = entries.find((entry) => entry.key === "execute")
|
||||||
if (reserved)
|
if (reserved)
|
||||||
return yield* Effect.fail(
|
return yield* Effect.fail(
|
||||||
new RegistrationError({ name: reserved.key, message: 'Tool name "execute" is reserved for CodeMode' }),
|
new RegistrationError({ name: reserved.key, message: 'Tool name "execute" is reserved for CodeMode' }),
|
||||||
@@ -166,9 +163,6 @@ const registryLayer = Layer.effect(
|
|||||||
registration: {
|
registration: {
|
||||||
identity: {},
|
identity: {},
|
||||||
tool: entry.tool,
|
tool: entry.tool,
|
||||||
name: entry.name,
|
|
||||||
group: entry.group,
|
|
||||||
deferred: options?.deferred ?? false,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
@@ -195,30 +189,18 @@ const registryLayer = Layer.effect(
|
|||||||
const usePatch = input.model.provider.toLowerCase() === "openai" || input.model.id.toLowerCase().includes("gpt")
|
const usePatch = input.model.provider.toLowerCase() === "openai" || input.model.id.toLowerCase().includes("gpt")
|
||||||
for (const [name, registration] of registrations) {
|
for (const [name, registration] of registrations) {
|
||||||
const wrongEditTool = name === "apply_patch" ? !usePatch : (name === "edit" || name === "write") && usePatch
|
const wrongEditTool = name === "apply_patch" ? !usePatch : (name === "edit" || name === "write") && usePatch
|
||||||
if (
|
if (wrongEditTool || whollyDisabled(permission(registration.tool, name), input.permissions ?? []))
|
||||||
wrongEditTool ||
|
|
||||||
(registration.deferred && !Flag.CODEMODE_ENABLED) ||
|
|
||||||
whollyDisabled(permission(registration.tool, name), input.permissions ?? [])
|
|
||||||
)
|
|
||||||
registrations.delete(name)
|
registrations.delete(name)
|
||||||
}
|
}
|
||||||
const direct = new Map(Array.from(registrations).filter(([, registration]) => !registration.deferred))
|
const execute = yield* codeMode.materialize({ permissions: input.permissions })
|
||||||
const deferred = new Map(Array.from(registrations).filter(([, registration]) => registration.deferred))
|
|
||||||
const execute =
|
|
||||||
deferred.size > 0 && !whollyDisabled("execute", input.permissions ?? [])
|
|
||||||
? ExecuteTool.create({
|
|
||||||
registrations: deferred,
|
|
||||||
current: (name) => local.get(name)?.at(-1)?.registration,
|
|
||||||
})
|
|
||||||
: undefined
|
|
||||||
return {
|
return {
|
||||||
definitions: [
|
definitions: [
|
||||||
...Array.from(direct, ([name, registration]) => definition(name, registration.tool)),
|
...Array.from(registrations, ([name, registration]) => definition(name, registration.tool)),
|
||||||
...(execute ? [definition("execute", execute)] : []),
|
...(execute ? [definition("execute", execute)] : []),
|
||||||
],
|
],
|
||||||
settle: (input) => {
|
settle: (input) => {
|
||||||
if (input.call.name === "execute" && execute) return settleTool(input, execute)
|
if (input.call.name === "execute" && execute) return settleTool(input, execute)
|
||||||
const registration = direct.get(input.call.name)
|
const registration = registrations.get(input.call.name)
|
||||||
if (registration) return settleWith(input, registration.identity)
|
if (registration) return settleWith(input, registration.identity)
|
||||||
return Effect.succeed({
|
return Effect.succeed({
|
||||||
result: { type: "error", value: `Unknown tool: ${input.call.name}` },
|
result: { type: "error", value: `Unknown tool: ${input.call.name}` },
|
||||||
@@ -244,11 +226,11 @@ function whollyDisabled(action: string, rules: PermissionV2.Ruleset) {
|
|||||||
export const node = makeLocationNode({
|
export const node = makeLocationNode({
|
||||||
service: Service,
|
service: Service,
|
||||||
layer,
|
layer,
|
||||||
deps: [ToolOutputStore.node, ToolHooks.node],
|
deps: [CodeModeV2.node, ToolOutputStore.node, ToolHooks.node],
|
||||||
})
|
})
|
||||||
|
|
||||||
export const toolsNode = makeLocationNode({
|
export const toolsNode = makeLocationNode({
|
||||||
service: Tools.Service,
|
service: Tools.Service,
|
||||||
layer,
|
layer,
|
||||||
deps: [ToolOutputStore.node, ToolHooks.node],
|
deps: [CodeModeV2.node, ToolOutputStore.node, ToolHooks.node],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ describe("PluginV2", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.effect("groups tool names and defers registrations from direct exposure", () =>
|
it.effect("registers direct and Code Mode tools through separate plugin domains", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const plugins = yield* PluginV2.Service
|
const plugins = yield* PluginV2.Service
|
||||||
const registry = yield* ToolRegistry.Service
|
const registry = yield* ToolRegistry.Service
|
||||||
@@ -205,13 +205,17 @@ describe("PluginV2", () => {
|
|||||||
const plugin = define({
|
const plugin = define({
|
||||||
id: "grouped-tools",
|
id: "grouped-tools",
|
||||||
effect: (ctx) =>
|
effect: (ctx) =>
|
||||||
ctx.tool
|
Effect.gen(function* () {
|
||||||
.transform((draft) => {
|
yield* ctx.tool
|
||||||
draft.add("plain", tool("Plain"))
|
.transform((draft) => {
|
||||||
draft.add("look/up", tool("Lookup"), { group: "context 7" })
|
draft.add("plain", tool("Plain"))
|
||||||
draft.add("search", tool("Search"), { group: "context 7", deferred: true })
|
draft.add("look/up", tool("Lookup"), { group: "context 7" })
|
||||||
})
|
})
|
||||||
.pipe(Effect.orDie),
|
.pipe(Effect.orDie)
|
||||||
|
yield* ctx.codemode
|
||||||
|
.register((draft) => draft.add(["context_7", "search"], tool("Search")))
|
||||||
|
.pipe(Effect.orDie)
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
yield* plugins.activate([{ plugin }])
|
yield* plugins.activate([{ plugin }])
|
||||||
@@ -221,6 +225,9 @@ describe("PluginV2", () => {
|
|||||||
"context_7_look_up",
|
"context_7_look_up",
|
||||||
"execute",
|
"execute",
|
||||||
])
|
])
|
||||||
|
|
||||||
|
yield* plugins.activate([])
|
||||||
|
expect((yield* registry.materialize({ model: testModel })).definitions).toEqual([])
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { AgentV2 } from "@opencode-ai/core/agent"
|
import { AgentV2 } from "@opencode-ai/core/agent"
|
||||||
import { AISDK } from "@opencode-ai/core/aisdk"
|
import { AISDK } from "@opencode-ai/core/aisdk"
|
||||||
import { Catalog } from "@opencode-ai/core/catalog"
|
import { Catalog } from "@opencode-ai/core/catalog"
|
||||||
|
import { CodeModeV2 } from "@opencode-ai/core/code-mode"
|
||||||
import { CommandV2 } from "@opencode-ai/core/command"
|
import { CommandV2 } from "@opencode-ai/core/command"
|
||||||
import { Credential } from "@opencode-ai/core/credential"
|
import { Credential } from "@opencode-ai/core/credential"
|
||||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||||
@@ -43,6 +44,7 @@ export const PluginTestLayer = AppNodeBuilder.build(
|
|||||||
AgentV2.node,
|
AgentV2.node,
|
||||||
AISDK.node,
|
AISDK.node,
|
||||||
Catalog.node,
|
Catalog.node,
|
||||||
|
CodeModeV2.node,
|
||||||
CommandV2.node,
|
CommandV2.node,
|
||||||
Integration.node,
|
Integration.node,
|
||||||
PluginRuntime.node,
|
PluginRuntime.node,
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ export function host(overrides: Overrides = {}): PluginContext {
|
|||||||
transform: () => Effect.die("unused command.transform"),
|
transform: () => Effect.die("unused command.transform"),
|
||||||
reload: () => Effect.die("unused command.reload"),
|
reload: () => Effect.die("unused command.reload"),
|
||||||
},
|
},
|
||||||
|
codemode: overrides.codemode ?? {
|
||||||
|
register: () => Effect.die("unused codemode.register"),
|
||||||
|
},
|
||||||
event: overrides.event ?? {
|
event: overrides.event ?? {
|
||||||
subscribe: () => Stream.empty,
|
subscribe: () => Stream.empty,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { describe, expect } from "bun:test"
|
import { describe, expect } from "bun:test"
|
||||||
import { Tool } from "@opencode-ai/core/tool/tool"
|
import { Tool } from "@opencode-ai/core/tool/tool"
|
||||||
import { AgentV2 } from "@opencode-ai/core/agent"
|
import { AgentV2 } from "@opencode-ai/core/agent"
|
||||||
|
import { CodeModeV2 } from "@opencode-ai/core/code-mode"
|
||||||
import type { PermissionV2 } from "@opencode-ai/core/permission"
|
import type { PermissionV2 } from "@opencode-ai/core/permission"
|
||||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||||
|
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||||
import { SessionV2 } from "@opencode-ai/core/session"
|
import { SessionV2 } from "@opencode-ai/core/session"
|
||||||
import { SessionMessage } from "@opencode-ai/core/session/message"
|
import { SessionMessage } from "@opencode-ai/core/session/message"
|
||||||
import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
|
import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
|
||||||
@@ -28,7 +30,9 @@ const outputStore = Layer.mock(ToolOutputStore.Service, {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const registryLayer = AppNodeBuilder.build(ToolRegistry.node, [[ToolOutputStore.node, outputStore]])
|
const registryLayer = AppNodeBuilder.build(LayerNode.group([ToolRegistry.node, CodeModeV2.node]), [
|
||||||
|
[ToolOutputStore.node, outputStore],
|
||||||
|
])
|
||||||
const it = testEffect(registryLayer)
|
const it = testEffect(registryLayer)
|
||||||
const identity = {
|
const identity = {
|
||||||
agent: AgentV2.ID.make("build"),
|
agent: AgentV2.ID.make("build"),
|
||||||
@@ -53,6 +57,30 @@ const make = (permission?: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("ToolRegistry", () => {
|
describe("ToolRegistry", () => {
|
||||||
|
it.effect("materializes nested Code Mode sources as execute", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const codeMode = yield* CodeModeV2.Service
|
||||||
|
const service = yield* ToolRegistry.Service
|
||||||
|
yield* codeMode.register((draft) => draft.add(["opencode", "v2", "echo"], make()))
|
||||||
|
|
||||||
|
const definitions = yield* toolDefinitions(service)
|
||||||
|
expect(definitions.map((tool) => tool.name)).toEqual(["execute"])
|
||||||
|
expect(definitions[0]?.description).toContain("tools.opencode.v2.echo")
|
||||||
|
expect(
|
||||||
|
yield* executeTool(service, {
|
||||||
|
sessionID,
|
||||||
|
...identity,
|
||||||
|
call: {
|
||||||
|
type: "tool-call",
|
||||||
|
id: "call-code-mode",
|
||||||
|
name: "execute",
|
||||||
|
input: { code: 'return await tools.opencode.v2.echo({ text: "hello" })' },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toEqual({ type: "text", value: '{\n "text": "hello"\n}' })
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.effect("filters disabled tools with edit aliases and ordered wildcard precedence", () =>
|
it.effect("filters disabled tools with edit aliases and ordered wildcard precedence", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const service = yield* ToolRegistry.Service
|
const service = yield* ToolRegistry.Service
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ export class Service extends ConfigService.Service<Service>()("@opencode/Runtime
|
|||||||
enableExperimentalModels: bool("OPENCODE_ENABLE_EXPERIMENTAL_MODELS"),
|
enableExperimentalModels: bool("OPENCODE_ENABLE_EXPERIMENTAL_MODELS"),
|
||||||
enableQuestionTool: bool("OPENCODE_ENABLE_QUESTION_TOOL"),
|
enableQuestionTool: bool("OPENCODE_ENABLE_QUESTION_TOOL"),
|
||||||
experimentalReferences: enabledByExperimental("OPENCODE_EXPERIMENTAL_REFERENCES"),
|
experimentalReferences: enabledByExperimental("OPENCODE_EXPERIMENTAL_REFERENCES"),
|
||||||
experimentalBackgroundSubagents: enabledByExperimental("OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS"),
|
|
||||||
experimentalLspTy: bool("OPENCODE_EXPERIMENTAL_LSP_TY"),
|
experimentalLspTy: bool("OPENCODE_EXPERIMENTAL_LSP_TY"),
|
||||||
experimentalLspTool: enabledByExperimental("OPENCODE_EXPERIMENTAL_LSP_TOOL"),
|
experimentalLspTool: enabledByExperimental("OPENCODE_EXPERIMENTAL_LSP_TOOL"),
|
||||||
experimentalOxfmt: enabledByExperimental("OPENCODE_EXPERIMENTAL_OXFMT"),
|
experimentalOxfmt: enabledByExperimental("OPENCODE_EXPERIMENTAL_OXFMT"),
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { Agent } from "@/agent/agent"
|
|||||||
import { Job } from "@/job"
|
import { Job } from "@/job"
|
||||||
import { Config } from "@/config/config"
|
import { Config } from "@/config/config"
|
||||||
import { InstanceState } from "@/effect/instance-state"
|
import { InstanceState } from "@/effect/instance-state"
|
||||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
|
||||||
import { MCP } from "@/mcp"
|
import { MCP } from "@/mcp"
|
||||||
import { Project } from "@/project/project"
|
import { Project } from "@/project/project"
|
||||||
import { Session } from "@/session/session"
|
import { Session } from "@/session/session"
|
||||||
@@ -34,10 +33,9 @@ export const experimentalHandlers = HttpApiBuilder.group(InstanceHttpApi, "exper
|
|||||||
const worktreeSvc = yield* Worktree.Service
|
const worktreeSvc = yield* Worktree.Service
|
||||||
const sessions = yield* Session.Service
|
const sessions = yield* Session.Service
|
||||||
const jobs = yield* Job.Service
|
const jobs = yield* Job.Service
|
||||||
const flags = yield* RuntimeFlags.Service
|
|
||||||
|
|
||||||
const capabilities = Effect.fn("ExperimentalHttpApi.capabilities")(function* () {
|
const capabilities = Effect.fn("ExperimentalHttpApi.capabilities")(function* () {
|
||||||
return { backgroundSubagents: flags.experimentalBackgroundSubagents }
|
return { backgroundSubagents: true }
|
||||||
})
|
})
|
||||||
|
|
||||||
const getConsole = Effect.fn("ExperimentalHttpApi.console")(function* () {
|
const getConsole = Effect.fn("ExperimentalHttpApi.console")(function* () {
|
||||||
@@ -159,7 +157,6 @@ export const experimentalHandlers = HttpApiBuilder.group(InstanceHttpApi, "exper
|
|||||||
const sessionBackground = Effect.fn("ExperimentalHttpApi.sessionBackground")(function* (ctx: {
|
const sessionBackground = Effect.fn("ExperimentalHttpApi.sessionBackground")(function* (ctx: {
|
||||||
params: { sessionID: SessionID }
|
params: { sessionID: SessionID }
|
||||||
}) {
|
}) {
|
||||||
if (!flags.experimentalBackgroundSubagents) return false
|
|
||||||
return (yield* jobs.backgroundAll({ sessionID: ctx.params.sessionID, type: "task" })).length > 0
|
return (yield* jobs.backgroundAll({ sessionID: ctx.params.sessionID, type: "task" })).length > 0
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import * as Tool from "./tool"
|
import * as Tool from "./tool"
|
||||||
import DESCRIPTION from "./task.txt"
|
import DESCRIPTION from "./task.txt"
|
||||||
import { ToolJsonSchema } from "./json-schema"
|
|
||||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||||
import { Job } from "@/job"
|
import { Job } from "@/job"
|
||||||
import { Session } from "@/session/session"
|
import { Session } from "@/session/session"
|
||||||
@@ -12,7 +11,6 @@ import type { SessionPrompt } from "../session/prompt"
|
|||||||
import { Config } from "@/config/config"
|
import { Config } from "@/config/config"
|
||||||
import { Effect, Exit, Schema, Scope } from "effect"
|
import { Effect, Exit, Schema, Scope } from "effect"
|
||||||
import { EffectBridge } from "@/effect/bridge"
|
import { EffectBridge } from "@/effect/bridge"
|
||||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
|
||||||
import { Database } from "@opencode-ai/core/database/database"
|
import { Database } from "@opencode-ai/core/database/database"
|
||||||
|
|
||||||
export interface TaskPromptOps {
|
export interface TaskPromptOps {
|
||||||
@@ -51,8 +49,6 @@ const BaseParameterFields = {
|
|||||||
command: Schema.optional(Schema.String).annotate({ description: "The command that triggered this task" }),
|
command: Schema.optional(Schema.String).annotate({ description: "The command that triggered this task" }),
|
||||||
}
|
}
|
||||||
|
|
||||||
const BaseParameters = Schema.Struct(BaseParameterFields)
|
|
||||||
|
|
||||||
export const Parameters = Schema.Struct({
|
export const Parameters = Schema.Struct({
|
||||||
...BaseParameterFields,
|
...BaseParameterFields,
|
||||||
background: Schema.optional(Schema.Boolean).annotate({
|
background: Schema.optional(Schema.Boolean).annotate({
|
||||||
@@ -86,7 +82,6 @@ export const TaskTool = Tool.define(
|
|||||||
const config = yield* Config.Service
|
const config = yield* Config.Service
|
||||||
const sessions = yield* Session.Service
|
const sessions = yield* Session.Service
|
||||||
const scope = yield* Scope.Scope
|
const scope = yield* Scope.Scope
|
||||||
const flags = yield* RuntimeFlags.Service
|
|
||||||
const database = yield* Database.Service
|
const database = yield* Database.Service
|
||||||
|
|
||||||
const run = Effect.fn("TaskTool.execute")(function* (
|
const run = Effect.fn("TaskTool.execute")(function* (
|
||||||
@@ -95,11 +90,6 @@ export const TaskTool = Tool.define(
|
|||||||
) {
|
) {
|
||||||
const cfg = yield* config.get()
|
const cfg = yield* config.get()
|
||||||
const runInBackground = params.background === true
|
const runInBackground = params.background === true
|
||||||
if (runInBackground && !flags.experimentalBackgroundSubagents) {
|
|
||||||
return yield* Effect.fail(
|
|
||||||
new Error("Background subagents require OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ctx.extra?.bypassAgentCheck) {
|
if (!ctx.extra?.bypassAgentCheck) {
|
||||||
yield* ctx.ask({
|
yield* ctx.ask({
|
||||||
@@ -333,11 +323,8 @@ export const TaskTool = Tool.define(
|
|||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
description: flags.experimentalBackgroundSubagents
|
description: [DESCRIPTION, BACKGROUND_DESCRIPTION].join("\n\n"),
|
||||||
? [DESCRIPTION, BACKGROUND_DESCRIPTION].join("\n\n")
|
|
||||||
: DESCRIPTION,
|
|
||||||
parameters: Parameters,
|
parameters: Parameters,
|
||||||
jsonSchema: flags.experimentalBackgroundSubagents ? undefined : ToolJsonSchema.fromSchema(BaseParameters),
|
|
||||||
execute: (params: Schema.Schema.Type<typeof Parameters>, ctx: Tool.Context) =>
|
execute: (params: Schema.Schema.Type<typeof Parameters>, ctx: Tool.Context) =>
|
||||||
run(params, ctx).pipe(Effect.orDie),
|
run(params, ctx).pipe(Effect.orDie),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -161,7 +161,6 @@ async function renderFooter(
|
|||||||
currentModel?: RunInput["model"]
|
currentModel?: RunInput["model"]
|
||||||
currentVariant?: string
|
currentVariant?: string
|
||||||
subagents?: FooterSubagentState
|
subagents?: FooterSubagentState
|
||||||
backgroundSubagents?: boolean
|
|
||||||
width?: number
|
width?: number
|
||||||
height?: number
|
height?: number
|
||||||
state?: Partial<FooterState>
|
state?: Partial<FooterState>
|
||||||
@@ -199,7 +198,6 @@ async function renderFooter(
|
|||||||
subagent={subagents}
|
subagent={subagents}
|
||||||
theme={input.theme ?? (() => RUN_THEME_FALLBACK)}
|
theme={input.theme ?? (() => RUN_THEME_FALLBACK)}
|
||||||
tuiConfig={config}
|
tuiConfig={config}
|
||||||
backgroundSubagents={input.backgroundSubagents ?? true}
|
|
||||||
agent="opencode"
|
agent="opencode"
|
||||||
onSubmit={input.onSubmit ?? (() => true)}
|
onSubmit={input.onSubmit ?? (() => true)}
|
||||||
onPermissionReply={() => {}}
|
onPermissionReply={() => {}}
|
||||||
@@ -998,7 +996,6 @@ test("direct footer shows editable prompts and additional queued work while runn
|
|||||||
]}
|
]}
|
||||||
theme={() => RUN_THEME_FALLBACK}
|
theme={() => RUN_THEME_FALLBACK}
|
||||||
tuiConfig={tuiConfig}
|
tuiConfig={tuiConfig}
|
||||||
backgroundSubagents={true}
|
|
||||||
agent="opencode"
|
agent="opencode"
|
||||||
onSubmit={() => true}
|
onSubmit={() => true}
|
||||||
onPermissionReply={() => {}}
|
onPermissionReply={() => {}}
|
||||||
@@ -1071,7 +1068,7 @@ test("direct footer shows editable prompts and additional queued work while runn
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
test("direct footer separates a lone context hint from model and command hint", async () => {
|
test("direct footer always offers backgrounding for a foreground subagent", async () => {
|
||||||
const app = await renderFooter({
|
const app = await renderFooter({
|
||||||
providers: [provider()],
|
providers: [provider()],
|
||||||
currentModel: { providerID: "opencode", modelID: "gpt-5" },
|
currentModel: { providerID: "opencode", modelID: "gpt-5" },
|
||||||
@@ -1082,7 +1079,6 @@ test("direct footer separates a lone context hint from model and command hint",
|
|||||||
permissions: [],
|
permissions: [],
|
||||||
questions: [],
|
questions: [],
|
||||||
},
|
},
|
||||||
backgroundSubagents: false,
|
|
||||||
width: 160,
|
width: 160,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1091,8 +1087,8 @@ test("direct footer separates a lone context hint from model and command hint",
|
|||||||
const frame = app.captureCharFrame()
|
const frame = app.captureCharFrame()
|
||||||
|
|
||||||
expect(frame).toContain("GPT-5")
|
expect(frame).toContain("GPT-5")
|
||||||
expect(frame).toContain("xhigh · ctrl+x down subagents · ctrl+p cmd")
|
expect(frame).toContain("xhigh · ctrl+b background · ctrl+x down subagents · ctrl+p cmd")
|
||||||
expect(frame).not.toContain("ctrl+b background")
|
expect(frame).toContain("ctrl+b background")
|
||||||
expect(frame).not.toContain("queued")
|
expect(frame).not.toContain("queued")
|
||||||
} finally {
|
} finally {
|
||||||
app.cleanup()
|
app.cleanup()
|
||||||
@@ -1110,7 +1106,6 @@ test("direct footer hides the subagent hint when only completed subagents remain
|
|||||||
permissions: [],
|
permissions: [],
|
||||||
questions: [],
|
questions: [],
|
||||||
},
|
},
|
||||||
backgroundSubagents: false,
|
|
||||||
width: 160,
|
width: 160,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -134,7 +134,6 @@ describe("run interactive runtime", () => {
|
|||||||
variant: undefined,
|
variant: undefined,
|
||||||
files: [],
|
files: [],
|
||||||
thinking: false,
|
thinking: false,
|
||||||
backgroundSubagents: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
createRuntimeLifecycle: async () => {
|
createRuntimeLifecycle: async () => {
|
||||||
@@ -233,7 +232,6 @@ describe("run interactive runtime", () => {
|
|||||||
variant: undefined,
|
variant: undefined,
|
||||||
files: [],
|
files: [],
|
||||||
thinking: false,
|
thinking: false,
|
||||||
backgroundSubagents: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
createRuntimeLifecycle: async () => {
|
createRuntimeLifecycle: async () => {
|
||||||
@@ -406,7 +404,6 @@ describe("run interactive runtime", () => {
|
|||||||
variant: undefined,
|
variant: undefined,
|
||||||
files: [],
|
files: [],
|
||||||
thinking: true,
|
thinking: true,
|
||||||
backgroundSubagents: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
createRuntimeLifecycle: async (input) => {
|
createRuntimeLifecycle: async (input) => {
|
||||||
@@ -496,7 +493,6 @@ describe("run interactive runtime", () => {
|
|||||||
variant: undefined,
|
variant: undefined,
|
||||||
files: [],
|
files: [],
|
||||||
thinking: false,
|
thinking: false,
|
||||||
backgroundSubagents: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
createRuntimeLifecycle: async (input) => {
|
createRuntimeLifecycle: async (input) => {
|
||||||
@@ -557,7 +553,6 @@ describe("run interactive runtime", () => {
|
|||||||
variant: undefined,
|
variant: undefined,
|
||||||
files: [],
|
files: [],
|
||||||
thinking: false,
|
thinking: false,
|
||||||
backgroundSubagents: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
createRuntimeLifecycle: async () => {
|
createRuntimeLifecycle: async () => {
|
||||||
@@ -603,7 +598,6 @@ describe("run interactive runtime", () => {
|
|||||||
variant: undefined,
|
variant: undefined,
|
||||||
files: [],
|
files: [],
|
||||||
thinking: false,
|
thinking: false,
|
||||||
backgroundSubagents: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
createRuntimeLifecycle: async (input) => {
|
createRuntimeLifecycle: async (input) => {
|
||||||
@@ -716,7 +710,6 @@ describe("run interactive runtime", () => {
|
|||||||
variant: "low",
|
variant: "low",
|
||||||
files: [],
|
files: [],
|
||||||
thinking: false,
|
thinking: false,
|
||||||
backgroundSubagents: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
createRuntimeLifecycle: async (input) => {
|
createRuntimeLifecycle: async (input) => {
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ describe("RuntimeFlags", () => {
|
|||||||
expect(flags.enableExperimentalModels).toBe(true)
|
expect(flags.enableExperimentalModels).toBe(true)
|
||||||
expect(flags.enableQuestionTool).toBe(true)
|
expect(flags.enableQuestionTool).toBe(true)
|
||||||
expect(flags.experimentalReferences).toBe(true)
|
expect(flags.experimentalReferences).toBe(true)
|
||||||
expect(flags.experimentalBackgroundSubagents).toBe(true)
|
|
||||||
expect(flags.experimentalLspTy).toBe(false)
|
expect(flags.experimentalLspTy).toBe(false)
|
||||||
expect(flags.experimentalLspTool).toBe(true)
|
expect(flags.experimentalLspTool).toBe(true)
|
||||||
expect(flags.experimentalOxfmt).toBe(true)
|
expect(flags.experimentalOxfmt).toBe(true)
|
||||||
|
|||||||
@@ -579,8 +579,10 @@ const scenarios: Scenario[] = [
|
|||||||
.at((ctx) => ({ path: "/experimental/session?roots=false&archived=false", headers: ctx.headers() }))
|
.at((ctx) => ({ path: "/experimental/session?roots=false&archived=false", headers: ctx.headers() }))
|
||||||
.json(200, array),
|
.json(200, array),
|
||||||
http.protected.get("/experimental/capabilities", "experimental.capabilities.get").json(200, (body) => {
|
http.protected.get("/experimental/capabilities", "experimental.capabilities.get").json(200, (body) => {
|
||||||
check(typeof body === "object" && body !== null, "capabilities should be an object")
|
check(
|
||||||
check("backgroundSubagents" in body, "capabilities should report background subagents")
|
typeof body === "object" && body !== null && "backgroundSubagents" in body && body.backgroundSubagents === true,
|
||||||
|
"capabilities should report background subagents as available",
|
||||||
|
)
|
||||||
}),
|
}),
|
||||||
http.protected
|
http.protected
|
||||||
.post("/experimental/session/{sessionID}/background", "experimental.session.background")
|
.post("/experimental/session/{sessionID}/background", "experimental.session.background")
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { afterEach, describe, expect, mock } from "bun:test"
|
import { afterEach, describe, expect, mock } from "bun:test"
|
||||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||||
import { Effect, Layer } from "effect"
|
import { Effect, Fiber, Layer } from "effect"
|
||||||
import { Session as SessionNs } from "@/session/session"
|
import { Session as SessionNs } from "@/session/session"
|
||||||
|
import { Job } from "@/job"
|
||||||
import { disposeAllInstances, TestInstance } from "../fixture/fixture"
|
import { disposeAllInstances, TestInstance } from "../fixture/fixture"
|
||||||
import { testEffect } from "../lib/effect"
|
import { pollWithTimeout, testEffect } from "../lib/effect"
|
||||||
import { httpApiLayer, requestInDirectory } from "./httpapi-layer"
|
import { httpApiLayer, requestInDirectory } from "./httpapi-layer"
|
||||||
|
|
||||||
const it = testEffect(Layer.mergeAll(LayerNode.compile(SessionNs.node), httpApiLayer))
|
const it = testEffect(Layer.mergeAll(LayerNode.compile(SessionNs.node), LayerNode.compile(Job.node), httpApiLayer))
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
mock.restore()
|
mock.restore()
|
||||||
@@ -14,6 +15,19 @@ afterEach(async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe("session action routes", () => {
|
describe("session action routes", () => {
|
||||||
|
it.instance(
|
||||||
|
"reports background subagents as available",
|
||||||
|
() =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const test = yield* TestInstance
|
||||||
|
const res = yield* requestInDirectory("/experimental/capabilities", test.directory)
|
||||||
|
|
||||||
|
expect(res.status).toBe(200)
|
||||||
|
expect(yield* res.json).toEqual({ backgroundSubagents: true })
|
||||||
|
}),
|
||||||
|
{ git: true },
|
||||||
|
)
|
||||||
|
|
||||||
it.instance(
|
it.instance(
|
||||||
"session routes expose metadata on create, update, get, and fork",
|
"session routes expose metadata on create, update, get, and fork",
|
||||||
() =>
|
() =>
|
||||||
@@ -107,4 +121,33 @@ describe("session action routes", () => {
|
|||||||
}),
|
}),
|
||||||
{ git: true },
|
{ git: true },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.instance(
|
||||||
|
"experimental background route backgrounds a synchronous subagent",
|
||||||
|
() =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const test = yield* TestInstance
|
||||||
|
const session = yield* Effect.acquireRelease(SessionNs.use.create({}), (created) =>
|
||||||
|
SessionNs.use.remove(created.id).pipe(Effect.ignore),
|
||||||
|
)
|
||||||
|
const jobs = yield* Job.Service
|
||||||
|
const job = yield* jobs.start({ type: "task", run: Effect.never })
|
||||||
|
const waiting = yield* jobs.block({ id: job.id, sessionID: session.id }).pipe(Effect.forkChild)
|
||||||
|
|
||||||
|
const backgrounded = yield* pollWithTimeout(
|
||||||
|
requestInDirectory(`/experimental/session/${session.id}/background`, test.directory, {
|
||||||
|
method: "POST",
|
||||||
|
}).pipe(
|
||||||
|
Effect.flatMap((res) => res.json),
|
||||||
|
Effect.map((value) => (value === true ? true : undefined)),
|
||||||
|
),
|
||||||
|
"background route never released the synchronous subagent",
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(backgrounded).toBe(true)
|
||||||
|
expect(yield* Fiber.join(waiting)).toMatchObject({ type: "backgrounded", info: { id: job.id } })
|
||||||
|
yield* jobs.cancel(job.id)
|
||||||
|
}),
|
||||||
|
{ git: true },
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ describe("tool.registry", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.instance("hides task background parameter unless experimental background subagents are enabled", () =>
|
it.instance("exposes the task background parameter by default", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const registry = yield* ToolRegistry.Service
|
const registry = yield* ToolRegistry.Service
|
||||||
const agent = yield* Agent.Service
|
const agent = yield* Agent.Service
|
||||||
@@ -162,8 +162,9 @@ describe("tool.registry", () => {
|
|||||||
agent: build,
|
agent: build,
|
||||||
})).find((tool) => tool.id === "task")
|
})).find((tool) => tool.id === "task")
|
||||||
|
|
||||||
expect(task?.jsonSchema).toBeDefined()
|
if (!task) throw new Error("task tool not found")
|
||||||
expect((task?.jsonSchema?.properties as Record<string, unknown> | undefined)?.background).toBeUndefined()
|
const jsonSchema = ToolJsonSchema.fromTool(task)
|
||||||
|
expect((jsonSchema.properties as Record<string, unknown> | undefined)?.background).toBeDefined()
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ const ref = {
|
|||||||
modelID: ModelV2.ID.make("test-model"),
|
modelID: ModelV2.ID.make("test-model"),
|
||||||
}
|
}
|
||||||
|
|
||||||
const layer = (flags: Partial<RuntimeFlags.Info> = {}) =>
|
const layer = () =>
|
||||||
LayerNode.compile(
|
LayerNode.compile(
|
||||||
LayerNode.group([
|
LayerNode.group([
|
||||||
Agent.node,
|
Agent.node,
|
||||||
@@ -52,11 +52,10 @@ const layer = (flags: Partial<RuntimeFlags.Info> = {}) =>
|
|||||||
RuntimeFlags.node,
|
RuntimeFlags.node,
|
||||||
Ripgrep.node,
|
Ripgrep.node,
|
||||||
]),
|
]),
|
||||||
[[RuntimeFlags.node, RuntimeFlags.layer(flags)]],
|
[[RuntimeFlags.node, RuntimeFlags.layer()]],
|
||||||
)
|
)
|
||||||
|
|
||||||
const it = testEffect(layer())
|
const it = testEffect(layer())
|
||||||
const background = testEffect(layer({ experimentalBackgroundSubagents: true }))
|
|
||||||
|
|
||||||
function defer<T>() {
|
function defer<T>() {
|
||||||
let resolve!: (value: T | PromiseLike<T>) => void
|
let resolve!: (value: T | PromiseLike<T>) => void
|
||||||
@@ -456,37 +455,6 @@ describe("tool.task", () => {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
it.instance("rejects background execution when the experiment is disabled", () =>
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const { chat, assistant } = yield* seed()
|
|
||||||
const tool = yield* TaskTool
|
|
||||||
const def = yield* tool.init()
|
|
||||||
|
|
||||||
const exit = yield* def
|
|
||||||
.execute(
|
|
||||||
{
|
|
||||||
description: "inspect bug",
|
|
||||||
prompt: "look into the cache key path",
|
|
||||||
subagent_type: "general",
|
|
||||||
background: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sessionID: chat.id,
|
|
||||||
messageID: assistant.id,
|
|
||||||
agent: "build",
|
|
||||||
abort: new AbortController().signal,
|
|
||||||
extra: { promptOps: stubOps() },
|
|
||||||
messages: [],
|
|
||||||
metadata: () => Effect.void,
|
|
||||||
ask: () => Effect.void,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.pipe(Effect.exit)
|
|
||||||
|
|
||||||
expect(Exit.isFailure(exit)).toBe(true)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
it.instance("backgrounds a running foreground task without restarting it", () =>
|
it.instance("backgrounds a running foreground task without restarting it", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const jobs = yield* Job.Service
|
const jobs = yield* Job.Service
|
||||||
@@ -558,7 +526,7 @@ describe("tool.task", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
background.instance("execute launches background tasks without waiting for completion", () =>
|
it.instance("execute launches background tasks without waiting for completion", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const jobs = yield* Job.Service
|
const jobs = yield* Job.Service
|
||||||
const { chat, assistant } = yield* seed()
|
const { chat, assistant } = yield* seed()
|
||||||
@@ -596,7 +564,7 @@ describe("tool.task", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
background.instance("running task_id reports the existing background task", () =>
|
it.instance("running task_id reports the existing background task", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const jobs = yield* Job.Service
|
const jobs = yield* Job.Service
|
||||||
const { chat, assistant } = yield* seed()
|
const { chat, assistant } = yield* seed()
|
||||||
@@ -661,7 +629,7 @@ describe("tool.task", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
background.instance("background tasks complete through the job service", () =>
|
it.instance("background tasks complete through the job service", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const jobs = yield* Job.Service
|
const jobs = yield* Job.Service
|
||||||
const { chat, assistant } = yield* seed()
|
const { chat, assistant } = yield* seed()
|
||||||
@@ -694,7 +662,7 @@ describe("tool.task", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
background.instance("background task completion does not wait for the parent async prompt", () =>
|
it.instance("background task completion does not wait for the parent async prompt", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const jobs = yield* Job.Service
|
const jobs = yield* Job.Service
|
||||||
const { chat, assistant } = yield* seed()
|
const { chat, assistant } = yield* seed()
|
||||||
@@ -732,7 +700,7 @@ describe("tool.task", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
background.instance("removing the parent session cancels running background tasks", () =>
|
it.instance("removing the parent session cancels running background tasks", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const jobs = yield* Job.Service
|
const jobs = yield* Job.Service
|
||||||
const sessions = yield* Session.Service
|
const sessions = yield* Session.Service
|
||||||
@@ -771,7 +739,7 @@ describe("tool.task", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
background.instance("removing the child task session cancels its running background task", () =>
|
it.instance("removing the child task session cancels its running background task", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const jobs = yield* Job.Service
|
const jobs = yield* Job.Service
|
||||||
const sessions = yield* Session.Service
|
const sessions = yield* Session.Service
|
||||||
@@ -810,7 +778,7 @@ describe("tool.task", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
background.instance("cancelling the parent run cancels running background tasks", () =>
|
it.instance("cancelling the parent run cancels running background tasks", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const jobs = yield* Job.Service
|
const jobs = yield* Job.Service
|
||||||
const runState = yield* SessionRunState.Service
|
const runState = yield* SessionRunState.Service
|
||||||
|
|||||||
@@ -475,6 +475,7 @@ export type TuiHostSlotMap = {
|
|||||||
}
|
}
|
||||||
sidebar_footer: {
|
sidebar_footer: {
|
||||||
session_id: string
|
session_id: string
|
||||||
|
directory: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import type { Effect, Scope } from "effect"
|
||||||
|
import type { AnyTool, RegistrationError } from "./tool.js"
|
||||||
|
|
||||||
|
export type Path = readonly [string, ...string[]]
|
||||||
|
|
||||||
|
export interface Draft {
|
||||||
|
add(path: Path, tool: AnyTool): void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Domain {
|
||||||
|
readonly register: (source: (draft: Draft) => void) => Effect.Effect<void, RegistrationError, Scope.Scope>
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import type { AgentHooks } from "./agent.js"
|
|||||||
import type { AISDKHooks } from "./aisdk.js"
|
import type { AISDKHooks } from "./aisdk.js"
|
||||||
import type { CatalogHooks } from "./catalog.js"
|
import type { CatalogHooks } from "./catalog.js"
|
||||||
import type { CommandHooks } from "./command.js"
|
import type { CommandHooks } from "./command.js"
|
||||||
|
import type { Domain } from "./code-mode.js"
|
||||||
import type { EventHooks } from "./event.js"
|
import type { EventHooks } from "./event.js"
|
||||||
import type { IntegrationHooks } from "./integration.js"
|
import type { IntegrationHooks } from "./integration.js"
|
||||||
import type { PluginDomain } from "./plugin.js"
|
import type { PluginDomain } from "./plugin.js"
|
||||||
@@ -17,6 +18,7 @@ export interface PluginContext {
|
|||||||
readonly aisdk: AISDKHooks
|
readonly aisdk: AISDKHooks
|
||||||
readonly catalog: CatalogHooks
|
readonly catalog: CatalogHooks
|
||||||
readonly command: CommandHooks
|
readonly command: CommandHooks
|
||||||
|
readonly codemode: Domain
|
||||||
readonly event: EventHooks
|
readonly event: EventHooks
|
||||||
readonly integration: IntegrationHooks
|
readonly integration: IntegrationHooks
|
||||||
readonly plugin: PluginDomain
|
readonly plugin: PluginDomain
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export type { AgentDraft, AgentHooks } from "./agent.js"
|
|||||||
export type { AISDKHooks } from "./aisdk.js"
|
export type { AISDKHooks } from "./aisdk.js"
|
||||||
export type { CatalogDraft, CatalogHooks, CatalogProviderRecord } from "./catalog.js"
|
export type { CatalogDraft, CatalogHooks, CatalogProviderRecord } from "./catalog.js"
|
||||||
export type { CommandDraft, CommandHooks } from "./command.js"
|
export type { CommandDraft, CommandHooks } from "./command.js"
|
||||||
|
export * as CodeMode from "./code-mode.js"
|
||||||
export type { EventHooks } from "./event.js"
|
export type { EventHooks } from "./event.js"
|
||||||
export type { IntegrationDraft, IntegrationHooks, IntegrationMethodRegistration } from "./integration.js"
|
export type { IntegrationDraft, IntegrationHooks, IntegrationMethodRegistration } from "./integration.js"
|
||||||
export type { ReferenceDraft, ReferenceHooks } from "./reference.js"
|
export type { ReferenceDraft, ReferenceHooks } from "./reference.js"
|
||||||
|
|||||||
@@ -246,7 +246,6 @@ export interface ToolExecuteAfterEvent {
|
|||||||
|
|
||||||
export interface RegisterOptions {
|
export interface RegisterOptions {
|
||||||
readonly group?: string
|
readonly group?: string
|
||||||
readonly deferred?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ToolDraft {
|
export interface ToolDraft {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ test.each([
|
|||||||
expect(entrypoint.Skill).toBe(Skill)
|
expect(entrypoint.Skill).toBe(Skill)
|
||||||
expect(Object.keys(entrypoint).sort()).toEqual([
|
expect(Object.keys(entrypoint).sort()).toEqual([
|
||||||
"Agent",
|
"Agent",
|
||||||
|
...(name === "effect" ? ["CodeMode"] : []),
|
||||||
"Command",
|
"Command",
|
||||||
"Connection",
|
"Connection",
|
||||||
"Credential",
|
"Credential",
|
||||||
|
|||||||
@@ -269,6 +269,25 @@ export const makeSessionGroup = <I extends HttpApiMiddleware.AnyId, S>(sessionLo
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.add(
|
||||||
|
HttpApiEndpoint.post("session.move", "/api/session/:sessionID/move", {
|
||||||
|
params: { sessionID: Session.ID },
|
||||||
|
payload: Schema.Struct({
|
||||||
|
destination: Schema.Struct({ directory: AbsolutePath }),
|
||||||
|
moveChanges: Schema.Boolean.pipe(Schema.optional),
|
||||||
|
}),
|
||||||
|
success: HttpApiSchema.NoContent,
|
||||||
|
error: [SessionNotFoundError, InvalidRequestError],
|
||||||
|
})
|
||||||
|
.middleware(sessionLocationMiddleware)
|
||||||
|
.annotateMerge(
|
||||||
|
OpenApi.annotations({
|
||||||
|
identifier: "v2.session.move",
|
||||||
|
summary: "Move session",
|
||||||
|
description: "Move a session to another project directory, optionally transferring local changes.",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
.add(
|
.add(
|
||||||
HttpApiEndpoint.post("session.prompt", "/api/session/:sessionID/prompt", {
|
HttpApiEndpoint.post("session.prompt", "/api/session/:sessionID/prompt", {
|
||||||
params: { sessionID: Session.ID },
|
params: { sessionID: Session.ID },
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { SessionV2 } from "@opencode-ai/core/session"
|
import { SessionV2 } from "@opencode-ai/core/session"
|
||||||
import { InstructionEntry } from "@opencode-ai/core/session/instruction-entry"
|
import { InstructionEntry } from "@opencode-ai/core/session/instruction-entry"
|
||||||
|
import { MoveSession } from "@opencode-ai/core/control-plane/move-session"
|
||||||
import { DateTime, Effect, Stream } from "effect"
|
import { DateTime, Effect, Stream } from "effect"
|
||||||
import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi"
|
import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi"
|
||||||
import { Api } from "../api"
|
import { Api } from "../api"
|
||||||
@@ -8,8 +9,8 @@ import {
|
|||||||
ConflictError,
|
ConflictError,
|
||||||
CommandEvaluationError,
|
CommandEvaluationError,
|
||||||
CommandNotFoundError,
|
CommandNotFoundError,
|
||||||
InvalidCursorError,
|
|
||||||
InvalidRequestError,
|
InvalidRequestError,
|
||||||
|
InvalidCursorError,
|
||||||
MessageNotFoundError,
|
MessageNotFoundError,
|
||||||
ServiceUnavailableError,
|
ServiceUnavailableError,
|
||||||
SessionBusyError,
|
SessionBusyError,
|
||||||
@@ -24,6 +25,7 @@ const DefaultSessionsLimit = 50
|
|||||||
export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handlers) =>
|
export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handlers) =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
|
const moveSession = yield* MoveSession.Service
|
||||||
|
|
||||||
return handlers
|
return handlers
|
||||||
.handle(
|
.handle(
|
||||||
@@ -201,6 +203,43 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
|
|||||||
return HttpApiSchema.NoContent.make()
|
return HttpApiSchema.NoContent.make()
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
.handle(
|
||||||
|
"session.move",
|
||||||
|
Effect.fn(function* (ctx) {
|
||||||
|
yield* moveSession.moveSession({
|
||||||
|
sessionID: ctx.params.sessionID,
|
||||||
|
destination: ctx.payload.destination,
|
||||||
|
moveChanges: ctx.payload.moveChanges,
|
||||||
|
}).pipe(
|
||||||
|
Effect.catchTag("Session.NotFoundError", (error) =>
|
||||||
|
Effect.fail(
|
||||||
|
new SessionNotFoundError({
|
||||||
|
sessionID: error.sessionID,
|
||||||
|
message: `Session not found: ${error.sessionID}`,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Effect.catchTag("MoveSession.DestinationProjectMismatchError", () =>
|
||||||
|
Effect.fail(new InvalidRequestError({ message: "Destination directory belongs to another project" })),
|
||||||
|
),
|
||||||
|
Effect.catchTag("MoveSession.ApplyChangesError", () =>
|
||||||
|
Effect.fail(
|
||||||
|
new InvalidRequestError({
|
||||||
|
message:
|
||||||
|
"Unable to apply your changes in the destination directory. The files may conflict with existing changes.",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Effect.catchTag("MoveSession.CaptureChangesError", (error) =>
|
||||||
|
Effect.fail(new InvalidRequestError({ message: error.message })),
|
||||||
|
),
|
||||||
|
Effect.catchTag("MoveSession.ResetSourceChangesError", (error) =>
|
||||||
|
Effect.fail(new InvalidRequestError({ message: error.message })),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return HttpApiSchema.NoContent.make()
|
||||||
|
}),
|
||||||
|
)
|
||||||
.handle(
|
.handle(
|
||||||
"session.prompt",
|
"session.prompt",
|
||||||
Effect.fn(function* (ctx) {
|
Effect.fn(function* (ctx) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { Observability } from "@opencode-ai/core/observability"
|
|||||||
import { Credential } from "@opencode-ai/core/credential"
|
import { Credential } from "@opencode-ai/core/credential"
|
||||||
import { PermissionSaved } from "@opencode-ai/core/permission/saved"
|
import { PermissionSaved } from "@opencode-ai/core/permission/saved"
|
||||||
import { PtyTicket } from "@opencode-ai/core/pty/ticket"
|
import { PtyTicket } from "@opencode-ai/core/pty/ticket"
|
||||||
|
import { MoveSession } from "@opencode-ai/core/control-plane/move-session"
|
||||||
import { Project } from "@opencode-ai/core/project"
|
import { Project } from "@opencode-ai/core/project"
|
||||||
import { SessionV2 } from "@opencode-ai/core/session"
|
import { SessionV2 } from "@opencode-ai/core/session"
|
||||||
import { SessionExecution } from "@opencode-ai/core/session/execution"
|
import { SessionExecution } from "@opencode-ai/core/session/execution"
|
||||||
@@ -37,6 +38,7 @@ const applicationServices = LayerNode.group([
|
|||||||
httpClient,
|
httpClient,
|
||||||
ToolOutputStore.cleanupNode,
|
ToolOutputStore.cleanupNode,
|
||||||
Job.node,
|
Job.node,
|
||||||
|
MoveSession.node,
|
||||||
Project.node,
|
Project.node,
|
||||||
SessionV2.node,
|
SessionV2.node,
|
||||||
PluginRuntime.providerNode,
|
PluginRuntime.providerNode,
|
||||||
|
|||||||
@@ -19,8 +19,9 @@ import { Spinner } from "./spinner"
|
|||||||
import { DialogWorkspaceFileChanges } from "./dialog-workspace-file-changes"
|
import { DialogWorkspaceFileChanges } from "./dialog-workspace-file-changes"
|
||||||
import type { ProjectDirectoriesOutput } from "@opencode-ai/client/promise"
|
import type { ProjectDirectoriesOutput } from "@opencode-ai/client/promise"
|
||||||
import { useRoute } from "../context/route"
|
import { useRoute } from "../context/route"
|
||||||
|
import { DialogProjectCopyName } from "./dialog-project-copy-name"
|
||||||
|
|
||||||
export type MoveSessionSelection = { type: "directory"; directory: string; subdirectory: boolean } | { type: "new" }
|
export type MoveSessionSelection = { type: "directory"; directory: string; subdirectory: boolean } | { type: "new"; name: string }
|
||||||
type ProjectDirectory = ProjectDirectoriesOutput[number]
|
type ProjectDirectory = ProjectDirectoriesOutput[number]
|
||||||
|
|
||||||
type DialogMoveSessionProps = {
|
type DialogMoveSessionProps = {
|
||||||
@@ -291,6 +292,12 @@ export function DialogMoveSession(props: DialogMoveSessionProps) {
|
|||||||
if (await removedCurrent(deletingCurrent)) return
|
if (await removedCurrent(deletingCurrent)) return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function create() {
|
||||||
|
const name = await DialogProjectCopyName.show(dialog)
|
||||||
|
if (name === null) return
|
||||||
|
props.onSelect({ type: "new", name })
|
||||||
|
}
|
||||||
|
|
||||||
const fullHeight = createMemo(() =>
|
const fullHeight = createMemo(() =>
|
||||||
Math.max(8, Math.min(16, dimensions().height - Math.floor(dimensions().height / 4) - 2)),
|
Math.max(8, Math.min(16, dimensions().height - Math.floor(dimensions().height / 4) - 2)),
|
||||||
)
|
)
|
||||||
@@ -334,7 +341,7 @@ export function DialogMoveSession(props: DialogMoveSessionProps) {
|
|||||||
{
|
{
|
||||||
command: "dialog.move_session.new",
|
command: "dialog.move_session.new",
|
||||||
title: "new",
|
title: "new",
|
||||||
onTrigger: () => props.onSelect({ type: "new" }),
|
onTrigger: () => void create(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
command: "dialog.move_session.delete",
|
command: "dialog.move_session.delete",
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import { InputRenderable, TextAttributes } from "@opentui/core"
|
||||||
|
import { Slug } from "@opencode-ai/core/util/slug"
|
||||||
|
import { createSignal, onMount } from "solid-js"
|
||||||
|
import { useTuiConfig } from "../config"
|
||||||
|
import { useTheme } from "../context/theme"
|
||||||
|
import { useBindings, useCommandShortcut } from "../keymap"
|
||||||
|
import { useDialog, type DialogContext } from "../ui/dialog"
|
||||||
|
|
||||||
|
export function DialogProjectCopyName(props: { onConfirm: (name: string) => void }) {
|
||||||
|
const dialog = useDialog()
|
||||||
|
const { theme } = useTheme()
|
||||||
|
const tuiConfig = useTuiConfig()
|
||||||
|
const generateShortcut = useCommandShortcut("dialog.project_copy.generate")
|
||||||
|
const [inputTarget, setInputTarget] = createSignal<InputRenderable>()
|
||||||
|
let input: InputRenderable
|
||||||
|
|
||||||
|
function generate() {
|
||||||
|
input.value = Slug.create()
|
||||||
|
input.gotoLineEnd()
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirm() {
|
||||||
|
props.onConfirm(slugify(input.value) || Slug.create())
|
||||||
|
}
|
||||||
|
|
||||||
|
useBindings(() => ({
|
||||||
|
target: inputTarget,
|
||||||
|
enabled: inputTarget() !== undefined,
|
||||||
|
priority: 1,
|
||||||
|
commands: [
|
||||||
|
{
|
||||||
|
name: "dialog.project_copy.generate",
|
||||||
|
title: "Generate project copy name",
|
||||||
|
category: "Dialog",
|
||||||
|
run: generate,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
bindings: tuiConfig.keybinds.get("dialog.project_copy.generate"),
|
||||||
|
}))
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
dialog.setSize("medium")
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!input || input.isDestroyed) return
|
||||||
|
input.focus()
|
||||||
|
}, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<box paddingLeft={2} paddingRight={2} gap={1}>
|
||||||
|
<box flexDirection="row" justifyContent="space-between">
|
||||||
|
<text attributes={TextAttributes.BOLD} fg={theme.text}>
|
||||||
|
Name project copy
|
||||||
|
</text>
|
||||||
|
<text fg={theme.textMuted} onMouseUp={() => dialog.clear()}>
|
||||||
|
esc
|
||||||
|
</text>
|
||||||
|
</box>
|
||||||
|
<input
|
||||||
|
ref={(value: InputRenderable) => {
|
||||||
|
input = value
|
||||||
|
setInputTarget(value)
|
||||||
|
}}
|
||||||
|
onSubmit={confirm}
|
||||||
|
placeholder="Project copy name"
|
||||||
|
placeholderColor={theme.textMuted}
|
||||||
|
textColor={theme.text}
|
||||||
|
focusedTextColor={theme.text}
|
||||||
|
cursorColor={theme.text}
|
||||||
|
/>
|
||||||
|
<box paddingBottom={1} flexDirection="row" gap={2}>
|
||||||
|
<text fg={theme.text}>
|
||||||
|
enter <span style={{ fg: theme.textMuted }}>submit</span>
|
||||||
|
</text>
|
||||||
|
<text fg={theme.text}>
|
||||||
|
{generateShortcut()} <span style={{ fg: theme.textMuted }}>generate one</span>
|
||||||
|
</text>
|
||||||
|
</box>
|
||||||
|
</box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
DialogProjectCopyName.show = (dialog: DialogContext) =>
|
||||||
|
new Promise<string | null>((resolve) => {
|
||||||
|
dialog.replace(() => <DialogProjectCopyName onConfirm={resolve} />, () => resolve(null))
|
||||||
|
})
|
||||||
|
|
||||||
|
function slugify(input: string) {
|
||||||
|
return input
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/[^a-z0-9]+/g, "-")
|
||||||
|
.replace(/^-+/, "")
|
||||||
|
.replace(/-+$/, "")
|
||||||
|
}
|
||||||
@@ -218,7 +218,10 @@ export function Prompt(props: PromptProps) {
|
|||||||
const editorContextLabelState = createMemo(() => editor.labelState())
|
const editorContextLabelState = createMemo(() => editor.labelState())
|
||||||
const [auto, setAuto] = createSignal<AutocompleteRef>()
|
const [auto, setAuto] = createSignal<AutocompleteRef>()
|
||||||
const workspace = usePromptWorkspace(props.sessionID)
|
const workspace = usePromptWorkspace(props.sessionID)
|
||||||
const move = usePromptMove({ projectID: project.project, sessionID: () => props.sessionID })
|
const move = usePromptMove({
|
||||||
|
projectID: () => (props.sessionID ? data.session.get(props.sessionID)?.projectID : undefined) ?? project.project(),
|
||||||
|
sessionID: () => props.sessionID,
|
||||||
|
})
|
||||||
const [cursorVersion, setCursorVersion] = createSignal(0)
|
const [cursorVersion, setCursorVersion] = createSignal(0)
|
||||||
const currentProviderLabel = createMemo(() => local.model.parsed().provider)
|
const currentProviderLabel = createMemo(() => local.model.parsed().provider)
|
||||||
const connected = useConnected()
|
const connected = useConnected()
|
||||||
@@ -955,13 +958,13 @@ export function Prompt(props: PromptProps) {
|
|||||||
if (workspace.creating() || move.creating()) return false
|
if (workspace.creating() || move.creating()) return false
|
||||||
if (auto()?.visible) return false
|
if (auto()?.visible) return false
|
||||||
if (!store.prompt.text) return false
|
if (!store.prompt.text) return false
|
||||||
const agent = local.agent.current()
|
|
||||||
if (!agent) return false
|
|
||||||
const trimmed = store.prompt.text.trim()
|
const trimmed = store.prompt.text.trim()
|
||||||
if (trimmed === "exit" || trimmed === "quit" || trimmed === ":q") {
|
if (trimmed === "exit" || trimmed === "quit" || trimmed === ":q") {
|
||||||
void exit()
|
void exit()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
const agent = local.agent.current()
|
||||||
|
if (!agent) return false
|
||||||
const selectedModel = local.model.current()
|
const selectedModel = local.model.current()
|
||||||
if (!selectedModel) {
|
if (!selectedModel) {
|
||||||
void promptModelWarning()
|
void promptModelWarning()
|
||||||
@@ -991,7 +994,7 @@ export function Prompt(props: PromptProps) {
|
|||||||
const selectedWorkspace = workspace.selection()
|
const selectedWorkspace = workspace.selection()
|
||||||
const workspaceID = selectedWorkspace?.type === "existing" ? selectedWorkspace.workspaceID : undefined
|
const workspaceID = selectedWorkspace?.type === "existing" ? selectedWorkspace.workspaceID : undefined
|
||||||
|
|
||||||
const directory = await move.getDirectory(store.prompt.text)
|
const directory = await move.getDirectory()
|
||||||
if (move.pending() && !directory) return false
|
if (move.pending() && !directory) return false
|
||||||
finishMoveProgress = Boolean(move.progress())
|
finishMoveProgress = Boolean(move.progress())
|
||||||
const location = data.location.default()
|
const location = data.location.default()
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import { useTuiPaths } from "../../context/runtime"
|
|||||||
import { errorMessage } from "../../util/error"
|
import { errorMessage } from "../../util/error"
|
||||||
import { useDialog } from "../../ui/dialog"
|
import { useDialog } from "../../ui/dialog"
|
||||||
import { useSDK } from "../../context/sdk"
|
import { useSDK } from "../../context/sdk"
|
||||||
import { useSync } from "../../context/sync"
|
|
||||||
import { useToast } from "../../ui/toast"
|
import { useToast } from "../../ui/toast"
|
||||||
import { DialogMoveSession, type MoveSessionSelection } from "../dialog-move-session"
|
import { DialogMoveSession, type MoveSessionSelection } from "../dialog-move-session"
|
||||||
import { DialogWorkspaceFileChanges } from "../dialog-workspace-file-changes"
|
import { DialogWorkspaceFileChanges } from "../dialog-workspace-file-changes"
|
||||||
import { useHomeSessionDestination } from "../../routes/home/session-destination"
|
import { useHomeSessionDestination } from "../../routes/home/session-destination"
|
||||||
import { useProject } from "../../context/project"
|
import { useProject } from "../../context/project"
|
||||||
|
import { useData } from "../../context/data"
|
||||||
|
|
||||||
function moveReminderText(directory: string) {
|
function moveReminderText(directory: string) {
|
||||||
return `<system-reminder>The user has changed the current working directory to "${directory}". This is still the same project but at a possibly new location; take this into account when working with any files from now on.</system-reminder>`
|
return `<system-reminder>The user has changed the current working directory to "${directory}". This is still the same project but at a possibly new location; take this into account when working with any files from now on.</system-reminder>`
|
||||||
@@ -18,38 +18,33 @@ function moveReminderText(directory: string) {
|
|||||||
export function usePromptMove(input: { projectID: () => string | undefined; sessionID: () => string | undefined }) {
|
export function usePromptMove(input: { projectID: () => string | undefined; sessionID: () => string | undefined }) {
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const sdk = useSDK()
|
const sdk = useSDK()
|
||||||
const sync = useSync()
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const homeDestination = useHomeSessionDestination()
|
const homeDestination = useHomeSessionDestination()
|
||||||
const project = useProject()
|
const project = useProject()
|
||||||
|
const data = useData()
|
||||||
const paths = useTuiPaths()
|
const paths = useTuiPaths()
|
||||||
const [creating, setCreating] = createSignal(false)
|
const [creating, setCreating] = createSignal(false)
|
||||||
const [creatingDots, setCreatingDots] = createSignal(3)
|
const [creatingDots, setCreatingDots] = createSignal(3)
|
||||||
const [progress, setProgress] = createSignal<string>()
|
const [progress, setProgress] = createSignal<string>()
|
||||||
|
|
||||||
async function create(context?: string) {
|
async function create(name: string) {
|
||||||
const projectID = input.projectID()
|
const projectID = await resolveProjectID()
|
||||||
if (!projectID) return
|
if (!projectID) return
|
||||||
setCreating(true)
|
setCreating(true)
|
||||||
setProgress("Creating copy")
|
setProgress("Creating copy")
|
||||||
try {
|
try {
|
||||||
const generated = await sdk.client.experimental.projectCopy.generateName(
|
|
||||||
{ projectID, context },
|
|
||||||
{ throwOnError: true },
|
|
||||||
)
|
|
||||||
const result = await sdk.api.projectCopy.create({
|
const result = await sdk.api.projectCopy.create({
|
||||||
projectID,
|
projectID,
|
||||||
location: { directory: project.instance.directory() || paths.cwd },
|
location: { directory: project.instance.directory() || paths.cwd },
|
||||||
strategy: "git_worktree",
|
strategy: "git_worktree",
|
||||||
directory: path.join(paths.worktree, projectID.slice(0, 6)),
|
directory: path.join(paths.worktree, projectID.slice(0, 6)),
|
||||||
name: generated.data.name,
|
name,
|
||||||
})
|
})
|
||||||
const directory = result.directory
|
const directory = result.directory
|
||||||
if (!directory) throw new Error("No project copy directory returned")
|
if (!directory) throw new Error("No project copy directory returned")
|
||||||
|
|
||||||
// Call a location-based route to make sure it's bootstrapped
|
// Call a location-based route to make sure it's bootstrapped before moving on.
|
||||||
// before moving on
|
await sdk.api.location.get({ location: { directory } })
|
||||||
await sdk.client.path.get({ directory }, { throwOnError: true })
|
|
||||||
|
|
||||||
setProgress("Creating session")
|
setProgress("Creating session")
|
||||||
return directory
|
return directory
|
||||||
@@ -62,11 +57,14 @@ export function usePromptMove(input: { projectID: () => string | undefined; sess
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function open() {
|
async function open() {
|
||||||
const projectID = input.projectID()
|
const projectID = await resolveProjectID()
|
||||||
if (!projectID) return
|
if (!projectID) {
|
||||||
|
toast.show({ message: "Unable to determine current project", variant: "error" })
|
||||||
|
return
|
||||||
|
}
|
||||||
const sessionID = input.sessionID()
|
const sessionID = input.sessionID()
|
||||||
const session = sessionID ? sync.session.get(sessionID) : undefined
|
const session = sessionID ? await resolveSession(sessionID) : undefined
|
||||||
dialog.replace(() => (
|
dialog.replace(() => (
|
||||||
<DialogMoveSession
|
<DialogMoveSession
|
||||||
projectID={projectID}
|
projectID={projectID}
|
||||||
@@ -75,8 +73,8 @@ export function usePromptMove(input: { projectID: () => string | undefined; sess
|
|||||||
(session
|
(session
|
||||||
? {
|
? {
|
||||||
type: "directory",
|
type: "directory",
|
||||||
directory: session.directory,
|
directory: session.location.directory,
|
||||||
subdirectory: !!session.path,
|
subdirectory: !!session.subpath,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
type: "directory",
|
type: "directory",
|
||||||
@@ -98,26 +96,13 @@ export function usePromptMove(input: { projectID: () => string | undefined; sess
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
function sessionContext(sessionID: string) {
|
|
||||||
const session = sync.session.get(sessionID)
|
|
||||||
const messages = (sync.data.message[sessionID] ?? [])
|
|
||||||
.slice(-6)
|
|
||||||
.map((message) =>
|
|
||||||
[
|
|
||||||
message.role + ":",
|
|
||||||
...(sync.data.part[message.id] ?? []).flatMap((part) => (part.type === "text" ? [part.text] : [])),
|
|
||||||
].join(" "),
|
|
||||||
)
|
|
||||||
return [session?.title, ...messages].filter(Boolean).join("\n") || undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
async function moveExistingSession(sessionID: string, selection: MoveSessionSelection) {
|
async function moveExistingSession(sessionID: string, selection: MoveSessionSelection) {
|
||||||
const session = sync.session.get(sessionID)
|
const session = await resolveSession(sessionID)
|
||||||
const status = await sdk.client.vcs.status({ directory: session?.directory }).catch(() => undefined)
|
const status = await sdk.client.vcs.status({ directory: session?.location.directory }).catch(() => undefined)
|
||||||
const choice = status?.data?.length ? await DialogWorkspaceFileChanges.show(dialog, status.data) : "no"
|
const choice = status?.data?.length ? await DialogWorkspaceFileChanges.show(dialog, status.data) : "no"
|
||||||
if (!choice) return
|
if (!choice) return
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
const directory = selection.type === "new" ? await create(sessionContext(sessionID)) : selection.directory
|
const directory = selection.type === "new" ? await create(selection.name) : selection.directory
|
||||||
if (!directory) {
|
if (!directory) {
|
||||||
setProgress(undefined)
|
setProgress(undefined)
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
@@ -125,27 +110,9 @@ export function usePromptMove(input: { projectID: () => string | undefined; sess
|
|||||||
}
|
}
|
||||||
setProgress("Moving session")
|
setProgress("Moving session")
|
||||||
try {
|
try {
|
||||||
await sdk.client.experimental.controlPlane.moveSession(
|
await sdk.api.session.move({ sessionID, destination: { directory }, moveChanges: choice === "yes" })
|
||||||
{
|
await sdk.api.session
|
||||||
sessionID,
|
.synthetic({ sessionID, text: moveReminderText(directory), resume: false })
|
||||||
destination: { directory },
|
|
||||||
moveChanges: choice === "yes",
|
|
||||||
},
|
|
||||||
{ throwOnError: true },
|
|
||||||
)
|
|
||||||
await sdk.client.session
|
|
||||||
.promptAsync({
|
|
||||||
sessionID,
|
|
||||||
directory,
|
|
||||||
noReply: true,
|
|
||||||
parts: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: moveReminderText(directory),
|
|
||||||
synthetic: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
.catch(() => undefined)
|
.catch(() => undefined)
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -157,16 +124,34 @@ export function usePromptMove(input: { projectID: () => string | undefined; sess
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function resolveProjectID() {
|
||||||
|
const projectID = input.projectID()
|
||||||
|
if (projectID) return projectID
|
||||||
|
const sessionID = input.sessionID()
|
||||||
|
if (sessionID) return (await resolveSession(sessionID))?.projectID
|
||||||
|
return sdk.api.project
|
||||||
|
.current({ location: { directory: project.instance.directory() || paths.cwd } })
|
||||||
|
.then((project) => project.id)
|
||||||
|
.catch(() => undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resolveSession(sessionID: string) {
|
||||||
|
const session = data.session.get(sessionID)
|
||||||
|
if (session) return session
|
||||||
|
await data.session.refresh(sessionID).catch(() => undefined)
|
||||||
|
return data.session.get(sessionID)
|
||||||
|
}
|
||||||
|
|
||||||
const pending = createMemo(() => Boolean(homeDestination?.destination()))
|
const pending = createMemo(() => Boolean(homeDestination?.destination()))
|
||||||
const pendingNew = createMemo(() => homeDestination?.destination()?.type === "new")
|
const pendingNew = createMemo(() => homeDestination?.destination()?.type === "new")
|
||||||
|
|
||||||
async function getDirectory(context?: string) {
|
async function getDirectory() {
|
||||||
const value = homeDestination?.destination()
|
const value = homeDestination?.destination()
|
||||||
if (!value) return
|
if (!value) return
|
||||||
if (value.type === "directory") {
|
if (value.type === "directory") {
|
||||||
return value.directory
|
return value.directory
|
||||||
}
|
}
|
||||||
return await create(context)
|
return await create(value.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
function startSubmit() {
|
function startSubmit() {
|
||||||
|
|||||||
@@ -207,6 +207,7 @@ export const Definitions = {
|
|||||||
"dialog.select.end": keybind("end", "Move to last dialog item"),
|
"dialog.select.end": keybind("end", "Move to last dialog item"),
|
||||||
"dialog.select.submit": keybind("return", "Submit selected dialog item"),
|
"dialog.select.submit": keybind("return", "Submit selected dialog item"),
|
||||||
"dialog.prompt.submit": keybind("return", "Submit dialog prompt"),
|
"dialog.prompt.submit": keybind("return", "Submit dialog prompt"),
|
||||||
|
"dialog.project_copy.generate": keybind("tab", "Generate project copy name"),
|
||||||
"dialog.mcp.toggle": keybind("space", "Toggle MCP in MCP dialog"),
|
"dialog.mcp.toggle": keybind("space", "Toggle MCP in MCP dialog"),
|
||||||
"dialog.move_session.new": keybind("ctrl+m", "New project copy"),
|
"dialog.move_session.new": keybind("ctrl+m", "New project copy"),
|
||||||
"dialog.move_session.delete": keybind("ctrl+d", "Delete project copy"),
|
"dialog.move_session.delete": keybind("ctrl+d", "Delete project copy"),
|
||||||
|
|||||||
@@ -304,6 +304,12 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||||||
if (store.session.info[event.data.sessionID])
|
if (store.session.info[event.data.sessionID])
|
||||||
setStore("session", "info", event.data.sessionID, "title", event.data.title)
|
setStore("session", "info", event.data.sessionID, "title", event.data.title)
|
||||||
break
|
break
|
||||||
|
case "session.moved":
|
||||||
|
if (store.session.info[event.data.sessionID]) {
|
||||||
|
setStore("session", "info", event.data.sessionID, "location", mutable(event.data.location))
|
||||||
|
setStore("session", "info", event.data.sessionID, "subpath", event.data.subpath)
|
||||||
|
}
|
||||||
|
break
|
||||||
case "session.prompt.promoted": {
|
case "session.prompt.promoted": {
|
||||||
message.update(event.data.sessionID, (draft, index) => {
|
message.update(event.data.sessionID, (draft, index) => {
|
||||||
const position = index.get(event.data.inputID)
|
const position = index.get(event.data.inputID)
|
||||||
|
|||||||
@@ -42,9 +42,6 @@ export const {
|
|||||||
provider_default: Record<string, string>
|
provider_default: Record<string, string>
|
||||||
provider_next: ProviderListResponse
|
provider_next: ProviderListResponse
|
||||||
console_state: ConsoleState
|
console_state: ConsoleState
|
||||||
capabilities: {
|
|
||||||
experimentalBackgroundSubagents: boolean
|
|
||||||
}
|
|
||||||
provider_auth: Record<string, ProviderAuthMethod[]>
|
provider_auth: Record<string, ProviderAuthMethod[]>
|
||||||
agent: Agent[]
|
agent: Agent[]
|
||||||
command: Command[]
|
command: Command[]
|
||||||
@@ -71,9 +68,6 @@ export const {
|
|||||||
connected: [],
|
connected: [],
|
||||||
},
|
},
|
||||||
console_state: emptyConsoleState,
|
console_state: emptyConsoleState,
|
||||||
capabilities: {
|
|
||||||
experimentalBackgroundSubagents: false,
|
|
||||||
},
|
|
||||||
provider_auth: {},
|
provider_auth: {},
|
||||||
agent: [],
|
agent: [],
|
||||||
command: [],
|
command: [],
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { useTuiPaths } from "../../context/runtime"
|
|||||||
|
|
||||||
const id = "internal:sidebar-footer"
|
const id = "internal:sidebar-footer"
|
||||||
|
|
||||||
function View(props: { api: TuiPluginApi; sessionID: string }) {
|
function View(props: { api: TuiPluginApi; directory: string }) {
|
||||||
const paths = useTuiPaths()
|
const paths = useTuiPaths()
|
||||||
const theme = () => props.api.theme.current
|
const theme = () => props.api.theme.current
|
||||||
const has = createMemo(() =>
|
const has = createMemo(() =>
|
||||||
@@ -17,10 +17,8 @@ function View(props: { api: TuiPluginApi; sessionID: string }) {
|
|||||||
const done = createMemo(() => props.api.kv.get("dismissed_getting_started", false))
|
const done = createMemo(() => props.api.kv.get("dismissed_getting_started", false))
|
||||||
const show = createMemo(() => !has() && !done())
|
const show = createMemo(() => !has() && !done())
|
||||||
const path = createMemo(() => {
|
const path = createMemo(() => {
|
||||||
const session = props.api.state.session.get(props.sessionID)
|
const out = abbreviateHome(props.directory, paths.home)
|
||||||
const dir = session?.directory || props.api.state.path.directory || paths.cwd
|
const branch = props.directory === props.api.state.path.directory ? props.api.state.vcs?.branch : undefined
|
||||||
const out = abbreviateHome(dir, paths.home)
|
|
||||||
const branch = session?.directory === props.api.state.path.directory ? props.api.state.vcs?.branch : undefined
|
|
||||||
const text = branch ? out + ":" + branch : out
|
const text = branch ? out + ":" + branch : out
|
||||||
const list = text.split("/")
|
const list = text.split("/")
|
||||||
return {
|
return {
|
||||||
@@ -84,7 +82,7 @@ const tui: TuiPlugin = async (api) => {
|
|||||||
order: 100,
|
order: 100,
|
||||||
slots: {
|
slots: {
|
||||||
sidebar_footer(_ctx, props) {
|
sidebar_footer(_ctx, props) {
|
||||||
return <View api={api} sessionID={props.session_id} />
|
return <View api={api} directory={props.directory} />
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
import { useSync } from "../../context/sync"
|
import { useSync } from "../../context/sync"
|
||||||
import { useTuiPaths } from "../../context/runtime"
|
import { useTuiPaths } from "../../context/runtime"
|
||||||
|
|
||||||
export type HomeSessionDestination = { type: "directory"; directory: string; subdirectory: boolean } | { type: "new" }
|
export type HomeSessionDestination = { type: "directory"; directory: string; subdirectory: boolean } | { type: "new"; name: string }
|
||||||
|
|
||||||
type Context = {
|
type Context = {
|
||||||
destination: Accessor<HomeSessionDestination | undefined>
|
destination: Accessor<HomeSessionDestination | undefined>
|
||||||
|
|||||||
@@ -85,7 +85,12 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) {
|
|||||||
</scrollbox>
|
</scrollbox>
|
||||||
|
|
||||||
<box flexShrink={0} gap={1} paddingTop={1}>
|
<box flexShrink={0} gap={1} paddingTop={1}>
|
||||||
<pluginRuntime.Slot name="sidebar_footer" mode="single_winner" session_id={props.sessionID}>
|
<pluginRuntime.Slot
|
||||||
|
name="sidebar_footer"
|
||||||
|
mode="single_winner"
|
||||||
|
session_id={props.sessionID}
|
||||||
|
directory={session()?.location.directory ?? ""}
|
||||||
|
>
|
||||||
<text fg={theme.textMuted}>
|
<text fg={theme.textMuted}>
|
||||||
<span style={{ fg: theme.success }}>•</span> <b>Open</b>
|
<span style={{ fg: theme.success }}>•</span> <b>Open</b>
|
||||||
<span style={{ fg: theme.text }}>
|
<span style={{ fg: theme.text }}>
|
||||||
|
|||||||
@@ -108,6 +108,68 @@ test("refreshes resources into reactive getters", async () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("updates session location when moved", async () => {
|
||||||
|
const events = createEventStream()
|
||||||
|
const destination = "/tmp/opencode-moved"
|
||||||
|
const calls = createFetch((url) => {
|
||||||
|
if (url.pathname === "/api/session/ses_test")
|
||||||
|
return json({
|
||||||
|
data: {
|
||||||
|
id: "ses_test",
|
||||||
|
projectID: "proj_test",
|
||||||
|
cost: 0,
|
||||||
|
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
|
||||||
|
time: { created: 0, updated: 0 },
|
||||||
|
title: "Test session",
|
||||||
|
location: { directory },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}, events)
|
||||||
|
let data!: ReturnType<typeof useData>
|
||||||
|
let ready!: () => void
|
||||||
|
const mounted = new Promise<void>((resolve) => {
|
||||||
|
ready = resolve
|
||||||
|
})
|
||||||
|
|
||||||
|
function Probe() {
|
||||||
|
data = useData()
|
||||||
|
onMount(ready)
|
||||||
|
return <box />
|
||||||
|
}
|
||||||
|
|
||||||
|
const app = await testRender(() => (
|
||||||
|
<TestTuiContexts>
|
||||||
|
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
||||||
|
<ProjectProvider>
|
||||||
|
<DataProvider>
|
||||||
|
<Probe />
|
||||||
|
</DataProvider>
|
||||||
|
</ProjectProvider>
|
||||||
|
</SDKProvider>
|
||||||
|
</TestTuiContexts>
|
||||||
|
))
|
||||||
|
|
||||||
|
try {
|
||||||
|
await mounted
|
||||||
|
await data.session.refresh("ses_test")
|
||||||
|
emitEvent(events, {
|
||||||
|
id: "evt_moved_1",
|
||||||
|
created: 1,
|
||||||
|
type: "session.moved",
|
||||||
|
durable: durable("ses_test"),
|
||||||
|
data: {
|
||||||
|
sessionID: "ses_test",
|
||||||
|
location: { directory: destination },
|
||||||
|
subpath: "packages/cli",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await wait(() => data.session.get("ses_test")?.location.directory === destination)
|
||||||
|
expect(data.session.get("ses_test")?.subpath).toBe("packages/cli")
|
||||||
|
} finally {
|
||||||
|
app.renderer.destroy()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
test("restores running manual compaction before applying live deltas", async () => {
|
test("restores running manual compaction before applying live deltas", async () => {
|
||||||
const events = createEventStream()
|
const events = createEventStream()
|
||||||
const calls = createFetch((url) => {
|
const calls = createFetch((url) => {
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export function createFetch(override?: FetchHandler, events?: ReturnType<typeof
|
|||||||
return json({})
|
return json({})
|
||||||
if (url.pathname === "/config/providers") return json({ providers: {}, default: {} })
|
if (url.pathname === "/config/providers") return json({ providers: {}, default: {} })
|
||||||
if (url.pathname === "/experimental/console") return json({ consoleManagedProviders: [], switchableOrgCount: 0 })
|
if (url.pathname === "/experimental/console") return json({ consoleManagedProviders: [], switchableOrgCount: 0 })
|
||||||
if (url.pathname === "/experimental/capabilities") return json({ backgroundSubagents: false })
|
if (url.pathname === "/experimental/capabilities") return json({ backgroundSubagents: true })
|
||||||
if (url.pathname === "/path") return json({ home: "", state: "", config: "", worktree, directory })
|
if (url.pathname === "/path") return json({ home: "", state: "", config: "", worktree, directory })
|
||||||
if (url.pathname === "/api/location") return json({ directory, project: { id: "proj_test", directory: worktree } })
|
if (url.pathname === "/api/location") return json({ directory, project: { id: "proj_test", directory: worktree } })
|
||||||
if (url.pathname === "/api/project/current") return json({ id: "proj_test", directory: worktree })
|
if (url.pathname === "/api/project/current") return json({ id: "proj_test", directory: worktree })
|
||||||
|
|||||||
@@ -608,7 +608,6 @@ opencode upgrade v0.1.48
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | تفعيل ميزات Exa التجريبية |
|
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | تفعيل ميزات Exa التجريبية |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | تمكين TY LSP لملفات python |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | تمكين TY LSP لملفات python |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | تفعيل وضع الخطة |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | تفعيل وضع الخطة |
|
||||||
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | boolean | تفعيل مهام subagent في الخلفية |
|
|
||||||
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolean | تفعيل نظام الأحداث التجريبي |
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolean | تفعيل نظام الأحداث التجريبي |
|
||||||
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolean | تفعيل مسار طلبات LLM الأصلي |
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolean | تفعيل مسار طلبات LLM الأصلي |
|
||||||
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolean | تفعيل تنفيذ بحث الويب بالتوازي |
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolean | تفعيل تنفيذ بحث الويب بالتوازي |
|
||||||
|
|||||||
@@ -606,7 +606,6 @@ Ove varijable okruženja omogućavaju eksperimentalne karakteristike koje se mog
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | Omogući eksperimentalne Exa funkcije |
|
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | Omogući eksperimentalne Exa funkcije |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | Omogući TY LSP za python datoteke |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | Omogući TY LSP za python datoteke |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Omogući Plan mod |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Omogući Plan mod |
|
||||||
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | boolean | Omogući pozadinske zadatke subagenata |
|
|
||||||
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolean | Omogući eksperimentalni sistem događaja |
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolean | Omogući eksperimentalni sistem događaja |
|
||||||
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolean | Omogući nativnu putanju LLM zahtjeva |
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolean | Omogući nativnu putanju LLM zahtjeva |
|
||||||
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolean | Omogući paralelno izvršavanje web pretrage |
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolean | Omogući paralelno izvršavanje web pretrage |
|
||||||
|
|||||||
@@ -725,7 +725,6 @@ These environment variables enable experimental features that may change or be r
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | Enable experimental Exa features |
|
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | Enable experimental Exa features |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | Enable TY LSP for python files |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | Enable TY LSP for python files |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Enable plan mode |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Enable plan mode |
|
||||||
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | boolean | Enable background subagent tasks |
|
|
||||||
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolean | Enable experimental event system |
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolean | Enable experimental event system |
|
||||||
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolean | Enable native LLM request path |
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolean | Enable native LLM request path |
|
||||||
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolean | Enable parallel web search execution |
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolean | Enable parallel web search execution |
|
||||||
|
|||||||
@@ -609,7 +609,6 @@ Disse miljøvariabler muliggør eksperimentelle funktioner, der kan ændres elle
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | Aktive eksperimenter Exa-funktioner |
|
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | Aktive eksperimenter Exa-funktioner |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | Aktiver TY LSP for python-filer |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | Aktiver TY LSP for python-filer |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Aktiver plantilstand |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Aktiver plantilstand |
|
||||||
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | boolean | Aktiver baggrundsopgaver for subagenter |
|
|
||||||
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolean | Aktiver eksperimentelt hændelsessystem |
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolean | Aktiver eksperimentelt hændelsessystem |
|
||||||
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolean | Aktiver native LLM-anmodningssti |
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolean | Aktiver native LLM-anmodningssti |
|
||||||
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolean | Aktiver parallel udførelse af websøgning |
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolean | Aktiver parallel udførelse af websøgning |
|
||||||
|
|||||||
@@ -608,7 +608,6 @@ Diese Umgebungsvariablen ermöglichen experimentelle Funktionen, die sich änder
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | boolescher Wert | Experimentelle Exa-Funktionen aktivieren |
|
| `OPENCODE_EXPERIMENTAL_EXA` | boolescher Wert | Experimentelle Exa-Funktionen aktivieren |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolescher Wert | TY LSP für Python-Dateien aktivieren |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolescher Wert | TY LSP für Python-Dateien aktivieren |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolescher Wert | Planmodus aktivieren |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolescher Wert | Planmodus aktivieren |
|
||||||
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | boolescher Wert | Hintergrundaufgaben für Subagenten aktivieren |
|
|
||||||
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolescher Wert | Experimentelles Ereignissystem aktivieren |
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolescher Wert | Experimentelles Ereignissystem aktivieren |
|
||||||
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolescher Wert | Nativen LLM-Anfragepfad aktivieren |
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolescher Wert | Nativen LLM-Anfragepfad aktivieren |
|
||||||
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolescher Wert | Parallele Websuche aktivieren |
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolescher Wert | Parallele Websuche aktivieren |
|
||||||
|
|||||||
@@ -608,7 +608,6 @@ Estas variables de entorno habilitan funciones experimentales que pueden cambiar
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | booleano | Habilitar funciones experimentales de Exa |
|
| `OPENCODE_EXPERIMENTAL_EXA` | booleano | Habilitar funciones experimentales de Exa |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | booleano | Habilitar Habilitar TY LSP para archivos python |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | booleano | Habilitar Habilitar TY LSP para archivos python |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | booleano | Habilitar modo de plan |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | booleano | Habilitar modo de plan |
|
||||||
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | booleano | Habilitar tareas de subagentes en segundo plano |
|
|
||||||
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | booleano | Habilitar sistema de eventos experimental |
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | booleano | Habilitar sistema de eventos experimental |
|
||||||
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | booleano | Habilitar ruta nativa de solicitud LLM |
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | booleano | Habilitar ruta nativa de solicitud LLM |
|
||||||
| `OPENCODE_EXPERIMENTAL_PARALLEL` | booleano | Habilitar ejecución paralela de búsqueda web |
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | booleano | Habilitar ejecución paralela de búsqueda web |
|
||||||
|
|||||||
@@ -609,7 +609,6 @@ Ces variables d'environnement activent des fonctionnalités expérimentales qui
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | booléen | Activer les fonctionnalités Exa expérimentales |
|
| `OPENCODE_EXPERIMENTAL_EXA` | booléen | Activer les fonctionnalités Exa expérimentales |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | booléen | Activer TY LSP pour les fichiers python |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | booléen | Activer TY LSP pour les fichiers python |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | booléen | Activer le mode plan |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | booléen | Activer le mode plan |
|
||||||
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | booléen | Activer les tâches de sous-agents en arrière-plan |
|
|
||||||
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | booléen | Activer le système d'événements expérimental |
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | booléen | Activer le système d'événements expérimental |
|
||||||
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | booléen | Activer le chemin de requête LLM natif |
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | booléen | Activer le chemin de requête LLM natif |
|
||||||
| `OPENCODE_EXPERIMENTAL_PARALLEL` | booléen | Activer l'exécution parallèle de la recherche web |
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | booléen | Activer l'exécution parallèle de la recherche web |
|
||||||
|
|||||||
@@ -609,7 +609,6 @@ Queste variabili d'ambiente abilitano funzionalità sperimentali che potrebbero
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | Abilita funzionalità Exa sperimentali |
|
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | Abilita funzionalità Exa sperimentali |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | Abilita TY LSP per i file python |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | Abilita TY LSP per i file python |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Abilita plan mode |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Abilita plan mode |
|
||||||
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | boolean | Abilita task subagent in background |
|
|
||||||
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolean | Abilita sistema eventi sperimentale |
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolean | Abilita sistema eventi sperimentale |
|
||||||
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolean | Abilita percorso nativo di richiesta LLM |
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolean | Abilita percorso nativo di richiesta LLM |
|
||||||
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolean | Abilita esecuzione parallela della ricerca web |
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolean | Abilita esecuzione parallela della ricerca web |
|
||||||
|
|||||||
@@ -608,7 +608,6 @@ OpenCode は環境変数を使用して構成できます。
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | ブール値 | 実験的な Exa 機能を有効にする |
|
| `OPENCODE_EXPERIMENTAL_EXA` | ブール値 | 実験的な Exa 機能を有効にする |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | ブール値 | python ファイルの TY LSP を有効にする |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | ブール値 | python ファイルの TY LSP を有効にする |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | ブール値 | プランモードを有効にする |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | ブール値 | プランモードを有効にする |
|
||||||
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | ブール値 | バックグラウンド subagent タスクを有効にする |
|
|
||||||
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | ブール値 | 実験的なイベントシステムを有効にする |
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | ブール値 | 実験的なイベントシステムを有効にする |
|
||||||
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | ブール値 | ネイティブ LLM リクエスト経路を有効にする |
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | ブール値 | ネイティブ LLM リクエスト経路を有効にする |
|
||||||
| `OPENCODE_EXPERIMENTAL_PARALLEL` | ブール値 | 並列 Web 検索実行を有効にする |
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | ブール値 | 並列 Web 検索実行を有効にする |
|
||||||
|
|||||||
@@ -608,7 +608,6 @@ OpenCode는 환경 변수로도 구성할 수 있습니다.
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | 실험적 Exa 기능 활성화 |
|
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | 실험적 Exa 기능 활성화 |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | python 파일에 대해 TY LSP 활성화 |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | python 파일에 대해 TY LSP 활성화 |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Plan mode 활성화 |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Plan mode 활성화 |
|
||||||
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | boolean | 백그라운드 subagent 작업 활성화 |
|
|
||||||
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolean | 실험적 이벤트 시스템 활성화 |
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolean | 실험적 이벤트 시스템 활성화 |
|
||||||
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolean | 네이티브 LLM 요청 경로 활성화 |
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolean | 네이티브 LLM 요청 경로 활성화 |
|
||||||
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolean | 병렬 웹 검색 실행 활성화 |
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolean | 병렬 웹 검색 실행 활성화 |
|
||||||
|
|||||||
@@ -609,7 +609,6 @@ Disse miljøvariablene muliggjør eksperimentelle funksjoner som kan endres elle
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | boolsk | Aktiver eksperimentelle Exa-funksjoner |
|
| `OPENCODE_EXPERIMENTAL_EXA` | boolsk | Aktiver eksperimentelle Exa-funksjoner |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolsk | Aktiver TY LSP for python-filer |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolsk | Aktiver TY LSP for python-filer |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolsk | Aktiver planmodus |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolsk | Aktiver planmodus |
|
||||||
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | boolsk | Aktiver bakgrunnsoppgaver for subagenter |
|
|
||||||
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolsk | Aktiver eksperimentelt hendelsessystem |
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolsk | Aktiver eksperimentelt hendelsessystem |
|
||||||
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolsk | Aktiver innebygd LLM-forespørselsvei |
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolsk | Aktiver innebygd LLM-forespørselsvei |
|
||||||
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolsk | Aktiver parallell kjøring av websøk |
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolsk | Aktiver parallell kjøring av websøk |
|
||||||
|
|||||||
@@ -609,7 +609,6 @@ Te zmienne włączają funkcje eksperymentalne, które mogą ulec zmianie lub zo
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | Włącz funkcje eksperymentalne Exa |
|
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | Włącz funkcje eksperymentalne Exa |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | Włącz TY LSP dla plików python |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | Włącz TY LSP dla plików python |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Włącz tryb planowania |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Włącz tryb planowania |
|
||||||
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | boolean | Włącz zadania subagentów w tle |
|
|
||||||
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolean | Włącz eksperymentalny system zdarzeń |
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolean | Włącz eksperymentalny system zdarzeń |
|
||||||
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolean | Włącz natywną ścieżkę żądań LLM |
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolean | Włącz natywną ścieżkę żądań LLM |
|
||||||
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolean | Włącz równoległe wykonywanie wyszukiwania web |
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolean | Włącz równoległe wykonywanie wyszukiwania web |
|
||||||
|
|||||||
@@ -608,7 +608,6 @@ Essas variáveis de ambiente habilitam recursos experimentais que podem mudar ou
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | Habilitar recursos experimentais do Exa |
|
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | Habilitar recursos experimentais do Exa |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | Habilitar TY LSP para arquivos python |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | Habilitar TY LSP para arquivos python |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Habilitar modo de plano |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Habilitar modo de plano |
|
||||||
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | boolean | Habilitar tarefas de subagentes em segundo plano |
|
|
||||||
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolean | Habilitar sistema de eventos experimental |
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolean | Habilitar sistema de eventos experimental |
|
||||||
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolean | Habilitar caminho nativo de requisição LLM |
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolean | Habilitar caminho nativo de requisição LLM |
|
||||||
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolean | Habilitar execução paralela de busca web |
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolean | Habilitar execução paralela de busca web |
|
||||||
|
|||||||
@@ -609,7 +609,6 @@ opencode можно настроить с помощью переменных с
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | логическое значение | Включить экспериментальные функции Exa |
|
| `OPENCODE_EXPERIMENTAL_EXA` | логическое значение | Включить экспериментальные функции Exa |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | логическое значение | Включить TY LSP для файлов python |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | логическое значение | Включить TY LSP для файлов python |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | логическое значение | Включить режим плана |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | логическое значение | Включить режим плана |
|
||||||
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | логическое значение | Включить фоновые задачи субагентов |
|
|
||||||
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | логическое значение | Включить экспериментальную систему событий |
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | логическое значение | Включить экспериментальную систему событий |
|
||||||
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | логическое значение | Включить нативный путь запросов LLM |
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | логическое значение | Включить нативный путь запросов LLM |
|
||||||
| `OPENCODE_EXPERIMENTAL_PARALLEL` | логическое значение | Включить параллельное выполнение веб-поиска |
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | логическое значение | Включить параллельное выполнение веб-поиска |
|
||||||
|
|||||||
@@ -610,7 +610,6 @@ OpenCode สามารถกำหนดค่าโดยใช้ตัว
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | Boolean | ฟีเจอร์ Exa ทดลอง |
|
| `OPENCODE_EXPERIMENTAL_EXA` | Boolean | ฟีเจอร์ Exa ทดลอง |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | Boolean | เปิดใช้งาน TY LSP สำหรับไฟล์ python |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | Boolean | เปิดใช้งาน TY LSP สำหรับไฟล์ python |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | Boolean | เปิดใช้งาน Plan mode |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | Boolean | เปิดใช้งาน Plan mode |
|
||||||
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | Boolean | เปิดใช้งานงาน subagent เบื้องหลัง |
|
|
||||||
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | Boolean | เปิดใช้งานระบบเหตุการณ์ทดลอง |
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | Boolean | เปิดใช้งานระบบเหตุการณ์ทดลอง |
|
||||||
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | Boolean | เปิดใช้งานเส้นทางคำขอ LLM แบบ native |
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | Boolean | เปิดใช้งานเส้นทางคำขอ LLM แบบ native |
|
||||||
| `OPENCODE_EXPERIMENTAL_PARALLEL` | Boolean | เปิดใช้งานการค้นหาเว็บแบบขนาน |
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | Boolean | เปิดใช้งานการค้นหาเว็บแบบขนาน |
|
||||||
|
|||||||
@@ -609,7 +609,6 @@ Bu ortam değişkenleri değişebilecek veya kaldırılabilecek deneysel özelli
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | Deneysel Exa özelliklerini etkinleştirin |
|
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | Deneysel Exa özelliklerini etkinleştirin |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | python dosyaları için TY LSP'yi etkinleştir |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | python dosyaları için TY LSP'yi etkinleştir |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Plan modunu etkinleştir |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Plan modunu etkinleştir |
|
||||||
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | boolean | Arka plan alt ajan görevlerini etkinleştir |
|
|
||||||
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolean | Deneysel olay sistemini etkinleştir |
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolean | Deneysel olay sistemini etkinleştir |
|
||||||
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolean | Yerel LLM istek yolunu etkinleştir |
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolean | Yerel LLM istek yolunu etkinleştir |
|
||||||
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolean | Paralel web araması yürütmesini etkinleştir |
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolean | Paralel web araması yürütmesini etkinleştir |
|
||||||
|
|||||||
@@ -609,7 +609,6 @@ OpenCode 可以通过环境变量进行配置。
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | 启用实验性 Exa 功能 |
|
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | 启用实验性 Exa 功能 |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | 为 python 文件启用 TY LSP |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | 为 python 文件启用 TY LSP |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | 启用计划模式 |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | 启用计划模式 |
|
||||||
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | boolean | 启用后台子代理任务 |
|
|
||||||
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolean | 启用实验性事件系统 |
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolean | 启用实验性事件系统 |
|
||||||
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolean | 启用原生 LLM 请求路径 |
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolean | 启用原生 LLM 请求路径 |
|
||||||
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolean | 启用并行 Web 搜索执行 |
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolean | 启用并行 Web 搜索执行 |
|
||||||
|
|||||||
@@ -609,7 +609,6 @@ OpenCode 可以透過環境變數進行設定。
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | 啟用實驗性 Exa 功能 |
|
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | 啟用實驗性 Exa 功能 |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | 為 python 檔案啟用 TY LSP |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | 為 python 檔案啟用 TY LSP |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | 啟用計畫模式 |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | 啟用計畫模式 |
|
||||||
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | boolean | 啟用背景子代理任務 |
|
|
||||||
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolean | 啟用實驗性事件系統 |
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolean | 啟用實驗性事件系統 |
|
||||||
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolean | 啟用原生 LLM 請求路徑 |
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolean | 啟用原生 LLM 請求路徑 |
|
||||||
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolean | 啟用平行 Web 搜尋執行 |
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolean | 啟用平行 Web 搜尋執行 |
|
||||||
|
|||||||
Reference in New Issue
Block a user