mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-06 01:00:54 -04:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fbdc9075b5 | |||
| ccba1c0df9 |
@@ -86,7 +86,7 @@ export async function runMini(input: MiniCommandInput) {
|
||||
) =>
|
||||
resolveSessionTarget({
|
||||
client,
|
||||
location: { directory: next.location.directory },
|
||||
location: { directory: next.location.directory, workspace: next.location.workspaceID },
|
||||
agent: next.agent,
|
||||
model: next.model
|
||||
? { providerID: next.model.providerID, id: next.model.modelID, variant: next.variant }
|
||||
|
||||
@@ -703,7 +703,7 @@ export async function runNonInteractivePrompt(input: Input) {
|
||||
? Promise.resolve(undefined)
|
||||
: input.client.form.request
|
||||
.list({
|
||||
location: { directory: input.location.directory },
|
||||
location: { directory: input.location.directory, workspace: input.location.workspaceID },
|
||||
})
|
||||
.catch(() => undefined),
|
||||
])
|
||||
@@ -757,6 +757,7 @@ function formRequestOptions(location: LocationRef | undefined): [] | [{ headers:
|
||||
{
|
||||
headers: {
|
||||
"x-opencode-directory": encodeURIComponent(location.directory),
|
||||
...(location.workspaceID ? { "x-opencode-workspace": location.workspaceID } : {}),
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
@@ -96,7 +96,7 @@ async function execute(input: RunCommandInput, prepared: Prepared, endpoint: End
|
||||
next.model ??
|
||||
(options.variant
|
||||
? await client.model
|
||||
.default({ location: { directory: next.location.directory } })
|
||||
.default({ location: { directory: next.location.directory, workspace: next.location.workspaceID } })
|
||||
.then((result) => result.data)
|
||||
: undefined)
|
||||
const model = selected
|
||||
|
||||
@@ -136,6 +136,7 @@ async function latestSession(
|
||||
const page = await client.session.list(
|
||||
{
|
||||
directory: location.directory,
|
||||
workspace: location.workspaceID,
|
||||
parentID: null,
|
||||
limit: SESSION_PAGE_LIMIT,
|
||||
order: "desc",
|
||||
|
||||
@@ -426,13 +426,14 @@ describe("runNonInteractivePrompt", () => {
|
||||
const globalOptions = {
|
||||
headers: {
|
||||
"x-opencode-directory": "%2Fwork%20tree",
|
||||
"x-opencode-workspace": "wrk_1",
|
||||
},
|
||||
}
|
||||
expect(sdk.form.cancel).toHaveBeenCalledWith({ sessionID: "global", formID: "frm_live" }, globalOptions)
|
||||
expect(sdk.form.cancel).toHaveBeenCalledWith({ sessionID: "ses_1", formID: "frm_pending" })
|
||||
expect(sdk.form.cancel).toHaveBeenCalledWith({ sessionID: "global", formID: "frm_pending_global" }, globalOptions)
|
||||
expect(sdk.form.request.list).toHaveBeenCalledWith({
|
||||
location: { directory: "/work tree" },
|
||||
location: { directory: "/work tree", workspace: "wrk_1" },
|
||||
})
|
||||
expect(sdk.question.list).not.toHaveBeenCalled()
|
||||
expect(sdk.question.reject).not.toHaveBeenCalled()
|
||||
|
||||
@@ -3,13 +3,13 @@ import type { Effect, Stream } from "effect"
|
||||
import type { Location } from "@opencode-ai/schema/location"
|
||||
import type { Agent } from "@opencode-ai/schema/agent"
|
||||
import type { Plugin } from "@opencode-ai/schema/plugin"
|
||||
import type { Workspace } from "@opencode-ai/schema/workspace"
|
||||
import type { Session } from "@opencode-ai/schema/session"
|
||||
import type { AbsolutePath } from "@opencode-ai/schema/schema"
|
||||
import type { Project } from "@opencode-ai/schema/project"
|
||||
import type { RelativePath } from "@opencode-ai/schema/schema"
|
||||
import type { Brand } from "effect"
|
||||
import type { Model } from "@opencode-ai/schema/model"
|
||||
import type { Workspace } from "@opencode-ai/schema/workspace"
|
||||
import type { SessionMessage } from "@opencode-ai/schema/session-message"
|
||||
import type { PromptInput } from "@opencode-ai/schema/prompt-input"
|
||||
import type { AgentAttachment } from "@opencode-ai/schema/prompt"
|
||||
@@ -58,7 +58,9 @@ export interface ServerApi<E = never> {
|
||||
readonly get: ServerGetOperation<E>
|
||||
}
|
||||
|
||||
export type Endpoint2_0Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||
export type Endpoint2_0Input = {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint2_0Output = Location.Info
|
||||
export type LocationGetOperation<E = never> = (input?: Endpoint2_0Input) => Effect.Effect<Endpoint2_0Output, E>
|
||||
|
||||
@@ -66,13 +68,15 @@ export interface LocationApi<E = never> {
|
||||
readonly get: LocationGetOperation<E>
|
||||
}
|
||||
|
||||
export type Endpoint3_0Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||
export type Endpoint3_0Input = {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint3_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Agent.Info> }
|
||||
export type AgentListOperation<E = never> = (input?: Endpoint3_0Input) => Effect.Effect<Endpoint3_0Output, E>
|
||||
|
||||
export type Endpoint3_1Input = {
|
||||
readonly agentID: Agent.ID
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint3_1Output = { readonly location: Location.Info; readonly data: Agent.Info }
|
||||
export type AgentGetOperation<E = never> = (input: Endpoint3_1Input) => Effect.Effect<Endpoint3_1Output, E>
|
||||
@@ -82,7 +86,9 @@ export interface AgentApi<E = never> {
|
||||
readonly get: AgentGetOperation<E>
|
||||
}
|
||||
|
||||
export type Endpoint4_0Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||
export type Endpoint4_0Input = {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint4_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Plugin.Info> }
|
||||
export type PluginListOperation<E = never> = (input?: Endpoint4_0Input) => Effect.Effect<Endpoint4_0Output, E>
|
||||
|
||||
@@ -91,6 +97,7 @@ export interface PluginApi<E = never> {
|
||||
}
|
||||
|
||||
export type Endpoint5_0Input = {
|
||||
readonly workspace?: Workspace.ID | undefined
|
||||
readonly limit?: number | undefined
|
||||
readonly order?: "asc" | "desc" | undefined
|
||||
readonly search?: string | undefined
|
||||
@@ -891,11 +898,15 @@ export interface MessageApi<E = never> {
|
||||
readonly list: MessageListOperation<E>
|
||||
}
|
||||
|
||||
export type Endpoint7_0Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||
export type Endpoint7_0Input = {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint7_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Model.Info> }
|
||||
export type ModelListOperation<E = never> = (input?: Endpoint7_0Input) => Effect.Effect<Endpoint7_0Output, E>
|
||||
|
||||
export type Endpoint7_1Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||
export type Endpoint7_1Input = {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint7_1Output = { readonly location: Location.Info; readonly data: Model.Info | undefined }
|
||||
export type ModelDefaultOperation<E = never> = (input?: Endpoint7_1Input) => Effect.Effect<Endpoint7_1Output, E>
|
||||
|
||||
@@ -905,7 +916,7 @@ export interface ModelApi<E = never> {
|
||||
}
|
||||
|
||||
export type Endpoint8_0Input = {
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly prompt: string
|
||||
readonly model?: Model.Ref | undefined
|
||||
}
|
||||
@@ -916,13 +927,15 @@ export interface GenerateApi<E = never> {
|
||||
readonly text: GenerateTextOperation<E>
|
||||
}
|
||||
|
||||
export type Endpoint9_0Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||
export type Endpoint9_0Input = {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint9_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Provider.Info> }
|
||||
export type ProviderListOperation<E = never> = (input?: Endpoint9_0Input) => Effect.Effect<Endpoint9_0Output, E>
|
||||
|
||||
export type Endpoint9_1Input = {
|
||||
readonly providerID: Provider.ID
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint9_1Output = { readonly location: Location.Info; readonly data: Provider.Info }
|
||||
export type ProviderGetOperation<E = never> = (input: Endpoint9_1Input) => Effect.Effect<Endpoint9_1Output, E>
|
||||
@@ -932,19 +945,21 @@ export interface ProviderApi<E = never> {
|
||||
readonly get: ProviderGetOperation<E>
|
||||
}
|
||||
|
||||
export type Endpoint10_0Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||
export type Endpoint10_0Input = {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint10_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Integration.Info> }
|
||||
export type IntegrationListOperation<E = never> = (input?: Endpoint10_0Input) => Effect.Effect<Endpoint10_0Output, E>
|
||||
|
||||
export type Endpoint10_1Input = {
|
||||
readonly integrationID: Integration.ID
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint10_1Output = { readonly location: Location.Info; readonly data: Integration.Info | undefined }
|
||||
export type IntegrationGetOperation<E = never> = (input: Endpoint10_1Input) => Effect.Effect<Endpoint10_1Output, E>
|
||||
|
||||
export type Endpoint10_2Input = {
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly url: string
|
||||
}
|
||||
export type Endpoint10_2Output = void
|
||||
@@ -954,7 +969,7 @@ export type IntegrationWellknownAddOperation<E = never> = (
|
||||
|
||||
export type Endpoint10_3Input = {
|
||||
readonly integrationID: Integration.ID
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly key: string
|
||||
readonly label?: string | undefined
|
||||
}
|
||||
@@ -965,7 +980,7 @@ export type IntegrationConnectKeyOperation<E = never> = (
|
||||
|
||||
export type Endpoint10_4Input = {
|
||||
readonly integrationID: Integration.ID
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly methodID: Integration.MethodID
|
||||
readonly inputs: { readonly [x: string]: string }
|
||||
readonly label?: string | undefined
|
||||
@@ -978,7 +993,7 @@ export type IntegrationOauthConnectOperation<E = never> = (
|
||||
export type Endpoint10_5Input = {
|
||||
readonly integrationID: Integration.ID
|
||||
readonly attemptID: Integration.AttemptID
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint10_5Output = { readonly location: Location.Info; readonly data: Integration.AttemptStatus }
|
||||
export type IntegrationOauthStatusOperation<E = never> = (
|
||||
@@ -988,7 +1003,7 @@ export type IntegrationOauthStatusOperation<E = never> = (
|
||||
export type Endpoint10_6Input = {
|
||||
readonly integrationID: Integration.ID
|
||||
readonly attemptID: Integration.AttemptID
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly code?: string | undefined
|
||||
}
|
||||
export type Endpoint10_6Output = void
|
||||
@@ -999,7 +1014,7 @@ export type IntegrationOauthCompleteOperation<E = never> = (
|
||||
export type Endpoint10_7Input = {
|
||||
readonly integrationID: Integration.ID
|
||||
readonly attemptID: Integration.AttemptID
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint10_7Output = void
|
||||
export type IntegrationOauthCancelOperation<E = never> = (
|
||||
@@ -1008,7 +1023,7 @@ export type IntegrationOauthCancelOperation<E = never> = (
|
||||
|
||||
export type Endpoint10_8Input = {
|
||||
readonly integrationID: Integration.ID
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly methodID: Integration.MethodID
|
||||
readonly label?: string | undefined
|
||||
}
|
||||
@@ -1020,7 +1035,7 @@ export type IntegrationCommandConnectOperation<E = never> = (
|
||||
export type Endpoint10_9Input = {
|
||||
readonly integrationID: Integration.ID
|
||||
readonly attemptID: Integration.AttemptID
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint10_9Output = { readonly location: Location.Info; readonly data: Integration.CommandAttemptStatus }
|
||||
export type IntegrationCommandStatusOperation<E = never> = (
|
||||
@@ -1030,7 +1045,7 @@ export type IntegrationCommandStatusOperation<E = never> = (
|
||||
export type Endpoint10_10Input = {
|
||||
readonly integrationID: Integration.ID
|
||||
readonly attemptID: Integration.AttemptID
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint10_10Output = void
|
||||
export type IntegrationCommandCancelOperation<E = never> = (
|
||||
@@ -1055,13 +1070,15 @@ export interface IntegrationApi<E = never> {
|
||||
}
|
||||
}
|
||||
|
||||
export type Endpoint11_0Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||
export type Endpoint11_0Input = {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint11_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Mcp.Server> }
|
||||
export type McpListOperation<E = never> = (input?: Endpoint11_0Input) => Effect.Effect<Endpoint11_0Output, E>
|
||||
|
||||
export type Endpoint11_1Input = {
|
||||
readonly server: string
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly config: Mcp.LocalConfig | Mcp.RemoteConfig
|
||||
}
|
||||
export type Endpoint11_1Output = void
|
||||
@@ -1069,26 +1086,28 @@ export type McpAddOperation<E = never> = (input: Endpoint11_1Input) => Effect.Ef
|
||||
|
||||
export type Endpoint11_2Input = {
|
||||
readonly server: string
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint11_2Output = void
|
||||
export type McpRemoveOperation<E = never> = (input: Endpoint11_2Input) => Effect.Effect<Endpoint11_2Output, E>
|
||||
|
||||
export type Endpoint11_3Input = {
|
||||
readonly server: string
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint11_3Output = void
|
||||
export type McpConnectOperation<E = never> = (input: Endpoint11_3Input) => Effect.Effect<Endpoint11_3Output, E>
|
||||
|
||||
export type Endpoint11_4Input = {
|
||||
readonly server: string
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint11_4Output = void
|
||||
export type McpDisconnectOperation<E = never> = (input: Endpoint11_4Input) => Effect.Effect<Endpoint11_4Output, E>
|
||||
|
||||
export type Endpoint11_5Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||
export type Endpoint11_5Input = {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint11_5Output = { readonly location: Location.Info; readonly data: Mcp.ResourceCatalog }
|
||||
export type McpResourceCatalogOperation<E = never> = (input?: Endpoint11_5Input) => Effect.Effect<Endpoint11_5Output, E>
|
||||
|
||||
@@ -1103,7 +1122,7 @@ export interface McpApi<E = never> {
|
||||
|
||||
export type Endpoint12_0Input = {
|
||||
readonly credentialID: Credential.ID
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly label: string
|
||||
}
|
||||
export type Endpoint12_0Output = void
|
||||
@@ -1111,7 +1130,7 @@ export type CredentialUpdateOperation<E = never> = (input: Endpoint12_0Input) =>
|
||||
|
||||
export type Endpoint12_1Input = {
|
||||
readonly credentialID: Credential.ID
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint12_1Output = void
|
||||
export type CredentialRemoveOperation<E = never> = (input: Endpoint12_1Input) => Effect.Effect<Endpoint12_1Output, E>
|
||||
@@ -1124,13 +1143,15 @@ export interface CredentialApi<E = never> {
|
||||
export type Endpoint13_0Output = ReadonlyArray<Project.Info>
|
||||
export type ProjectListOperation<E = never> = () => Effect.Effect<Endpoint13_0Output, E>
|
||||
|
||||
export type Endpoint13_1Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||
export type Endpoint13_1Input = {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint13_1Output = Project.Current
|
||||
export type ProjectCurrentOperation<E = never> = (input?: Endpoint13_1Input) => Effect.Effect<Endpoint13_1Output, E>
|
||||
|
||||
export type Endpoint13_2Input = {
|
||||
readonly projectID: Project.ID
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint13_2Output = Project.Directories
|
||||
export type ProjectDirectoriesOperation<E = never> = (input: Endpoint13_2Input) => Effect.Effect<Endpoint13_2Output, E>
|
||||
@@ -1141,7 +1162,9 @@ export interface ProjectApi<E = never> {
|
||||
readonly directories: ProjectDirectoriesOperation<E>
|
||||
}
|
||||
|
||||
export type Endpoint14_0Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||
export type Endpoint14_0Input = {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint14_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Form.Info> }
|
||||
export type FormRequestListOperation<E = never> = (input?: Endpoint14_0Input) => Effect.Effect<Endpoint14_0Output, E>
|
||||
|
||||
@@ -1185,7 +1208,9 @@ export interface FormApi<E = never> {
|
||||
readonly cancel: FormCancelOperation<E>
|
||||
}
|
||||
|
||||
export type Endpoint15_0Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||
export type Endpoint15_0Input = {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint15_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Permission.Request> }
|
||||
export type PermissionRequestListOperation<E = never> = (
|
||||
input?: Endpoint15_0Input,
|
||||
@@ -1243,14 +1268,14 @@ export interface PermissionApi<E = never> {
|
||||
}
|
||||
|
||||
export type Endpoint16_0Input = {
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly path?: RelativePath | undefined
|
||||
}
|
||||
export type Endpoint16_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<FileSystem.Entry> }
|
||||
export type FileListOperation<E = never> = (input?: Endpoint16_0Input) => Effect.Effect<Endpoint16_0Output, E>
|
||||
|
||||
export type Endpoint16_1Input = {
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly query: string
|
||||
readonly type?: "file" | "directory" | undefined
|
||||
readonly limit?: number | undefined
|
||||
@@ -1263,7 +1288,9 @@ export interface FileApi<E = never> {
|
||||
readonly find: FileFindOperation<E>
|
||||
}
|
||||
|
||||
export type Endpoint17_0Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||
export type Endpoint17_0Input = {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint17_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Command.Info> }
|
||||
export type CommandListOperation<E = never> = (input?: Endpoint17_0Input) => Effect.Effect<Endpoint17_0Output, E>
|
||||
|
||||
@@ -1271,7 +1298,9 @@ export interface CommandApi<E = never> {
|
||||
readonly list: CommandListOperation<E>
|
||||
}
|
||||
|
||||
export type Endpoint18_0Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||
export type Endpoint18_0Input = {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint18_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Skill.Info> }
|
||||
export type SkillListOperation<E = never> = (input?: Endpoint18_0Input) => Effect.Effect<Endpoint18_0Output, E>
|
||||
|
||||
@@ -1286,12 +1315,14 @@ export interface EventApi<E = never> {
|
||||
readonly subscribe: EventSubscribeOperation<E>
|
||||
}
|
||||
|
||||
export type Endpoint20_0Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||
export type Endpoint20_0Input = {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint20_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Pty.Info> }
|
||||
export type PtyListOperation<E = never> = (input?: Endpoint20_0Input) => Effect.Effect<Endpoint20_0Output, E>
|
||||
|
||||
export type Endpoint20_1Input = {
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly command?: string | undefined
|
||||
readonly args?: ReadonlyArray<string> | undefined
|
||||
readonly cwd?: string | undefined
|
||||
@@ -1303,14 +1334,14 @@ export type PtyCreateOperation<E = never> = (input?: Endpoint20_1Input) => Effec
|
||||
|
||||
export type Endpoint20_2Input = {
|
||||
readonly ptyID: Pty.ID
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint20_2Output = { readonly location: Location.Info; readonly data: Pty.Info }
|
||||
export type PtyGetOperation<E = never> = (input: Endpoint20_2Input) => Effect.Effect<Endpoint20_2Output, E>
|
||||
|
||||
export type Endpoint20_3Input = {
|
||||
readonly ptyID: Pty.ID
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly title?: string | undefined
|
||||
readonly size?: { readonly rows: number; readonly cols: number } | undefined
|
||||
}
|
||||
@@ -1319,7 +1350,7 @@ export type PtyUpdateOperation<E = never> = (input: Endpoint20_3Input) => Effect
|
||||
|
||||
export type Endpoint20_4Input = {
|
||||
readonly ptyID: Pty.ID
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint20_4Output = void
|
||||
export type PtyRemoveOperation<E = never> = (input: Endpoint20_4Input) => Effect.Effect<Endpoint20_4Output, E>
|
||||
@@ -1332,12 +1363,14 @@ export interface PtyApi<E = never> {
|
||||
readonly remove: PtyRemoveOperation<E>
|
||||
}
|
||||
|
||||
export type Endpoint21_0Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||
export type Endpoint21_0Input = {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint21_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Shell.Info> }
|
||||
export type ShellListOperation<E = never> = (input?: Endpoint21_0Input) => Effect.Effect<Endpoint21_0Output, E>
|
||||
|
||||
export type Endpoint21_1Input = {
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly command: string
|
||||
readonly cwd?: string | undefined
|
||||
readonly timeout: number
|
||||
@@ -1348,14 +1381,14 @@ export type ShellCreateOperation<E = never> = (input: Endpoint21_1Input) => Effe
|
||||
|
||||
export type Endpoint21_2Input = {
|
||||
readonly id: Shell.ID
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint21_2Output = { readonly location: Location.Info; readonly data: Shell.Info }
|
||||
export type ShellGetOperation<E = never> = (input: Endpoint21_2Input) => Effect.Effect<Endpoint21_2Output, E>
|
||||
|
||||
export type Endpoint21_3Input = {
|
||||
readonly id: Shell.ID
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly timeout: number
|
||||
}
|
||||
export type Endpoint21_3Output = { readonly location: Location.Info; readonly data: Shell.Info }
|
||||
@@ -1363,7 +1396,7 @@ export type ShellTimeoutOperation<E = never> = (input: Endpoint21_3Input) => Eff
|
||||
|
||||
export type Endpoint21_4Input = {
|
||||
readonly id: Shell.ID
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly cursor?: number | undefined
|
||||
readonly limit?: number | undefined
|
||||
}
|
||||
@@ -1380,7 +1413,7 @@ export type ShellOutputOperation<E = never> = (input: Endpoint21_4Input) => Effe
|
||||
|
||||
export type Endpoint21_5Input = {
|
||||
readonly id: Shell.ID
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint21_5Output = void
|
||||
export type ShellRemoveOperation<E = never> = (input: Endpoint21_5Input) => Effect.Effect<Endpoint21_5Output, E>
|
||||
@@ -1394,7 +1427,9 @@ export interface ShellApi<E = never> {
|
||||
readonly remove: ShellRemoveOperation<E>
|
||||
}
|
||||
|
||||
export type Endpoint22_0Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||
export type Endpoint22_0Input = {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint22_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Question.Request> }
|
||||
export type QuestionRequestListOperation<E = never> = (
|
||||
input?: Endpoint22_0Input,
|
||||
@@ -1423,7 +1458,9 @@ export interface QuestionApi<E = never> {
|
||||
readonly reject: QuestionRejectOperation<E>
|
||||
}
|
||||
|
||||
export type Endpoint23_0Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||
export type Endpoint23_0Input = {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint23_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Reference.Info> }
|
||||
export type ReferenceListOperation<E = never> = (input?: Endpoint23_0Input) => Effect.Effect<Endpoint23_0Output, E>
|
||||
|
||||
@@ -1433,7 +1470,7 @@ export interface ReferenceApi<E = never> {
|
||||
|
||||
export type Endpoint24_0Input = {
|
||||
readonly projectID: Project.ID
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly strategy: ProjectCopy.StrategyID
|
||||
readonly directory: AbsolutePath
|
||||
readonly name?: string | undefined
|
||||
@@ -1443,7 +1480,7 @@ export type ProjectCopyCreateOperation<E = never> = (input: Endpoint24_0Input) =
|
||||
|
||||
export type Endpoint24_1Input = {
|
||||
readonly projectID: Project.ID
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly directory: AbsolutePath
|
||||
readonly force: boolean
|
||||
}
|
||||
@@ -1452,7 +1489,7 @@ export type ProjectCopyRemoveOperation<E = never> = (input: Endpoint24_1Input) =
|
||||
|
||||
export type Endpoint24_2Input = {
|
||||
readonly projectID: Project.ID
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint24_2Output = void
|
||||
export type ProjectCopyRefreshOperation<E = never> = (input: Endpoint24_2Input) => Effect.Effect<Endpoint24_2Output, E>
|
||||
@@ -1463,16 +1500,20 @@ export interface ProjectCopyApi<E = never> {
|
||||
readonly refresh: ProjectCopyRefreshOperation<E>
|
||||
}
|
||||
|
||||
export type Endpoint25_0Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||
export type Endpoint25_0Input = {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint25_0Output = { readonly location: Location.Info; readonly data: Vcs.Info }
|
||||
export type VcsGetOperation<E = never> = (input?: Endpoint25_0Input) => Effect.Effect<Endpoint25_0Output, E>
|
||||
|
||||
export type Endpoint25_1Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||
export type Endpoint25_1Input = {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint25_1Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Vcs.FileStatus> }
|
||||
export type VcsStatusOperation<E = never> = (input?: Endpoint25_1Input) => Effect.Effect<Endpoint25_1Output, E>
|
||||
|
||||
export type Endpoint25_2Input = {
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly mode: Vcs.Mode
|
||||
readonly context?: number | undefined
|
||||
}
|
||||
@@ -1488,7 +1529,9 @@ export interface VcsApi<E = never> {
|
||||
export type Endpoint26_0Output = ReadonlyArray<Location.Ref>
|
||||
export type DebugLocationListOperation<E = never> = () => Effect.Effect<Endpoint26_0Output, E>
|
||||
|
||||
export type Endpoint26_1Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||
export type Endpoint26_1Input = {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint26_1Output = void
|
||||
export type DebugLocationEvictOperation<E = never> = (input?: Endpoint26_1Input) => Effect.Effect<Endpoint26_1Output, E>
|
||||
|
||||
@@ -1496,12 +1539,14 @@ export interface DebugApi<E = never> {
|
||||
readonly location: { readonly list: DebugLocationListOperation<E>; readonly evict: DebugLocationEvictOperation<E> }
|
||||
}
|
||||
|
||||
export type Endpoint27_0Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||
export type Endpoint27_0Input = {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}
|
||||
export type Endpoint27_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<WebSearch.Provider> }
|
||||
export type WebsearchProvidersOperation<E = never> = (input?: Endpoint27_0Input) => Effect.Effect<Endpoint27_0Output, E>
|
||||
|
||||
export type Endpoint27_1Input = {
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly query: string
|
||||
readonly providerID?: WebSearch.ID | undefined
|
||||
}
|
||||
|
||||
@@ -285,6 +285,7 @@ const Endpoint5_0 = (raw: RawClient["server.session"]) => (input?: Endpoint5_0In
|
||||
preserveEffect<Endpoint5_0Output>()(
|
||||
raw["session.list"]({
|
||||
query: {
|
||||
workspace: input?.["workspace"],
|
||||
limit: input?.["limit"],
|
||||
order: input?.["order"],
|
||||
search: input?.["search"],
|
||||
|
||||
@@ -441,6 +441,7 @@ export function make(options: ClientOptions) {
|
||||
method: "GET",
|
||||
path: `/api/session`,
|
||||
query: {
|
||||
workspace: input?.["workspace"],
|
||||
limit: input?.["limit"],
|
||||
order: input?.["order"],
|
||||
search: input?.["search"],
|
||||
|
||||
@@ -2514,7 +2514,9 @@ export type HealthStopOutput = ServiceStopResponse
|
||||
export type ServerGetOutput = { urls: Array<string> }
|
||||
|
||||
export type LocationGetInput = {
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type LocationGetOutput = {
|
||||
@@ -2524,7 +2526,9 @@ export type LocationGetOutput = {
|
||||
}
|
||||
|
||||
export type AgentListInput = {
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type AgentListOutput = {
|
||||
@@ -2534,7 +2538,9 @@ export type AgentListOutput = {
|
||||
|
||||
export type AgentGetInput = {
|
||||
readonly agentID: { readonly agentID: string }["agentID"]
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type AgentGetOutput = {
|
||||
@@ -2543,7 +2549,9 @@ export type AgentGetOutput = {
|
||||
}
|
||||
|
||||
export type PluginListInput = {
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type PluginListOutput = {
|
||||
@@ -2552,7 +2560,19 @@ export type PluginListOutput = {
|
||||
}
|
||||
|
||||
export type SessionListInput = {
|
||||
readonly workspace?: {
|
||||
readonly workspace?: string | undefined
|
||||
readonly limit?: number | undefined
|
||||
readonly order?: "asc" | "desc" | undefined
|
||||
readonly search?: string | undefined
|
||||
readonly parentID?: string | null | undefined
|
||||
readonly directory?: string | undefined
|
||||
readonly project?: string | undefined
|
||||
readonly subpath?: string | undefined
|
||||
readonly cursor?: string | undefined
|
||||
}["workspace"]
|
||||
readonly limit?: {
|
||||
readonly workspace?: string | undefined
|
||||
readonly limit?: number | undefined
|
||||
readonly order?: "asc" | "desc" | undefined
|
||||
readonly search?: string | undefined
|
||||
@@ -2563,6 +2583,7 @@ export type SessionListInput = {
|
||||
readonly cursor?: string | undefined
|
||||
}["limit"]
|
||||
readonly order?: {
|
||||
readonly workspace?: string | undefined
|
||||
readonly limit?: number | undefined
|
||||
readonly order?: "asc" | "desc" | undefined
|
||||
readonly search?: string | undefined
|
||||
@@ -2573,6 +2594,7 @@ export type SessionListInput = {
|
||||
readonly cursor?: string | undefined
|
||||
}["order"]
|
||||
readonly search?: {
|
||||
readonly workspace?: string | undefined
|
||||
readonly limit?: number | undefined
|
||||
readonly order?: "asc" | "desc" | undefined
|
||||
readonly search?: string | undefined
|
||||
@@ -2583,6 +2605,7 @@ export type SessionListInput = {
|
||||
readonly cursor?: string | undefined
|
||||
}["search"]
|
||||
readonly parentID?: {
|
||||
readonly workspace?: string | undefined
|
||||
readonly limit?: number | undefined
|
||||
readonly order?: "asc" | "desc" | undefined
|
||||
readonly search?: string | undefined
|
||||
@@ -2593,6 +2616,7 @@ export type SessionListInput = {
|
||||
readonly cursor?: string | undefined
|
||||
}["parentID"]
|
||||
readonly directory?: {
|
||||
readonly workspace?: string | undefined
|
||||
readonly limit?: number | undefined
|
||||
readonly order?: "asc" | "desc" | undefined
|
||||
readonly search?: string | undefined
|
||||
@@ -2603,6 +2627,7 @@ export type SessionListInput = {
|
||||
readonly cursor?: string | undefined
|
||||
}["directory"]
|
||||
readonly project?: {
|
||||
readonly workspace?: string | undefined
|
||||
readonly limit?: number | undefined
|
||||
readonly order?: "asc" | "desc" | undefined
|
||||
readonly search?: string | undefined
|
||||
@@ -2613,6 +2638,7 @@ export type SessionListInput = {
|
||||
readonly cursor?: string | undefined
|
||||
}["project"]
|
||||
readonly subpath?: {
|
||||
readonly workspace?: string | undefined
|
||||
readonly limit?: number | undefined
|
||||
readonly order?: "asc" | "desc" | undefined
|
||||
readonly search?: string | undefined
|
||||
@@ -2623,6 +2649,7 @@ export type SessionListInput = {
|
||||
readonly cursor?: string | undefined
|
||||
}["subpath"]
|
||||
readonly cursor?: {
|
||||
readonly workspace?: string | undefined
|
||||
readonly limit?: number | undefined
|
||||
readonly order?: "asc" | "desc" | undefined
|
||||
readonly search?: string | undefined
|
||||
@@ -3217,7 +3244,9 @@ export type MessageListInput = {
|
||||
export type MessageListOutput = SessionMessagesResponse
|
||||
|
||||
export type ModelListInput = {
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type ModelListOutput = {
|
||||
@@ -3226,7 +3255,9 @@ export type ModelListOutput = {
|
||||
}
|
||||
|
||||
export type ModelDefaultInput = {
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type ModelDefaultOutput = {
|
||||
@@ -3235,7 +3266,9 @@ export type ModelDefaultOutput = {
|
||||
}
|
||||
|
||||
export type GenerateTextInput = {
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
readonly prompt: {
|
||||
readonly prompt: string
|
||||
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
|
||||
@@ -3249,7 +3282,9 @@ export type GenerateTextInput = {
|
||||
export type GenerateTextOutput = GenerateTextResponse["data"]
|
||||
|
||||
export type ProviderListInput = {
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type ProviderListOutput = {
|
||||
@@ -3259,7 +3294,9 @@ export type ProviderListOutput = {
|
||||
|
||||
export type ProviderGetInput = {
|
||||
readonly providerID: { readonly providerID: string }["providerID"]
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type ProviderGetOutput = {
|
||||
@@ -3268,7 +3305,9 @@ export type ProviderGetOutput = {
|
||||
}
|
||||
|
||||
export type IntegrationListInput = {
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type IntegrationListOutput = {
|
||||
@@ -3278,7 +3317,9 @@ export type IntegrationListOutput = {
|
||||
|
||||
export type IntegrationGetInput = {
|
||||
readonly integrationID: { readonly integrationID: string }["integrationID"]
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type IntegrationGetOutput = {
|
||||
@@ -3287,7 +3328,9 @@ export type IntegrationGetOutput = {
|
||||
}
|
||||
|
||||
export type IntegrationWellknownAddInput = {
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
readonly url: { readonly url: string }["url"]
|
||||
}
|
||||
|
||||
@@ -3295,7 +3338,9 @@ export type IntegrationWellknownAddOutput = void
|
||||
|
||||
export type IntegrationConnectKeyInput = {
|
||||
readonly integrationID: { readonly integrationID: string }["integrationID"]
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
readonly key: { readonly key: string; readonly label?: string | undefined }["key"]
|
||||
readonly label?: { readonly key: string; readonly label?: string | undefined }["label"]
|
||||
}
|
||||
@@ -3304,7 +3349,9 @@ export type IntegrationConnectKeyOutput = void
|
||||
|
||||
export type IntegrationOauthConnectInput = {
|
||||
readonly integrationID: { readonly integrationID: string }["integrationID"]
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
readonly methodID: {
|
||||
readonly methodID: string
|
||||
readonly inputs: { readonly [x: string]: string }
|
||||
@@ -3336,7 +3383,9 @@ export type IntegrationOauthConnectOutput = {
|
||||
export type IntegrationOauthStatusInput = {
|
||||
readonly integrationID: { readonly integrationID: string; readonly attemptID: string }["integrationID"]
|
||||
readonly attemptID: { readonly integrationID: string; readonly attemptID: string }["attemptID"]
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type IntegrationOauthStatusOutput = {
|
||||
@@ -3347,7 +3396,9 @@ export type IntegrationOauthStatusOutput = {
|
||||
export type IntegrationOauthCompleteInput = {
|
||||
readonly integrationID: { readonly integrationID: string; readonly attemptID: string }["integrationID"]
|
||||
readonly attemptID: { readonly integrationID: string; readonly attemptID: string }["attemptID"]
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
readonly code?: { readonly code?: string | undefined }["code"]
|
||||
}
|
||||
|
||||
@@ -3356,14 +3407,18 @@ export type IntegrationOauthCompleteOutput = void
|
||||
export type IntegrationOauthCancelInput = {
|
||||
readonly integrationID: { readonly integrationID: string; readonly attemptID: string }["integrationID"]
|
||||
readonly attemptID: { readonly integrationID: string; readonly attemptID: string }["attemptID"]
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type IntegrationOauthCancelOutput = void
|
||||
|
||||
export type IntegrationCommandConnectInput = {
|
||||
readonly integrationID: { readonly integrationID: string }["integrationID"]
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
readonly methodID: { readonly methodID: string; readonly label?: string | undefined }["methodID"]
|
||||
readonly label?: { readonly methodID: string; readonly label?: string | undefined }["label"]
|
||||
}
|
||||
@@ -3376,7 +3431,9 @@ export type IntegrationCommandConnectOutput = {
|
||||
export type IntegrationCommandStatusInput = {
|
||||
readonly integrationID: { readonly integrationID: string; readonly attemptID: string }["integrationID"]
|
||||
readonly attemptID: { readonly integrationID: string; readonly attemptID: string }["attemptID"]
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type IntegrationCommandStatusOutput = {
|
||||
@@ -3387,13 +3444,17 @@ export type IntegrationCommandStatusOutput = {
|
||||
export type IntegrationCommandCancelInput = {
|
||||
readonly integrationID: { readonly integrationID: string; readonly attemptID: string }["integrationID"]
|
||||
readonly attemptID: { readonly integrationID: string; readonly attemptID: string }["attemptID"]
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type IntegrationCommandCancelOutput = void
|
||||
|
||||
export type McpListInput = {
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type McpListOutput = {
|
||||
@@ -3403,7 +3464,9 @@ export type McpListOutput = {
|
||||
|
||||
export type McpAddInput = {
|
||||
readonly server: { readonly server: string }["server"]
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
readonly config: {
|
||||
readonly config:
|
||||
| {
|
||||
@@ -3452,27 +3515,35 @@ export type McpAddOutput = void
|
||||
|
||||
export type McpRemoveInput = {
|
||||
readonly server: { readonly server: string }["server"]
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type McpRemoveOutput = void
|
||||
|
||||
export type McpConnectInput = {
|
||||
readonly server: { readonly server: string }["server"]
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type McpConnectOutput = void
|
||||
|
||||
export type McpDisconnectInput = {
|
||||
readonly server: { readonly server: string }["server"]
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type McpDisconnectOutput = void
|
||||
|
||||
export type McpResourceCatalogInput = {
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type McpResourceCatalogOutput = {
|
||||
@@ -3482,7 +3553,9 @@ export type McpResourceCatalogOutput = {
|
||||
|
||||
export type CredentialUpdateInput = {
|
||||
readonly credentialID: { readonly credentialID: string }["credentialID"]
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
readonly label: { readonly label: string }["label"]
|
||||
}
|
||||
|
||||
@@ -3490,7 +3563,9 @@ export type CredentialUpdateOutput = void
|
||||
|
||||
export type CredentialRemoveInput = {
|
||||
readonly credentialID: { readonly credentialID: string }["credentialID"]
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type CredentialRemoveOutput = void
|
||||
@@ -3498,20 +3573,26 @@ export type CredentialRemoveOutput = void
|
||||
export type ProjectListOutput = Array<Project>
|
||||
|
||||
export type ProjectCurrentInput = {
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type ProjectCurrentOutput = ProjectCurrent
|
||||
|
||||
export type ProjectDirectoriesInput = {
|
||||
readonly projectID: { readonly projectID: string }["projectID"]
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type ProjectDirectoriesOutput = ProjectDirectories
|
||||
|
||||
export type FormRequestListInput = {
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type FormRequestListOutput = {
|
||||
@@ -4365,7 +4446,9 @@ export type FormCancelInput = {
|
||||
export type FormCancelOutput = void
|
||||
|
||||
export type PermissionRequestListInput = {
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type PermissionRequestListOutput = {
|
||||
@@ -4471,7 +4554,9 @@ export type PermissionReplyInput = {
|
||||
export type PermissionReplyOutput = void
|
||||
|
||||
export type FileReadInput = {
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
readonly path: string
|
||||
}
|
||||
|
||||
@@ -4479,11 +4564,11 @@ export type FileReadOutput = globalThis.Uint8Array
|
||||
|
||||
export type FileListInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly path?: string | undefined
|
||||
}["location"]
|
||||
readonly path?: {
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly path?: string | undefined
|
||||
}["path"]
|
||||
}
|
||||
@@ -4495,25 +4580,25 @@ export type FileListOutput = {
|
||||
|
||||
export type FileFindInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly query: string
|
||||
readonly type?: "file" | "directory" | undefined
|
||||
readonly limit?: number | undefined
|
||||
}["location"]
|
||||
readonly query: {
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly query: string
|
||||
readonly type?: "file" | "directory" | undefined
|
||||
readonly limit?: number | undefined
|
||||
}["query"]
|
||||
readonly type?: {
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly query: string
|
||||
readonly type?: "file" | "directory" | undefined
|
||||
readonly limit?: number | undefined
|
||||
}["type"]
|
||||
readonly limit?: {
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly query: string
|
||||
readonly type?: "file" | "directory" | undefined
|
||||
readonly limit?: number | undefined
|
||||
@@ -4526,7 +4611,9 @@ export type FileFindOutput = {
|
||||
}
|
||||
|
||||
export type CommandListInput = {
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type CommandListOutput = {
|
||||
@@ -4535,7 +4622,9 @@ export type CommandListOutput = {
|
||||
}
|
||||
|
||||
export type SkillListInput = {
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type SkillListOutput = {
|
||||
@@ -4546,7 +4635,9 @@ export type SkillListOutput = {
|
||||
export type EventSubscribeOutput = V2Event
|
||||
|
||||
export type PtyListInput = {
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type PtyListOutput = {
|
||||
@@ -4555,7 +4646,9 @@ export type PtyListOutput = {
|
||||
}
|
||||
|
||||
export type PtyCreateInput = {
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
readonly command?: {
|
||||
readonly command?: string
|
||||
readonly args?: ReadonlyArray<string>
|
||||
@@ -4600,7 +4693,9 @@ export type PtyCreateOutput = {
|
||||
|
||||
export type PtyGetInput = {
|
||||
readonly ptyID: { readonly ptyID: string }["ptyID"]
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type PtyGetOutput = {
|
||||
@@ -4610,7 +4705,9 @@ export type PtyGetOutput = {
|
||||
|
||||
export type PtyUpdateInput = {
|
||||
readonly ptyID: { readonly ptyID: string }["ptyID"]
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
readonly title?: {
|
||||
readonly title?: string
|
||||
readonly size?: { readonly rows: number; readonly cols: number }
|
||||
@@ -4625,13 +4722,17 @@ export type PtyUpdateOutput = {
|
||||
|
||||
export type PtyRemoveInput = {
|
||||
readonly ptyID: { readonly ptyID: string }["ptyID"]
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type PtyRemoveOutput = void
|
||||
|
||||
export type ShellListInput = {
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type ShellListOutput = {
|
||||
@@ -4640,7 +4741,9 @@ export type ShellListOutput = {
|
||||
}
|
||||
|
||||
export type ShellCreateInput = {
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
readonly command: {
|
||||
readonly command: string
|
||||
readonly cwd?: string
|
||||
@@ -4674,7 +4777,9 @@ export type ShellCreateOutput = {
|
||||
|
||||
export type ShellGetInput = {
|
||||
readonly id: { readonly id: string }["id"]
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type ShellGetOutput = {
|
||||
@@ -4684,7 +4789,9 @@ export type ShellGetOutput = {
|
||||
|
||||
export type ShellTimeoutInput = {
|
||||
readonly id: { readonly id: string }["id"]
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
readonly timeout: { readonly timeout: number }["timeout"]
|
||||
}
|
||||
|
||||
@@ -4696,17 +4803,17 @@ export type ShellTimeoutOutput = {
|
||||
export type ShellOutputInput = {
|
||||
readonly id: { readonly id: string }["id"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly cursor?: number | undefined
|
||||
readonly limit?: number | undefined
|
||||
}["location"]
|
||||
readonly cursor?: {
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly cursor?: number | undefined
|
||||
readonly limit?: number | undefined
|
||||
}["cursor"]
|
||||
readonly limit?: {
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly cursor?: number | undefined
|
||||
readonly limit?: number | undefined
|
||||
}["limit"]
|
||||
@@ -4719,13 +4826,17 @@ export type ShellOutputOutput = {
|
||||
|
||||
export type ShellRemoveInput = {
|
||||
readonly id: { readonly id: string }["id"]
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type ShellRemoveOutput = void
|
||||
|
||||
export type QuestionRequestListInput = {
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type QuestionRequestListOutput = {
|
||||
@@ -4753,7 +4864,9 @@ export type QuestionRejectInput = {
|
||||
export type QuestionRejectOutput = void
|
||||
|
||||
export type ReferenceListInput = {
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type ReferenceListOutput = {
|
||||
@@ -4763,7 +4876,9 @@ export type ReferenceListOutput = {
|
||||
|
||||
export type ProjectCopyCreateInput = {
|
||||
readonly projectID: { readonly projectID: string }["projectID"]
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
readonly strategy: { readonly strategy: string; readonly directory: string; readonly name?: string }["strategy"]
|
||||
readonly directory: { readonly strategy: string; readonly directory: string; readonly name?: string }["directory"]
|
||||
readonly name?: { readonly strategy: string; readonly directory: string; readonly name?: string }["name"]
|
||||
@@ -4773,7 +4888,9 @@ export type ProjectCopyCreateOutput = ProjectCopyCopy
|
||||
|
||||
export type ProjectCopyRemoveInput = {
|
||||
readonly projectID: { readonly projectID: string }["projectID"]
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
readonly directory: { readonly directory: string; readonly force: boolean }["directory"]
|
||||
readonly force: { readonly directory: string; readonly force: boolean }["force"]
|
||||
}
|
||||
@@ -4782,13 +4899,17 @@ export type ProjectCopyRemoveOutput = void
|
||||
|
||||
export type ProjectCopyRefreshInput = {
|
||||
readonly projectID: { readonly projectID: string }["projectID"]
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type ProjectCopyRefreshOutput = void
|
||||
|
||||
export type VcsGetInput = {
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type VcsGetOutput = {
|
||||
@@ -4797,7 +4918,9 @@ export type VcsGetOutput = {
|
||||
}
|
||||
|
||||
export type VcsStatusInput = {
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type VcsStatusOutput = {
|
||||
@@ -4807,17 +4930,17 @@ export type VcsStatusOutput = {
|
||||
|
||||
export type VcsDiffInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly mode: "working" | "branch"
|
||||
readonly context?: number | undefined
|
||||
}["location"]
|
||||
readonly mode: {
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly mode: "working" | "branch"
|
||||
readonly context?: number | undefined
|
||||
}["mode"]
|
||||
readonly context?: {
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly mode: "working" | "branch"
|
||||
readonly context?: number | undefined
|
||||
}["context"]
|
||||
@@ -4831,13 +4954,17 @@ export type VcsDiffOutput = {
|
||||
export type DebugLocationListOutput = Array<LocationRef>
|
||||
|
||||
export type DebugLocationEvictInput = {
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type DebugLocationEvictOutput = void
|
||||
|
||||
export type WebsearchProvidersInput = {
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type WebsearchProvidersOutput = {
|
||||
@@ -4846,7 +4973,9 @@ export type WebsearchProvidersOutput = {
|
||||
}
|
||||
|
||||
export type WebsearchQueryInput = {
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
readonly query: { readonly query: string; readonly providerID?: string }["query"]
|
||||
readonly providerID?: { readonly query: string; readonly providerID?: string }["providerID"]
|
||||
}
|
||||
|
||||
@@ -58,3 +58,13 @@ export const setMethods = new Set([
|
||||
"isSupersetOf",
|
||||
"isDisjointFrom",
|
||||
])
|
||||
|
||||
export const spreadItems = (value: unknown): Array<unknown> | undefined => {
|
||||
if (Array.isArray(value)) return value
|
||||
if (typeof value === "string") return Array.from(value)
|
||||
if (value instanceof CodeModeMap) return Array.from(value.map.entries(), ([key, item]) => [key, item])
|
||||
if (value instanceof CodeModeSet) return Array.from(value.set.values())
|
||||
if (value instanceof CodeModeURLSearchParams) return Array.from(value.params.entries(), ([key, item]) => [key, item])
|
||||
return undefined
|
||||
}
|
||||
import { CodeModeMap, CodeModeSet, CodeModeURLSearchParams } from "../values.js"
|
||||
|
||||
@@ -1021,12 +1021,13 @@ describe("OpenAPI.fromSpec", () => {
|
||||
|
||||
await Effect.runPromise(
|
||||
location
|
||||
.execute({ location: { directory: "/tmp" } })
|
||||
.execute({ location: { directory: "/tmp", workspace: "workspace-1" } })
|
||||
.pipe(Effect.provide(client.layer)),
|
||||
)
|
||||
|
||||
const url = new URL(client.requests[0]!.url)
|
||||
expect(url.searchParams.get("location[directory]")).toBe("/tmp")
|
||||
expect(url.searchParams.get("location[workspace]")).toBe("workspace-1")
|
||||
})
|
||||
|
||||
test("serializes supported simple and form parameter shapes", async () => {
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
[data-component="desktop-promo"] {
|
||||
--promo-background: hsl(0, 20%, 99%);
|
||||
--promo-background-weak: hsl(0, 8%, 97%);
|
||||
--promo-text: hsl(0, 1%, 39%);
|
||||
--promo-text-strong: hsl(0, 5%, 12%);
|
||||
--promo-border: hsla(0, 100%, 3%, 0.12);
|
||||
|
||||
position: fixed;
|
||||
z-index: 20;
|
||||
right: 1.5rem;
|
||||
bottom: 1.5rem;
|
||||
width: min(28rem, calc(100vw - 2rem));
|
||||
padding: 4px;
|
||||
overflow: hidden;
|
||||
color: var(--promo-text);
|
||||
border: 1px solid var(--promo-border);
|
||||
border-radius: 8px;
|
||||
background: var(--promo-background);
|
||||
box-shadow: 0 0.75rem 2rem rgb(0 0 0 / 15%);
|
||||
font-family: var(--font-mono);
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
--promo-background: hsl(0, 9%, 7%);
|
||||
--promo-background-weak: hsl(0, 6%, 10%);
|
||||
--promo-text: hsl(0, 4%, 71%);
|
||||
--promo-text-strong: hsl(0, 15%, 94%);
|
||||
--promo-border: hsl(0, 4%, 23%);
|
||||
}
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
right: 1rem;
|
||||
bottom: 1rem;
|
||||
}
|
||||
|
||||
[data-slot="desktop-promo-link"] {
|
||||
display: block;
|
||||
color: var(--promo-text);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
[data-slot="desktop-promo-link"]:focus-visible {
|
||||
outline: 2px solid var(--promo-text-strong);
|
||||
outline-offset: -3px;
|
||||
}
|
||||
|
||||
video {
|
||||
display: block;
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
object-fit: cover;
|
||||
border-radius: 4px;
|
||||
background: var(--promo-background-weak);
|
||||
}
|
||||
|
||||
[data-slot="desktop-promo-copy"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.35rem;
|
||||
padding: 1rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
[data-slot="desktop-promo-copy"] strong {
|
||||
color: var(--promo-text-strong);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
[data-slot="desktop-promo-close"] {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 0.5rem;
|
||||
display: grid;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
padding: 0;
|
||||
place-items: center;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 0.25rem;
|
||||
background: rgb(0 0 0 / 70%);
|
||||
opacity: 0;
|
||||
transition:
|
||||
opacity 150ms ease,
|
||||
background 150ms ease;
|
||||
}
|
||||
|
||||
&:hover [data-slot="desktop-promo-close"],
|
||||
[data-slot="desktop-promo-close"]:focus-visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
[data-slot="desktop-promo-close"]:hover {
|
||||
background: rgb(0 0 0 / 90%);
|
||||
}
|
||||
|
||||
@media (hover: none) {
|
||||
[data-slot="desktop-promo-close"] {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import "./desktop-promo.css"
|
||||
import { A, useLocation } from "@solidjs/router"
|
||||
import { createSignal, Show } from "solid-js"
|
||||
import { getRequestEvent } from "solid-js/web"
|
||||
import desktopPromoVideo from "~/asset/lander/desktop-tabs-landscape.mp4"
|
||||
import { useI18n } from "~/context/i18n"
|
||||
import { useLanguage } from "~/context/language"
|
||||
import { strip } from "~/lib/language"
|
||||
|
||||
const DISMISSED_COOKIE = "desktop_promo_dismissed"
|
||||
|
||||
export function DesktopPromo() {
|
||||
const i18n = useI18n()
|
||||
const language = useLanguage()
|
||||
const location = useLocation()
|
||||
const request = getRequestEvent()?.request
|
||||
const cookie = request?.headers.get("cookie") ?? (typeof document === "object" ? document.cookie : "")
|
||||
const [visible, setVisible] = createSignal(
|
||||
!cookie.split(";").some((value) => value.trim() === `${DISMISSED_COOKIE}=1`),
|
||||
)
|
||||
const hostname = request ? new URL(request.url).hostname : typeof window === "object" ? window.location.hostname : ""
|
||||
const primaryHost =
|
||||
hostname === "opencode.ai" || hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1"
|
||||
|
||||
return (
|
||||
<Show
|
||||
when={
|
||||
visible() &&
|
||||
primaryHost &&
|
||||
strip(location.pathname) !== "/download" &&
|
||||
!strip(location.pathname).startsWith("/download/")
|
||||
}
|
||||
>
|
||||
<aside data-component="desktop-promo">
|
||||
<A href={language.route("/download")} data-slot="desktop-promo-link">
|
||||
<video src={desktopPromoVideo} autoplay playsinline loop muted preload="metadata" aria-hidden="true" />
|
||||
<span data-slot="desktop-promo-copy">
|
||||
<strong>{i18n.t("home.promo.title")}</strong>
|
||||
<span>
|
||||
{i18n.t("home.promo.body")} {i18n.t("home.promo.cta")}
|
||||
</span>
|
||||
</span>
|
||||
</A>
|
||||
<button
|
||||
type="button"
|
||||
data-slot="desktop-promo-close"
|
||||
onClick={() => {
|
||||
document.cookie = `${DISMISSED_COOKIE}=1; Path=/; Max-Age=31536000; SameSite=Lax`
|
||||
setVisible(false)
|
||||
}}
|
||||
>
|
||||
<span class="sr-only">{i18n.t("home.promo.close")}</span>
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden="true">
|
||||
<path d="M5 5L15 15M15 5L5 15" stroke="currentColor" stroke-width="1.5" />
|
||||
</svg>
|
||||
</button>
|
||||
</aside>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
@@ -19,6 +19,10 @@ export function Span({ children, ...props }: SpanProps) {
|
||||
return React.createElement("span", props, children)
|
||||
}
|
||||
|
||||
export function Wbr({ children, ...props }: WbrProps) {
|
||||
return React.createElement("wbr", props, children)
|
||||
}
|
||||
|
||||
export function Fonts({ assetsUrl }: { assetsUrl: string }) {
|
||||
return (
|
||||
<>
|
||||
@@ -55,3 +59,14 @@ export function Fonts({ assetsUrl }: { assetsUrl: string }) {
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function SplitString({ text, split }: { text: string; split: number }) {
|
||||
const segments: JSX.Element[] = []
|
||||
for (let i = 0; i < text.length; i += split) {
|
||||
segments.push(<>{text.slice(i, i + split)}</>)
|
||||
if (i + split < text.length) {
|
||||
segments.push(<Wbr key={`${i}wbr`} />)
|
||||
}
|
||||
}
|
||||
return <>{segments}</>
|
||||
}
|
||||
|
||||
+110
-2
@@ -1,11 +1,15 @@
|
||||
{
|
||||
"version": "7",
|
||||
"dialect": "sqlite",
|
||||
"id": "6307bc18-2612-47e2-acfc-2a6a68ff28c5",
|
||||
"id": "e43ed7e2-b9fc-4178-beae-3646e4a976e1",
|
||||
"prevIds": [
|
||||
"e43ed7e2-b9fc-4178-beae-3646e4a976e1"
|
||||
"db37a97f-9b5e-4c87-be8b-4feace35136c"
|
||||
],
|
||||
"ddl": [
|
||||
{
|
||||
"name": "workspace",
|
||||
"entityType": "tables"
|
||||
},
|
||||
{
|
||||
"name": "data_migration",
|
||||
"entityType": "tables"
|
||||
@@ -86,6 +90,86 @@
|
||||
"name": "session_share",
|
||||
"entityType": "tables"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": null,
|
||||
"generated": null,
|
||||
"name": "id",
|
||||
"entityType": "columns",
|
||||
"table": "workspace"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": null,
|
||||
"generated": null,
|
||||
"name": "type",
|
||||
"entityType": "columns",
|
||||
"table": "workspace"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "''",
|
||||
"generated": null,
|
||||
"name": "name",
|
||||
"entityType": "columns",
|
||||
"table": "workspace"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": null,
|
||||
"generated": null,
|
||||
"name": "branch",
|
||||
"entityType": "columns",
|
||||
"table": "workspace"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": null,
|
||||
"generated": null,
|
||||
"name": "directory",
|
||||
"entityType": "columns",
|
||||
"table": "workspace"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": null,
|
||||
"generated": null,
|
||||
"name": "extra",
|
||||
"entityType": "columns",
|
||||
"table": "workspace"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": null,
|
||||
"generated": null,
|
||||
"name": "project_id",
|
||||
"entityType": "columns",
|
||||
"table": "workspace"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": null,
|
||||
"generated": null,
|
||||
"name": "time_used",
|
||||
"entityType": "columns",
|
||||
"table": "workspace"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"notNull": false,
|
||||
@@ -1506,6 +1590,21 @@
|
||||
"entityType": "columns",
|
||||
"table": "session_share"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
"project_id"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "CASCADE",
|
||||
"nameExplicit": false,
|
||||
"name": "fk_workspace_project_id_project_id_fk",
|
||||
"entityType": "fks",
|
||||
"table": "workspace"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
"active_account_id"
|
||||
@@ -1716,6 +1815,15 @@
|
||||
"entityType": "pks",
|
||||
"table": "instruction_entry"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
"id"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "workspace_pk",
|
||||
"table": "workspace",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
"name"
|
||||
|
||||
@@ -44,6 +44,7 @@ export type Draft = {
|
||||
|
||||
export interface Interface extends State.Transformable<Draft> {
|
||||
readonly get: (id: ID) => Effect.Effect<Info | undefined>
|
||||
readonly default: () => Effect.Effect<Info | undefined>
|
||||
readonly resolve: (id?: ID | string) => Effect.Effect<Info | undefined>
|
||||
readonly select: (id?: ID | string) => Effect.Effect<Selection>
|
||||
readonly list: () => Effect.Effect<Info[]>
|
||||
@@ -109,6 +110,9 @@ const layer = Layer.effect(
|
||||
get: Effect.fn("Agent.get")(function* (id) {
|
||||
return state.get().agents.get(id)
|
||||
}),
|
||||
default: Effect.fn("Agent.default")(function* () {
|
||||
return selectedDefault()
|
||||
}),
|
||||
resolve: Effect.fn("Agent.resolve")(function* (id) {
|
||||
if (id !== undefined) return state.get().agents.get(ID.make(id))
|
||||
return selectedDefault()
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core"
|
||||
import { ProjectTable } from "../project/sql"
|
||||
import { Project } from "../project"
|
||||
import { Workspace } from "../workspace"
|
||||
|
||||
export const WorkspaceTable = sqliteTable("workspace", {
|
||||
id: text().$type<Workspace.ID>().primaryKey(),
|
||||
type: text().notNull(),
|
||||
name: text().notNull().default(""),
|
||||
branch: text(),
|
||||
directory: text(),
|
||||
extra: text({ mode: "json" }),
|
||||
project_id: text()
|
||||
.$type<Project.ID>()
|
||||
.notNull()
|
||||
.references(() => ProjectTable.id, { onDelete: "cascade" }),
|
||||
time_used: integer()
|
||||
.notNull()
|
||||
.$default(() => Date.now()),
|
||||
})
|
||||
-1
@@ -59,6 +59,5 @@ export const migrations = (
|
||||
import("./migration/20260722170000_canonical_tool_results"),
|
||||
import("./migration/20260729022634_session_fork_boundary"),
|
||||
import("./migration/20260730195856_optional_session_title"),
|
||||
import("./migration/20260805225117_remove_workspace"),
|
||||
])
|
||||
).map((module) => module.default) satisfies DatabaseMigration.Migration[]
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import { Effect } from "effect"
|
||||
import type { DatabaseMigration } from "../migration"
|
||||
|
||||
export default {
|
||||
id: "20260805225117_remove_workspace",
|
||||
up(tx) {
|
||||
return Effect.gen(function* () {
|
||||
yield* tx.run(`DROP TABLE \`workspace\`;`)
|
||||
})
|
||||
},
|
||||
} satisfies DatabaseMigration.Migration
|
||||
@@ -4,6 +4,19 @@ import type { DatabaseMigration } from "./migration"
|
||||
export default {
|
||||
up(tx) {
|
||||
return Effect.gen(function* () {
|
||||
yield* tx.run(`
|
||||
CREATE TABLE \`workspace\` (
|
||||
\`id\` text PRIMARY KEY,
|
||||
\`type\` text NOT NULL,
|
||||
\`name\` text DEFAULT '' NOT NULL,
|
||||
\`branch\` text,
|
||||
\`directory\` text,
|
||||
\`extra\` text,
|
||||
\`project_id\` text NOT NULL,
|
||||
\`time_used\` integer NOT NULL,
|
||||
CONSTRAINT \`fk_workspace_project_id_project_id_fk\` FOREIGN KEY (\`project_id\`) REFERENCES \`project\`(\`id\`) ON DELETE CASCADE
|
||||
);
|
||||
`)
|
||||
yield* tx.run(`
|
||||
CREATE TABLE \`data_migration\` (
|
||||
\`name\` text PRIMARY KEY,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Glob } from "@opencode-ai/util/glob"
|
||||
|
||||
const FOLDERS = new Set([
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
@@ -45,4 +47,21 @@ const FILES = [
|
||||
|
||||
export const PATTERNS = [...FILES, ...FOLDERS, `**/{${Array.from(FOLDERS).join(",")}}/**`]
|
||||
|
||||
export function match(filepath: string, opts?: { extra?: string[]; whitelist?: string[] }) {
|
||||
for (const pattern of opts?.whitelist || []) {
|
||||
if (Glob.match(pattern, filepath)) return false
|
||||
}
|
||||
|
||||
const parts = filepath.split(/[/\\]/)
|
||||
for (const part of parts) {
|
||||
if (FOLDERS.has(part)) return true
|
||||
}
|
||||
|
||||
for (const pattern of [...FILES, ...(opts?.extra || [])]) {
|
||||
if (Glob.match(pattern, filepath)) return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export * as Ignore from "./ignore"
|
||||
|
||||
@@ -10,6 +10,7 @@ const prefixes = {
|
||||
part: "prt",
|
||||
pty: "pty",
|
||||
tool: "tool",
|
||||
workspace: "wrk",
|
||||
} as const
|
||||
|
||||
export function ascending(prefix: keyof typeof prefixes, given?: string) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Credential } from "../credential"
|
||||
import { Bus } from "../bus"
|
||||
import { Form } from "../form"
|
||||
import { Integration } from "../integration"
|
||||
import { IntegrationConnection } from "../integration/connection"
|
||||
import { KeyedMutex } from "../effect/keyed-mutex"
|
||||
import { Location } from "../location"
|
||||
import { waitForAbort } from "@opencode-ai/util/process"
|
||||
@@ -30,6 +31,7 @@ export class ServerInfo extends Schema.Class<ServerInfo>("MCP.ServerInfo")({
|
||||
name: ServerName,
|
||||
status: Status,
|
||||
integrationID: Integration.ID.pipe(Schema.optional),
|
||||
connection: IntegrationConnection.Info.pipe(Schema.optional),
|
||||
}) {}
|
||||
|
||||
export class ServerInstructions extends Schema.Class<ServerInstructions>("MCP.ServerInstructions")({
|
||||
@@ -245,6 +247,14 @@ export const layer = (options?: Options) => Layer.effect(
|
||||
return { name, entry }
|
||||
})
|
||||
|
||||
const info = (name: ServerName, entry: ServerEntry, connection: IntegrationConnection.Info | undefined) =>
|
||||
new ServerInfo({
|
||||
name,
|
||||
status: entry.status,
|
||||
integrationID: entry.integrationID,
|
||||
connection,
|
||||
})
|
||||
|
||||
// Builds the connect-time auth provider for a remote OAuth-integration server. The SDK presents and
|
||||
// refreshes stored tokens, persisting refreshes back to the same credential row. The provider never
|
||||
// opens a browser, so an auth-gated connect ends in UnauthorizedError -> needs_auth rather than a redirect.
|
||||
@@ -593,12 +603,15 @@ export const layer = (options?: Options) => Layer.effect(
|
||||
)
|
||||
return Service.of({
|
||||
servers: Effect.fn("MCP.servers")(function* () {
|
||||
return Array.from(runtime)
|
||||
.toSorted(([a], [b]) => a.localeCompare(b))
|
||||
.map(
|
||||
([name, entry]) =>
|
||||
new ServerInfo({ name, status: entry.status, integrationID: entry.integrationID }),
|
||||
)
|
||||
const entries = Array.from(runtime).toSorted(([a], [b]) => a.localeCompare(b))
|
||||
return yield* Effect.forEach(entries, ([name, entry]) =>
|
||||
Effect.gen(function* () {
|
||||
const connection = entry.integrationID
|
||||
? yield* integration.connection.active(entry.integrationID)
|
||||
: undefined
|
||||
return info(name, entry, connection)
|
||||
}),
|
||||
)
|
||||
}),
|
||||
add: Effect.fn("MCP.add")(function* (server, config) {
|
||||
const name = ServerName.make(server)
|
||||
|
||||
@@ -2,7 +2,6 @@ export * as PluginPromise from "./promise"
|
||||
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
import type { Context, Plugin } from "@opencode-ai/plugin/promise/plugin"
|
||||
import type { SessionHooks, SessionHttp, SessionHttpMiddleware } from "@opencode-ai/plugin/promise/session"
|
||||
import type { Info } from "@opencode-ai/plugin/promise/tool"
|
||||
import { Agent } from "@opencode-ai/schema/agent"
|
||||
import { Integration } from "@opencode-ai/schema/integration"
|
||||
@@ -58,62 +57,6 @@ export function fromPromise(plugin: Plugin) {
|
||||
}),
|
||||
)
|
||||
|
||||
function sessionHook<Name extends keyof SessionHooks>(
|
||||
name: Name,
|
||||
callback: (event: SessionHooks[Name]) => Promise<void> | void,
|
||||
): Promise<Registration>
|
||||
function sessionHook(
|
||||
...registration: {
|
||||
[Name in keyof SessionHooks]: [
|
||||
name: Name,
|
||||
callback: (event: SessionHooks[Name]) => Promise<void> | void,
|
||||
]
|
||||
}[keyof SessionHooks]
|
||||
) {
|
||||
if (registration[0] !== "http")
|
||||
return register(
|
||||
host.session.hook(registration[0], (event) =>
|
||||
Effect.promise(() => Promise.resolve(registration[1](event))),
|
||||
),
|
||||
)
|
||||
return register(
|
||||
host.session.hook("http", (event) => {
|
||||
const middlewares: SessionHttpMiddleware[] = []
|
||||
const output: SessionHttp = {
|
||||
...event,
|
||||
use: (item) => {
|
||||
middlewares.push(item)
|
||||
},
|
||||
}
|
||||
return Effect.promise(() => Promise.resolve(registration[1](output))).pipe(
|
||||
Effect.flatMap(() =>
|
||||
Effect.forEach(
|
||||
middlewares,
|
||||
(item) =>
|
||||
event.use((input, next) =>
|
||||
Effect.tryPromise({
|
||||
try: (signal) => {
|
||||
const inputSignal = AbortSignal.any([signal, input.signal])
|
||||
return Promise.resolve(
|
||||
item(new Request(input, { signal: inputSignal }), (request) => {
|
||||
const requestSignal = AbortSignal.any([signal, request.signal])
|
||||
return Effect.runPromiseWith(
|
||||
context,
|
||||
)(next(new Request(request, { signal: requestSignal })), { signal: requestSignal })
|
||||
}),
|
||||
)
|
||||
},
|
||||
catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))),
|
||||
}),
|
||||
),
|
||||
{ discard: true },
|
||||
),
|
||||
),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
const context2: Context = {
|
||||
app: host.app,
|
||||
options: host.options,
|
||||
@@ -322,7 +265,8 @@ export function fromPromise(plugin: Plugin) {
|
||||
),
|
||||
},
|
||||
session: {
|
||||
hook: sessionHook,
|
||||
hook: (name, callback) =>
|
||||
register(host.session.hook(name, (event) => Effect.promise(() => Promise.resolve(callback(event))))),
|
||||
create: (input) =>
|
||||
run(
|
||||
host.session.create(
|
||||
|
||||
@@ -225,14 +225,14 @@ export const OpenAIPlugin = define({
|
||||
})
|
||||
}
|
||||
})
|
||||
yield* ctx.session.hook("http", (evt) =>
|
||||
evt.use((request, next) => {
|
||||
if (!chatgpt || evt.model.providerID !== Provider.ID.openai) return next(request)
|
||||
const url = new URL(request.url)
|
||||
request.headers.set("originator", "opencode")
|
||||
request.headers.set("session-id", evt.sessionID)
|
||||
if (url.origin !== "https://api.openai.com") return next(request)
|
||||
return next(new Request(`${codexBaseURL}${url.pathname.replace(/^\/v1/, "")}${url.search}`, request))
|
||||
yield* ctx.session.hook("http.request", (evt) =>
|
||||
Effect.sync(() => {
|
||||
if (!chatgpt || evt.model.providerID !== Provider.ID.openai) return
|
||||
const url = new URL(evt.request.url)
|
||||
evt.request.headers.set("originator", "opencode")
|
||||
evt.request.headers.set("session-id", evt.sessionID)
|
||||
if (url.origin !== "https://api.openai.com") return
|
||||
evt.request = new Request(`${codexBaseURL}${url.pathname.replace(/^\/v1/, "")}${url.search}`, evt.request)
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@@ -70,6 +70,11 @@ export class StrategyUnavailableError extends Schema.TaggedErrorClass<StrategyUn
|
||||
{ strategy: StrategyID },
|
||||
) {}
|
||||
|
||||
export class DuplicateStrategyError extends Schema.TaggedErrorClass<DuplicateStrategyError>()(
|
||||
"ProjectCopy.DuplicateStrategyError",
|
||||
{ strategy: StrategyID },
|
||||
) {}
|
||||
|
||||
export type Error =
|
||||
| SourceDirectoryNotFoundError
|
||||
| DestinationExistsError
|
||||
@@ -94,6 +99,7 @@ export interface Strategy {
|
||||
export { Event }
|
||||
|
||||
export interface Interface {
|
||||
readonly register: (strategy: Strategy) => Effect.Effect<void, DuplicateStrategyError>
|
||||
readonly create: (input: CreateInput) => Effect.Effect<Copy, Error>
|
||||
readonly remove: (input: RemoveInput) => Effect.Effect<void, Error>
|
||||
readonly refresh: (input: RefreshInput) => Effect.Effect<RefreshResult, Error>
|
||||
@@ -138,18 +144,29 @@ const layer = Layer.effect(
|
||||
return resolved
|
||||
})
|
||||
|
||||
const strategy = makeGitWorktreeStrategy({ git, canonical })
|
||||
const registry = new Map<StrategyID, Strategy>()
|
||||
|
||||
const register = Effect.fn("ProjectCopy.register")(function* (strategy: Strategy) {
|
||||
if (registry.has(strategy.id)) return yield* new DuplicateStrategyError({ strategy: strategy.id })
|
||||
registry.set(strategy.id, strategy)
|
||||
})
|
||||
|
||||
// Register default strategies
|
||||
yield* register(makeGitWorktreeStrategy({ git, canonical })).pipe(Effect.orDie)
|
||||
|
||||
const strategies = () => Array.from(registry.values())
|
||||
|
||||
const source = Effect.fnUntraced(function* (input: AbsolutePath, projectID: Project.ID) {
|
||||
const sourceDirectory = yield* canonical(input)
|
||||
if ((yield* directories.get({ projectID, directory: sourceDirectory })) === undefined)
|
||||
if (!(yield* directories.contains({ projectID, directory: sourceDirectory })))
|
||||
return yield* new SourceDirectoryNotFoundError({ directory: sourceDirectory })
|
||||
return sourceDirectory
|
||||
})
|
||||
|
||||
const getStrategy = Effect.fnUntraced(function* (id: StrategyID) {
|
||||
if (id !== strategy.id) return yield* new StrategyUnavailableError({ strategy: id })
|
||||
return strategy
|
||||
const found = registry.get(id)
|
||||
if (!found) return yield* new StrategyUnavailableError({ strategy: id })
|
||||
return found
|
||||
})
|
||||
|
||||
const create = Effect.fn("ProjectCopy.create")(function* (input: CreateInput) {
|
||||
@@ -209,18 +226,20 @@ const layer = Layer.effect(
|
||||
const discovered = yield* Effect.forEach(
|
||||
sourceDirectories,
|
||||
(sourceDirectory) =>
|
||||
strategy.list(sourceDirectory).pipe(
|
||||
Effect.catchTag("ProjectCopy.DirectoryUnavailableError", () => Effect.succeed([])),
|
||||
Effect.map((items) =>
|
||||
items.map((item) => ({
|
||||
directory: item.directory,
|
||||
strategy: item.type === "copy" ? strategy.id : undefined,
|
||||
})),
|
||||
Effect.forEach(strategies(), (strategy) =>
|
||||
strategy.list(sourceDirectory).pipe(
|
||||
Effect.catchTag("ProjectCopy.DirectoryUnavailableError", () => Effect.succeed([])),
|
||||
Effect.map((items) =>
|
||||
items.map((item) => ({
|
||||
directory: item.directory,
|
||||
strategy: item.type === "copy" ? strategy.id : undefined,
|
||||
})),
|
||||
),
|
||||
),
|
||||
),
|
||||
{ concurrency: "unbounded" },
|
||||
).pipe(
|
||||
Effect.map((sets) => new Map(sets.flat().map((item) => [item.directory, item] as const)).values().toArray()),
|
||||
Effect.map((sets) => new Map(sets.flat(2).map((item) => [item.directory, item] as const)).values().toArray()),
|
||||
)
|
||||
const removed = checked.filter((item) => !item.exists).map((item) => item.directory)
|
||||
const result = yield* db
|
||||
@@ -252,6 +271,7 @@ const layer = Layer.effect(
|
||||
})
|
||||
|
||||
return Service.of({
|
||||
register,
|
||||
create,
|
||||
remove,
|
||||
refresh,
|
||||
|
||||
@@ -41,6 +41,7 @@ export interface Interface {
|
||||
projectID: ProjectSchema.ID
|
||||
directory: AbsolutePath
|
||||
}) => Effect.Effect<Directory | undefined>
|
||||
readonly contains: (input: { projectID: ProjectSchema.ID; directory: AbsolutePath }) => Effect.Effect<boolean>
|
||||
readonly create: (input: CreateInput, tx?: Transaction) => Effect.Effect<boolean>
|
||||
readonly remove: (input: RemoveInput, tx?: Transaction) => Effect.Effect<boolean>
|
||||
}
|
||||
@@ -98,6 +99,25 @@ const layer = Layer.effect(
|
||||
return rows.map((row) => ({ directory: row.directory, strategy: row.strategy ?? undefined }))
|
||||
})
|
||||
|
||||
const contains = Effect.fn("ProjectDirectories.contains")(function* (input: {
|
||||
projectID: ProjectSchema.ID
|
||||
directory: AbsolutePath
|
||||
}) {
|
||||
return (
|
||||
(yield* db
|
||||
.select({ directory: ProjectDirectoryTable.directory })
|
||||
.from(ProjectDirectoryTable)
|
||||
.where(
|
||||
and(
|
||||
eq(ProjectDirectoryTable.project_id, input.projectID),
|
||||
eq(ProjectDirectoryTable.directory, input.directory),
|
||||
),
|
||||
)
|
||||
.get()
|
||||
.pipe(Effect.orDie)) !== undefined
|
||||
)
|
||||
})
|
||||
|
||||
const get = Effect.fn("ProjectDirectories.get")(function* (input: {
|
||||
projectID: ProjectSchema.ID
|
||||
directory: AbsolutePath
|
||||
@@ -119,6 +139,7 @@ const layer = Layer.effect(
|
||||
return Service.of({
|
||||
list,
|
||||
get,
|
||||
contains,
|
||||
create,
|
||||
remove,
|
||||
})
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export * as PtyTicket from "./ticket"
|
||||
|
||||
import { Workspace } from "../workspace"
|
||||
import { PtyTicket } from "@opencode-ai/schema/pty-ticket"
|
||||
import { PtyID } from "./schema"
|
||||
import { Cache, Context, Duration, Effect, Layer } from "effect"
|
||||
@@ -13,6 +14,7 @@ export const ConnectToken = PtyTicket.ConnectToken
|
||||
export type Scope = {
|
||||
readonly ptyID: PtyID
|
||||
readonly directory?: string
|
||||
readonly workspaceID?: Workspace.ID
|
||||
}
|
||||
|
||||
export interface Interface {
|
||||
@@ -23,7 +25,9 @@ export interface Interface {
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/PtyTicket") {}
|
||||
|
||||
function matches(record: Scope, input: Scope) {
|
||||
return record.ptyID === input.ptyID && record.directory === input.directory
|
||||
return (
|
||||
record.ptyID === input.ptyID && record.directory === input.directory && record.workspaceID === input.workspaceID
|
||||
)
|
||||
}
|
||||
|
||||
// Tickets are inserted via Cache.set and removed atomically via invalidateWhen. The lookup is
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Effect, Layer, Schema, Context, Stream, Scope } from "effect"
|
||||
import { ListAnchor } from "@opencode-ai/schema/session"
|
||||
import { and, asc, desc, eq, gt, isNotNull, isNull, like, lt, ne, or, type SQL } from "drizzle-orm"
|
||||
import { Project } from "./project"
|
||||
import { Workspace } from "./workspace"
|
||||
import { Model } from "./model"
|
||||
import { Location } from "./location"
|
||||
import { SessionMessage } from "./session/message"
|
||||
@@ -51,6 +52,9 @@ import { Shell as ShellSchema } from "@opencode-ai/schema/shell"
|
||||
import { KeyedMutex } from "./effect/keyed-mutex"
|
||||
import { fileURLToPath } from "url"
|
||||
|
||||
export const RevertState = Session.Revert
|
||||
export type RevertState = Session.Revert
|
||||
|
||||
// get project -> project.locations
|
||||
//
|
||||
// get all sessions
|
||||
@@ -58,9 +62,12 @@ import { fileURLToPath } from "url"
|
||||
|
||||
// - by project
|
||||
// - by subpath
|
||||
// - by workspace (home is special)
|
||||
|
||||
export { ListAnchor }
|
||||
|
||||
const ListInputBase = {
|
||||
workspaceID: Workspace.ID.pipe(Schema.optional),
|
||||
search: Schema.String.pipe(Schema.optional),
|
||||
limit: PositiveInt.pipe(Schema.optional),
|
||||
order: Schema.Literals(["asc", "desc"]).pipe(Schema.optional),
|
||||
@@ -103,6 +110,13 @@ type ForkInput = {
|
||||
boundary: Session.ForkRequestBoundary
|
||||
}
|
||||
|
||||
export class OperationUnavailableError extends Schema.TaggedErrorClass<OperationUnavailableError>()(
|
||||
"Session.OperationUnavailableError",
|
||||
{
|
||||
operation: Schema.Literals(["move", "skill", "switchAgent", "compact"]),
|
||||
},
|
||||
) {}
|
||||
|
||||
export { MessageDecodeError, NotFoundError }
|
||||
|
||||
export class PromptConflictError extends Schema.TaggedErrorClass<PromptConflictError>()("Session.PromptConflictError", {
|
||||
@@ -146,6 +160,23 @@ export class DestinationNotDirectoryError extends Schema.TaggedErrorClass<Destin
|
||||
export const MessageNotFoundError = SessionRevert.MessageNotFoundError
|
||||
export type MessageNotFoundError = SessionRevert.MessageNotFoundError
|
||||
|
||||
export type Error =
|
||||
| NotFoundError
|
||||
| MessageDecodeError
|
||||
| OperationUnavailableError
|
||||
| PromptConflictError
|
||||
| SyntheticConflictError
|
||||
| AttachmentError
|
||||
| CompactionConflictError
|
||||
| BusyError
|
||||
| SkillNotFoundError
|
||||
| DestinationNotFoundError
|
||||
| DestinationNotDirectoryError
|
||||
| Command.NotFoundError
|
||||
| Command.EvaluationError
|
||||
| MessageNotFoundError
|
||||
| SessionGenerate.Error
|
||||
|
||||
export interface Interface {
|
||||
readonly list: (input?: ListInput) => Effect.Effect<{
|
||||
readonly data: SessionSchema.Info[]
|
||||
@@ -202,6 +233,7 @@ export interface Interface {
|
||||
readonly move: (input: {
|
||||
sessionID: SessionSchema.ID
|
||||
directory: AbsolutePath
|
||||
workspaceID?: Location.Ref["workspaceID"]
|
||||
}) => Effect.Effect<void, NotFoundError | DestinationNotFoundError | DestinationNotDirectoryError>
|
||||
readonly prompt: (input: {
|
||||
id?: SessionMessage.ID
|
||||
@@ -341,6 +373,7 @@ const layer = Layer.effect(
|
||||
parentID: input.parentID,
|
||||
directory: location.directory,
|
||||
path: path.relative(project.directory, location.directory).replaceAll("\\", "/"),
|
||||
workspaceID: location.workspaceID ? Workspace.ID.make(location.workspaceID) : undefined,
|
||||
title: input.title,
|
||||
agent: input.agent,
|
||||
model: input.model
|
||||
@@ -428,6 +461,7 @@ const layer = Layer.effect(
|
||||
const sortColumn = SessionTable.time_updated
|
||||
const conditions: SQL[] = []
|
||||
if ("directory" in input) conditions.push(eq(SessionTable.directory, input.directory))
|
||||
if (input.workspaceID) conditions.push(eq(SessionTable.workspace_id, input.workspaceID))
|
||||
if ("project" in input) conditions.push(eq(SessionTable.project_id, input.project))
|
||||
if ("project" in input && input.subpath !== undefined) conditions.push(eq(SessionTable.path, input.subpath))
|
||||
if (input.search) conditions.push(like(SessionTable.title, `%${input.search}%`))
|
||||
@@ -705,7 +739,11 @@ const layer = Layer.effect(
|
||||
const info = yield* fs.stat(directory).pipe(Effect.catch(() => Effect.succeed(undefined)))
|
||||
if (!info) return yield* new DestinationNotFoundError({ directory })
|
||||
if (info.type !== "Directory") return yield* new DestinationNotDirectoryError({ directory })
|
||||
if (current.location.directory === directory) return
|
||||
if (
|
||||
current.location.directory === directory &&
|
||||
current.location.workspaceID === input.workspaceID
|
||||
)
|
||||
return
|
||||
const project = yield* projects.resolve(directory)
|
||||
yield* persistProject(project)
|
||||
if ((yield* execution.active).has(input.sessionID)) {
|
||||
@@ -714,7 +752,7 @@ const layer = Layer.effect(
|
||||
}
|
||||
yield* bus.publish(SessionEvent.Moved, {
|
||||
sessionID: input.sessionID,
|
||||
location: Location.Ref.make({ directory }),
|
||||
location: Location.Ref.make({ directory, workspaceID: input.workspaceID }),
|
||||
projectID: project.id,
|
||||
subpath: RelativePath.make(path.relative(project.directory, directory).replaceAll("\\", "/")),
|
||||
})
|
||||
|
||||
@@ -2,7 +2,6 @@ export * as SessionModelRequest from "./model-request"
|
||||
|
||||
import { LLM, Message, SystemPart, type LLMRequest } from "@opencode-ai/ai"
|
||||
import type { StreamOptions } from "@opencode-ai/ai/route"
|
||||
import type { SessionHttpHandler, SessionHttpMiddleware } from "@opencode-ai/plugin/effect/session"
|
||||
import type { Content } from "@opencode-ai/schema/tool"
|
||||
import { SessionError } from "@opencode-ai/schema/session-error"
|
||||
import { Cause, Config, Context, Effect, Layer, Result, Stream } from "effect"
|
||||
@@ -230,44 +229,31 @@ export const layer = Layer.effect(
|
||||
const options: StreamOptions = {
|
||||
http: (request, handler) =>
|
||||
Effect.gen(function* () {
|
||||
let latest = request
|
||||
const origins = new WeakMap<Response, HttpClientRequest.HttpClientRequest>()
|
||||
const middlewares: SessionHttpMiddleware[] = []
|
||||
const web = yield* HttpClientRequest.toWeb(request)
|
||||
yield* hooks.trigger("session", "http", {
|
||||
const before = yield* hooks.trigger("session", "http.request", {
|
||||
sessionID: session.id,
|
||||
agent: agent.id,
|
||||
model: resolved.ref,
|
||||
use: (item) =>
|
||||
Effect.sync(() => {
|
||||
middlewares.push(item)
|
||||
}),
|
||||
request: yield* HttpClientRequest.toWeb(request),
|
||||
})
|
||||
const send = (input: Request) =>
|
||||
Effect.gen(function* () {
|
||||
let sent = HttpClientRequest.fromWeb(input)
|
||||
if (input.body)
|
||||
sent = HttpClientRequest.bodyUint8Array(
|
||||
sent,
|
||||
new Uint8Array(yield* Effect.promise(() => input.clone().arrayBuffer())),
|
||||
input.headers.get("content-type") ?? undefined,
|
||||
)
|
||||
latest = sent
|
||||
const response = yield* handler(sent)
|
||||
const body = [204, 205, 304].includes(response.status)
|
||||
? null
|
||||
: yield* Stream.toReadableStreamEffect(response.stream)
|
||||
const output = new Response(body, { status: response.status, headers: response.headers })
|
||||
origins.set(output, sent)
|
||||
return output
|
||||
})
|
||||
const dispatch = middlewares.reduce<SessionHttpHandler>(
|
||||
(next, item) => (input: Request) => item(input, next),
|
||||
send,
|
||||
)
|
||||
const response = yield* dispatch(web)
|
||||
const origin = origins.get(response) ?? latest
|
||||
return HttpClientResponse.fromWeb(origin, response)
|
||||
let sent = HttpClientRequest.fromWeb(before.request)
|
||||
if (before.request.body)
|
||||
sent = HttpClientRequest.bodyUint8Array(
|
||||
sent,
|
||||
new Uint8Array(yield* Effect.promise(() => before.request.clone().arrayBuffer())),
|
||||
before.request.headers.get("content-type") ?? undefined,
|
||||
)
|
||||
const response = yield* handler(sent)
|
||||
const after = yield* hooks.trigger("session", "http.response", {
|
||||
sessionID: session.id,
|
||||
agent: agent.id,
|
||||
model: resolved.ref,
|
||||
request: before.request,
|
||||
response: new Response(
|
||||
[204, 205, 304].includes(response.status) ? null : yield* Stream.toReadableStreamEffect(response.stream),
|
||||
{ status: response.status, headers: response.headers },
|
||||
),
|
||||
})
|
||||
return HttpClientResponse.fromWeb(sent, after.response)
|
||||
}).pipe(Effect.mapError((cause) => (cause instanceof Error ? cause : new Error(String(cause))))),
|
||||
}
|
||||
if (promptCacheSnapshots) {
|
||||
|
||||
@@ -8,9 +8,11 @@ import { makeGlobalNode } from "@opencode-ai/util/effect/app-node"
|
||||
import { Model } from "../model"
|
||||
import { SessionEvent } from "./event"
|
||||
import { SessionV1 } from "../v1/session"
|
||||
import { WorkspaceTable } from "../control-plane/workspace.sql"
|
||||
import { SessionMessage } from "./message"
|
||||
import { SessionMessageUpdater } from "./message-updater"
|
||||
import { SessionPending } from "./pending"
|
||||
import { Workspace } from "../workspace"
|
||||
import { InstructionState } from "./instruction-state"
|
||||
import { MessageTable, PartTable, SessionPendingTable, SessionMessageTable, SessionTable } from "./sql"
|
||||
import type { DeepMutable } from "../schema"
|
||||
@@ -57,7 +59,7 @@ function sessionRow(info: SessionV1.SessionInfo): typeof SessionTable.$inferInse
|
||||
return {
|
||||
id: info.id,
|
||||
project_id: info.projectID,
|
||||
workspace_id: null,
|
||||
workspace_id: info.workspaceID ?? null,
|
||||
parent_id: info.parentID,
|
||||
slug: info.slug,
|
||||
directory: info.directory,
|
||||
@@ -427,6 +429,14 @@ const layer = Layer.effectDiscard(
|
||||
.get()
|
||||
.pipe(Effect.orDie)
|
||||
if (!stored) return yield* Effect.die(new SessionAlreadyProjected())
|
||||
if (event.data.info.workspaceID) {
|
||||
yield* db
|
||||
.update(WorkspaceTable)
|
||||
.set({ time_used: Date.now() })
|
||||
.where(eq(WorkspaceTable.id, event.data.info.workspaceID))
|
||||
.run()
|
||||
.pipe(Effect.orDie)
|
||||
}
|
||||
}),
|
||||
)
|
||||
yield* bus.project(SessionV1.Event.Updated, (event) =>
|
||||
@@ -445,7 +455,7 @@ const layer = Layer.effectDiscard(
|
||||
directory: event.data.location.directory,
|
||||
path: event.data.subpath,
|
||||
...(event.data.projectID ? { project_id: event.data.projectID } : {}),
|
||||
workspace_id: null,
|
||||
workspace_id: event.data.location.workspaceID ? Workspace.ID.make(event.data.location.workspaceID) : null,
|
||||
time_updated: DateTime.toEpochMillis(event.created),
|
||||
})
|
||||
.where(eq(SessionTable.id, event.data.sessionID))
|
||||
|
||||
@@ -7,16 +7,16 @@ import { Schema } from "effect"
|
||||
import { FSUtil } from "@opencode-ai/util/fs-util"
|
||||
import { which } from "../util/which"
|
||||
|
||||
const META: Record<string, { deny?: boolean; login?: boolean; ps?: boolean }> = {
|
||||
bash: { login: true },
|
||||
dash: { login: true },
|
||||
const META: Record<string, { deny?: boolean; login?: boolean; posix?: boolean; ps?: boolean }> = {
|
||||
bash: { login: true, posix: true },
|
||||
dash: { login: true, posix: true },
|
||||
fish: { deny: true, login: true },
|
||||
ksh: { login: true },
|
||||
ksh: { login: true, posix: true },
|
||||
nu: { deny: true },
|
||||
powershell: { ps: true },
|
||||
pwsh: { ps: true },
|
||||
sh: { login: true },
|
||||
zsh: { login: true },
|
||||
sh: { login: true, posix: true },
|
||||
zsh: { login: true, posix: true },
|
||||
}
|
||||
|
||||
export type Item = {
|
||||
@@ -116,6 +116,10 @@ export function login(file: string) {
|
||||
return meta(file)?.login === true
|
||||
}
|
||||
|
||||
export function posix(file: string) {
|
||||
return meta(file)?.posix === true
|
||||
}
|
||||
|
||||
export function ps(file: string) {
|
||||
return meta(file)?.ps === true
|
||||
}
|
||||
|
||||
@@ -967,6 +967,9 @@ describe("DatabaseMigration", () => {
|
||||
yield* db.run(
|
||||
sql`INSERT INTO project (id, worktree, time_created, time_updated, sandboxes) VALUES ('global', '/project', 1, 1, '[]')`,
|
||||
)
|
||||
yield* db.run(
|
||||
sql`INSERT INTO workspace (id, type, project_id, time_used) VALUES ('workspace', 'local', 'global', 1)`,
|
||||
)
|
||||
yield* db.run(
|
||||
sql`INSERT INTO session (id, project_id, workspace_id, slug, directory, title, version, time_created, time_updated) VALUES ('session', 'global', 'workspace', 'session', '/project', 'Before', 'test', 1, 1)`,
|
||||
)
|
||||
@@ -991,7 +994,6 @@ describe("DatabaseMigration", () => {
|
||||
// must drop before the historical rename dance and recreate after.
|
||||
yield* db.run(sql`DROP INDEX session_pending_session_compaction_idx`)
|
||||
yield* db.run(sql`ALTER TABLE session_pending RENAME TO session_input`)
|
||||
yield* db.run(sql`CREATE TABLE workspace (id text PRIMARY KEY)`)
|
||||
yield* db.run(sql`DELETE FROM migration WHERE id = ${simplifySessionPendingMigration.id}`)
|
||||
yield* DatabaseMigration.applyOnly(db, [simplifySessionPendingMigration])
|
||||
yield* db.run(sql`DROP TABLE session_context_epoch`)
|
||||
@@ -1027,6 +1029,7 @@ describe("DatabaseMigration", () => {
|
||||
(SELECT workspace_id FROM session WHERE id = 'session') AS workspaceID,
|
||||
(SELECT COUNT(*) FROM message WHERE id = 'message') AS messages,
|
||||
(SELECT COUNT(*) FROM part WHERE id = 'part') AS parts,
|
||||
(SELECT COUNT(*) FROM workspace) AS workspaces,
|
||||
(SELECT COUNT(*) FROM session_pending) AS sessionInputs,
|
||||
(SELECT COUNT(*) FROM session_message) AS sessionMessages,
|
||||
(SELECT COUNT(*) FROM instruction_state) AS instructionStates,
|
||||
@@ -1038,6 +1041,7 @@ describe("DatabaseMigration", () => {
|
||||
workspaceID: null,
|
||||
messages: 1,
|
||||
parts: 1,
|
||||
workspaces: 0,
|
||||
sessionInputs: 0,
|
||||
sessionMessages: 0,
|
||||
instructionStates: 0,
|
||||
|
||||
@@ -3,6 +3,14 @@ import { Ignore } from "@opencode-ai/core/filesystem/ignore"
|
||||
// @ts-ignore
|
||||
import { createWrapper } from "@parcel/watcher/wrapper"
|
||||
|
||||
test("match nested and non-nested", () => {
|
||||
expect(Ignore.match("node_modules/index.js")).toBe(true)
|
||||
expect(Ignore.match("node_modules")).toBe(true)
|
||||
expect(Ignore.match("node_modules/")).toBe(true)
|
||||
expect(Ignore.match("node_modules/bar")).toBe(true)
|
||||
expect(Ignore.match("node_modules/bar/")).toBe(true)
|
||||
})
|
||||
|
||||
test("parcel patterns ignore built-in folders at any depth", async () => {
|
||||
let ignoreGlobs: string[] = []
|
||||
const watcher = createWrapper({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect } from "bun:test"
|
||||
import { Message, SystemPart } from "@opencode-ai/ai"
|
||||
import { DateTime, Deferred, Effect, Fiber, Schema } from "effect"
|
||||
import { DateTime, Effect, Schema } from "effect"
|
||||
import { Agent } from "@opencode-ai/core/agent"
|
||||
import { Catalog } from "@opencode-ai/core/catalog"
|
||||
import { Model } from "@opencode-ai/core/model"
|
||||
@@ -15,7 +15,7 @@ import { SessionPending } from "@opencode-ai/core/session/pending"
|
||||
import { Tool } from "@opencode-ai/core/tool"
|
||||
import { Provider } from "@opencode-ai/core/provider"
|
||||
import { define } from "@opencode-ai/plugin/promise/plugin"
|
||||
import type { SessionHooks, SessionHttpHandler } from "@opencode-ai/plugin/effect/session"
|
||||
import type { SessionHooks } from "@opencode-ai/plugin/effect/session"
|
||||
import { testEffect } from "../lib/effect"
|
||||
import { PluginTestLayer } from "./fixture"
|
||||
import { host as testHost } from "./host"
|
||||
@@ -223,102 +223,45 @@ describe("fromPromise", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("adapts promise session HTTP hooks", () =>
|
||||
it.effect("adapts promise session HTTP request and response hooks", () =>
|
||||
Effect.gen(function* () {
|
||||
const plugin = yield* Plugin.Service
|
||||
const hooks = yield* PluginHooks.Service
|
||||
const host = yield* PluginHost.make(plugin)
|
||||
const bodies: string[] = []
|
||||
yield* PluginPromise.fromPromise(
|
||||
define({
|
||||
id: "promise-session-http",
|
||||
setup: async (ctx) => {
|
||||
await ctx.session.hook("http", (event) => {
|
||||
event.use(async (request, next) => {
|
||||
request.headers.set("x-hook", "promise")
|
||||
await next(request)
|
||||
const response = await next(request)
|
||||
return new Response(`${await response.text()}-response`)
|
||||
})
|
||||
await ctx.session.hook("http.request", (event) => {
|
||||
event.request = new Request("https://provider.test/changed", event.request)
|
||||
event.request.headers.set("x-hook", "promise")
|
||||
})
|
||||
await ctx.session.hook("http", (event) => {
|
||||
event.use(async (request, next) => {
|
||||
const response = await next(request)
|
||||
return new Response(`${await response.text()}-outer`)
|
||||
await ctx.session.hook("http.response", async (event) => {
|
||||
event.response = new Response(`${await event.response.text()}-response`, {
|
||||
status: event.response.status,
|
||||
})
|
||||
})
|
||||
},
|
||||
}),
|
||||
).effect(host)
|
||||
const middlewares: Parameters<PluginHooks.Domains["session"]["http"]["use"]>[0][] = []
|
||||
const event: PluginHooks.Domains["session"]["http"] = {
|
||||
const context = {
|
||||
sessionID: Session.ID.make("ses_promise_session_http"),
|
||||
agent: Agent.ID.make("build"),
|
||||
model: Model.Ref.make({ providerID: Provider.ID.make("test"), id: Model.ID.make("model") }),
|
||||
use: (item) =>
|
||||
Effect.sync(() => {
|
||||
middlewares.push(item)
|
||||
}),
|
||||
}
|
||||
|
||||
yield* hooks.trigger("session", "http", event)
|
||||
const request = middlewares.reduce<SessionHttpHandler>(
|
||||
(next, item) => (input: Request) => item(input, next),
|
||||
(input: Request) =>
|
||||
Effect.promise(() => input.text()).pipe(
|
||||
Effect.tap((body) => Effect.sync(() => bodies.push(body))),
|
||||
Effect.as(new Response(input.headers.get("x-hook") ?? "missing")),
|
||||
),
|
||||
)
|
||||
const response = yield* request(new Request("https://provider.test", { method: "POST", body: "payload" }))
|
||||
const request = yield* hooks.trigger("session", "http.request", {
|
||||
...context,
|
||||
request: new Request("https://provider.test", { method: "POST", body: "payload" }),
|
||||
})
|
||||
const response = yield* hooks.trigger("session", "http.response", {
|
||||
...context,
|
||||
request: request.request,
|
||||
response: new Response(request.request.headers.get("x-hook") ?? "missing"),
|
||||
})
|
||||
|
||||
expect(bodies).toEqual(["payload", "payload"])
|
||||
expect(yield* Effect.promise(() => response.text())).toBe("promise-response-outer")
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("interrupts the Effect request through a promise session HTTP hook", () =>
|
||||
Effect.gen(function* () {
|
||||
const plugin = yield* Plugin.Service
|
||||
const hooks = yield* PluginHooks.Service
|
||||
const host = yield* PluginHost.make(plugin)
|
||||
yield* PluginPromise.fromPromise(
|
||||
define({
|
||||
id: "promise-session-http-interrupt",
|
||||
setup: async (ctx) => {
|
||||
await ctx.session.hook("http", (event) => {
|
||||
event.use((request, next) => next(request))
|
||||
})
|
||||
},
|
||||
}),
|
||||
).effect(host)
|
||||
const started = yield* Deferred.make<void>()
|
||||
const interrupted = yield* Deferred.make<void>()
|
||||
const middlewares: Parameters<PluginHooks.Domains["session"]["http"]["use"]>[0][] = []
|
||||
const event: PluginHooks.Domains["session"]["http"] = {
|
||||
sessionID: Session.ID.make("ses_promise_session_http_interrupt"),
|
||||
agent: Agent.ID.make("build"),
|
||||
model: Model.Ref.make({ providerID: Provider.ID.make("test"), id: Model.ID.make("model") }),
|
||||
use: (item) =>
|
||||
Effect.sync(() => {
|
||||
middlewares.push(item)
|
||||
}),
|
||||
}
|
||||
|
||||
yield* hooks.trigger("session", "http", event)
|
||||
const request = middlewares.reduce<SessionHttpHandler>(
|
||||
(next, item) => (input: Request) => item(input, next),
|
||||
() =>
|
||||
Deferred.succeed(started, undefined).pipe(
|
||||
Effect.andThen(Effect.never),
|
||||
Effect.onInterrupt(() => Deferred.succeed(interrupted, undefined)),
|
||||
),
|
||||
)
|
||||
const fiber = yield* request(new Request("https://provider.test")).pipe(Effect.forkChild)
|
||||
yield* Deferred.await(started)
|
||||
yield* Fiber.interrupt(fiber)
|
||||
|
||||
expect(yield* Deferred.isDone(interrupted)).toBeTrue()
|
||||
expect(request.request.url).toBe("https://provider.test/changed")
|
||||
expect(yield* Effect.promise(() => response.response.text())).toBe("promise-response")
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import { PluginHost } from "@opencode-ai/core/plugin/host"
|
||||
import { PluginHooks } from "@opencode-ai/core/plugin/hooks"
|
||||
import { OpenAIPlugin } from "@opencode-ai/core/plugin/provider/openai"
|
||||
import { Provider } from "@opencode-ai/core/provider"
|
||||
import type { SessionHttpHandler } from "@opencode-ai/plugin/effect/session"
|
||||
import { testEffect } from "../lib/effect"
|
||||
import { PluginTestLayer } from "./fixture"
|
||||
|
||||
@@ -31,26 +30,13 @@ function required<T>(value: T | undefined): T {
|
||||
}
|
||||
|
||||
const http = Effect.fn(function* (providerID: Provider.ID, url: string) {
|
||||
const middlewares: Parameters<PluginHooks.Domains["session"]["http"]["use"]>[0][] = []
|
||||
yield* (yield* PluginHooks.Service).trigger("session", "http", {
|
||||
const event = yield* (yield* PluginHooks.Service).trigger("session", "http.request", {
|
||||
sessionID: Session.ID.make("ses_test"),
|
||||
agent: Agent.ID.make("build"),
|
||||
model: Model.Ref.make({ providerID, id: Model.ID.make("gpt-5.5") }),
|
||||
use: (item) =>
|
||||
Effect.sync(() => {
|
||||
middlewares.push(item)
|
||||
}),
|
||||
request: new Request(url, { method: "POST", body: "{}" }),
|
||||
})
|
||||
const request = middlewares.reduce<SessionHttpHandler>(
|
||||
(next, item) => (input: Request) => item(input, next),
|
||||
(input: Request) => {
|
||||
const headers = new Headers(input.headers)
|
||||
headers.set("x-seen-url", input.url)
|
||||
return Effect.succeed(new Response(null, { headers }))
|
||||
},
|
||||
)
|
||||
const response = yield* request(new Request(url, { method: "POST", body: "{}" }))
|
||||
return { url: response.headers.get("x-seen-url"), headers: Object.fromEntries(response.headers.entries()) }
|
||||
return { url: event.request.url, headers: Object.fromEntries(event.request.headers.entries()) }
|
||||
})
|
||||
|
||||
describe("OpenAIPlugin", () => {
|
||||
|
||||
@@ -83,10 +83,20 @@ describe("ProjectCopy", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("reports unavailable strategy ids", () =>
|
||||
it.effect("rejects duplicate strategies and reports unavailable ids", () =>
|
||||
Effect.gen(function* () {
|
||||
const input = yield* setup()
|
||||
const copy = yield* ProjectCopy.Service
|
||||
const strategy: ProjectCopy.Strategy = {
|
||||
id: ProjectCopy.StrategyID.make("test/duplicate"),
|
||||
create: () => Effect.die("unused"),
|
||||
remove: () => Effect.die("unused"),
|
||||
list: () => Effect.succeed([]),
|
||||
}
|
||||
|
||||
yield* copy.register(strategy)
|
||||
expect(yield* copy.register(strategy).pipe(Effect.flip)).toBeInstanceOf(ProjectCopy.DuplicateStrategyError)
|
||||
|
||||
const unavailable = ProjectCopy.StrategyID.make("acme/missing")
|
||||
const error = yield* copy
|
||||
.create({
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Effect, Layer } from "effect"
|
||||
import { LayerNode } from "@opencode-ai/util/effect/layer-node"
|
||||
import { PtyID } from "@opencode-ai/core/pty/schema"
|
||||
import { PtyTicket } from "@opencode-ai/core/pty/ticket"
|
||||
import { Workspace } from "@opencode-ai/core/workspace"
|
||||
import { testEffect } from "../lib/effect"
|
||||
|
||||
const it = testEffect(LayerNode.compile(PtyTicket.node))
|
||||
@@ -45,14 +46,17 @@ describe("PTY websocket tickets", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("rejects tickets scoped to a different pty", () =>
|
||||
it.live("rejects tickets scoped to a different workspace", () =>
|
||||
Effect.gen(function* () {
|
||||
const tickets = yield* PtyTicket.Service
|
||||
const ptyID = PtyID.ascending()
|
||||
const issued = yield* tickets.issue({ ptyID })
|
||||
const workspaceID = Workspace.ID.ascending()
|
||||
const issued = yield* tickets.issue({ ptyID, workspaceID })
|
||||
|
||||
expect(yield* tickets.consume({ ptyID: PtyID.ascending(), ticket: issued.ticket })).toBe(false)
|
||||
expect(yield* tickets.consume({ ptyID, ticket: issued.ticket })).toBe(true)
|
||||
expect(yield* tickets.consume({ ptyID, workspaceID: Workspace.ID.ascending(), ticket: issued.ticket })).toBe(
|
||||
false,
|
||||
)
|
||||
expect(yield* tickets.consume({ ptyID, workspaceID, ticket: issued.ticket })).toBe(true)
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -24,6 +24,7 @@ import { SessionPending } from "@opencode-ai/core/session/pending"
|
||||
import { SessionEvent } from "@opencode-ai/core/session/event"
|
||||
import { SessionTable } from "@opencode-ai/core/session/sql"
|
||||
import { SessionStore } from "@opencode-ai/core/session/store"
|
||||
import { Workspace } from "@opencode-ai/core/workspace"
|
||||
import { testEffect } from "./lib/effect"
|
||||
import { tmpdir } from "./fixture/tmpdir"
|
||||
|
||||
@@ -118,6 +119,7 @@ describe("Session.create", () => {
|
||||
it.effect("stores supplied immutable create attributes", () =>
|
||||
Effect.gen(function* () {
|
||||
const session = yield* Session.Service
|
||||
const workspaceID = Workspace.ID.make("wrk_test")
|
||||
const model = Model.Ref.make({
|
||||
id: Model.ID.make("sonnet"),
|
||||
providerID: Provider.ID.anthropic,
|
||||
@@ -126,11 +128,11 @@ describe("Session.create", () => {
|
||||
|
||||
expect(
|
||||
yield* session.create({
|
||||
location: Location.Ref.make({ directory: location.directory }),
|
||||
location: Location.Ref.make({ directory: location.directory, workspaceID }),
|
||||
agent: Agent.ID.make("build"),
|
||||
model,
|
||||
}),
|
||||
).toMatchObject({ location: { directory: location.directory }, agent: "build", model })
|
||||
).toMatchObject({ location: { directory: location.directory, workspaceID }, agent: "build", model })
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@@ -254,6 +254,7 @@ describe("SessionRunnerLLM recorded", () => {
|
||||
describe("SessionModelRequest HTTP bridge", () => {
|
||||
const bodies: Uint8Array[] = []
|
||||
const methods: string[] = []
|
||||
const headers: Array<string | undefined> = []
|
||||
const response = [
|
||||
'data: {"id":"chatcmpl_test","object":"chat.completion.chunk","created":0,"model":"gpt-4o-mini","choices":[{"index":0,"delta":{"role":"assistant","content":"Hello!"},"finish_reason":null}]}',
|
||||
'data: {"id":"chatcmpl_test","object":"chat.completion.chunk","created":0,"model":"gpt-4o-mini","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}',
|
||||
@@ -267,6 +268,7 @@ describe("SessionModelRequest HTTP bridge", () => {
|
||||
if (request.body._tag !== "Uint8Array") throw new Error(`Unexpected request body: ${request.body._tag}`)
|
||||
methods.push(request.method)
|
||||
bodies.push(request.body.body.slice())
|
||||
headers.push(request.headers["x-hook"])
|
||||
return HttpClientResponse.fromWeb(
|
||||
request,
|
||||
new Response(response, { headers: { "content-type": "text/event-stream" } }),
|
||||
@@ -274,14 +276,16 @@ describe("SessionModelRequest HTTP bridge", () => {
|
||||
}),
|
||||
),
|
||||
)
|
||||
const retryIt = testEffect(
|
||||
const httpIt = testEffect(
|
||||
testLayer(LLMClient.layer.pipe(Layer.provide(RequestExecutor.layer.pipe(Layer.provide(transport))))),
|
||||
)
|
||||
|
||||
retryIt.effect("lets an Effect plugin send the same POST Request twice", () =>
|
||||
httpIt.effect("runs Effect HTTP request and response hooks around one provider request", () =>
|
||||
Effect.gen(function* () {
|
||||
bodies.length = 0
|
||||
methods.length = 0
|
||||
headers.length = 0
|
||||
const seen: string[] = []
|
||||
const agents = yield* Agent.Service
|
||||
const catalog = yield* Catalog.Service
|
||||
const hooks = yield* PluginHooks.Service
|
||||
@@ -296,13 +300,20 @@ describe("SessionModelRequest HTTP bridge", () => {
|
||||
catalog: catalogHost(catalog),
|
||||
session: { hook: (name, callback) => hooks.register("session", name, callback) },
|
||||
})
|
||||
yield* pluginHost.session.hook("http", (event) =>
|
||||
event.use((request, next) =>
|
||||
Effect.gen(function* () {
|
||||
yield* next(request).pipe(Effect.flatMap((response) => Effect.promise(() => response.text())))
|
||||
return yield* next(request)
|
||||
}),
|
||||
),
|
||||
yield* pluginHost.session.hook("http.request", (event) =>
|
||||
Effect.sync(() => {
|
||||
seen.push("request")
|
||||
event.request.headers.set("x-hook", "effect")
|
||||
}),
|
||||
)
|
||||
yield* pluginHost.session.hook("http.response", (event) =>
|
||||
Effect.gen(function* () {
|
||||
seen.push(`response:${event.response.status}:${event.request.headers.get("x-hook")}`)
|
||||
event.response = new Response(
|
||||
(yield* Effect.promise(() => event.response.text())).replace("Hello!", "Hooked!"),
|
||||
event.response,
|
||||
)
|
||||
}),
|
||||
)
|
||||
yield* Effect.forEach(SystemPromptPlugin.Plugins, (plugin) => plugin.effect(pluginHost), { discard: true })
|
||||
const { db } = yield* Database.Service
|
||||
@@ -330,10 +341,15 @@ describe("SessionModelRequest HTTP bridge", () => {
|
||||
|
||||
yield* session.resume(retrySessionID)
|
||||
|
||||
expect(methods).toEqual(["POST", "POST"])
|
||||
expect(bodies).toHaveLength(2)
|
||||
expect(methods).toEqual(["POST"])
|
||||
expect(headers).toEqual(["effect"])
|
||||
expect(seen).toEqual(["request", "response:200:effect"])
|
||||
expect(bodies).toHaveLength(1)
|
||||
expect(bodies[0]?.byteLength).toBeGreaterThan(0)
|
||||
expect(bodies[1]).toEqual(bodies[0])
|
||||
expect((yield* session.context(retrySessionID))[1]).toMatchObject({
|
||||
type: "assistant",
|
||||
content: [{ type: "text", text: "Hooked!" }],
|
||||
})
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -34,6 +34,12 @@ describe("shell", () => {
|
||||
expect(ShellSelect.login("C:/tools/pwsh.exe")).toBe(false)
|
||||
})
|
||||
|
||||
test("detects posix shells", () => {
|
||||
expect(ShellSelect.posix("/bin/bash")).toBe(true)
|
||||
expect(ShellSelect.posix("/bin/fish")).toBe(false)
|
||||
expect(ShellSelect.posix("C:/tools/pwsh.exe")).toBe(false)
|
||||
})
|
||||
|
||||
test("falls back when configured shell cannot be resolved", async () => {
|
||||
await withShell(undefined, async () => {
|
||||
const preferred = ShellSelect.preferred()
|
||||
|
||||
Vendored
+12
@@ -5,3 +5,15 @@ interface ImportMetaEnv {
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv
|
||||
}
|
||||
|
||||
declare module "virtual:opencode-server" {
|
||||
export namespace Server {
|
||||
export const listen: typeof import("../../../opencode/dist/types/src/node").Server.listen
|
||||
export type Listener = import("../../../opencode/dist/types/src/node").Server.Listener
|
||||
}
|
||||
export namespace Config {
|
||||
export const get: typeof import("../../../opencode/dist/types/src/node").Config.get
|
||||
export type Info = import("../../../opencode/dist/types/src/node").Config.Info
|
||||
}
|
||||
export const bootstrap: typeof import("../../../opencode/dist/types/src/node").bootstrap
|
||||
}
|
||||
|
||||
@@ -1,8 +1,32 @@
|
||||
import { dirname, join } from "node:path"
|
||||
import { fileURLToPath } from "node:url"
|
||||
import { app, utilityProcess } from "electron"
|
||||
import type { Details } from "electron"
|
||||
import { getLogger } from "./logging"
|
||||
import { getUserShell, loadShellEnv } from "./shell-env"
|
||||
import { getStore } from "./store"
|
||||
import { DEFAULT_SERVER_URL_KEY } from "./store-keys"
|
||||
|
||||
export type HealthCheck = { wait: Promise<void> }
|
||||
|
||||
type SidecarMessage =
|
||||
| { type: "ready" }
|
||||
| { type: "stopped" }
|
||||
| { type: "error"; error: { message: string; stack?: string } }
|
||||
|
||||
export type SidecarListener = { stop: () => Promise<void> }
|
||||
|
||||
const SIDECAR_SERVICE_NAME = "opencode server"
|
||||
const SIDECAR_START_STALL_TIMEOUT = 60_000
|
||||
const SIDECAR_STOP_TIMEOUT = 6_000
|
||||
|
||||
type SpawnLocalServerOptions = {
|
||||
userDataPath: string
|
||||
onStdout?: (message: string) => void
|
||||
onStderr?: (message: string) => void
|
||||
onExit?: (code: number) => void
|
||||
}
|
||||
|
||||
export function getDefaultServerUrl(): string | null {
|
||||
const value = getStore().get(DEFAULT_SERVER_URL_KEY)
|
||||
return typeof value === "string" ? value : null
|
||||
@@ -30,6 +54,135 @@ export function preferAppEnv(userDataPath: string) {
|
||||
return shellEnv
|
||||
}
|
||||
|
||||
export async function spawnLocalServer(
|
||||
hostname: string,
|
||||
port: number,
|
||||
password: string,
|
||||
options: SpawnLocalServerOptions,
|
||||
) {
|
||||
const sidecar = join(dirname(fileURLToPath(import.meta.url)), "sidecar.js")
|
||||
const child = utilityProcess.fork(sidecar, [], {
|
||||
cwd: process.cwd(),
|
||||
env: createSidecarEnv(),
|
||||
serviceName: SIDECAR_SERVICE_NAME,
|
||||
stdio: "pipe",
|
||||
})
|
||||
let exited = false
|
||||
const exit = defer<number>()
|
||||
|
||||
const onProcessGone = (_event: unknown, details: Details) => {
|
||||
if (details.type !== "Utility" || details.name !== SIDECAR_SERVICE_NAME) return
|
||||
options.onStderr?.(`utility process gone reason=${details.reason} exitCode=${details.exitCode}`)
|
||||
}
|
||||
|
||||
app.on("child-process-gone", onProcessGone)
|
||||
child.once("exit", (code) => {
|
||||
exited = true
|
||||
app.off("child-process-gone", onProcessGone)
|
||||
options.onExit?.(code)
|
||||
exit.resolve(code)
|
||||
})
|
||||
child.on("error", (error) => options.onStderr?.(`utility process error: ${serializeError(error).message}`))
|
||||
|
||||
child.stdout?.on("data", (chunk: Buffer) => options.onStdout?.(chunk.toString("utf8").trimEnd()))
|
||||
child.stderr?.on("data", (chunk: Buffer) => options.onStderr?.(chunk.toString("utf8").trimEnd()))
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
let done = false
|
||||
let timeout: NodeJS.Timeout
|
||||
|
||||
const fail = (error: Error) => {
|
||||
if (done) return
|
||||
done = true
|
||||
cleanup()
|
||||
reject(error)
|
||||
}
|
||||
|
||||
const refreshTimeout = () => {
|
||||
clearTimeout(timeout)
|
||||
timeout = setTimeout(() => {
|
||||
fail(new Error(`Sidecar did not become ready within ${SIDECAR_START_STALL_TIMEOUT}ms: ${sidecar}`))
|
||||
}, SIDECAR_START_STALL_TIMEOUT)
|
||||
}
|
||||
|
||||
const onMessage = (message: SidecarMessage) => {
|
||||
if (message.type === "ready") {
|
||||
if (done) return
|
||||
done = true
|
||||
cleanup()
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
if (message.type === "error") {
|
||||
fail(Object.assign(new Error(message.error.message), { stack: message.error.stack }))
|
||||
}
|
||||
}
|
||||
const onExit = (code: number) => {
|
||||
fail(new Error(`Sidecar exited before ready with code ${code}`))
|
||||
}
|
||||
const cleanup = () => {
|
||||
clearTimeout(timeout)
|
||||
child.off("message", onMessage)
|
||||
child.off("exit", onExit)
|
||||
}
|
||||
|
||||
child.on("message", onMessage)
|
||||
child.on("exit", onExit)
|
||||
refreshTimeout()
|
||||
child.postMessage({
|
||||
type: "start",
|
||||
hostname,
|
||||
port,
|
||||
password,
|
||||
userDataPath: options.userDataPath,
|
||||
})
|
||||
}).catch((error) => {
|
||||
if (!exited) child.kill()
|
||||
throw error
|
||||
})
|
||||
|
||||
const wait = (async () => {
|
||||
const url = `http://${hostname}:${port}`
|
||||
let healthy = false
|
||||
const gone = exit.promise.then((code) => {
|
||||
if (healthy) return
|
||||
throw new Error(`Sidecar exited before health check passed with code ${code}`)
|
||||
})
|
||||
|
||||
const ready = async () => {
|
||||
while (true) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
if (await checkHealth(url, password)) {
|
||||
healthy = true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.race([ready(), gone])
|
||||
})()
|
||||
|
||||
let stopping: Promise<void> | undefined
|
||||
|
||||
return {
|
||||
listener: {
|
||||
stop: () => {
|
||||
if (stopping) return stopping
|
||||
if (exited) return Promise.resolve()
|
||||
child.postMessage({ type: "stop" })
|
||||
stopping = Promise.race([
|
||||
exit.promise.then(() => undefined),
|
||||
delay(SIDECAR_STOP_TIMEOUT).then(() => {
|
||||
if (!exited) child.kill()
|
||||
}),
|
||||
])
|
||||
return stopping
|
||||
},
|
||||
},
|
||||
health: { wait },
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkHealth(url: string, password?: string | null): Promise<boolean> {
|
||||
let healthUrls: URL[]
|
||||
try {
|
||||
@@ -56,3 +209,31 @@ export async function checkHealth(url: string, password?: string | null): Promis
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function createSidecarEnv(): Record<string, string> {
|
||||
const env = Object.fromEntries(
|
||||
Object.entries(process.env).flatMap(([key, value]) => (value === undefined ? [] : [[key, String(value)]])),
|
||||
)
|
||||
delete env.DEBUG
|
||||
if (process.platform === "linux") delete env.LD_PRELOAD
|
||||
return env
|
||||
}
|
||||
|
||||
function delay(ms: number) {
|
||||
return new Promise<void>((resolve) => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
function serializeError(error: unknown) {
|
||||
if (error instanceof Error) return { message: error.message, stack: error.stack }
|
||||
return { message: String(error) }
|
||||
}
|
||||
|
||||
function defer<T>() {
|
||||
let resolve!: (value: T) => void
|
||||
let reject!: (error: Error) => void
|
||||
const promise = new Promise<T>((res, rej) => {
|
||||
resolve = res
|
||||
reject = rej
|
||||
})
|
||||
return { promise, resolve, reject }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
import * as http from "node:http"
|
||||
import * as tls from "node:tls"
|
||||
|
||||
type NodeHttpWithEnvProxy = typeof http & {
|
||||
setGlobalProxyFromEnv: () => void
|
||||
}
|
||||
|
||||
type NodeTlsWithSystemCertificates = typeof tls & {
|
||||
getCACertificates: (type: "default" | "system") => string[]
|
||||
setDefaultCACertificates: (certificates: string[]) => void
|
||||
}
|
||||
|
||||
type StartCommand = {
|
||||
type: "start"
|
||||
hostname: string
|
||||
port: number
|
||||
password: string
|
||||
userDataPath: string
|
||||
}
|
||||
|
||||
type StopCommand = { type: "stop" }
|
||||
type SidecarCommand = StartCommand | StopCommand
|
||||
|
||||
type SidecarMessage =
|
||||
| { type: "ready" }
|
||||
| { type: "stopped" }
|
||||
| { type: "error"; error: { message: string; stack?: string } }
|
||||
|
||||
type ParentPort = {
|
||||
postMessage(message: SidecarMessage): void
|
||||
on(event: "message", listener: (event: { data: unknown }) => void): void
|
||||
}
|
||||
|
||||
type Listener = {
|
||||
stop(close?: boolean): void | Promise<void>
|
||||
}
|
||||
|
||||
const parentPort = getParentPort()
|
||||
let listener: Listener | undefined
|
||||
|
||||
parentPort.on("message", (event) => {
|
||||
const command = parseCommand(event.data)
|
||||
if (!command) return
|
||||
if (command.type === "stop") {
|
||||
void stop()
|
||||
return
|
||||
}
|
||||
void start(command)
|
||||
})
|
||||
|
||||
async function start(command: StartCommand) {
|
||||
try {
|
||||
prepareSidecarEnv(command.password, command.userDataPath)
|
||||
ensureLoopbackNoProxy()
|
||||
useSystemCertificates()
|
||||
useEnvProxy()
|
||||
const { Server } = await import("virtual:opencode-server")
|
||||
|
||||
listener = await Server.listen({
|
||||
port: command.port,
|
||||
hostname: command.hostname,
|
||||
username: "opencode",
|
||||
password: command.password,
|
||||
cors: ["oc://renderer"],
|
||||
})
|
||||
parentPort.postMessage({ type: "ready" })
|
||||
} catch (error) {
|
||||
parentPort.postMessage({ type: "error", error: serializeError(error) })
|
||||
setImmediate(() => process.exit(1))
|
||||
}
|
||||
}
|
||||
|
||||
async function stop() {
|
||||
try {
|
||||
await listener?.stop()
|
||||
} finally {
|
||||
listener = undefined
|
||||
parentPort.postMessage({ type: "stopped" })
|
||||
setImmediate(() => process.exit(0))
|
||||
}
|
||||
}
|
||||
|
||||
function prepareSidecarEnv(password: string, userDataPath: string) {
|
||||
Object.assign(process.env, {
|
||||
OPENCODE_SERVER_USERNAME: "opencode",
|
||||
OPENCODE_SERVER_PASSWORD: password,
|
||||
XDG_STATE_HOME: process.env.XDG_STATE_HOME ?? userDataPath,
|
||||
})
|
||||
}
|
||||
|
||||
function ensureLoopbackNoProxy() {
|
||||
const loopback = ["127.0.0.1", "localhost", "::1"]
|
||||
const upsert = (key: string) => {
|
||||
const items = (process.env[key] ?? "")
|
||||
.split(",")
|
||||
.map((value: string) => value.trim())
|
||||
.filter((value: string) => Boolean(value))
|
||||
|
||||
for (const host of loopback) {
|
||||
if (items.some((value: string) => value.toLowerCase() === host)) continue
|
||||
items.push(host)
|
||||
}
|
||||
|
||||
process.env[key] = items.join(",")
|
||||
}
|
||||
|
||||
upsert("NO_PROXY")
|
||||
upsert("no_proxy")
|
||||
}
|
||||
|
||||
function useSystemCertificates() {
|
||||
try {
|
||||
const nodeTls = tls as NodeTlsWithSystemCertificates
|
||||
nodeTls.setDefaultCACertificates([
|
||||
...new Set([...nodeTls.getCACertificates("default"), ...nodeTls.getCACertificates("system")]),
|
||||
])
|
||||
} catch (error) {
|
||||
console.warn("failed to load system certificates", error)
|
||||
}
|
||||
}
|
||||
|
||||
function useEnvProxy() {
|
||||
try {
|
||||
;(http as NodeHttpWithEnvProxy).setGlobalProxyFromEnv()
|
||||
} catch (error) {
|
||||
console.warn("failed to load proxy environment", error)
|
||||
}
|
||||
}
|
||||
|
||||
function parseCommand(value: unknown): SidecarCommand | undefined {
|
||||
if (!value || typeof value !== "object") return
|
||||
const command = value as Partial<StartCommand | StopCommand>
|
||||
if (command.type === "stop") return { type: "stop" }
|
||||
if (command.type !== "start") return
|
||||
if (typeof command.hostname !== "string") return
|
||||
if (typeof command.port !== "number") return
|
||||
if (typeof command.password !== "string") return
|
||||
if (typeof command.userDataPath !== "string") return
|
||||
return {
|
||||
type: "start",
|
||||
hostname: command.hostname,
|
||||
port: command.port,
|
||||
password: command.password,
|
||||
userDataPath: command.userDataPath,
|
||||
}
|
||||
}
|
||||
|
||||
function serializeError(error: unknown) {
|
||||
if (error instanceof Error) return { message: error.message, stack: error.stack }
|
||||
return { message: String(error) }
|
||||
}
|
||||
|
||||
function getParentPort() {
|
||||
const port = process.parentPort as ParentPort | undefined
|
||||
if (!port) throw new Error("Sidecar parent port unavailable")
|
||||
return port
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import type { Message, SystemPart } from "@opencode-ai/ai"
|
||||
import type { Agent } from "@opencode-ai/schema/agent"
|
||||
import type { Model } from "@opencode-ai/schema/model"
|
||||
import type { Session } from "@opencode-ai/schema/session"
|
||||
import type { Effect, JsonSchema } from "effect"
|
||||
import type { JsonSchema } from "effect"
|
||||
import type { Hooks } from "./registration.js"
|
||||
|
||||
export interface SessionContext {
|
||||
@@ -15,23 +15,25 @@ export interface SessionContext {
|
||||
tools: Record<string, { description: string; input: JsonSchema.JsonSchema }>
|
||||
}
|
||||
|
||||
export interface SessionHttp {
|
||||
export interface SessionHttpRequest {
|
||||
readonly sessionID: Session.ID
|
||||
readonly agent: Agent.ID
|
||||
readonly model: Model.Ref
|
||||
readonly use: (middleware: SessionHttpMiddleware) => Effect.Effect<void>
|
||||
request: Request
|
||||
}
|
||||
|
||||
export type SessionHttpHandler = (request: Request) => Effect.Effect<Response, Error>
|
||||
|
||||
export type SessionHttpMiddleware = (
|
||||
request: Request,
|
||||
next: SessionHttpHandler,
|
||||
) => Effect.Effect<Response, Error>
|
||||
export interface SessionHttpResponse {
|
||||
readonly sessionID: Session.ID
|
||||
readonly agent: Agent.ID
|
||||
readonly model: Model.Ref
|
||||
readonly request: Request
|
||||
response: Response
|
||||
}
|
||||
|
||||
export interface SessionHooks {
|
||||
readonly context: SessionContext
|
||||
readonly http: SessionHttp
|
||||
readonly "http.request": SessionHttpRequest
|
||||
readonly "http.response": SessionHttpResponse
|
||||
}
|
||||
|
||||
export type SessionDomain = Pick<
|
||||
|
||||
@@ -15,23 +15,25 @@ export interface SessionContext {
|
||||
tools: Record<string, { description: string; input: JsonSchema.JsonSchema }>
|
||||
}
|
||||
|
||||
export interface SessionHttp {
|
||||
export interface SessionHttpRequest {
|
||||
readonly sessionID: Session.ID
|
||||
readonly agent: Agent.ID
|
||||
readonly model: Model.Ref
|
||||
readonly use: (middleware: SessionHttpMiddleware) => void
|
||||
request: Request
|
||||
}
|
||||
|
||||
export type SessionHttpHandler = (request: Request) => Promise<Response>
|
||||
|
||||
export type SessionHttpMiddleware = (
|
||||
request: Request,
|
||||
next: SessionHttpHandler,
|
||||
) => Promise<Response> | Response
|
||||
export interface SessionHttpResponse {
|
||||
readonly sessionID: Session.ID
|
||||
readonly agent: Agent.ID
|
||||
readonly model: Model.Ref
|
||||
readonly request: Request
|
||||
response: Response
|
||||
}
|
||||
|
||||
export interface SessionHooks {
|
||||
readonly context: SessionContext
|
||||
readonly http: SessionHttp
|
||||
readonly "http.request": SessionHttpRequest
|
||||
readonly "http.response": SessionHttpResponse
|
||||
}
|
||||
|
||||
export type SessionDomain = Pick<
|
||||
|
||||
@@ -23,11 +23,44 @@ export type ProviderContext = {
|
||||
options: Record<string, any>
|
||||
}
|
||||
|
||||
export type WorkspaceInfo = {
|
||||
id: string
|
||||
type: string
|
||||
name: string
|
||||
branch: string | null
|
||||
directory: string | null
|
||||
extra: unknown | null
|
||||
projectID: string
|
||||
}
|
||||
|
||||
export type WorkspaceTarget =
|
||||
| {
|
||||
type: "local"
|
||||
directory: string
|
||||
}
|
||||
| {
|
||||
type: "remote"
|
||||
url: string | URL
|
||||
headers?: HeadersInit
|
||||
}
|
||||
|
||||
export type WorkspaceAdapter = {
|
||||
name: string
|
||||
description: string
|
||||
configure(config: WorkspaceInfo): WorkspaceInfo | Promise<WorkspaceInfo>
|
||||
create(config: WorkspaceInfo, env: Record<string, string | undefined>, from?: WorkspaceInfo): Promise<void>
|
||||
remove(config: WorkspaceInfo): Promise<void>
|
||||
target(config: WorkspaceInfo): WorkspaceTarget | Promise<WorkspaceTarget>
|
||||
}
|
||||
|
||||
export type PluginInput = {
|
||||
client: ReturnType<typeof createOpencodeClient>
|
||||
project: Project
|
||||
directory: string
|
||||
worktree: string
|
||||
experimental_workspace: {
|
||||
register(type: string, adapter: WorkspaceAdapter): void
|
||||
}
|
||||
serverUrl: URL
|
||||
$: BunShell
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ export const LocationQuery = Schema.Struct({
|
||||
location: Schema.optional(
|
||||
Schema.Struct({
|
||||
directory: Schema.optional(Schema.String),
|
||||
workspace: Schema.optional(Schema.String),
|
||||
}),
|
||||
),
|
||||
}).annotate({ identifier: "LocationQuery" })
|
||||
|
||||
@@ -130,7 +130,7 @@ export const PtyGroup = HttpApiGroup.make("server.pty")
|
||||
"x-websocket": true,
|
||||
parameters: [
|
||||
...(operation.parameters ?? []),
|
||||
...["location[directory]", "cursor", PTY_CONNECT_TICKET_QUERY].map((name) => ({
|
||||
...["location[directory]", "location[workspace]", "cursor", PTY_CONNECT_TICKET_QUERY].map((name) => ({
|
||||
in: "query",
|
||||
name,
|
||||
schema: { type: "string" },
|
||||
|
||||
@@ -6,6 +6,7 @@ import { InstructionEntry } from "@opencode-ai/schema/instruction-entry"
|
||||
import { Project } from "@opencode-ai/schema/project"
|
||||
import { AbsolutePath, PositiveInt, RelativePath, statics } from "@opencode-ai/schema/schema"
|
||||
import { Event } from "@opencode-ai/schema/event"
|
||||
import { Workspace } from "@opencode-ai/schema/workspace"
|
||||
import { Context, Effect, Encoding, Result, Schema, SchemaGetter, Struct } from "effect"
|
||||
import { HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware, HttpApiSchema, OpenApi } from "effect/unstable/httpapi"
|
||||
import {
|
||||
@@ -41,6 +42,7 @@ const ParentIDFilter = Schema.Union([
|
||||
})
|
||||
|
||||
const SessionsQueryFields = {
|
||||
workspace: Workspace.ID.pipe(Schema.optional),
|
||||
limit: Schema.NumberFromString.pipe(Schema.decodeTo(PositiveInt), Schema.optional).annotate({
|
||||
description: "Maximum number of sessions to return. Defaults to the newest 50 sessions.",
|
||||
}),
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Session } from "@opencode-ai/schema/session"
|
||||
describe("SessionsCursor", () => {
|
||||
test("round trips without Node globals", async () => {
|
||||
const input = {
|
||||
workspace: undefined,
|
||||
search: "protocol",
|
||||
order: "desc" as const,
|
||||
anchor: { id: Session.ID.make("ses_test"), time: 1, direction: "next" as const },
|
||||
|
||||
@@ -32,6 +32,7 @@ import { SessionStatusEvent } from "./session-status-event.js"
|
||||
import { SessionV1 } from "./session-v1.js"
|
||||
import { TuiEvent } from "./tui-event.js"
|
||||
import { VcsEvent } from "./vcs-event.js"
|
||||
import { WorkspaceEvent } from "./workspace-event.js"
|
||||
import { WorktreeEvent } from "./worktree-event.js"
|
||||
import { WebSearch } from "./websearch.js"
|
||||
|
||||
@@ -99,6 +100,7 @@ export const Definitions = Event.inventory(
|
||||
...SessionStatusEvent.Definitions,
|
||||
...SessionCompactionEvent.Definitions,
|
||||
...VcsEvent.Definitions,
|
||||
...WorkspaceEvent.Definitions,
|
||||
...WorktreeEvent.Definitions,
|
||||
...ServerEvent.Definitions,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
export * as WorkspaceEvent from "./workspace-event.js"
|
||||
|
||||
import { Schema } from "effect"
|
||||
import { Event } from "./event.js"
|
||||
import { WorkspaceID } from "./workspace-id.js"
|
||||
|
||||
export const ConnectionStatus = Schema.Struct({
|
||||
workspaceID: WorkspaceID,
|
||||
status: Schema.Literals(["connected", "connecting", "disconnected", "error"]),
|
||||
}).annotate({ identifier: "WorkspaceEvent.ConnectionStatus" })
|
||||
export interface ConnectionStatus extends Schema.Schema.Type<typeof ConnectionStatus> {}
|
||||
|
||||
export const Ready = Event.ephemeral({
|
||||
type: "workspace.ready",
|
||||
schema: {
|
||||
name: Schema.String,
|
||||
},
|
||||
})
|
||||
|
||||
export const Failed = Event.ephemeral({
|
||||
type: "workspace.failed",
|
||||
schema: {
|
||||
message: Schema.String,
|
||||
},
|
||||
})
|
||||
|
||||
export const Status = Event.ephemeral({
|
||||
type: "workspace.status",
|
||||
schema: ConnectionStatus.fields,
|
||||
})
|
||||
|
||||
export const Definitions = Event.inventory(Ready, Failed, Status)
|
||||
@@ -1,6 +1,9 @@
|
||||
export * as Workspace from "./workspace.js"
|
||||
|
||||
import { WorkspaceEvent } from "./workspace-event.js"
|
||||
import { WorkspaceID } from "./workspace-id.js"
|
||||
|
||||
export const ID = WorkspaceID
|
||||
export type ID = WorkspaceID
|
||||
|
||||
export const Event = WorkspaceEvent
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
Project,
|
||||
Reference,
|
||||
Session,
|
||||
Workspace,
|
||||
} from "../src/index.js"
|
||||
import { EventManifest } from "../src/event-manifest.js"
|
||||
import { FileSystemV1 } from "../src/filesystem-v1.js"
|
||||
@@ -19,6 +20,7 @@ import { SessionEvent } from "../src/session-event.js"
|
||||
import { SessionID } from "../src/session-id.js"
|
||||
import { SessionMessage } from "../src/session-message.js"
|
||||
import { SessionV1 } from "../src/session-v1.js"
|
||||
import { WorkspaceEvent } from "../src/workspace-event.js"
|
||||
|
||||
describe("public event manifest", () => {
|
||||
test("owns the complete public event surface", () => {
|
||||
@@ -58,6 +60,8 @@ describe("public event manifest", () => {
|
||||
test("uses canonical definitions for current public events", () => {
|
||||
expect(Session.Event).toBe(SessionEvent)
|
||||
expect(Session.Event.Definitions).toBe(SessionEvent.Definitions)
|
||||
expect(Workspace.Event).toBe(WorkspaceEvent)
|
||||
expect(Workspace.Event.Definitions).toBe(WorkspaceEvent.Definitions)
|
||||
expect(EventManifest.Latest.get("session.step.ended")).toBe(SessionEvent.Step.Ended)
|
||||
expect(EventManifest.Latest.get("agent.updated")).toBe(Agent.Event.Updated)
|
||||
expect(EventManifest.Latest.get("project.updated")).toBe(Project.Event.Updated)
|
||||
|
||||
@@ -10,6 +10,7 @@ export const LocationHandler = HttpApiBuilder.group(Api, "server.location", (han
|
||||
const location = yield* Location.Service
|
||||
return new Location.Info({
|
||||
directory: location.directory,
|
||||
workspaceID: location.workspaceID,
|
||||
project: location.project,
|
||||
})
|
||||
}),
|
||||
|
||||
@@ -19,7 +19,7 @@ import { PtyEnvironment } from "../pty-environment"
|
||||
|
||||
const ticketScope = Effect.gen(function* () {
|
||||
const location = yield* Location.Service
|
||||
return { directory: location.directory as string }
|
||||
return { directory: location.directory as string, workspaceID: location.workspaceID }
|
||||
})
|
||||
|
||||
export const PtyHandler = HttpApiBuilder.group(Api, "server.pty", (handlers) =>
|
||||
|
||||
@@ -37,6 +37,7 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
|
||||
: ctx.query
|
||||
const page = yield* session.list({
|
||||
...query,
|
||||
workspaceID: query.workspace,
|
||||
limit: ctx.query.limit ?? DefaultSessionsLimit,
|
||||
})
|
||||
const sessions = page.data
|
||||
@@ -212,6 +213,7 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
|
||||
.move({
|
||||
sessionID: ctx.params.sessionID,
|
||||
directory: ctx.payload.directory,
|
||||
workspaceID: ctx.payload.workspaceID,
|
||||
})
|
||||
.pipe(
|
||||
Effect.catchTag("Session.NotFoundError", (error) =>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Location } from "@opencode-ai/core/location"
|
||||
import { LocationServiceMap } from "@opencode-ai/core/location-services"
|
||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||
import { Workspace } from "@opencode-ai/core/workspace"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { HttpServerRequest } from "effect/unstable/http"
|
||||
import { HttpApiMiddleware } from "effect/unstable/httpapi"
|
||||
@@ -17,6 +18,7 @@ export function response<A, E, R>(data: Effect.Effect<A, E, R>) {
|
||||
return {
|
||||
location: new Location.Info({
|
||||
directory: location.directory,
|
||||
workspaceID: location.workspaceID,
|
||||
project: location.project,
|
||||
}),
|
||||
data: yield* data,
|
||||
@@ -26,11 +28,13 @@ export function response<A, E, R>(data: Effect.Effect<A, E, R>) {
|
||||
|
||||
export function requestRef(request: HttpServerRequest.HttpServerRequest): Location.Ref {
|
||||
const query = new URL(request.url, "http://localhost").searchParams
|
||||
const workspaceID = query.get("location[workspace]") || request.headers["x-opencode-workspace"]
|
||||
const directory =
|
||||
query.get("location[directory]") ||
|
||||
(request.headers["x-opencode-directory"] ? decode(request.headers["x-opencode-directory"]) : process.cwd())
|
||||
return Location.Ref.make({
|
||||
directory: AbsolutePath.make(directory),
|
||||
workspaceID: workspaceID ? Workspace.ID.make(workspaceID) : undefined,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Location } from "@opencode-ai/core/location"
|
||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||
import { Session } from "@opencode-ai/core/session"
|
||||
import { SessionTable } from "@opencode-ai/core/session/sql"
|
||||
import { Workspace } from "@opencode-ai/core/workspace"
|
||||
import { eq } from "drizzle-orm"
|
||||
import { Effect, Layer, Schema } from "effect"
|
||||
import { HttpRouter } from "effect/unstable/http"
|
||||
@@ -39,7 +40,7 @@ export const sessionLocationLayer = Layer.effect(
|
||||
),
|
||||
)
|
||||
const row = yield* db
|
||||
.select({ directory: SessionTable.directory })
|
||||
.select({ directory: SessionTable.directory, workspaceID: SessionTable.workspace_id })
|
||||
.from(SessionTable)
|
||||
.where(eq(SessionTable.id, sessionID))
|
||||
.get()
|
||||
@@ -55,6 +56,7 @@ export const sessionLocationLayer = Layer.effect(
|
||||
locations.get(
|
||||
Location.Ref.make({
|
||||
directory: AbsolutePath.make(row.directory),
|
||||
workspaceID: row.workspaceID ? Workspace.ID.make(row.workspaceID) : undefined,
|
||||
}),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -34,6 +34,10 @@ export function SessionFilePanelV2(props: {
|
||||
)
|
||||
}
|
||||
|
||||
export function SessionFilePanelV2Title(props: ParentProps) {
|
||||
return <div data-slot="session-review-v2-toolbar-title">{props.children}</div>
|
||||
}
|
||||
|
||||
export function SessionFilePanelV2Empty(props: ParentProps) {
|
||||
return <div data-slot="session-review-v2-empty">{props.children}</div>
|
||||
}
|
||||
|
||||
@@ -600,6 +600,7 @@ function App(props: { pair?: DialogPairCredentials }) {
|
||||
order: "desc",
|
||||
parentID: null,
|
||||
directory: location.directory,
|
||||
workspace: location.workspaceID,
|
||||
})
|
||||
.then((response) => {
|
||||
const match = response.data[0]?.id
|
||||
|
||||
@@ -684,7 +684,7 @@ async function disconnected(
|
||||
|
||||
function location(data: ReturnType<typeof useData>) {
|
||||
const current = data.location.default()
|
||||
return { directory: current.directory }
|
||||
return { directory: current.directory, workspace: current.workspaceID }
|
||||
}
|
||||
|
||||
function message(cause: unknown) {
|
||||
|
||||
@@ -81,7 +81,7 @@ export function DialogMcp() {
|
||||
if (!server || server.status.status === "pending") return
|
||||
setLoading(name)
|
||||
const current = data.location.default()
|
||||
const input = { server: name, location: { directory: current.directory } }
|
||||
const input = { server: name, location: { directory: current.directory, workspace: current.workspaceID } }
|
||||
const call = server.status.status === "connected" ? client.api.mcp.disconnect(input) : client.api.mcp.connect(input)
|
||||
void call.catch(toast.error).finally(() => setLoading(null))
|
||||
}
|
||||
|
||||
@@ -7,14 +7,12 @@ import { DialogVariant } from "./dialog-variant"
|
||||
import * as fuzzysort from "fuzzysort"
|
||||
import { useConnected } from "./use-connected"
|
||||
import { useData } from "../context/data"
|
||||
import { modelPreferenceKey } from "../model-preference"
|
||||
|
||||
export function DialogModel(props: { providerID?: string }) {
|
||||
const local = useLocal()
|
||||
const data = useData()
|
||||
const dialog = useDialog()
|
||||
const [query, setQuery] = createSignal("")
|
||||
const favoritePriority = new Set(local.model.favorite().map(modelPreferenceKey))
|
||||
|
||||
const connected = useConnected()
|
||||
const providers = createMemo(() => new Map((data.location.provider.list() ?? []).map((item) => [item.id, item])))
|
||||
@@ -65,14 +63,14 @@ export function DialogModel(props: { providerID?: string }) {
|
||||
.filter((model) => (props.providerID ? model.providerID === props.providerID : true))
|
||||
.map((model) => {
|
||||
const provider = providers().get(model.providerID)
|
||||
const key = modelPreferenceKey({ providerID: model.providerID, modelID: model.id })
|
||||
const favorite = favorites.some((item) => modelPreferenceKey(item) === key)
|
||||
const favorite = favorites.some((item) => item.providerID === model.providerID && item.modelID === model.id)
|
||||
return {
|
||||
value: { providerID: model.providerID, modelID: model.id },
|
||||
providerID: model.providerID,
|
||||
providerName: provider?.name ?? model.providerID,
|
||||
title: model.name,
|
||||
releaseDate: model.time.released,
|
||||
favorite,
|
||||
description: favorite ? "(Favorite)" : undefined,
|
||||
category: connected() ? (provider?.name ?? model.providerID) : undefined,
|
||||
footer: free(model) ? "Free" : undefined,
|
||||
@@ -100,7 +98,6 @@ export function DialogModel(props: { providerID?: string }) {
|
||||
if (needle) {
|
||||
return prioritizeFavorites(
|
||||
fuzzysort.go(needle, modelOptions, { keys: ["title", "category"] }).map((item) => item.obj),
|
||||
favoritePriority,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -165,13 +162,8 @@ export function DialogModel(props: { providerID?: string }) {
|
||||
)
|
||||
}
|
||||
|
||||
export function prioritizeFavorites<T extends { value: { providerID: string; modelID: string } }>(
|
||||
options: T[],
|
||||
favorites: Set<string>,
|
||||
) {
|
||||
return options.toSorted(
|
||||
(a, b) => Number(favorites.has(modelPreferenceKey(b.value))) - Number(favorites.has(modelPreferenceKey(a.value))),
|
||||
)
|
||||
export function prioritizeFavorites<T extends { favorite: boolean }>(options: T[]) {
|
||||
return options.toSorted((a, b) => Number(b.favorite) - Number(a.favorite))
|
||||
}
|
||||
|
||||
export function sortModelOptions<
|
||||
|
||||
@@ -290,6 +290,7 @@ export function Autocomplete(props: {
|
||||
limit: 20,
|
||||
location: {
|
||||
directory: input.location?.directory,
|
||||
workspace: input.location?.workspaceID ?? data.location.default().workspaceID,
|
||||
},
|
||||
})
|
||||
.then(
|
||||
|
||||
@@ -19,6 +19,7 @@ function location(ref: LocationRef) {
|
||||
return {
|
||||
location: {
|
||||
directory: ref.directory,
|
||||
workspace: ref.workspaceID,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,6 +151,7 @@ function formRequestOptions(location: LocationRef | undefined) {
|
||||
return {
|
||||
headers: {
|
||||
"x-opencode-directory": encodeURIComponent(location.directory),
|
||||
...(location.workspaceID ? { "x-opencode-workspace": location.workspaceID } : {}),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -235,7 +236,7 @@ async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDep
|
||||
.find({
|
||||
query,
|
||||
type: "file",
|
||||
location: { directory: state.location.directory },
|
||||
location: { directory: state.location.directory, workspace: state.location.workspaceID },
|
||||
})
|
||||
.then((result) => result.data.map((file) => file.path))
|
||||
.catch(() => []),
|
||||
@@ -650,6 +651,7 @@ async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDep
|
||||
{
|
||||
location: {
|
||||
directory: state.location.directory,
|
||||
workspace: state.location.workspaceID,
|
||||
},
|
||||
},
|
||||
{ signal: attempt.signal },
|
||||
|
||||
@@ -421,6 +421,7 @@ async function resolveSelectedModel(
|
||||
? {
|
||||
location: {
|
||||
directory: input.location.directory,
|
||||
workspace: input.location.workspaceID,
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
@@ -818,7 +819,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
||||
input.location
|
||||
? client.form.request.list(
|
||||
{
|
||||
location: { directory: input.location.directory },
|
||||
location: { directory: input.location.directory, workspace: input.location.workspaceID },
|
||||
},
|
||||
options,
|
||||
)
|
||||
|
||||
@@ -85,7 +85,7 @@ export function ShellTab(props: { sessionID: string }) {
|
||||
if (!entry) return
|
||||
void client.api.shell.remove({
|
||||
id: entry.id,
|
||||
location: { directory: entry.location.directory },
|
||||
location: { directory: entry.location.directory, workspace: entry.location.workspaceID },
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
@@ -37,6 +37,7 @@ function requestOptions(form: FormWithLocation) {
|
||||
return {
|
||||
headers: {
|
||||
"x-opencode-directory": encodeURIComponent(form.location.directory),
|
||||
...(form.location.workspaceID ? { "x-opencode-workspace": form.location.workspaceID } : {}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2540,7 +2540,7 @@ function Shell(props: ToolProps) {
|
||||
id,
|
||||
cursor,
|
||||
limit: SHELL_DISPLAY_LIMIT,
|
||||
location: location ? { directory: location.directory } : undefined,
|
||||
location: location ? { directory: location.directory, workspace: location.workspaceID } : undefined,
|
||||
})
|
||||
.catch(() => undefined)
|
||||
if (!response) break
|
||||
|
||||
@@ -2,16 +2,13 @@ import { describe, expect, test } from "bun:test"
|
||||
import { prioritizeFavorites, sortModelOptions } from "../../../../src/component/dialog-model"
|
||||
|
||||
describe("prioritizeFavorites", () => {
|
||||
test("uses the favorite order captured when the dialog opened", () => {
|
||||
const prioritized = prioritizeFavorites(
|
||||
[
|
||||
{ title: "Best match", value: { providerID: "test", modelID: "best" } },
|
||||
{ title: "Favorite match", value: { providerID: "test", modelID: "favorite" } },
|
||||
{ title: "Second best match", value: { providerID: "test", modelID: "second-best" } },
|
||||
{ title: "Second favorite match", value: { providerID: "test", modelID: "second-favorite" } },
|
||||
],
|
||||
new Set(["test/favorite", "test/second-favorite"]),
|
||||
)
|
||||
test("moves favorites first while preserving fuzzy result order", () => {
|
||||
const prioritized = prioritizeFavorites([
|
||||
{ title: "Best match", favorite: false },
|
||||
{ title: "Favorite match", favorite: true },
|
||||
{ title: "Second best match", favorite: false },
|
||||
{ title: "Second favorite match", favorite: true },
|
||||
])
|
||||
|
||||
expect(prioritized.map((model) => model.title)).toEqual([
|
||||
"Favorite match",
|
||||
|
||||
@@ -1916,7 +1916,7 @@ test("keeps shell state scoped to location", async () => {
|
||||
app.mockInput.pressKey("d", { ctrl: true })
|
||||
await wait(() => removed !== undefined)
|
||||
expect(removed?.searchParams.get("location[directory]")).toBe(other)
|
||||
expect(removed?.searchParams.get("location[workspace]")).toBeNull()
|
||||
expect(removed?.searchParams.get("location[workspace]")).toBe(workspace)
|
||||
|
||||
events.emit({
|
||||
id: "evt_shell_created",
|
||||
|
||||
@@ -240,6 +240,7 @@ describe("run interactive runtime", () => {
|
||||
{
|
||||
headers: {
|
||||
"x-opencode-directory": "%2Fremote%20work",
|
||||
"x-opencode-workspace": "wrk_1",
|
||||
},
|
||||
},
|
||||
)
|
||||
@@ -574,7 +575,7 @@ describe("run interactive runtime", () => {
|
||||
painted.resolve()
|
||||
await task
|
||||
|
||||
const query = { location: { directory: "/session" } }
|
||||
const query = { location: { directory: "/session", workspace: "work-1" } }
|
||||
expect(getDirectory?.()).toBe("/session")
|
||||
expect(transportLocation).toMatchObject({ directory: "/session", workspaceID: "work-1" })
|
||||
expect(catalogs.provider).toHaveBeenCalledWith(query, { signal: expect.any(AbortSignal) })
|
||||
|
||||
@@ -2219,7 +2219,7 @@ describe("V2 mini transport", () => {
|
||||
{ signal: undefined },
|
||||
)
|
||||
expect(defaultModel).toHaveBeenCalledWith(
|
||||
{ location: { directory: "/project" } },
|
||||
{ location: { directory: "/project", workspace: "wrk_1" } },
|
||||
{ signal: undefined },
|
||||
)
|
||||
await transport.close()
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,6 +1,7 @@
|
||||
import { createContext, createSignal, useContext } from "solid-js"
|
||||
import { createContext, createSignal, splitProps, useContext } from "solid-js"
|
||||
import type { JSX } from "solid-js/jsx-runtime"
|
||||
import { makeResizeObserver } from "@solid-primitives/resize-observer"
|
||||
import { IconCheckCircle, IconHashtag } from "../icons"
|
||||
|
||||
export type ShareMessages = { locale: string } & Record<string, string>
|
||||
|
||||
@@ -40,6 +41,42 @@ export function formatCount(value: number, locale: string, singular: string, plu
|
||||
return `${formatNumber(value, locale)} ${unit}`
|
||||
}
|
||||
|
||||
interface AnchorProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
||||
id: string
|
||||
}
|
||||
export function AnchorIcon(props: AnchorProps) {
|
||||
const [local, rest] = splitProps(props, ["id", "children"])
|
||||
const [copied, setCopied] = createSignal(false)
|
||||
const messages = useShareMessages()
|
||||
|
||||
return (
|
||||
<div {...rest} data-element-anchor title={messages.link_to_message} data-status={copied() ? "copied" : ""}>
|
||||
<a
|
||||
href={`#${local.id}`}
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
|
||||
const anchor = e.currentTarget
|
||||
const hash = anchor.getAttribute("href") || ""
|
||||
const { origin, pathname, search } = window.location
|
||||
|
||||
navigator.clipboard
|
||||
.writeText(`${origin}${pathname}${search}${hash}`)
|
||||
.catch((err) => console.error("Copy failed", err))
|
||||
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 3000)
|
||||
}}
|
||||
>
|
||||
{local.children}
|
||||
<IconHashtag width={18} height={18} />
|
||||
<IconCheckCircle width={18} height={18} />
|
||||
</a>
|
||||
<span data-element-tooltip>{messages.copied}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function createOverflow() {
|
||||
const [overflow, setOverflow] = createSignal(false)
|
||||
return {
|
||||
|
||||
+16
-8
@@ -246,19 +246,27 @@ Runtime hooks intercept live operations:
|
||||
| `ctx.aisdk.hook("sdk", callback)` | `sdk`, after inspecting `model`, `package`, and `options` |
|
||||
| `ctx.aisdk.hook("language", callback)` | `language`, after inspecting `model`, `sdk`, and `options` |
|
||||
| `ctx.session.hook("context", callback)` | `system`, `messages`, and the `tools` record immediately before model dispatch |
|
||||
| `ctx.session.hook("http", callback)` | `use`, registering request and response handling |
|
||||
| `ctx.session.hook("http.request", callback)` | `request`, immediately before provider dispatch |
|
||||
| `ctx.session.hook("http.response", callback)` | `response`, immediately after the provider responds |
|
||||
| `ctx.tool.hook("execute.before", callback)` | `input`, before the selected tool executes |
|
||||
| `ctx.tool.hook("execute.after", callback)` | Terminal `result` on success or `error` on failure |
|
||||
|
||||
HTTP hooks can modify requests, inspect responses, retry, or return a
|
||||
response without calling the provider. It applies to native models; AI SDK
|
||||
models do not currently pass through this hook.
|
||||
HTTP hooks can modify requests and responses. They apply to native models; AI
|
||||
SDK models do not currently pass through these hooks. Request and response
|
||||
bodies are one-shot streams. Use `clone()` when you intentionally need a
|
||||
separate reader, but be aware that its slower branch may buffer data. To inspect
|
||||
or modify chunks while preserving streaming, replace the body with one piped
|
||||
through a `TransformStream`.
|
||||
|
||||
```ts
|
||||
await ctx.session.hook("http", (event) => {
|
||||
event.use((request, next) => {
|
||||
request.headers.set("x-session-id", event.sessionID)
|
||||
return next(request)
|
||||
await ctx.session.hook("http.request", (event) => {
|
||||
event.request.headers.set("x-session-id", event.sessionID)
|
||||
})
|
||||
|
||||
await ctx.session.hook("http.response", (event) => {
|
||||
event.response = new Response(event.response.body, {
|
||||
status: event.response.status,
|
||||
headers: { ...Object.fromEntries(event.response.headers), "x-plugin": "enabled" },
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user