Compare commits

...

20 Commits

Author SHA1 Message Date
Aiden Cline 44a898f3ee fix(core): preserve model capability semantics 2026-07-07 23:33:15 -05:00
Aiden Cline 77fce8b24c chore(core): checkpoint model capability defaults 2026-07-07 22:37:19 -05:00
opencode-agent[bot] ed4f833813 fix(core): preserve provider metadata namespaces (#35817)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2026-07-07 21:45:58 -05:00
opencode-agent[bot] e96c24ce2e test(core): fix mutable plugin fixture (#35837) 2026-07-07 22:22:12 -04:00
Kit Langton ed6ad272ec refactor(schema): apply session review decisions (#35793) 2026-07-07 22:10:11 -04:00
Kit Langton d27746e5b3 test(core): simplify runner test fixtures (#35832) 2026-07-07 21:58:52 -04:00
Kit Langton eb661e6e6d fix(core): settle unadvertised tool calls and repair session hook test contexts 2026-07-07 21:51:23 -04:00
Dax Raad 3379e44abb refactor(core): rename ApplyPatchTool to PatchTool 2026-07-07 21:16:00 -04:00
Dax e1abf59c59 fix(core): provide PluginHooks in plugin test layer (#35811) 2026-07-07 20:03:41 -04:00
Dax 4f976bcf1a feat(plugin): add session request hook (#35794) 2026-07-07 19:56:47 -04:00
Kit Langton e25a724bc2 test(tui): cover patch diff display aliases (#35810) 2026-07-07 19:44:54 -04:00
Kit Langton 521ea87192 fix(core): retain reasoning delta state (#35758) 2026-07-07 19:44:40 -04:00
Aiden Cline 3100701488 fix(tui): restore prompt context usage (#35795) 2026-07-07 17:49:33 -05:00
Kit Langton 9c54291ae8 fix(tui): restore forms on reconnect
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
2026-07-07 22:47:13 +00:00
Dax Raad 52ad916ba4 fix(tui): restore permissions on reconnect
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
2026-07-07 22:44:26 +00:00
Dax Raad 5db320b02d refactor: rename apply_patch tool 2026-07-07 16:27:25 -04:00
Aiden Cline 910e37f6d8 fix: update v2 session usage metrics (#35468) 2026-07-07 14:31:54 -05:00
Kit Langton 81f6e06681 feat: enable background subagents by default 2026-07-07 15:18:46 -04:00
James Long da68e2865e feat(tui): wire move session command (#35203) 2026-07-07 14:49:11 -04:00
Aiden Cline f86e1cc1ff fix(codemode): preserve collection value identity (#35776) 2026-07-07 13:40:55 -05:00
332 changed files with 13323 additions and 8958 deletions
@@ -20,7 +20,7 @@ const profiles = [
{ name: "edit", tool: "edit", input: { filePath: "src/edit.ts" } }, { name: "edit", tool: "edit", input: { filePath: "src/edit.ts" } },
{ {
name: "multi patch", name: "multi patch",
tool: "apply_patch", tool: "patch",
input: { files: ["src/a.ts", "src/b.ts", "src/old.ts", "src/moved.ts"] }, input: { files: ["src/a.ts", "src/b.ts", "src/old.ts", "src/moved.ts"] },
}, },
] as const ] as const
@@ -25,7 +25,7 @@ test("adds patch files incrementally without resetting outer expansion", async (
userMessage(), userMessage(),
assistantMessage( assistantMessage(
[ [
toolPart(patchID, "apply_patch", "running", { files: [first.filePath] }, { metadata: { files: [first] } }), toolPart(patchID, "patch", "running", { files: [first.filePath] }, { metadata: { files: [first] } }),
textPart(followingID, "Following incremental patch"), textPart(followingID, "Following incremental patch"),
], ],
{ completed: false }, { completed: false },
@@ -49,7 +49,7 @@ test("adds patch files incrementally without resetting outer expansion", async (
partUpdated( partUpdated(
toolPart( toolPart(
patchID, patchID,
"apply_patch", "patch",
"running", "running",
{ files: [first.filePath, second.filePath] }, { files: [first.filePath, second.filePath] },
{ metadata: { files: [first, second] } }, { metadata: { files: [first, second] } },
@@ -61,7 +61,7 @@ test("adds patch files incrementally without resetting outer expansion", async (
partUpdated( partUpdated(
toolPart( toolPart(
patchID, patchID,
"apply_patch", "patch",
"completed", "completed",
{ files: [first.filePath, second.filePath, third.filePath] }, { files: [first.filePath, second.filePath, third.filePath] },
{ metadata: { files: [first, second, third] } }, { metadata: { files: [first, second, third] } },
@@ -295,7 +295,7 @@ function performanceTurn(index: number) {
messageID: assistantID, messageID: assistantID,
type: "tool", type: "tool",
callID: `call_0000_${suffix}_patch`, callID: `call_0000_${suffix}_patch`,
tool: "apply_patch", tool: "patch",
state: { state: {
status: "completed", status: "completed",
input: { patchText: realisticPatch(index) }, input: { patchText: realisticPatch(index) },
@@ -131,7 +131,7 @@ function toolPart(
): MessagePart { ): MessagePart {
const metadata = const metadata =
metadataOverride ?? metadataOverride ??
(tool === "apply_patch" (tool === "patch"
? { files: [patchFile(index, "update"), patchFile(index + 1, index % 2 === 0 ? "add" : "delete")] } ? { files: [patchFile(index, "update"), patchFile(index + 1, index % 2 === 0 ? "add" : "delete")] }
: tool === "edit" || tool === "write" : tool === "edit" || tool === "write"
? { ? {
@@ -219,7 +219,7 @@ function turn(index: number): Message[] {
? [toolPart(index, 7, "write", { filePath: `src/generated/write-${index}.ts`, content: code(index, 28) }, 560)] ? [toolPart(index, 7, "write", { filePath: `src/generated/write-${index}.ts`, content: code(index, 28) }, 560)]
: []), : []),
...(index % 8 === 0 ...(index % 8 === 0
? [toolPart(index, 8, "apply_patch", { files: [`src/generated/patch-${index}.ts`] }, 620)] ? [toolPart(index, 8, "patch", { files: [`src/generated/patch-${index}.ts`] }, 620)]
: []), : []),
...(index % 7 === 0 ...(index % 7 === 0
? [toolPart(index, 4, "bash", { command: "bun typecheck", description: "Verify generated output" }, 620)] ? [toolPart(index, 4, "bash", { command: "bun typecheck", description: "Verify generated output" }, 620)]
@@ -24,7 +24,7 @@ test("renders a completed single-file patch", async ({ page }) => {
assistantMessage([ assistantMessage([
toolPart( toolPart(
id, id,
"apply_patch", "patch",
"completed", "completed",
{ files: ["src/a.ts"] }, { files: ["src/a.ts"] },
{ {
@@ -35,7 +35,7 @@ test("preserves nested patch file state through outer collapse and reopen", asyn
assistantMessage([ assistantMessage([
toolPart( toolPart(
patchID, patchID,
"apply_patch", "patch",
"completed", "completed",
{ files: files.map((file) => file.filePath) }, { files: files.map((file) => file.filePath) },
{ metadata: { files } }, { metadata: { files } },
@@ -246,7 +246,7 @@ function editPart(id: string) {
function patchPart(id: string) { function patchPart(id: string) {
return toolPart( return toolPart(
id, id,
"apply_patch", "patch",
"completed", "completed",
{ files: ["src/a.ts", "src/b.ts"] }, { files: ["src/a.ts", "src/b.ts"] },
{ {
@@ -8,7 +8,7 @@ import {
} from "../performance/timeline-stability/fixture" } from "../performance/timeline-stability/fixture"
test("renders every tool error outcome without leaking hidden tools", async ({ page }) => { test("renders every tool error outcome without leaking hidden tools", async ({ page }) => {
const ordinary = ["bash", "edit", "write", "apply_patch", "webfetch", "websearch", "task", "skill", "mcp_probe"] const ordinary = ["bash", "edit", "write", "patch", "webfetch", "websearch", "task", "skill", "mcp_probe"]
const parts = ordinary.map((tool, index) => const parts = ordinary.map((tool, index) =>
toolPart(`prt_error_${index}`, tool, "error", errorInput(tool), { error: `${tool} failed visibly` }), toolPart(`prt_error_${index}`, tool, "error", errorInput(tool), { error: `${tool} failed visibly` }),
) )
@@ -90,7 +90,7 @@ function questionInput() {
function errorInput(tool: string) { function errorInput(tool: string) {
if (tool === "bash") return { command: "exit 1" } if (tool === "bash") return { command: "exit 1" }
if (["edit", "write"].includes(tool)) return { filePath: "src/error.ts", content: "" } if (["edit", "write"].includes(tool)) return { filePath: "src/error.ts", content: "" }
if (tool === "apply_patch") return { files: ["src/error.ts"] } if (tool === "patch") return { files: ["src/error.ts"] }
if (tool === "webfetch") return { url: "https://example.com" } if (tool === "webfetch") return { url: "https://example.com" }
if (tool === "websearch") return { query: "failure" } if (tool === "websearch") return { query: "failure" }
if (tool === "task") return { description: "Fail task", subagent_type: "explore" } if (tool === "task") return { description: "Fail task", subagent_type: "explore" }
@@ -120,7 +120,7 @@ function toolPart(
outputLength = 160, outputLength = 160,
): MessagePart { ): MessagePart {
const metadata = const metadata =
tool === "apply_patch" tool === "patch"
? { files: [patchFile(index, "update"), patchFile(index + 1, index % 2 === 0 ? "add" : "delete")] } ? { files: [patchFile(index, "update"), patchFile(index + 1, index % 2 === 0 ? "add" : "delete")] }
: tool === "edit" || tool === "write" : tool === "edit" || tool === "write"
? { ? {
@@ -199,7 +199,7 @@ function turn(index: number): Message[] {
? [toolPart(index, 7, "write", { filePath: `src/generated/write-${index}.ts`, content: code(index, 28) }, 560)] ? [toolPart(index, 7, "write", { filePath: `src/generated/write-${index}.ts`, content: code(index, 28) }, 560)]
: []), : []),
...(index % 8 === 0 ...(index % 8 === 0
? [toolPart(index, 8, "apply_patch", { files: [`src/generated/patch-${index}.ts`] }, 620)] ? [toolPart(index, 8, "patch", { files: [`src/generated/patch-${index}.ts`] }, 620)]
: []), : []),
...(index % 7 === 0 ? [toolPart(index, 4, "bash", { command: "bun typecheck" }, 620)] : []), ...(index % 7 === 0 ? [toolPart(index, 4, "bash", { command: "bun typecheck" }, 620)] : []),
...(index % 10 === 0 ? [toolPart(index, 9, "webfetch", { url: "https://example.com/docs/sample" }, 120)] : []), ...(index % 10 === 0 ? [toolPart(index, 9, "webfetch", { url: "https://example.com/docs/sample" }, 120)] : []),
+1 -1
View File
@@ -55,7 +55,7 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) {
const path = url.pathname const path = url.pathname
if (path === "/global/event" || path === "/event") return sse(route, config.events?.(), config.eventRetry) if (path === "/global/event" || path === "/event") return sse(route, config.events?.(), config.eventRetry)
if (path === "/global/health") return json(route, { healthy: true }) if (path === "/global/health") return json(route, { healthy: true })
if (path === "/experimental/capabilities") return json(route, { backgroundSubagents: false }) if (path === "/experimental/capabilities") return json(route, { backgroundSubagents: true })
if (path === "/permission") if (path === "/permission")
return json(route, typeof config.permissions === "function" ? config.permissions() : (config.permissions ?? [])) return json(route, typeof config.permissions === "function" ? config.permissions() : (config.permissions ?? []))
if (path === "/question") if (path === "/question")
@@ -8,7 +8,7 @@ import type {
QuestionRequest, QuestionRequest,
Session, Session,
SessionStatus, SessionStatus,
SnapshotFileDiff, FileDiffInfo,
Todo, Todo,
} from "@opencode-ai/sdk/v2/client" } from "@opencode-ai/sdk/v2/client"
import type { State, VcsCache } from "./types" import type { State, VcsCache } from "./types"
@@ -188,7 +188,7 @@ export function applyDirectoryEvent(input: {
break break
} }
case "session.diff": { case "session.diff": {
const props = event.properties as { sessionID: string; diff: SnapshotFileDiff[] } const props = event.properties as { sessionID: string; diff: FileDiffInfo[] }
input.setStore("session_diff", props.sessionID, reconcile(list(props.diff), { key: "file" })) input.setStore("session_diff", props.sessionID, reconcile(list(props.diff), { key: "file" }))
break break
} }
@@ -5,7 +5,7 @@ import type {
PermissionRequest, PermissionRequest,
QuestionRequest, QuestionRequest,
SessionStatus, SessionStatus,
SnapshotFileDiff, FileDiffInfo,
Todo, Todo,
} from "@opencode-ai/sdk/v2/client" } from "@opencode-ai/sdk/v2/client"
import { dropSessionCaches, pickSessionCacheEvictions } from "./session-cache" import { dropSessionCaches, pickSessionCacheEvictions } from "./session-cache"
@@ -33,7 +33,7 @@ describe("app session cache", () => {
test("dropSessionCaches clears orphaned parts without message rows", () => { test("dropSessionCaches clears orphaned parts without message rows", () => {
const store: { const store: {
session_status: Record<string, SessionStatus | undefined> session_status: Record<string, SessionStatus | undefined>
session_diff: Record<string, SnapshotFileDiff[] | undefined> session_diff: Record<string, FileDiffInfo[] | undefined>
todo: Record<string, Todo[] | undefined> todo: Record<string, Todo[] | undefined>
message: Record<string, Message[] | undefined> message: Record<string, Message[] | undefined>
part: Record<string, Part[] | undefined> part: Record<string, Part[] | undefined>
@@ -67,7 +67,7 @@ describe("app session cache", () => {
const m = msg("msg_1", "ses_1") const m = msg("msg_1", "ses_1")
const store: { const store: {
session_status: Record<string, SessionStatus | undefined> session_status: Record<string, SessionStatus | undefined>
session_diff: Record<string, SnapshotFileDiff[] | undefined> session_diff: Record<string, FileDiffInfo[] | undefined>
todo: Record<string, Todo[] | undefined> todo: Record<string, Todo[] | undefined>
message: Record<string, Message[] | undefined> message: Record<string, Message[] | undefined>
part: Record<string, Part[] | undefined> part: Record<string, Part[] | undefined>
@@ -4,7 +4,7 @@ import type {
PermissionRequest, PermissionRequest,
QuestionRequest, QuestionRequest,
SessionStatus, SessionStatus,
SnapshotFileDiff, FileDiffInfo,
Todo, Todo,
} from "@opencode-ai/sdk/v2/client" } from "@opencode-ai/sdk/v2/client"
@@ -12,7 +12,7 @@ export const SESSION_CACHE_LIMIT = 40
type SessionCache = { type SessionCache = {
session_status: Record<string, SessionStatus | undefined> session_status: Record<string, SessionStatus | undefined>
session_diff: Record<string, SnapshotFileDiff[] | undefined> session_diff: Record<string, FileDiffInfo[] | undefined>
todo: Record<string, Todo[] | undefined> todo: Record<string, Todo[] | undefined>
message: Record<string, Message[] | undefined> message: Record<string, Message[] | undefined>
part: Record<string, Part[] | undefined> part: Record<string, Part[] | undefined>
@@ -13,7 +13,7 @@ import type {
ReferenceInfo, ReferenceInfo,
Session, Session,
SessionStatus, SessionStatus,
SnapshotFileDiff, FileDiffInfo,
Todo, Todo,
VcsInfo, VcsInfo,
} from "@opencode-ai/sdk/v2/client" } from "@opencode-ai/sdk/v2/client"
@@ -51,7 +51,7 @@ export type State = {
} }
session_working(id: string): boolean session_working(id: string): boolean
session_diff: { session_diff: {
[sessionID: string]: SnapshotFileDiff[] [sessionID: string]: FileDiffInfo[]
} }
todo: { todo: {
[sessionID: string]: Todo[] [sessionID: string]: Todo[]
+3 -3
View File
@@ -8,7 +8,7 @@ import type {
QuestionRequest, QuestionRequest,
Session, Session,
SessionStatus, SessionStatus,
SnapshotFileDiff, FileDiffInfo,
Todo, Todo,
} from "@opencode-ai/sdk/v2/client" } from "@opencode-ai/sdk/v2/client"
import { batch } from "solid-js" import { batch } from "solid-js"
@@ -139,7 +139,7 @@ export function createServerSession(client: OpencodeClient, options?: { retry?:
const [data, setData] = createStore({ const [data, setData] = createStore({
info: {} as Record<string, Session | undefined>, info: {} as Record<string, Session | undefined>,
session_status: {} as Record<string, SessionStatus>, session_status: {} as Record<string, SessionStatus>,
session_diff: {} as Record<string, SnapshotFileDiff[]>, session_diff: {} as Record<string, FileDiffInfo[]>,
todo: {} as Record<string, Todo[]>, todo: {} as Record<string, Todo[]>,
permission: {} as Record<string, PermissionRequest[]>, permission: {} as Record<string, PermissionRequest[]>,
question: {} as Record<string, QuestionRequest[]>, question: {} as Record<string, QuestionRequest[]>,
@@ -769,7 +769,7 @@ export function createServerSession(client: OpencodeClient, options?: { retry?:
return return
} }
case "session.diff": { case "session.diff": {
const props = event.properties as { sessionID: string; diff: SnapshotFileDiff[] } const props = event.properties as { sessionID: string; diff: FileDiffInfo[] }
setData("session_diff", props.sessionID, reconcile(cleanDiffs(props.diff), { key: "file" })) setData("session_diff", props.sessionID, reconcile(cleanDiffs(props.diff), { key: "file" }))
return return
} }
@@ -1,6 +1,6 @@
import { createEffect, onCleanup, type JSX } from "solid-js" import { createEffect, onCleanup, type JSX } from "solid-js"
import { makeEventListener } from "@solid-primitives/event-listener" import { makeEventListener } from "@solid-primitives/event-listener"
import type { SnapshotFileDiff, VcsFileDiff } from "@opencode-ai/sdk/v2" import type { FileDiffInfo, VcsFileDiff } from "@opencode-ai/sdk/v2"
import { SessionReview } from "@opencode-ai/session-ui/session-review" import { SessionReview } from "@opencode-ai/session-ui/session-review"
import type { import type {
SessionReviewCommentActions, SessionReviewCommentActions,
@@ -14,7 +14,7 @@ import type { LineComment } from "@/context/comments"
export type DiffStyle = "unified" | "split" export type DiffStyle = "unified" | "split"
type ReviewDiff = SnapshotFileDiff | VcsFileDiff type ReviewDiff = FileDiffInfo | VcsFileDiff
export interface SessionReviewTabProps { export interface SessionReviewTabProps {
title?: JSX.Element title?: JSX.Element
@@ -8,7 +8,7 @@ import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
import { Mark } from "@opencode-ai/ui/logo" import { Mark } from "@opencode-ai/ui/logo"
import { DragDropProvider, DragDropSensors, DragOverlay, SortableProvider, closestCenter } from "@thisbeyond/solid-dnd" import { DragDropProvider, DragDropSensors, DragOverlay, SortableProvider, closestCenter } from "@thisbeyond/solid-dnd"
import type { DragEvent } from "@thisbeyond/solid-dnd" import type { DragEvent } from "@thisbeyond/solid-dnd"
import type { SnapshotFileDiff, VcsFileDiff } from "@opencode-ai/sdk/v2" import type { FileDiffInfo, VcsFileDiff } from "@opencode-ai/sdk/v2"
import { ConstrainDragYAxis, getDraggableId } from "@/utils/solid-dnd" import { ConstrainDragYAxis, getDraggableId } from "@/utils/solid-dnd"
import { useDialog } from "@opencode-ai/ui/context/dialog" import { useDialog } from "@opencode-ai/ui/context/dialog"
@@ -23,7 +23,6 @@ import { useFile, type SelectedLineRange } from "@/context/file"
import { useLanguage } from "@/context/language" import { useLanguage } from "@/context/language"
import { useLayout } from "@/context/layout" import { useLayout } from "@/context/layout"
import { useSettings } from "@/context/settings" import { useSettings } from "@/context/settings"
import { useSync } from "@/context/sync"
import { createFileTabListSync } from "@/pages/session/file-tab-scroll" import { createFileTabListSync } from "@/pages/session/file-tab-scroll"
import { FileTabContent } from "@/pages/session/file-tabs" import { FileTabContent } from "@/pages/session/file-tabs"
import { import {
@@ -36,15 +35,9 @@ import {
import { setSessionHandoff } from "@/pages/session/handoff" import { setSessionHandoff } from "@/pages/session/handoff"
import { useSessionLayout } from "@/pages/session/session-layout" import { useSessionLayout } from "@/pages/session/session-layout"
type RenderDiff = (SnapshotFileDiff & { file: string }) | VcsFileDiff
function renderDiff(value: SnapshotFileDiff | VcsFileDiff): value is RenderDiff {
return typeof value.file === "string"
}
export function SessionSidePanel(props: { export function SessionSidePanel(props: {
canReview: () => boolean canReview: () => boolean
diffs: () => (SnapshotFileDiff | VcsFileDiff)[] diffs: () => (FileDiffInfo | VcsFileDiff)[]
diffsReady: () => boolean diffsReady: () => boolean
empty: () => string empty: () => string
hasReview: () => boolean hasReview: () => boolean
@@ -59,7 +52,6 @@ export function SessionSidePanel(props: {
}) { }) {
const layout = useLayout() const layout = useLayout()
const settings = useSettings() const settings = useSettings()
const sync = useSync()
const file = useFile() const file = useFile()
const language = useLanguage() const language = useLanguage()
const command = useCommand() const command = useCommand()
@@ -88,7 +80,7 @@ export function SessionSidePanel(props: {
}) })
const treeWidth = createMemo(() => (fileOpen() ? `${layout.fileTree.width()}px` : "0px")) const treeWidth = createMemo(() => (fileOpen() ? `${layout.fileTree.width()}px` : "0px"))
const diffs = createMemo(() => props.diffs().filter(renderDiff)) const diffs = createMemo(() => props.diffs())
const diffFiles = createMemo(() => diffs().map((d) => d.file)) const diffFiles = createMemo(() => diffs().map((d) => d.file))
const kinds = createMemo(() => { const kinds = createMemo(() => {
const merge = (a: "add" | "del" | "mix" | undefined, b: "add" | "del" | "mix") => { const merge = (a: "add" | "del" | "mix" | undefined, b: "add" | "del" | "mix") => {
@@ -1245,7 +1245,7 @@ export function MessageTimeline(props: {
const value = row() const value = row()
if (value._tag !== "AssistantPart" || value.group.type !== "part") return false if (value._tag !== "AssistantPart" || value.group.type !== "part") return false
const part = getMsgPart(value.group.ref.messageID, value.group.ref.partID) const part = getMsgPart(value.group.ref.messageID, value.group.ref.partID)
return part?.type === "tool" && ["edit", "write", "apply_patch"].includes(part.tool) return part?.type === "tool" && ["edit", "write", "patch", "apply_patch"].includes(part.tool)
} }
const [ready, setReady] = createSignal(initialItem.size <= timelineFallbackItemSize || !asyncFile()) const [ready, setReady] = createSignal(initialItem.size <= timelineFallbackItemSize || !asyncFile())
let contentMeasureFrame: number | undefined let contentMeasureFrame: number | undefined
@@ -1,17 +1,13 @@
import type { SnapshotFileDiff, VcsFileDiff } from "@opencode-ai/sdk/v2" import type { FileDiffInfo, VcsFileDiff } from "@opencode-ai/sdk/v2"
import type { Kind } from "@/components/file-tree-v2" import type { Kind } from "@/components/file-tree-v2"
import { normalizeFileTreeV2Path } from "@/components/file-tree-v2-model" import { normalizeFileTreeV2Path } from "@/components/file-tree-v2-model"
export type RenderDiff = (SnapshotFileDiff & { file: string }) | VcsFileDiff export type RenderDiff = FileDiffInfo | VcsFileDiff
export function normalizePath(p: string) { export function normalizePath(p: string) {
return normalizeFileTreeV2Path(p) return normalizeFileTreeV2Path(p)
} }
export function filterRenderableDiff(value: SnapshotFileDiff | VcsFileDiff): value is RenderDiff {
return typeof value.file === "string"
}
export function reviewDiffKinds(diffs: RenderDiff[]) { export function reviewDiffKinds(diffs: RenderDiff[]) {
const merge = (a: Kind | undefined, b: Kind) => { const merge = (a: Kind | undefined, b: Kind) => {
if (!a) return b if (!a) return b
@@ -1,5 +1,5 @@
import { createMemo, createSignal, Show, type JSX } from "solid-js" import { createMemo, createSignal, Show, type JSX } from "solid-js"
import type { SnapshotFileDiff, VcsFileDiff } from "@opencode-ai/sdk/v2" import type { FileDiffInfo, VcsFileDiff } from "@opencode-ai/sdk/v2"
import { import {
SESSION_REVIEW_V2_SIDEBAR_WIDTH_MAX, SESSION_REVIEW_V2_SIDEBAR_WIDTH_MAX,
SESSION_REVIEW_V2_SIDEBAR_WIDTH_MIN, SESSION_REVIEW_V2_SIDEBAR_WIDTH_MIN,
@@ -21,16 +21,11 @@ import type {
import FileTreeV2 from "@/components/file-tree-v2" import FileTreeV2 from "@/components/file-tree-v2"
import { useLanguage } from "@/context/language" import { useLanguage } from "@/context/language"
import { useSDK } from "@/context/sdk" import { useSDK } from "@/context/sdk"
import { import { filterReviewFiles, reviewDiffKinds, type RenderDiff } from "@/pages/session/v2/review-diff-kinds"
filterRenderableDiff,
filterReviewFiles,
reviewDiffKinds,
type RenderDiff,
} from "@/pages/session/v2/review-diff-kinds"
import type { ReviewPanelV2State } from "@/pages/session/v2/review-panel-v2-state" import type { ReviewPanelV2State } from "@/pages/session/v2/review-panel-v2-state"
import { applyFileListKeyDown, SessionFileListV2 } from "@/pages/session/v2/session-file-list-v2" import { applyFileListKeyDown, SessionFileListV2 } from "@/pages/session/v2/session-file-list-v2"
type ReviewDiff = SnapshotFileDiff | VcsFileDiff type ReviewDiff = FileDiffInfo | VcsFileDiff
export type ReviewPanelV2Props = { export type ReviewPanelV2Props = {
title?: JSX.Element title?: JSX.Element
@@ -54,7 +49,7 @@ export type ReviewPanelV2Props = {
export function ReviewPanelV2(props: ReviewPanelV2Props) { export function ReviewPanelV2(props: ReviewPanelV2Props) {
const sdk = useSDK() const sdk = useSDK()
const diffs = createMemo(() => props.diffs().filter(filterRenderableDiff)) const diffs = createMemo(() => props.diffs())
const filteredFiles = createMemo(() => const filteredFiles = createMemo(() =>
filterReviewFiles( filterReviewFiles(
diffs().map((diff) => diff.file), diffs().map((diff) => diff.file),
+2 -2
View File
@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test" import { describe, expect, test } from "bun:test"
import type { SnapshotFileDiff } from "@opencode-ai/sdk/v2" import type { FileDiffInfo } from "@opencode-ai/sdk/v2"
import type { Message } from "@opencode-ai/sdk/v2/client" import type { Message } from "@opencode-ai/sdk/v2/client"
import { diffs, message } from "./diffs" import { diffs, message } from "./diffs"
@@ -9,7 +9,7 @@ const item = {
additions: 1, additions: 1,
deletions: 1, deletions: 1,
status: "modified", status: "modified",
} satisfies SnapshotFileDiff } satisfies FileDiffInfo
describe("diffs", () => { describe("diffs", () => {
test("keeps valid arrays", () => { test("keeps valid arrays", () => {
+2 -2
View File
@@ -1,7 +1,7 @@
import type { SnapshotFileDiff, VcsFileDiff } from "@opencode-ai/sdk/v2" import type { FileDiffInfo } from "@opencode-ai/sdk/v2"
import type { Message } from "@opencode-ai/sdk/v2/client" import type { Message } from "@opencode-ai/sdk/v2/client"
type Diff = SnapshotFileDiff | VcsFileDiff type Diff = FileDiffInfo
function diff(value: unknown): value is Diff { function diff(value: unknown): value is Diff {
if (!value || typeof value !== "object" || Array.isArray(value)) return false if (!value || typeof value !== "object" || Array.isArray(value)) return false
+3 -2
View File
@@ -37,7 +37,8 @@ function defaultCost(model: CurrentModel) {
export function runAgent(input: CurrentAgent): RunAgent { export function runAgent(input: CurrentAgent): RunAgent {
return { return {
name: input.id, id: input.id,
name: input.name,
description: input.description, description: input.description,
mode: input.mode, mode: input.mode,
hidden: input.hidden, hidden: input.hidden,
@@ -53,7 +54,7 @@ export function runCommand(input: CurrentCommand): RunCommand {
export function runSkill(input: CurrentSkill): RunCommand { export function runSkill(input: CurrentSkill): RunCommand {
return { return {
name: input.name, name: input.id,
description: input.description, description: input.description,
source: "skill", source: "skill",
} }
+2 -2
View File
@@ -628,11 +628,11 @@ function emitEdit(state: State): void {
function emitPatch(state: State): void { function emitPatch(state: State): void {
const file = path.join(process.cwd(), "src", "demo-format.ts") const file = path.join(process.cwd(), "src", "demo-format.ts")
const ref = make(state, "apply_patch", { const ref = make(state, "patch", {
patchText: "*** Begin Patch\n*** End Patch", patchText: "*** Begin Patch\n*** End Patch",
}) })
doneTool(state, ref, { doneTool(state, ref, {
title: "apply_patch", title: "patch",
output: "", output: "",
metadata: { metadata: {
files: [ files: [
+2 -2
View File
@@ -333,10 +333,10 @@ export function createPromptState(input: PromptInput): PromptState {
.map((item) => ({ .map((item) => ({
kind: "mention", kind: "mention",
display: "@" + item.name, display: "@" + item.name,
value: item.name, value: item.id,
part: { part: {
type: "agent", type: "agent",
name: item.name, name: item.id,
source: { source: {
start: 0, start: 0,
end: 0, end: 0,
-2
View File
@@ -84,7 +84,6 @@ type RunFooterOptions = {
theme: RunTheme theme: RunTheme
keymap: Keymap<Renderable, KeyEvent> keymap: Keymap<Renderable, KeyEvent>
tuiConfig: RunTuiConfig tuiConfig: RunTuiConfig
backgroundSubagents: boolean
diffStyle: RunDiffStyle diffStyle: RunDiffStyle
onPermissionReply: (input: PermissionReply) => void | Promise<void> onPermissionReply: (input: PermissionReply) => void | Promise<void>
onQuestionReply: (input: QuestionReply) => void | Promise<void> onQuestionReply: (input: QuestionReply) => void | Promise<void>
@@ -326,7 +325,6 @@ export class RunFooter implements FooterApi {
theme: footer.theme, theme: footer.theme,
diffStyle: options.diffStyle, diffStyle: options.diffStyle,
tuiConfig: options.tuiConfig, tuiConfig: options.tuiConfig,
backgroundSubagents: options.backgroundSubagents,
history: footer.history, history: footer.history,
agent: options.agentLabel, agent: options.agentLabel,
onSubmit: footer.handlePrompt, onSubmit: footer.handlePrompt,
+1 -4
View File
@@ -89,7 +89,6 @@ type RunFooterViewProps = {
theme: () => RunTheme theme: () => RunTheme
diffStyle?: RunDiffStyle diffStyle?: RunDiffStyle
tuiConfig: RunTuiConfig tuiConfig: RunTuiConfig
backgroundSubagents: boolean
history?: () => RunPrompt[] history?: () => RunPrompt[]
agent: string agent: string
onSubmit: (input: RunPrompt) => boolean onSubmit: (input: RunPrompt) => boolean
@@ -169,9 +168,7 @@ export function RunFooterView(props: RunFooterViewProps) {
return tabs().findIndex((item) => item.sessionID === sessionID) + 1 return tabs().findIndex((item) => item.sessionID === sessionID) + 1
}) })
const foregroundSubagents = createMemo( const foregroundSubagents = createMemo(() => activeTabs().some((item) => !item.background))
() => props.backgroundSubagents && activeTabs().some((item) => !item.background),
)
const model = createMemo(() => { const model = createMemo(() => {
const current = props.currentModel() const current = props.currentModel()
return current ? modelInfo(props.providers(), current) : { model: props.state().model, provider: undefined } return current ? modelInfo(props.providers(), current) : { model: props.state().model, provider: undefined }
-4
View File
@@ -1,6 +1,5 @@
import { NodeFileSystem } from "@effect/platform-node" import { NodeFileSystem } from "@effect/platform-node"
import { OpenCode, type OpenCodeClient } from "@opencode-ai/client/promise" import { OpenCode, type OpenCodeClient } from "@opencode-ai/client/promise"
import { truthy } from "@opencode-ai/core/flag/flag"
import { Global } from "@opencode-ai/core/global" import { Global } from "@opencode-ai/core/global"
import { Effect } from "effect" import { Effect } from "effect"
import path from "node:path" import path from "node:path"
@@ -84,9 +83,6 @@ export async function runMini(input: MiniCommandInput) {
files: [], files: [],
initialInput, initialInput,
thinking: true, thinking: true,
backgroundSubagents:
truthy("OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS") ||
(process.env.OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS === undefined && truthy("OPENCODE_EXPERIMENTAL")),
replay: input.replay ?? true, replay: input.replay ?? true,
replayLimit: input.replayLimit, replayLimit: input.replayLimit,
demo: input.demo, demo: input.demo,
-1
View File
@@ -281,7 +281,6 @@ export async function runNonInteractivePrompt(input: Input) {
metadata: { metadata: {
structured: event.data.structured, structured: event.data.structured,
content: event.data.content, content: event.data.content,
outputPaths: event.data.outputPaths,
result: event.data.result, result: event.data.result,
providerCall: current.provider, providerCall: current.provider,
providerResult: { executed: event.data.executed, state: event.data.resultState }, providerResult: { executed: event.data.executed, state: event.data.resultState },
+5 -8
View File
@@ -89,12 +89,11 @@ async function execute(input: RunCommandInput, prepared: Prepared, transport: Tr
!explicitModel && !sessionModel !explicitModel && !sessionModel
? await client.model ? await client.model
.default({ location: { directory: cwd, workspace } }) .default({ location: { directory: cwd, workspace } })
.then((result) => .then((result) => (result.data ? { providerID: result.data.providerID, modelID: result.data.id } : undefined))
result.data ? { providerID: result.data.providerID, modelID: result.data.id } : undefined,
)
: undefined : undefined
const model = pickRunModel(explicitModel, input.variant, sessionModel, defaultModel) const model = pickRunModel(explicitModel, input.variant, sessionModel, defaultModel)
if (input.variant && !model) return reportError(input, "Cannot select a variant before selecting a model", session?.id) if (input.variant && !model)
return reportError(input, "Cannot select a variant before selecting a model", session?.id)
if (model) { if (model) {
await waitForCatalogReady({ sdk: client, directory: cwd, workspace, model }) await waitForCatalogReady({ sdk: client, directory: cwd, workspace, model })
const available = await client.model.list({ location: { directory: cwd, workspace } }) const available = await client.model.list({ location: { directory: cwd, workspace } })
@@ -112,9 +111,7 @@ async function execute(input: RunCommandInput, prepared: Prepared, transport: Tr
if (!session && input.title !== undefined) { if (!session && input.title !== undefined) {
await client.session.rename({ await client.session.rename({
sessionID: selected.id, sessionID: selected.id,
title: title: input.title || prepared.message.slice(0, 50) + (prepared.message.length > 50 ? "..." : ""),
input.title ||
prepared.message.slice(0, 50) + (prepared.message.length > 50 ? "..." : ""),
}) })
} }
@@ -200,7 +197,7 @@ async function validateAgent(client: OpenCodeClient, directory: string, name?: s
warning(`failed to list agents${server ? ` from ${server}` : ""}. Falling back to default agent`) warning(`failed to list agents${server ? ` from ${server}` : ""}. Falling back to default agent`)
return return
} }
const agent = agents.find((item) => item.name === name) const agent = agents.find((item) => item.id === name)
if (!agent) { if (!agent) {
warning(`agent "${name}" not found. Falling back to default agent`) warning(`agent "${name}" not found. Falling back to default agent`)
return return
@@ -64,7 +64,6 @@ export type LifecycleInput = {
model: RunInput["model"] model: RunInput["model"]
variant: string | undefined variant: string | undefined
tuiConfig: RunTuiConfig | Promise<RunTuiConfig> tuiConfig: RunTuiConfig | Promise<RunTuiConfig>
backgroundSubagents: boolean
onPermissionReply: (input: PermissionReply) => void | Promise<void> onPermissionReply: (input: PermissionReply) => void | Promise<void>
onQuestionReply: (input: QuestionReply) => void | Promise<void> onQuestionReply: (input: QuestionReply) => void | Promise<void>
onQuestionReject: (input: QuestionReject) => void | Promise<void> onQuestionReject: (input: QuestionReject) => void | Promise<void>
@@ -236,7 +235,6 @@ export async function createRuntimeLifecycle(input: LifecycleInput): Promise<Lif
wrote, wrote,
keymap, keymap,
tuiConfig, tuiConfig,
backgroundSubagents: input.backgroundSubagents,
diffStyle: tuiConfig.diff_style ?? "auto", diffStyle: tuiConfig.diff_style ?? "auto",
onPermissionReply: input.onPermissionReply, onPermissionReply: input.onPermissionReply,
onQuestionReply: input.onQuestionReply, onQuestionReply: input.onQuestionReply,
-5
View File
@@ -56,7 +56,6 @@ type RunRuntimeInput = {
files: RunInput["files"] files: RunInput["files"]
initialInput?: string initialInput?: string
thinking: boolean thinking: boolean
backgroundSubagents: boolean
replay?: boolean replay?: boolean
replayLimit?: number replayLimit?: number
demo?: RunInput["demo"] demo?: RunInput["demo"]
@@ -75,7 +74,6 @@ type RunDeferredInput = {
files: RunInput["files"] files: RunInput["files"]
initialInput?: string initialInput?: string
thinking: boolean thinking: boolean
backgroundSubagents: boolean
replay?: boolean replay?: boolean
replayLimit?: number replayLimit?: number
demo?: RunInput["demo"] demo?: RunInput["demo"]
@@ -270,7 +268,6 @@ async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDep
model: state.model, model: state.model,
variant: state.activeVariant, variant: state.activeVariant,
tuiConfig: tuiConfigTask, tuiConfig: tuiConfigTask,
backgroundSubagents: input.backgroundSubagents,
onPermissionReply: async (next) => { onPermissionReply: async (next) => {
if (state.demo?.permission(next)) { if (state.demo?.permission(next)) {
return return
@@ -876,7 +873,6 @@ export async function runInteractiveDeferredMode(input: RunDeferredInput, deps?:
files: input.files, files: input.files,
initialInput: input.initialInput, initialInput: input.initialInput,
thinking: input.thinking, thinking: input.thinking,
backgroundSubagents: input.backgroundSubagents,
replay: input.replay, replay: input.replay,
replayLimit: input.replayLimit, replayLimit: input.replayLimit,
demo: input.demo, demo: input.demo,
@@ -928,7 +924,6 @@ export async function runInteractiveMode(
files: input.files, files: input.files,
initialInput: input.initialInput, initialInput: input.initialInput,
thinking: input.thinking, thinking: input.thinking,
backgroundSubagents: input.backgroundSubagents,
replay: input.replay, replay: input.replay,
replayLimit: input.replayLimit, replayLimit: input.replayLimit,
demo: input.demo, demo: input.demo,
+6 -8
View File
@@ -16,7 +16,7 @@
// backgrounding is intentionally absent: subagent jobs block the parent // backgrounding is intentionally absent: subagent jobs block the parent
// session, so only whole-session `v2.session.background(parentID)` exists. // session, so only whole-session `v2.session.background(parentID)` exists.
import type { EventSubscribeOutput, OpenCodeClient } from "@opencode-ai/client/promise" import type { EventSubscribeOutput, OpenCodeClient } from "@opencode-ai/client/promise"
import type { SessionMessage, SessionMessageAssistantTool, ToolPart } from "@opencode-ai/sdk/v2" import type { SessionMessageAssistantTool, SessionMessageInfo, ToolPart } from "@opencode-ai/sdk/v2"
import { Locale } from "@opencode-ai/tui/util/locale" import { Locale } from "@opencode-ai/tui/util/locale"
import type { FooterSubagentDetail, FooterSubagentState, FooterSubagentTab, StreamCommit } from "./types" import type { FooterSubagentDetail, FooterSubagentState, FooterSubagentTab, StreamCommit } from "./types"
@@ -54,7 +54,7 @@ export function legacyTool(input: {
callID: tool.id, callID: tool.id,
tool: tool.name, tool: tool.name,
} }
if (tool.state.status === "pending") { if (tool.state.status === "streaming") {
return { return {
...base, ...base,
state: { status: "pending", input: {}, raw: tool.state.input }, state: { status: "pending", input: {}, raw: tool.state.input },
@@ -83,7 +83,6 @@ export function legacyTool(input: {
metadata: { metadata: {
structured: tool.state.structured, structured: tool.state.structured,
content: tool.state.content, content: tool.state.content,
outputPaths: tool.state.outputPaths,
result: tool.state.result, result: tool.state.result,
providerCall, providerCall,
providerResult, providerResult,
@@ -179,7 +178,7 @@ export type SubagentTrackerInput = {
export type SubagentTracker = { export type SubagentTracker = {
main(event: V2Event): void main(event: V2Event): void
foreign(sessionID: string, event: V2Event): void foreign(sessionID: string, event: V2Event): void
hydrate(next: { messages: SessionMessage[]; active: Record<string, unknown> }): Promise<void> hydrate(next: { messages: SessionMessageInfo[]; active: Record<string, unknown> }): Promise<void>
select(sessionID: string | undefined): void select(sessionID: string | undefined): void
snapshot(): FooterSubagentState snapshot(): FooterSubagentState
} }
@@ -316,7 +315,7 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
messageID, messageID,
tool: item, tool: item,
}) })
if (item.state.status === "pending") return if (item.state.status === "streaming") return
child.callIDs.add(item.id) child.callIDs.add(item.id)
if (item.state.status === "running") { if (item.state.status === "running") {
setFrame(child, `tool:${item.id}`, toolCommit(part, "start")) setFrame(child, `tool:${item.id}`, toolCommit(part, "start"))
@@ -327,7 +326,7 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
setFrame(child, `tool:${item.id}`, toolCommit(part, "final")) setFrame(child, `tool:${item.id}`, toolCommit(part, "final"))
} }
const rebuild = (child: ChildState, messages: SessionMessage[]) => { const rebuild = (child: ChildState, messages: SessionMessageInfo[]) => {
child.frames = [] child.frames = []
child.text.clear() child.text.clear()
child.projectedText.clear() child.projectedText.clear()
@@ -412,7 +411,7 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
for (const [id, prompt] of pendingPrompts) { for (const [id, prompt] of pendingPrompts) {
if (!child.prompts.has(id)) child.prompts.set(id, prompt) if (!child.prompts.has(id)) child.prompts.set(id, prompt)
} }
rebuild(child, structuredClone(response.data).toReversed() as SessionMessage[]) rebuild(child, structuredClone(response.data).toReversed() as SessionMessageInfo[])
for (const [id, tool] of pendingTools) { for (const [id, tool] of pendingTools) {
if (!child.finishedTools.has(id) && !child.tools.has(id)) child.tools.set(id, tool) if (!child.finishedTools.has(id) && !child.tools.has(id)) child.tools.set(id, tool)
} }
@@ -622,7 +621,6 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
input: current?.input ?? {}, input: current?.input ?? {},
structured: event.data.structured, structured: event.data.structured,
content: event.data.content, content: event.data.content,
outputPaths: event.data.outputPaths,
result: event.data.result, result: event.data.result,
}, },
time: { time: {
+15 -16
View File
@@ -2,7 +2,7 @@ import type { EventSubscribeOutput, OpenCodeClient } from "@opencode-ai/client/p
import type { import type {
PermissionRequest, PermissionRequest,
QuestionRequest, QuestionRequest,
SessionMessage, SessionMessageInfo,
SessionMessageAssistantTool, SessionMessageAssistantTool,
} from "@opencode-ai/sdk/v2" } from "@opencode-ai/sdk/v2"
import { Event } from "@opencode-ai/schema/event" import { Event } from "@opencode-ai/schema/event"
@@ -389,7 +389,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
messageID, messageID,
tool: item, tool: item,
}) })
if (item.state.status === "pending") return if (item.state.status === "streaming") return
if (item.state.status === "running") { if (item.state.status === "running") {
if (state.tools.get(item.id)?.running) return if (state.tools.get(item.id)?.running) return
state.tools.set(item.id, { state.tools.set(item.id, {
@@ -417,7 +417,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
]) ])
} }
const renderMessage = (message: SessionMessage, render: boolean, reuseVisibleWait: boolean) => { const renderMessage = (message: SessionMessageInfo, render: boolean, reuseVisibleWait: boolean) => {
if (message.type === "user") { if (message.type === "user") {
const waiting = state.wait?.messageID === message.id const waiting = state.wait?.messageID === message.id
if (waiting && state.wait) state.wait.promoted = true if (waiting && state.wait) state.wait.promoted = true
@@ -438,34 +438,34 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
return return
} }
if (message.type === "shell") { if (message.type === "shell") {
state.shellCommands.set(message.shell.id, message.shell.command) state.shellCommands.set(message.shellID, message.command)
if (state.shellWait?.messageID === message.id) state.shellWait.callID = message.shell.id if (state.shellWait?.messageID === message.id) state.shellWait.callID = message.shellID
const completed = message.time.completed !== undefined const completed = message.time.completed !== undefined
if (!render) { if (!render) {
// Suppressed history: mark settled shells rendered so live redelivery // Suppressed history: mark settled shells rendered so live redelivery
// stays silent. A still-running shell stays unmarked and renders in // stays silent. A still-running shell stays unmarked and renders in
// full when its live shell.ended event arrives. // full when its live shell.ended event arrives.
if (completed) { if (completed) {
state.shellStarted.add(message.shell.id) state.shellStarted.add(message.shellID)
state.shellEnded.add(message.shell.id) state.shellEnded.add(message.shellID)
} }
return return
} }
if (!state.shellStarted.has(message.shell.id)) { if (!state.shellStarted.has(message.shellID)) {
state.shellStarted.add(message.shell.id) state.shellStarted.add(message.shellID)
write([ write([
shellCommit(message.shell.id, message.shell.command, { shellCommit(message.shellID, message.command, {
text: "running shell", text: "running shell",
phase: "start", phase: "start",
toolState: "running", toolState: "running",
}), }),
]) ])
} }
if (completed && message.output && !state.shellEnded.has(message.shell.id)) { if (completed && message.output && !state.shellEnded.has(message.shellID)) {
state.shellEnded.add(message.shell.id) state.shellEnded.add(message.shellID)
write(shellTerminal(message.shell.id, message.shell.command, message.shell, message.output)) write(shellTerminal(message.shellID, message.command, message, message.output))
} }
if (completed && state.shellWait?.callID === message.shell.id) state.shellWait.resolve() if (completed && state.shellWait?.callID === message.shellID) state.shellWait.resolve()
return return
} }
if (message.type !== "assistant") return if (message.type !== "assistant") return
@@ -534,7 +534,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
input.sdk.question.list({ sessionID: input.sessionID }), input.sdk.question.list({ sessionID: input.sessionID }),
input.sdk.session.active(), input.sdk.session.active(),
]) ])
const projected = structuredClone(messages.data).toReversed() as SessionMessage[] const projected = structuredClone(messages.data).toReversed() as SessionMessageInfo[]
for (const message of projected) renderMessage(message, next.render, next.reuseVisibleWait) for (const message of projected) renderMessage(message, next.render, next.reuseVisibleWait)
state.permissions = permissions.map(permission) state.permissions = permissions.map(permission)
state.questions = questions.map(question) state.questions = questions.map(question)
@@ -762,7 +762,6 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
input: current?.input ?? {}, input: current?.input ?? {},
structured: event.data.structured, structured: event.data.structured,
content: event.data.content, content: event.data.content,
outputPaths: event.data.outputPaths,
result: event.data.result, result: event.data.result,
}, },
time: { created: current?.started ?? event.created, ran: current?.started, completed: event.created }, time: { created: current?.started ?? event.created, ran: current?.started, completed: event.created },
+2 -2
View File
@@ -119,7 +119,7 @@ type ToolName =
| "bash" | "bash"
| "write" | "write"
| "edit" | "edit"
| "apply_patch" | "patch"
| "batch" | "batch"
| "task" | "task"
| "todowrite" | "todowrite"
@@ -1094,7 +1094,7 @@ const TOOL_RULES = {
}, },
permission: permEdit, permission: permEdit,
}, },
apply_patch: { patch: {
view: { view: {
output: false, output: false,
final: true, final: true,
+1 -1
View File
@@ -113,6 +113,7 @@ export type FooterQueuedPrompt = {
} }
export type RunAgent = { export type RunAgent = {
id: string
name: string name: string
description?: string description?: string
mode: "subagent" | "primary" | "all" mode: "subagent" | "primary" | "all"
@@ -135,7 +136,6 @@ export type RunInput = {
files: RunFilePart[] files: RunFilePart[]
initialInput?: string initialInput?: string
thinking: boolean thinking: boolean
backgroundSubagents: boolean
demo?: boolean demo?: boolean
} }
+2 -3
View File
@@ -3,15 +3,14 @@ import { compile, emitEffectImported, emitEffectShape, emitPromise, write } from
import { import {
ClientApi, ClientApi,
effectOmitEndpoints, effectOmitEndpoints,
endpointNames,
groupNames, groupNames,
promiseOmitEndpoints, promiseOmitEndpoints,
} from "@opencode-ai/protocol/client" } from "@opencode-ai/protocol/client"
import { Effect } from "effect" import { Effect } from "effect"
import { fileURLToPath } from "url" import { fileURLToPath } from "url"
const promiseContract = compile(ClientApi, { groupNames, endpointNames, omitEndpoints: promiseOmitEndpoints }) const promiseContract = compile(ClientApi, { groupNames, omitEndpoints: promiseOmitEndpoints })
const effectContract = compile(ClientApi, { groupNames, endpointNames, omitEndpoints: effectOmitEndpoints }) const effectContract = compile(ClientApi, { groupNames, omitEndpoints: effectOmitEndpoints })
await Effect.runPromise( await Effect.runPromise(
Effect.all( Effect.all(
-1
View File
@@ -1,7 +1,6 @@
export { export {
ClientApi, ClientApi,
effectOmitEndpoints, effectOmitEndpoints,
endpointNames,
groupNames, groupNames,
promiseOmitEndpoints, promiseOmitEndpoints,
} from "@opencode-ai/protocol/client" } from "@opencode-ai/protocol/client"
+132 -116
View File
@@ -111,158 +111,167 @@ export type Endpoint4_8Input = {
export type Endpoint4_8Output = EffectValue<ReturnType<RawClient["server.session"]["session.rename"]>> export type Endpoint4_8Output = EffectValue<ReturnType<RawClient["server.session"]["session.rename"]>>
export type SessionRenameOperation<E = never> = (input: Endpoint4_8Input) => Effect.Effect<Endpoint4_8Output, E> export type SessionRenameOperation<E = never> = (input: Endpoint4_8Input) => Effect.Effect<Endpoint4_8Output, E>
type Endpoint4_9Request = Parameters<RawClient["server.session"]["session.prompt"]>[0] type Endpoint4_9Request = Parameters<RawClient["server.session"]["session.move"]>[0]
export type Endpoint4_9Input = { export type Endpoint4_9Input = {
readonly sessionID: Endpoint4_9Request["params"]["sessionID"] readonly sessionID: Endpoint4_9Request["params"]["sessionID"]
readonly id?: Endpoint4_9Request["payload"]["id"] readonly destination: Endpoint4_9Request["payload"]["destination"]
readonly prompt: Endpoint4_9Request["payload"]["prompt"] readonly moveChanges?: Endpoint4_9Request["payload"]["moveChanges"]
readonly delivery?: Endpoint4_9Request["payload"]["delivery"]
readonly resume?: Endpoint4_9Request["payload"]["resume"]
} }
export type Endpoint4_9Output = EffectValue<ReturnType<RawClient["server.session"]["session.prompt"]>>["data"] export type Endpoint4_9Output = EffectValue<ReturnType<RawClient["server.session"]["session.move"]>>
export type SessionPromptOperation<E = never> = (input: Endpoint4_9Input) => Effect.Effect<Endpoint4_9Output, E> export type SessionMoveOperation<E = never> = (input: Endpoint4_9Input) => Effect.Effect<Endpoint4_9Output, E>
type Endpoint4_10Request = Parameters<RawClient["server.session"]["session.command"]>[0] type Endpoint4_10Request = Parameters<RawClient["server.session"]["session.prompt"]>[0]
export type Endpoint4_10Input = { export type Endpoint4_10Input = {
readonly sessionID: Endpoint4_10Request["params"]["sessionID"] readonly sessionID: Endpoint4_10Request["params"]["sessionID"]
readonly id?: Endpoint4_10Request["payload"]["id"] readonly id?: Endpoint4_10Request["payload"]["id"]
readonly command: Endpoint4_10Request["payload"]["command"] readonly prompt: Endpoint4_10Request["payload"]["prompt"]
readonly arguments?: Endpoint4_10Request["payload"]["arguments"]
readonly agent?: Endpoint4_10Request["payload"]["agent"]
readonly model?: Endpoint4_10Request["payload"]["model"]
readonly files?: Endpoint4_10Request["payload"]["files"]
readonly agents?: Endpoint4_10Request["payload"]["agents"]
readonly delivery?: Endpoint4_10Request["payload"]["delivery"] readonly delivery?: Endpoint4_10Request["payload"]["delivery"]
readonly resume?: Endpoint4_10Request["payload"]["resume"] readonly resume?: Endpoint4_10Request["payload"]["resume"]
} }
export type Endpoint4_10Output = EffectValue<ReturnType<RawClient["server.session"]["session.command"]>>["data"] export type Endpoint4_10Output = EffectValue<ReturnType<RawClient["server.session"]["session.prompt"]>>["data"]
export type SessionCommandOperation<E = never> = (input: Endpoint4_10Input) => Effect.Effect<Endpoint4_10Output, E> export type SessionPromptOperation<E = never> = (input: Endpoint4_10Input) => Effect.Effect<Endpoint4_10Output, E>
type Endpoint4_11Request = Parameters<RawClient["server.session"]["session.skill"]>[0] type Endpoint4_11Request = Parameters<RawClient["server.session"]["session.command"]>[0]
export type Endpoint4_11Input = { export type Endpoint4_11Input = {
readonly sessionID: Endpoint4_11Request["params"]["sessionID"] readonly sessionID: Endpoint4_11Request["params"]["sessionID"]
readonly id?: Endpoint4_11Request["payload"]["id"] readonly id?: Endpoint4_11Request["payload"]["id"]
readonly skill: Endpoint4_11Request["payload"]["skill"] readonly command: Endpoint4_11Request["payload"]["command"]
readonly arguments?: Endpoint4_11Request["payload"]["arguments"]
readonly agent?: Endpoint4_11Request["payload"]["agent"]
readonly model?: Endpoint4_11Request["payload"]["model"]
readonly files?: Endpoint4_11Request["payload"]["files"]
readonly agents?: Endpoint4_11Request["payload"]["agents"]
readonly delivery?: Endpoint4_11Request["payload"]["delivery"]
readonly resume?: Endpoint4_11Request["payload"]["resume"] readonly resume?: Endpoint4_11Request["payload"]["resume"]
} }
export type Endpoint4_11Output = EffectValue<ReturnType<RawClient["server.session"]["session.skill"]>> export type Endpoint4_11Output = EffectValue<ReturnType<RawClient["server.session"]["session.command"]>>["data"]
export type SessionSkillOperation<E = never> = (input: Endpoint4_11Input) => Effect.Effect<Endpoint4_11Output, E> export type SessionCommandOperation<E = never> = (input: Endpoint4_11Input) => Effect.Effect<Endpoint4_11Output, E>
type Endpoint4_12Request = Parameters<RawClient["server.session"]["session.synthetic"]>[0] type Endpoint4_12Request = Parameters<RawClient["server.session"]["session.skill"]>[0]
export type Endpoint4_12Input = { export type Endpoint4_12Input = {
readonly sessionID: Endpoint4_12Request["params"]["sessionID"] readonly sessionID: Endpoint4_12Request["params"]["sessionID"]
readonly text: Endpoint4_12Request["payload"]["text"] readonly id?: Endpoint4_12Request["payload"]["id"]
readonly description?: Endpoint4_12Request["payload"]["description"] readonly skill: Endpoint4_12Request["payload"]["skill"]
readonly metadata?: Endpoint4_12Request["payload"]["metadata"]
readonly resume?: Endpoint4_12Request["payload"]["resume"] readonly resume?: Endpoint4_12Request["payload"]["resume"]
} }
export type Endpoint4_12Output = EffectValue<ReturnType<RawClient["server.session"]["session.synthetic"]>> export type Endpoint4_12Output = EffectValue<ReturnType<RawClient["server.session"]["session.skill"]>>
export type SessionSyntheticOperation<E = never> = (input: Endpoint4_12Input) => Effect.Effect<Endpoint4_12Output, E> export type SessionSkillOperation<E = never> = (input: Endpoint4_12Input) => Effect.Effect<Endpoint4_12Output, E>
type Endpoint4_13Request = Parameters<RawClient["server.session"]["session.shell"]>[0] type Endpoint4_13Request = Parameters<RawClient["server.session"]["session.synthetic"]>[0]
export type Endpoint4_13Input = { export type Endpoint4_13Input = {
readonly sessionID: Endpoint4_13Request["params"]["sessionID"] readonly sessionID: Endpoint4_13Request["params"]["sessionID"]
readonly id?: Endpoint4_13Request["payload"]["id"] readonly text: Endpoint4_13Request["payload"]["text"]
readonly command: Endpoint4_13Request["payload"]["command"] readonly description?: Endpoint4_13Request["payload"]["description"]
readonly metadata?: Endpoint4_13Request["payload"]["metadata"]
readonly resume?: Endpoint4_13Request["payload"]["resume"]
} }
export type Endpoint4_13Output = EffectValue<ReturnType<RawClient["server.session"]["session.shell"]>> export type Endpoint4_13Output = EffectValue<ReturnType<RawClient["server.session"]["session.synthetic"]>>
export type SessionShellOperation<E = never> = (input: Endpoint4_13Input) => Effect.Effect<Endpoint4_13Output, E> export type SessionSyntheticOperation<E = never> = (input: Endpoint4_13Input) => Effect.Effect<Endpoint4_13Output, E>
type Endpoint4_14Request = Parameters<RawClient["server.session"]["session.compact"]>[0] type Endpoint4_14Request = Parameters<RawClient["server.session"]["session.shell"]>[0]
export type Endpoint4_14Input = { export type Endpoint4_14Input = {
readonly sessionID: Endpoint4_14Request["params"]["sessionID"] readonly sessionID: Endpoint4_14Request["params"]["sessionID"]
readonly id?: Endpoint4_14Request["payload"]["id"] readonly id?: Endpoint4_14Request["payload"]["id"]
readonly command: Endpoint4_14Request["payload"]["command"]
} }
export type Endpoint4_14Output = EffectValue<ReturnType<RawClient["server.session"]["session.compact"]>>["data"] export type Endpoint4_14Output = EffectValue<ReturnType<RawClient["server.session"]["session.shell"]>>
export type SessionCompactOperation<E = never> = (input: Endpoint4_14Input) => Effect.Effect<Endpoint4_14Output, E> export type SessionShellOperation<E = never> = (input: Endpoint4_14Input) => Effect.Effect<Endpoint4_14Output, E>
type Endpoint4_15Request = Parameters<RawClient["server.session"]["session.wait"]>[0] type Endpoint4_15Request = Parameters<RawClient["server.session"]["session.compact"]>[0]
export type Endpoint4_15Input = { readonly sessionID: Endpoint4_15Request["params"]["sessionID"] } export type Endpoint4_15Input = {
export type Endpoint4_15Output = EffectValue<ReturnType<RawClient["server.session"]["session.wait"]>> readonly sessionID: Endpoint4_15Request["params"]["sessionID"]
export type SessionWaitOperation<E = never> = (input: Endpoint4_15Input) => Effect.Effect<Endpoint4_15Output, E> readonly id?: Endpoint4_15Request["payload"]["id"]
type Endpoint4_16Request = Parameters<RawClient["server.session"]["session.revert.stage"]>[0]
export type Endpoint4_16Input = {
readonly sessionID: Endpoint4_16Request["params"]["sessionID"]
readonly messageID: Endpoint4_16Request["payload"]["messageID"]
readonly files?: Endpoint4_16Request["payload"]["files"]
} }
export type Endpoint4_16Output = EffectValue<ReturnType<RawClient["server.session"]["session.revert.stage"]>>["data"] export type Endpoint4_15Output = EffectValue<ReturnType<RawClient["server.session"]["session.compact"]>>["data"]
export type SessionRevertStageOperation<E = never> = (input: Endpoint4_16Input) => Effect.Effect<Endpoint4_16Output, E> export type SessionCompactOperation<E = never> = (input: Endpoint4_15Input) => Effect.Effect<Endpoint4_15Output, E>
type Endpoint4_17Request = Parameters<RawClient["server.session"]["session.revert.clear"]>[0] type Endpoint4_16Request = Parameters<RawClient["server.session"]["session.wait"]>[0]
export type Endpoint4_17Input = { readonly sessionID: Endpoint4_17Request["params"]["sessionID"] } export type Endpoint4_16Input = { readonly sessionID: Endpoint4_16Request["params"]["sessionID"] }
export type Endpoint4_17Output = EffectValue<ReturnType<RawClient["server.session"]["session.revert.clear"]>> export type Endpoint4_16Output = EffectValue<ReturnType<RawClient["server.session"]["session.wait"]>>
export type SessionRevertClearOperation<E = never> = (input: Endpoint4_17Input) => Effect.Effect<Endpoint4_17Output, E> export type SessionWaitOperation<E = never> = (input: Endpoint4_16Input) => Effect.Effect<Endpoint4_16Output, E>
type Endpoint4_18Request = Parameters<RawClient["server.session"]["session.revert.commit"]>[0] type Endpoint4_17Request = Parameters<RawClient["server.session"]["session.revert.stage"]>[0]
export type Endpoint4_17Input = {
readonly sessionID: Endpoint4_17Request["params"]["sessionID"]
readonly messageID: Endpoint4_17Request["payload"]["messageID"]
readonly files?: Endpoint4_17Request["payload"]["files"]
}
export type Endpoint4_17Output = EffectValue<ReturnType<RawClient["server.session"]["session.revert.stage"]>>["data"]
export type SessionRevertStageOperation<E = never> = (input: Endpoint4_17Input) => Effect.Effect<Endpoint4_17Output, E>
type Endpoint4_18Request = Parameters<RawClient["server.session"]["session.revert.clear"]>[0]
export type Endpoint4_18Input = { readonly sessionID: Endpoint4_18Request["params"]["sessionID"] } export type Endpoint4_18Input = { readonly sessionID: Endpoint4_18Request["params"]["sessionID"] }
export type Endpoint4_18Output = EffectValue<ReturnType<RawClient["server.session"]["session.revert.commit"]>> export type Endpoint4_18Output = EffectValue<ReturnType<RawClient["server.session"]["session.revert.clear"]>>
export type SessionRevertCommitOperation<E = never> = (input: Endpoint4_18Input) => Effect.Effect<Endpoint4_18Output, E> export type SessionRevertClearOperation<E = never> = (input: Endpoint4_18Input) => Effect.Effect<Endpoint4_18Output, E>
type Endpoint4_19Request = Parameters<RawClient["server.session"]["session.context"]>[0] type Endpoint4_19Request = Parameters<RawClient["server.session"]["session.revert.commit"]>[0]
export type Endpoint4_19Input = { readonly sessionID: Endpoint4_19Request["params"]["sessionID"] } export type Endpoint4_19Input = { readonly sessionID: Endpoint4_19Request["params"]["sessionID"] }
export type Endpoint4_19Output = EffectValue<ReturnType<RawClient["server.session"]["session.context"]>>["data"] export type Endpoint4_19Output = EffectValue<ReturnType<RawClient["server.session"]["session.revert.commit"]>>
export type SessionContextOperation<E = never> = (input: Endpoint4_19Input) => Effect.Effect<Endpoint4_19Output, E> export type SessionRevertCommitOperation<E = never> = (input: Endpoint4_19Input) => Effect.Effect<Endpoint4_19Output, E>
type Endpoint4_20Request = Parameters<RawClient["server.session"]["session.instructions.entry.list"]>[0] type Endpoint4_20Request = Parameters<RawClient["server.session"]["session.context"]>[0]
export type Endpoint4_20Input = { readonly sessionID: Endpoint4_20Request["params"]["sessionID"] } export type Endpoint4_20Input = { readonly sessionID: Endpoint4_20Request["params"]["sessionID"] }
export type Endpoint4_20Output = EffectValue< export type Endpoint4_20Output = EffectValue<ReturnType<RawClient["server.session"]["session.context"]>>["data"]
export type SessionContextOperation<E = never> = (input: Endpoint4_20Input) => Effect.Effect<Endpoint4_20Output, E>
type Endpoint4_21Request = Parameters<RawClient["server.session"]["session.instructions.entry.list"]>[0]
export type Endpoint4_21Input = { readonly sessionID: Endpoint4_21Request["params"]["sessionID"] }
export type Endpoint4_21Output = EffectValue<
ReturnType<RawClient["server.session"]["session.instructions.entry.list"]> ReturnType<RawClient["server.session"]["session.instructions.entry.list"]>
>["data"] >["data"]
export type SessionInstructionsEntryListOperation<E = never> = ( export type SessionInstructionsEntryListOperation<E = never> = (
input: Endpoint4_20Input,
) => Effect.Effect<Endpoint4_20Output, E>
type Endpoint4_21Request = Parameters<RawClient["server.session"]["session.instructions.entry.put"]>[0]
export type Endpoint4_21Input = {
readonly sessionID: Endpoint4_21Request["params"]["sessionID"]
readonly key: Endpoint4_21Request["params"]["key"]
readonly value: Endpoint4_21Request["payload"]["value"]
}
export type Endpoint4_21Output = EffectValue<ReturnType<RawClient["server.session"]["session.instructions.entry.put"]>>
export type SessionInstructionsEntryPutOperation<E = never> = (
input: Endpoint4_21Input, input: Endpoint4_21Input,
) => Effect.Effect<Endpoint4_21Output, E> ) => Effect.Effect<Endpoint4_21Output, E>
type Endpoint4_22Request = Parameters<RawClient["server.session"]["session.instructions.entry.remove"]>[0] type Endpoint4_22Request = Parameters<RawClient["server.session"]["session.instructions.entry.put"]>[0]
export type Endpoint4_22Input = { export type Endpoint4_22Input = {
readonly sessionID: Endpoint4_22Request["params"]["sessionID"] readonly sessionID: Endpoint4_22Request["params"]["sessionID"]
readonly key: Endpoint4_22Request["params"]["key"] readonly key: Endpoint4_22Request["params"]["key"]
readonly value: Endpoint4_22Request["payload"]["value"]
} }
export type Endpoint4_22Output = EffectValue< export type Endpoint4_22Output = EffectValue<ReturnType<RawClient["server.session"]["session.instructions.entry.put"]>>
ReturnType<RawClient["server.session"]["session.instructions.entry.remove"]> export type SessionInstructionsEntryPutOperation<E = never> = (
>
export type SessionInstructionsEntryRemoveOperation<E = never> = (
input: Endpoint4_22Input, input: Endpoint4_22Input,
) => Effect.Effect<Endpoint4_22Output, E> ) => Effect.Effect<Endpoint4_22Output, E>
type Endpoint4_23Request = Parameters<RawClient["server.session"]["session.log"]>[0] type Endpoint4_23Request = Parameters<RawClient["server.session"]["session.instructions.entry.remove"]>[0]
export type Endpoint4_23Input = { export type Endpoint4_23Input = {
readonly sessionID: Endpoint4_23Request["params"]["sessionID"] readonly sessionID: Endpoint4_23Request["params"]["sessionID"]
readonly after?: Endpoint4_23Request["query"]["after"] readonly key: Endpoint4_23Request["params"]["key"]
readonly follow?: Endpoint4_23Request["query"]["follow"]
} }
export type Endpoint4_23Output = StreamValue<EffectValue<ReturnType<RawClient["server.session"]["session.log"]>>> export type Endpoint4_23Output = EffectValue<
export type SessionLogOperation<E = never> = (input: Endpoint4_23Input) => Stream.Stream<Endpoint4_23Output, E> ReturnType<RawClient["server.session"]["session.instructions.entry.remove"]>
>
export type SessionInstructionsEntryRemoveOperation<E = never> = (
input: Endpoint4_23Input,
) => Effect.Effect<Endpoint4_23Output, E>
type Endpoint4_24Request = Parameters<RawClient["server.session"]["session.interrupt"]>[0] type Endpoint4_24Request = Parameters<RawClient["server.session"]["session.log"]>[0]
export type Endpoint4_24Input = { readonly sessionID: Endpoint4_24Request["params"]["sessionID"] } export type Endpoint4_24Input = {
export type Endpoint4_24Output = EffectValue<ReturnType<RawClient["server.session"]["session.interrupt"]>> readonly sessionID: Endpoint4_24Request["params"]["sessionID"]
export type SessionInterruptOperation<E = never> = (input: Endpoint4_24Input) => Effect.Effect<Endpoint4_24Output, E> readonly after?: Endpoint4_24Request["query"]["after"]
readonly follow?: Endpoint4_24Request["query"]["follow"]
}
export type Endpoint4_24Output = StreamValue<EffectValue<ReturnType<RawClient["server.session"]["session.log"]>>>
export type SessionLogOperation<E = never> = (input: Endpoint4_24Input) => Stream.Stream<Endpoint4_24Output, E>
type Endpoint4_25Request = Parameters<RawClient["server.session"]["session.background"]>[0] type Endpoint4_25Request = Parameters<RawClient["server.session"]["session.interrupt"]>[0]
export type Endpoint4_25Input = { readonly sessionID: Endpoint4_25Request["params"]["sessionID"] } export type Endpoint4_25Input = { readonly sessionID: Endpoint4_25Request["params"]["sessionID"] }
export type Endpoint4_25Output = EffectValue<ReturnType<RawClient["server.session"]["session.background"]>> export type Endpoint4_25Output = EffectValue<ReturnType<RawClient["server.session"]["session.interrupt"]>>
export type SessionBackgroundOperation<E = never> = (input: Endpoint4_25Input) => Effect.Effect<Endpoint4_25Output, E> export type SessionInterruptOperation<E = never> = (input: Endpoint4_25Input) => Effect.Effect<Endpoint4_25Output, E>
type Endpoint4_26Request = Parameters<RawClient["server.session"]["session.message"]>[0] type Endpoint4_26Request = Parameters<RawClient["server.session"]["session.background"]>[0]
export type Endpoint4_26Input = { export type Endpoint4_26Input = { readonly sessionID: Endpoint4_26Request["params"]["sessionID"] }
readonly sessionID: Endpoint4_26Request["params"]["sessionID"] export type Endpoint4_26Output = EffectValue<ReturnType<RawClient["server.session"]["session.background"]>>
readonly messageID: Endpoint4_26Request["params"]["messageID"] export type SessionBackgroundOperation<E = never> = (input: Endpoint4_26Input) => Effect.Effect<Endpoint4_26Output, E>
type Endpoint4_27Request = Parameters<RawClient["server.session"]["session.message"]>[0]
export type Endpoint4_27Input = {
readonly sessionID: Endpoint4_27Request["params"]["sessionID"]
readonly messageID: Endpoint4_27Request["params"]["messageID"]
} }
export type Endpoint4_26Output = EffectValue<ReturnType<RawClient["server.session"]["session.message"]>>["data"] export type Endpoint4_27Output = EffectValue<ReturnType<RawClient["server.session"]["session.message"]>>["data"]
export type SessionMessageOperation<E = never> = (input: Endpoint4_26Input) => Effect.Effect<Endpoint4_26Output, E> export type SessionMessageOperation<E = never> = (input: Endpoint4_27Input) => Effect.Effect<Endpoint4_27Output, E>
export interface SessionApi<E = never> { export interface SessionApi<E = never> {
readonly list: SessionListOperation<E> readonly list: SessionListOperation<E>
@@ -274,6 +283,7 @@ export interface SessionApi<E = never> {
readonly switchAgent: SessionSwitchAgentOperation<E> readonly switchAgent: SessionSwitchAgentOperation<E>
readonly switchModel: SessionSwitchModelOperation<E> readonly switchModel: SessionSwitchModelOperation<E>
readonly rename: SessionRenameOperation<E> readonly rename: SessionRenameOperation<E>
readonly move: SessionMoveOperation<E>
readonly prompt: SessionPromptOperation<E> readonly prompt: SessionPromptOperation<E>
readonly command: SessionCommandOperation<E> readonly command: SessionCommandOperation<E>
readonly skill: SessionSkillOperation<E> readonly skill: SessionSkillOperation<E>
@@ -281,9 +291,11 @@ export interface SessionApi<E = never> {
readonly shell: SessionShellOperation<E> readonly shell: SessionShellOperation<E>
readonly compact: SessionCompactOperation<E> readonly compact: SessionCompactOperation<E>
readonly wait: SessionWaitOperation<E> readonly wait: SessionWaitOperation<E>
readonly revertStage: SessionRevertStageOperation<E> readonly revert: {
readonly revertClear: SessionRevertClearOperation<E> readonly stage: SessionRevertStageOperation<E>
readonly revertCommit: SessionRevertCommitOperation<E> readonly clear: SessionRevertClearOperation<E>
readonly commit: SessionRevertCommitOperation<E>
}
readonly context: SessionContextOperation<E> readonly context: SessionContextOperation<E>
readonly instructions: { readonly instructions: {
readonly entry: { readonly entry: {
@@ -428,11 +440,15 @@ export type IntegrationAttemptCancelOperation<E = never> = (
export interface IntegrationApi<E = never> { export interface IntegrationApi<E = never> {
readonly list: IntegrationListOperation<E> readonly list: IntegrationListOperation<E>
readonly get: IntegrationGetOperation<E> readonly get: IntegrationGetOperation<E>
readonly connectKey: IntegrationConnectKeyOperation<E> readonly connect: {
readonly connectOauth: IntegrationConnectOauthOperation<E> readonly key: IntegrationConnectKeyOperation<E>
readonly attemptStatus: IntegrationAttemptStatusOperation<E> readonly oauth: IntegrationConnectOauthOperation<E>
readonly attemptComplete: IntegrationAttemptCompleteOperation<E> }
readonly attemptCancel: IntegrationAttemptCancelOperation<E> readonly attempt: {
readonly status: IntegrationAttemptStatusOperation<E>
readonly complete: IntegrationAttemptCompleteOperation<E>
readonly cancel: IntegrationAttemptCancelOperation<E>
}
} }
type Endpoint10_0Request = Parameters<RawClient["server.mcp"]["mcp.list"]>[0] type Endpoint10_0Request = Parameters<RawClient["server.mcp"]["mcp.list"]>[0]
@@ -443,11 +459,13 @@ export type ServerMcpListOperation<E = never> = (input?: Endpoint10_0Input) => E
type Endpoint10_1Request = Parameters<RawClient["server.mcp"]["mcp.resource.catalog"]>[0] type Endpoint10_1Request = Parameters<RawClient["server.mcp"]["mcp.resource.catalog"]>[0]
export type Endpoint10_1Input = { readonly location?: Endpoint10_1Request["query"]["location"] } export type Endpoint10_1Input = { readonly location?: Endpoint10_1Request["query"]["location"] }
export type Endpoint10_1Output = EffectValue<ReturnType<RawClient["server.mcp"]["mcp.resource.catalog"]>> export type Endpoint10_1Output = EffectValue<ReturnType<RawClient["server.mcp"]["mcp.resource.catalog"]>>
export type ServerMcpCatalogOperation<E = never> = (input?: Endpoint10_1Input) => Effect.Effect<Endpoint10_1Output, E> export type ServerMcpResourceCatalogOperation<E = never> = (
input?: Endpoint10_1Input,
) => Effect.Effect<Endpoint10_1Output, E>
export interface ServerMcpApi<E = never> { export interface ServerMcpApi<E = never> {
readonly list: ServerMcpListOperation<E> readonly list: ServerMcpListOperation<E>
readonly catalog: ServerMcpCatalogOperation<E> readonly resource: { readonly catalog: ServerMcpResourceCatalogOperation<E> }
} }
type Endpoint11_0Request = Parameters<RawClient["server.credential"]["credential.update"]>[0] type Endpoint11_0Request = Parameters<RawClient["server.credential"]["credential.update"]>[0]
@@ -497,7 +515,7 @@ export interface ProjectApi<E = never> {
type Endpoint13_0Request = Parameters<RawClient["server.form"]["form.request.list"]>[0] type Endpoint13_0Request = Parameters<RawClient["server.form"]["form.request.list"]>[0]
export type Endpoint13_0Input = { readonly location?: Endpoint13_0Request["query"]["location"] } export type Endpoint13_0Input = { readonly location?: Endpoint13_0Request["query"]["location"] }
export type Endpoint13_0Output = EffectValue<ReturnType<RawClient["server.form"]["form.request.list"]>> export type Endpoint13_0Output = EffectValue<ReturnType<RawClient["server.form"]["form.request.list"]>>
export type FormListRequestsOperation<E = never> = (input?: Endpoint13_0Input) => Effect.Effect<Endpoint13_0Output, E> export type FormRequestListOperation<E = never> = (input?: Endpoint13_0Input) => Effect.Effect<Endpoint13_0Output, E>
type Endpoint13_1Request = Parameters<RawClient["server.form"]["session.form.list"]>[0] type Endpoint13_1Request = Parameters<RawClient["server.form"]["session.form.list"]>[0]
export type Endpoint13_1Input = { readonly sessionID: Endpoint13_1Request["params"]["sessionID"] } export type Endpoint13_1Input = { readonly sessionID: Endpoint13_1Request["params"]["sessionID"] }
@@ -551,7 +569,7 @@ export type Endpoint13_6Output = EffectValue<ReturnType<RawClient["server.form"]
export type FormCancelOperation<E = never> = (input: Endpoint13_6Input) => Effect.Effect<Endpoint13_6Output, E> export type FormCancelOperation<E = never> = (input: Endpoint13_6Input) => Effect.Effect<Endpoint13_6Output, E>
export interface FormApi<E = never> { export interface FormApi<E = never> {
readonly listRequests: FormListRequestsOperation<E> readonly request: { readonly list: FormRequestListOperation<E> }
readonly list: FormListOperation<E> readonly list: FormListOperation<E>
readonly create: FormCreateOperation<E> readonly create: FormCreateOperation<E>
readonly get: FormGetOperation<E> readonly get: FormGetOperation<E>
@@ -563,7 +581,7 @@ export interface FormApi<E = never> {
type Endpoint14_0Request = Parameters<RawClient["server.permission"]["permission.request.list"]>[0] type Endpoint14_0Request = Parameters<RawClient["server.permission"]["permission.request.list"]>[0]
export type Endpoint14_0Input = { readonly location?: Endpoint14_0Request["query"]["location"] } export type Endpoint14_0Input = { readonly location?: Endpoint14_0Request["query"]["location"] }
export type Endpoint14_0Output = EffectValue<ReturnType<RawClient["server.permission"]["permission.request.list"]>> export type Endpoint14_0Output = EffectValue<ReturnType<RawClient["server.permission"]["permission.request.list"]>>
export type PermissionListRequestsOperation<E = never> = ( export type PermissionRequestListOperation<E = never> = (
input?: Endpoint14_0Input, input?: Endpoint14_0Input,
) => Effect.Effect<Endpoint14_0Output, E> ) => Effect.Effect<Endpoint14_0Output, E>
@@ -572,14 +590,14 @@ export type Endpoint14_1Input = { readonly projectID?: Endpoint14_1Request["quer
export type Endpoint14_1Output = EffectValue< export type Endpoint14_1Output = EffectValue<
ReturnType<RawClient["server.permission"]["permission.saved.list"]> ReturnType<RawClient["server.permission"]["permission.saved.list"]>
>["data"] >["data"]
export type PermissionListSavedOperation<E = never> = ( export type PermissionSavedListOperation<E = never> = (
input?: Endpoint14_1Input, input?: Endpoint14_1Input,
) => Effect.Effect<Endpoint14_1Output, E> ) => Effect.Effect<Endpoint14_1Output, E>
type Endpoint14_2Request = Parameters<RawClient["server.permission"]["permission.saved.remove"]>[0] type Endpoint14_2Request = Parameters<RawClient["server.permission"]["permission.saved.remove"]>[0]
export type Endpoint14_2Input = { readonly id: Endpoint14_2Request["params"]["id"] } export type Endpoint14_2Input = { readonly id: Endpoint14_2Request["params"]["id"] }
export type Endpoint14_2Output = EffectValue<ReturnType<RawClient["server.permission"]["permission.saved.remove"]>> export type Endpoint14_2Output = EffectValue<ReturnType<RawClient["server.permission"]["permission.saved.remove"]>>
export type PermissionRemoveSavedOperation<E = never> = ( export type PermissionSavedRemoveOperation<E = never> = (
input: Endpoint14_2Input, input: Endpoint14_2Input,
) => Effect.Effect<Endpoint14_2Output, E> ) => Effect.Effect<Endpoint14_2Output, E>
@@ -627,9 +645,8 @@ export type Endpoint14_6Output = EffectValue<ReturnType<RawClient["server.permis
export type PermissionReplyOperation<E = never> = (input: Endpoint14_6Input) => Effect.Effect<Endpoint14_6Output, E> export type PermissionReplyOperation<E = never> = (input: Endpoint14_6Input) => Effect.Effect<Endpoint14_6Output, E>
export interface PermissionApi<E = never> { export interface PermissionApi<E = never> {
readonly listRequests: PermissionListRequestsOperation<E> readonly request: { readonly list: PermissionRequestListOperation<E> }
readonly listSaved: PermissionListSavedOperation<E> readonly saved: { readonly list: PermissionSavedListOperation<E>; readonly remove: PermissionSavedRemoveOperation<E> }
readonly removeSaved: PermissionRemoveSavedOperation<E>
readonly create: PermissionCreateOperation<E> readonly create: PermissionCreateOperation<E>
readonly list: PermissionListOperation<E> readonly list: PermissionListOperation<E>
readonly get: PermissionGetOperation<E> readonly get: PermissionGetOperation<E>
@@ -798,7 +815,7 @@ export interface ShellApi<E = never> {
type Endpoint21_0Request = Parameters<RawClient["server.question"]["question.request.list"]>[0] type Endpoint21_0Request = Parameters<RawClient["server.question"]["question.request.list"]>[0]
export type Endpoint21_0Input = { readonly location?: Endpoint21_0Request["query"]["location"] } export type Endpoint21_0Input = { readonly location?: Endpoint21_0Request["query"]["location"] }
export type Endpoint21_0Output = EffectValue<ReturnType<RawClient["server.question"]["question.request.list"]>> export type Endpoint21_0Output = EffectValue<ReturnType<RawClient["server.question"]["question.request.list"]>>
export type QuestionListRequestsOperation<E = never> = ( export type QuestionRequestListOperation<E = never> = (
input?: Endpoint21_0Input, input?: Endpoint21_0Input,
) => Effect.Effect<Endpoint21_0Output, E> ) => Effect.Effect<Endpoint21_0Output, E>
@@ -825,7 +842,7 @@ export type Endpoint21_3Output = EffectValue<ReturnType<RawClient["server.questi
export type QuestionRejectOperation<E = never> = (input: Endpoint21_3Input) => Effect.Effect<Endpoint21_3Output, E> export type QuestionRejectOperation<E = never> = (input: Endpoint21_3Input) => Effect.Effect<Endpoint21_3Output, E>
export interface QuestionApi<E = never> { export interface QuestionApi<E = never> {
readonly listRequests: QuestionListRequestsOperation<E> readonly request: { readonly list: QuestionRequestListOperation<E> }
readonly list: QuestionListOperation<E> readonly list: QuestionListOperation<E>
readonly reply: QuestionReplyOperation<E> readonly reply: QuestionReplyOperation<E>
readonly reject: QuestionRejectOperation<E> readonly reject: QuestionRejectOperation<E>
@@ -895,16 +912,15 @@ export interface VcsApi<E = never> {
} }
export type Endpoint25_0Output = EffectValue<ReturnType<RawClient["server.debug"]["debug.location"]>> export type Endpoint25_0Output = EffectValue<ReturnType<RawClient["server.debug"]["debug.location"]>>
export type DebugLocationOperation<E = never> = () => Effect.Effect<Endpoint25_0Output, E> export type DebugLocationListOperation<E = never> = () => Effect.Effect<Endpoint25_0Output, E>
type Endpoint25_1Request = Parameters<RawClient["server.debug"]["debug.location.evict"]>[0] type Endpoint25_1Request = Parameters<RawClient["server.debug"]["debug.location.evict"]>[0]
export type Endpoint25_1Input = { readonly location?: Endpoint25_1Request["query"]["location"] } export type Endpoint25_1Input = { readonly location?: Endpoint25_1Request["query"]["location"] }
export type Endpoint25_1Output = EffectValue<ReturnType<RawClient["server.debug"]["debug.location.evict"]>> export type Endpoint25_1Output = EffectValue<ReturnType<RawClient["server.debug"]["debug.location.evict"]>>
export type DebugEvictLocationOperation<E = never> = (input?: Endpoint25_1Input) => Effect.Effect<Endpoint25_1Output, E> export type DebugLocationEvictOperation<E = never> = (input?: Endpoint25_1Input) => Effect.Effect<Endpoint25_1Output, E>
export interface DebugApi<E = never> { export interface DebugApi<E = never> {
readonly location: DebugLocationOperation<E> readonly location: { readonly list: DebugLocationListOperation<E>; readonly evict: DebugLocationEvictOperation<E> }
readonly evictLocation: DebugEvictLocationOperation<E>
} }
export interface AppApi<E = never> { export interface AppApi<E = never> {
+130 -121
View File
@@ -141,15 +141,27 @@ const Endpoint4_8 = (raw: RawClient["server.session"]) => (input: Endpoint4_8Inp
Effect.mapError(mapClientError), Effect.mapError(mapClientError),
) )
type Endpoint4_9Request = Parameters<RawClient["server.session"]["session.prompt"]>[0] type Endpoint4_9Request = Parameters<RawClient["server.session"]["session.move"]>[0]
type Endpoint4_9Input = { type Endpoint4_9Input = {
readonly sessionID: Endpoint4_9Request["params"]["sessionID"] readonly sessionID: Endpoint4_9Request["params"]["sessionID"]
readonly id?: Endpoint4_9Request["payload"]["id"] readonly destination: Endpoint4_9Request["payload"]["destination"]
readonly prompt: Endpoint4_9Request["payload"]["prompt"] readonly moveChanges?: Endpoint4_9Request["payload"]["moveChanges"]
readonly delivery?: Endpoint4_9Request["payload"]["delivery"]
readonly resume?: Endpoint4_9Request["payload"]["resume"]
} }
const Endpoint4_9 = (raw: RawClient["server.session"]) => (input: Endpoint4_9Input) => const Endpoint4_9 = (raw: RawClient["server.session"]) => (input: Endpoint4_9Input) =>
raw["session.move"]({
params: { sessionID: input["sessionID"] },
payload: { destination: input["destination"], moveChanges: input["moveChanges"] },
}).pipe(Effect.mapError(mapClientError))
type Endpoint4_10Request = Parameters<RawClient["server.session"]["session.prompt"]>[0]
type Endpoint4_10Input = {
readonly sessionID: Endpoint4_10Request["params"]["sessionID"]
readonly id?: Endpoint4_10Request["payload"]["id"]
readonly prompt: Endpoint4_10Request["payload"]["prompt"]
readonly delivery?: Endpoint4_10Request["payload"]["delivery"]
readonly resume?: Endpoint4_10Request["payload"]["resume"]
}
const Endpoint4_10 = (raw: RawClient["server.session"]) => (input: Endpoint4_10Input) =>
raw["session.prompt"]({ raw["session.prompt"]({
params: { sessionID: input["sessionID"] }, params: { sessionID: input["sessionID"] },
payload: { id: input["id"], prompt: input["prompt"], delivery: input["delivery"], resume: input["resume"] }, payload: { id: input["id"], prompt: input["prompt"], delivery: input["delivery"], resume: input["resume"] },
@@ -158,20 +170,20 @@ const Endpoint4_9 = (raw: RawClient["server.session"]) => (input: Endpoint4_9Inp
Effect.map((value) => value.data), Effect.map((value) => value.data),
) )
type Endpoint4_10Request = Parameters<RawClient["server.session"]["session.command"]>[0] type Endpoint4_11Request = Parameters<RawClient["server.session"]["session.command"]>[0]
type Endpoint4_10Input = { type Endpoint4_11Input = {
readonly sessionID: Endpoint4_10Request["params"]["sessionID"] readonly sessionID: Endpoint4_11Request["params"]["sessionID"]
readonly id?: Endpoint4_10Request["payload"]["id"] readonly id?: Endpoint4_11Request["payload"]["id"]
readonly command: Endpoint4_10Request["payload"]["command"] readonly command: Endpoint4_11Request["payload"]["command"]
readonly arguments?: Endpoint4_10Request["payload"]["arguments"] readonly arguments?: Endpoint4_11Request["payload"]["arguments"]
readonly agent?: Endpoint4_10Request["payload"]["agent"] readonly agent?: Endpoint4_11Request["payload"]["agent"]
readonly model?: Endpoint4_10Request["payload"]["model"] readonly model?: Endpoint4_11Request["payload"]["model"]
readonly files?: Endpoint4_10Request["payload"]["files"] readonly files?: Endpoint4_11Request["payload"]["files"]
readonly agents?: Endpoint4_10Request["payload"]["agents"] readonly agents?: Endpoint4_11Request["payload"]["agents"]
readonly delivery?: Endpoint4_10Request["payload"]["delivery"] readonly delivery?: Endpoint4_11Request["payload"]["delivery"]
readonly resume?: Endpoint4_10Request["payload"]["resume"] readonly resume?: Endpoint4_11Request["payload"]["resume"]
} }
const Endpoint4_10 = (raw: RawClient["server.session"]) => (input: Endpoint4_10Input) => const Endpoint4_11 = (raw: RawClient["server.session"]) => (input: Endpoint4_11Input) =>
raw["session.command"]({ raw["session.command"]({
params: { sessionID: input["sessionID"] }, params: { sessionID: input["sessionID"] },
payload: { payload: {
@@ -190,28 +202,28 @@ const Endpoint4_10 = (raw: RawClient["server.session"]) => (input: Endpoint4_10I
Effect.map((value) => value.data), Effect.map((value) => value.data),
) )
type Endpoint4_11Request = Parameters<RawClient["server.session"]["session.skill"]>[0] type Endpoint4_12Request = Parameters<RawClient["server.session"]["session.skill"]>[0]
type Endpoint4_11Input = { type Endpoint4_12Input = {
readonly sessionID: Endpoint4_11Request["params"]["sessionID"] readonly sessionID: Endpoint4_12Request["params"]["sessionID"]
readonly id?: Endpoint4_11Request["payload"]["id"] readonly id?: Endpoint4_12Request["payload"]["id"]
readonly skill: Endpoint4_11Request["payload"]["skill"] readonly skill: Endpoint4_12Request["payload"]["skill"]
readonly resume?: Endpoint4_11Request["payload"]["resume"] readonly resume?: Endpoint4_12Request["payload"]["resume"]
} }
const Endpoint4_11 = (raw: RawClient["server.session"]) => (input: Endpoint4_11Input) => const Endpoint4_12 = (raw: RawClient["server.session"]) => (input: Endpoint4_12Input) =>
raw["session.skill"]({ raw["session.skill"]({
params: { sessionID: input["sessionID"] }, params: { sessionID: input["sessionID"] },
payload: { id: input["id"], skill: input["skill"], resume: input["resume"] }, payload: { id: input["id"], skill: input["skill"], resume: input["resume"] },
}).pipe(Effect.mapError(mapClientError)) }).pipe(Effect.mapError(mapClientError))
type Endpoint4_12Request = Parameters<RawClient["server.session"]["session.synthetic"]>[0] type Endpoint4_13Request = Parameters<RawClient["server.session"]["session.synthetic"]>[0]
type Endpoint4_12Input = { type Endpoint4_13Input = {
readonly sessionID: Endpoint4_12Request["params"]["sessionID"] readonly sessionID: Endpoint4_13Request["params"]["sessionID"]
readonly text: Endpoint4_12Request["payload"]["text"] readonly text: Endpoint4_13Request["payload"]["text"]
readonly description?: Endpoint4_12Request["payload"]["description"] readonly description?: Endpoint4_13Request["payload"]["description"]
readonly metadata?: Endpoint4_12Request["payload"]["metadata"] readonly metadata?: Endpoint4_13Request["payload"]["metadata"]
readonly resume?: Endpoint4_12Request["payload"]["resume"] readonly resume?: Endpoint4_13Request["payload"]["resume"]
} }
const Endpoint4_12 = (raw: RawClient["server.session"]) => (input: Endpoint4_12Input) => const Endpoint4_13 = (raw: RawClient["server.session"]) => (input: Endpoint4_13Input) =>
raw["session.synthetic"]({ raw["session.synthetic"]({
params: { sessionID: input["sessionID"] }, params: { sessionID: input["sessionID"] },
payload: { payload: {
@@ -222,41 +234,41 @@ const Endpoint4_12 = (raw: RawClient["server.session"]) => (input: Endpoint4_12I
}, },
}).pipe(Effect.mapError(mapClientError)) }).pipe(Effect.mapError(mapClientError))
type Endpoint4_13Request = Parameters<RawClient["server.session"]["session.shell"]>[0] type Endpoint4_14Request = Parameters<RawClient["server.session"]["session.shell"]>[0]
type Endpoint4_13Input = { type Endpoint4_14Input = {
readonly sessionID: Endpoint4_13Request["params"]["sessionID"] readonly sessionID: Endpoint4_14Request["params"]["sessionID"]
readonly id?: Endpoint4_13Request["payload"]["id"] readonly id?: Endpoint4_14Request["payload"]["id"]
readonly command: Endpoint4_13Request["payload"]["command"] readonly command: Endpoint4_14Request["payload"]["command"]
} }
const Endpoint4_13 = (raw: RawClient["server.session"]) => (input: Endpoint4_13Input) => const Endpoint4_14 = (raw: RawClient["server.session"]) => (input: Endpoint4_14Input) =>
raw["session.shell"]({ raw["session.shell"]({
params: { sessionID: input["sessionID"] }, params: { sessionID: input["sessionID"] },
payload: { id: input["id"], command: input["command"] }, payload: { id: input["id"], command: input["command"] },
}).pipe(Effect.mapError(mapClientError)) }).pipe(Effect.mapError(mapClientError))
type Endpoint4_14Request = Parameters<RawClient["server.session"]["session.compact"]>[0] type Endpoint4_15Request = Parameters<RawClient["server.session"]["session.compact"]>[0]
type Endpoint4_14Input = { type Endpoint4_15Input = {
readonly sessionID: Endpoint4_14Request["params"]["sessionID"] readonly sessionID: Endpoint4_15Request["params"]["sessionID"]
readonly id?: Endpoint4_14Request["payload"]["id"] readonly id?: Endpoint4_15Request["payload"]["id"]
} }
const Endpoint4_14 = (raw: RawClient["server.session"]) => (input: Endpoint4_14Input) => const Endpoint4_15 = (raw: RawClient["server.session"]) => (input: Endpoint4_15Input) =>
raw["session.compact"]({ params: { sessionID: input["sessionID"] }, payload: { id: input["id"] } }).pipe( raw["session.compact"]({ params: { sessionID: input["sessionID"] }, payload: { id: input["id"] } }).pipe(
Effect.mapError(mapClientError), Effect.mapError(mapClientError),
Effect.map((value) => value.data), Effect.map((value) => value.data),
) )
type Endpoint4_15Request = Parameters<RawClient["server.session"]["session.wait"]>[0] type Endpoint4_16Request = Parameters<RawClient["server.session"]["session.wait"]>[0]
type Endpoint4_15Input = { readonly sessionID: Endpoint4_15Request["params"]["sessionID"] } type Endpoint4_16Input = { readonly sessionID: Endpoint4_16Request["params"]["sessionID"] }
const Endpoint4_15 = (raw: RawClient["server.session"]) => (input: Endpoint4_15Input) => const Endpoint4_16 = (raw: RawClient["server.session"]) => (input: Endpoint4_16Input) =>
raw["session.wait"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) raw["session.wait"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint4_16Request = Parameters<RawClient["server.session"]["session.revert.stage"]>[0] type Endpoint4_17Request = Parameters<RawClient["server.session"]["session.revert.stage"]>[0]
type Endpoint4_16Input = { type Endpoint4_17Input = {
readonly sessionID: Endpoint4_16Request["params"]["sessionID"] readonly sessionID: Endpoint4_17Request["params"]["sessionID"]
readonly messageID: Endpoint4_16Request["payload"]["messageID"] readonly messageID: Endpoint4_17Request["payload"]["messageID"]
readonly files?: Endpoint4_16Request["payload"]["files"] readonly files?: Endpoint4_17Request["payload"]["files"]
} }
const Endpoint4_16 = (raw: RawClient["server.session"]) => (input: Endpoint4_16Input) => const Endpoint4_17 = (raw: RawClient["server.session"]) => (input: Endpoint4_17Input) =>
raw["session.revert.stage"]({ raw["session.revert.stage"]({
params: { sessionID: input["sessionID"] }, params: { sessionID: input["sessionID"] },
payload: { messageID: input["messageID"], files: input["files"] }, payload: { messageID: input["messageID"], files: input["files"] },
@@ -265,61 +277,61 @@ const Endpoint4_16 = (raw: RawClient["server.session"]) => (input: Endpoint4_16I
Effect.map((value) => value.data), Effect.map((value) => value.data),
) )
type Endpoint4_17Request = Parameters<RawClient["server.session"]["session.revert.clear"]>[0] type Endpoint4_18Request = Parameters<RawClient["server.session"]["session.revert.clear"]>[0]
type Endpoint4_17Input = { readonly sessionID: Endpoint4_17Request["params"]["sessionID"] }
const Endpoint4_17 = (raw: RawClient["server.session"]) => (input: Endpoint4_17Input) =>
raw["session.revert.clear"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint4_18Request = Parameters<RawClient["server.session"]["session.revert.commit"]>[0]
type Endpoint4_18Input = { readonly sessionID: Endpoint4_18Request["params"]["sessionID"] } type Endpoint4_18Input = { readonly sessionID: Endpoint4_18Request["params"]["sessionID"] }
const Endpoint4_18 = (raw: RawClient["server.session"]) => (input: Endpoint4_18Input) => const Endpoint4_18 = (raw: RawClient["server.session"]) => (input: Endpoint4_18Input) =>
raw["session.revert.commit"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) raw["session.revert.clear"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint4_19Request = Parameters<RawClient["server.session"]["session.context"]>[0] type Endpoint4_19Request = Parameters<RawClient["server.session"]["session.revert.commit"]>[0]
type Endpoint4_19Input = { readonly sessionID: Endpoint4_19Request["params"]["sessionID"] } type Endpoint4_19Input = { readonly sessionID: Endpoint4_19Request["params"]["sessionID"] }
const Endpoint4_19 = (raw: RawClient["server.session"]) => (input: Endpoint4_19Input) => const Endpoint4_19 = (raw: RawClient["server.session"]) => (input: Endpoint4_19Input) =>
raw["session.revert.commit"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint4_20Request = Parameters<RawClient["server.session"]["session.context"]>[0]
type Endpoint4_20Input = { readonly sessionID: Endpoint4_20Request["params"]["sessionID"] }
const Endpoint4_20 = (raw: RawClient["server.session"]) => (input: Endpoint4_20Input) =>
raw["session.context"]({ params: { sessionID: input["sessionID"] } }).pipe( raw["session.context"]({ params: { sessionID: input["sessionID"] } }).pipe(
Effect.mapError(mapClientError), Effect.mapError(mapClientError),
Effect.map((value) => value.data), Effect.map((value) => value.data),
) )
type Endpoint4_20Request = Parameters<RawClient["server.session"]["session.instructions.entry.list"]>[0] type Endpoint4_21Request = Parameters<RawClient["server.session"]["session.instructions.entry.list"]>[0]
type Endpoint4_20Input = { readonly sessionID: Endpoint4_20Request["params"]["sessionID"] } type Endpoint4_21Input = { readonly sessionID: Endpoint4_21Request["params"]["sessionID"] }
const Endpoint4_20 = (raw: RawClient["server.session"]) => (input: Endpoint4_20Input) => const Endpoint4_21 = (raw: RawClient["server.session"]) => (input: Endpoint4_21Input) =>
raw["session.instructions.entry.list"]({ params: { sessionID: input["sessionID"] } }).pipe( raw["session.instructions.entry.list"]({ params: { sessionID: input["sessionID"] } }).pipe(
Effect.mapError(mapClientError), Effect.mapError(mapClientError),
Effect.map((value) => value.data), Effect.map((value) => value.data),
) )
type Endpoint4_21Request = Parameters<RawClient["server.session"]["session.instructions.entry.put"]>[0] type Endpoint4_22Request = Parameters<RawClient["server.session"]["session.instructions.entry.put"]>[0]
type Endpoint4_21Input = { type Endpoint4_22Input = {
readonly sessionID: Endpoint4_21Request["params"]["sessionID"] readonly sessionID: Endpoint4_22Request["params"]["sessionID"]
readonly key: Endpoint4_21Request["params"]["key"] readonly key: Endpoint4_22Request["params"]["key"]
readonly value: Endpoint4_21Request["payload"]["value"] readonly value: Endpoint4_22Request["payload"]["value"]
} }
const Endpoint4_21 = (raw: RawClient["server.session"]) => (input: Endpoint4_21Input) => const Endpoint4_22 = (raw: RawClient["server.session"]) => (input: Endpoint4_22Input) =>
raw["session.instructions.entry.put"]({ raw["session.instructions.entry.put"]({
params: { sessionID: input["sessionID"], key: input["key"] }, params: { sessionID: input["sessionID"], key: input["key"] },
payload: { value: input["value"] }, payload: { value: input["value"] },
}).pipe(Effect.mapError(mapClientError)) }).pipe(Effect.mapError(mapClientError))
type Endpoint4_22Request = Parameters<RawClient["server.session"]["session.instructions.entry.remove"]>[0] type Endpoint4_23Request = Parameters<RawClient["server.session"]["session.instructions.entry.remove"]>[0]
type Endpoint4_22Input = { type Endpoint4_23Input = {
readonly sessionID: Endpoint4_22Request["params"]["sessionID"] readonly sessionID: Endpoint4_23Request["params"]["sessionID"]
readonly key: Endpoint4_22Request["params"]["key"] readonly key: Endpoint4_23Request["params"]["key"]
} }
const Endpoint4_22 = (raw: RawClient["server.session"]) => (input: Endpoint4_22Input) => const Endpoint4_23 = (raw: RawClient["server.session"]) => (input: Endpoint4_23Input) =>
raw["session.instructions.entry.remove"]({ params: { sessionID: input["sessionID"], key: input["key"] } }).pipe( raw["session.instructions.entry.remove"]({ params: { sessionID: input["sessionID"], key: input["key"] } }).pipe(
Effect.mapError(mapClientError), Effect.mapError(mapClientError),
) )
type Endpoint4_23Request = Parameters<RawClient["server.session"]["session.log"]>[0] type Endpoint4_24Request = Parameters<RawClient["server.session"]["session.log"]>[0]
type Endpoint4_23Input = { type Endpoint4_24Input = {
readonly sessionID: Endpoint4_23Request["params"]["sessionID"] readonly sessionID: Endpoint4_24Request["params"]["sessionID"]
readonly after?: Endpoint4_23Request["query"]["after"] readonly after?: Endpoint4_24Request["query"]["after"]
readonly follow?: Endpoint4_23Request["query"]["follow"] readonly follow?: Endpoint4_24Request["query"]["follow"]
} }
const Endpoint4_23 = (raw: RawClient["server.session"]) => (input: Endpoint4_23Input) => const Endpoint4_24 = (raw: RawClient["server.session"]) => (input: Endpoint4_24Input) =>
Stream.unwrap( Stream.unwrap(
raw["session.log"]({ raw["session.log"]({
params: { sessionID: input["sessionID"] }, params: { sessionID: input["sessionID"] },
@@ -330,22 +342,22 @@ const Endpoint4_23 = (raw: RawClient["server.session"]) => (input: Endpoint4_23I
), ),
) )
type Endpoint4_24Request = Parameters<RawClient["server.session"]["session.interrupt"]>[0] type Endpoint4_25Request = Parameters<RawClient["server.session"]["session.interrupt"]>[0]
type Endpoint4_24Input = { readonly sessionID: Endpoint4_24Request["params"]["sessionID"] }
const Endpoint4_24 = (raw: RawClient["server.session"]) => (input: Endpoint4_24Input) =>
raw["session.interrupt"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint4_25Request = Parameters<RawClient["server.session"]["session.background"]>[0]
type Endpoint4_25Input = { readonly sessionID: Endpoint4_25Request["params"]["sessionID"] } type Endpoint4_25Input = { readonly sessionID: Endpoint4_25Request["params"]["sessionID"] }
const Endpoint4_25 = (raw: RawClient["server.session"]) => (input: Endpoint4_25Input) => const Endpoint4_25 = (raw: RawClient["server.session"]) => (input: Endpoint4_25Input) =>
raw["session.interrupt"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint4_26Request = Parameters<RawClient["server.session"]["session.background"]>[0]
type Endpoint4_26Input = { readonly sessionID: Endpoint4_26Request["params"]["sessionID"] }
const Endpoint4_26 = (raw: RawClient["server.session"]) => (input: Endpoint4_26Input) =>
raw["session.background"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) raw["session.background"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint4_26Request = Parameters<RawClient["server.session"]["session.message"]>[0] type Endpoint4_27Request = Parameters<RawClient["server.session"]["session.message"]>[0]
type Endpoint4_26Input = { type Endpoint4_27Input = {
readonly sessionID: Endpoint4_26Request["params"]["sessionID"] readonly sessionID: Endpoint4_27Request["params"]["sessionID"]
readonly messageID: Endpoint4_26Request["params"]["messageID"] readonly messageID: Endpoint4_27Request["params"]["messageID"]
} }
const Endpoint4_26 = (raw: RawClient["server.session"]) => (input: Endpoint4_26Input) => const Endpoint4_27 = (raw: RawClient["server.session"]) => (input: Endpoint4_27Input) =>
raw["session.message"]({ params: { sessionID: input["sessionID"], messageID: input["messageID"] } }).pipe( raw["session.message"]({ params: { sessionID: input["sessionID"], messageID: input["messageID"] } }).pipe(
Effect.mapError(mapClientError), Effect.mapError(mapClientError),
Effect.map((value) => value.data), Effect.map((value) => value.data),
@@ -361,22 +373,21 @@ const adaptGroup4 = (raw: RawClient["server.session"]) => ({
switchAgent: Endpoint4_6(raw), switchAgent: Endpoint4_6(raw),
switchModel: Endpoint4_7(raw), switchModel: Endpoint4_7(raw),
rename: Endpoint4_8(raw), rename: Endpoint4_8(raw),
prompt: Endpoint4_9(raw), move: Endpoint4_9(raw),
command: Endpoint4_10(raw), prompt: Endpoint4_10(raw),
skill: Endpoint4_11(raw), command: Endpoint4_11(raw),
synthetic: Endpoint4_12(raw), skill: Endpoint4_12(raw),
shell: Endpoint4_13(raw), synthetic: Endpoint4_13(raw),
compact: Endpoint4_14(raw), shell: Endpoint4_14(raw),
wait: Endpoint4_15(raw), compact: Endpoint4_15(raw),
revertStage: Endpoint4_16(raw), wait: Endpoint4_16(raw),
revertClear: Endpoint4_17(raw), revert: { stage: Endpoint4_17(raw), clear: Endpoint4_18(raw), commit: Endpoint4_19(raw) },
revertCommit: Endpoint4_18(raw), context: Endpoint4_20(raw),
context: Endpoint4_19(raw), instructions: { entry: { list: Endpoint4_21(raw), put: Endpoint4_22(raw), remove: Endpoint4_23(raw) } },
instructions: { entry: { list: Endpoint4_20(raw), put: Endpoint4_21(raw), remove: Endpoint4_22(raw) } }, log: Endpoint4_24(raw),
log: Endpoint4_23(raw), interrupt: Endpoint4_25(raw),
interrupt: Endpoint4_24(raw), background: Endpoint4_26(raw),
background: Endpoint4_25(raw), message: Endpoint4_27(raw),
message: Endpoint4_26(raw),
}) })
type Endpoint5_0Request = Parameters<RawClient["server.message"]["session.messages"]>[0] type Endpoint5_0Request = Parameters<RawClient["server.message"]["session.messages"]>[0]
@@ -523,11 +534,8 @@ const Endpoint9_6 = (raw: RawClient["server.integration"]) => (input: Endpoint9_
const adaptGroup9 = (raw: RawClient["server.integration"]) => ({ const adaptGroup9 = (raw: RawClient["server.integration"]) => ({
list: Endpoint9_0(raw), list: Endpoint9_0(raw),
get: Endpoint9_1(raw), get: Endpoint9_1(raw),
connectKey: Endpoint9_2(raw), connect: { key: Endpoint9_2(raw), oauth: Endpoint9_3(raw) },
connectOauth: Endpoint9_3(raw), attempt: { status: Endpoint9_4(raw), complete: Endpoint9_5(raw), cancel: Endpoint9_6(raw) },
attemptStatus: Endpoint9_4(raw),
attemptComplete: Endpoint9_5(raw),
attemptCancel: Endpoint9_6(raw),
}) })
type Endpoint10_0Request = Parameters<RawClient["server.mcp"]["mcp.list"]>[0] type Endpoint10_0Request = Parameters<RawClient["server.mcp"]["mcp.list"]>[0]
@@ -540,7 +548,10 @@ type Endpoint10_1Input = { readonly location?: Endpoint10_1Request["query"]["loc
const Endpoint10_1 = (raw: RawClient["server.mcp"]) => (input?: Endpoint10_1Input) => const Endpoint10_1 = (raw: RawClient["server.mcp"]) => (input?: Endpoint10_1Input) =>
raw["mcp.resource.catalog"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)) raw["mcp.resource.catalog"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
const adaptGroup10 = (raw: RawClient["server.mcp"]) => ({ list: Endpoint10_0(raw), catalog: Endpoint10_1(raw) }) const adaptGroup10 = (raw: RawClient["server.mcp"]) => ({
list: Endpoint10_0(raw),
resource: { catalog: Endpoint10_1(raw) },
})
type Endpoint11_0Request = Parameters<RawClient["server.credential"]["credential.update"]>[0] type Endpoint11_0Request = Parameters<RawClient["server.credential"]["credential.update"]>[0]
type Endpoint11_0Input = { type Endpoint11_0Input = {
@@ -677,7 +688,7 @@ const Endpoint13_6 = (raw: RawClient["server.form"]) => (input: Endpoint13_6Inpu
) )
const adaptGroup13 = (raw: RawClient["server.form"]) => ({ const adaptGroup13 = (raw: RawClient["server.form"]) => ({
listRequests: Endpoint13_0(raw), request: { list: Endpoint13_0(raw) },
list: Endpoint13_1(raw), list: Endpoint13_1(raw),
create: Endpoint13_2(raw), create: Endpoint13_2(raw),
get: Endpoint13_3(raw), get: Endpoint13_3(raw),
@@ -765,9 +776,8 @@ const Endpoint14_6 = (raw: RawClient["server.permission"]) => (input: Endpoint14
}).pipe(Effect.mapError(mapClientError)) }).pipe(Effect.mapError(mapClientError))
const adaptGroup14 = (raw: RawClient["server.permission"]) => ({ const adaptGroup14 = (raw: RawClient["server.permission"]) => ({
listRequests: Endpoint14_0(raw), request: { list: Endpoint14_0(raw) },
listSaved: Endpoint14_1(raw), saved: { list: Endpoint14_1(raw), remove: Endpoint14_2(raw) },
removeSaved: Endpoint14_2(raw),
create: Endpoint14_3(raw), create: Endpoint14_3(raw),
list: Endpoint14_4(raw), list: Endpoint14_4(raw),
get: Endpoint14_5(raw), get: Endpoint14_5(raw),
@@ -1000,7 +1010,7 @@ const Endpoint21_3 = (raw: RawClient["server.question"]) => (input: Endpoint21_3
) )
const adaptGroup21 = (raw: RawClient["server.question"]) => ({ const adaptGroup21 = (raw: RawClient["server.question"]) => ({
listRequests: Endpoint21_0(raw), request: { list: Endpoint21_0(raw) },
list: Endpoint21_1(raw), list: Endpoint21_1(raw),
reply: Endpoint21_2(raw), reply: Endpoint21_2(raw),
reject: Endpoint21_3(raw), reject: Endpoint21_3(raw),
@@ -1086,8 +1096,7 @@ const Endpoint25_1 = (raw: RawClient["server.debug"]) => (input?: Endpoint25_1In
raw["debug.location.evict"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)) raw["debug.location.evict"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
const adaptGroup25 = (raw: RawClient["server.debug"]) => ({ const adaptGroup25 = (raw: RawClient["server.debug"]) => ({
location: Endpoint25_0(raw), location: { list: Endpoint25_0(raw), evict: Endpoint25_1(raw) },
evictLocation: Endpoint25_1(raw),
}) })
const adaptClient = (raw: RawClient) => ({ const adaptClient = (raw: RawClient) => ({
+238 -206
View File
@@ -23,6 +23,8 @@ import type {
SessionSwitchModelOutput, SessionSwitchModelOutput,
SessionRenameInput, SessionRenameInput,
SessionRenameOutput, SessionRenameOutput,
SessionMoveInput,
SessionMoveOutput,
SessionPromptInput, SessionPromptInput,
SessionPromptOutput, SessionPromptOutput,
SessionCommandInput, SessionCommandInput,
@@ -87,8 +89,8 @@ import type {
IntegrationAttemptCancelOutput, IntegrationAttemptCancelOutput,
ServerMcpListInput, ServerMcpListInput,
ServerMcpListOutput, ServerMcpListOutput,
ServerMcpCatalogInput, ServerMcpResourceCatalogInput,
ServerMcpCatalogOutput, ServerMcpResourceCatalogOutput,
CredentialUpdateInput, CredentialUpdateInput,
CredentialUpdateOutput, CredentialUpdateOutput,
CredentialRemoveInput, CredentialRemoveInput,
@@ -98,8 +100,8 @@ import type {
ProjectCurrentOutput, ProjectCurrentOutput,
ProjectDirectoriesInput, ProjectDirectoriesInput,
ProjectDirectoriesOutput, ProjectDirectoriesOutput,
FormListRequestsInput, FormRequestListInput,
FormListRequestsOutput, FormRequestListOutput,
FormListInput, FormListInput,
FormListOutput, FormListOutput,
FormCreateInput, FormCreateInput,
@@ -112,12 +114,12 @@ import type {
FormReplyOutput, FormReplyOutput,
FormCancelInput, FormCancelInput,
FormCancelOutput, FormCancelOutput,
PermissionListRequestsInput, PermissionRequestListInput,
PermissionListRequestsOutput, PermissionRequestListOutput,
PermissionListSavedInput, PermissionSavedListInput,
PermissionListSavedOutput, PermissionSavedListOutput,
PermissionRemoveSavedInput, PermissionSavedRemoveInput,
PermissionRemoveSavedOutput, PermissionSavedRemoveOutput,
PermissionCreateInput, PermissionCreateInput,
PermissionCreateOutput, PermissionCreateOutput,
PermissionListInput, PermissionListInput,
@@ -159,8 +161,8 @@ import type {
ShellOutputOutput, ShellOutputOutput,
ShellRemoveInput, ShellRemoveInput,
ShellRemoveOutput, ShellRemoveOutput,
QuestionListRequestsInput, QuestionRequestListInput,
QuestionListRequestsOutput, QuestionRequestListOutput,
QuestionListInput, QuestionListInput,
QuestionListOutput, QuestionListOutput,
QuestionReplyInput, QuestionReplyInput,
@@ -179,9 +181,9 @@ import type {
VcsStatusOutput, VcsStatusOutput,
VcsDiffInput, VcsDiffInput,
VcsDiffOutput, VcsDiffOutput,
DebugLocationOutput, DebugLocationListOutput,
DebugEvictLocationInput, DebugLocationEvictInput,
DebugEvictLocationOutput, DebugLocationEvictOutput,
} from "./types" } from "./types"
import { ClientError } from "./client-error" import { ClientError } from "./client-error"
@@ -489,6 +491,18 @@ export function make(options: ClientOptions) {
}, },
requestOptions, requestOptions,
), ),
move: (input: SessionMoveInput, requestOptions?: RequestOptions) =>
request<SessionMoveOutput>(
{
method: "POST",
path: `/api/session/${encodeURIComponent(input.sessionID)}/move`,
body: { destination: input["destination"], moveChanges: input["moveChanges"] },
successStatus: 204,
declaredStatuses: [404, 400, 401],
empty: true,
},
requestOptions,
),
prompt: (input: SessionPromptInput, requestOptions?: RequestOptions) => prompt: (input: SessionPromptInput, requestOptions?: RequestOptions) =>
request<{ readonly data: SessionPromptOutput }>( request<{ readonly data: SessionPromptOutput }>(
{ {
@@ -587,40 +601,42 @@ export function make(options: ClientOptions) {
}, },
requestOptions, requestOptions,
), ),
revertStage: (input: SessionRevertStageInput, requestOptions?: RequestOptions) => revert: {
request<{ readonly data: SessionRevertStageOutput }>( stage: (input: SessionRevertStageInput, requestOptions?: RequestOptions) =>
{ request<{ readonly data: SessionRevertStageOutput }>(
method: "POST", {
path: `/api/session/${encodeURIComponent(input.sessionID)}/revert/stage`, method: "POST",
body: { messageID: input["messageID"], files: input["files"] }, path: `/api/session/${encodeURIComponent(input.sessionID)}/revert/stage`,
successStatus: 200, body: { messageID: input["messageID"], files: input["files"] },
declaredStatuses: [404, 409, 500, 400, 401], successStatus: 200,
empty: false, declaredStatuses: [404, 409, 500, 400, 401],
}, empty: false,
requestOptions, },
).then((value) => value.data), requestOptions,
revertClear: (input: SessionRevertClearInput, requestOptions?: RequestOptions) => ).then((value) => value.data),
request<SessionRevertClearOutput>( clear: (input: SessionRevertClearInput, requestOptions?: RequestOptions) =>
{ request<SessionRevertClearOutput>(
method: "POST", {
path: `/api/session/${encodeURIComponent(input.sessionID)}/revert/clear`, method: "POST",
successStatus: 204, path: `/api/session/${encodeURIComponent(input.sessionID)}/revert/clear`,
declaredStatuses: [404, 409, 500, 400, 401], successStatus: 204,
empty: true, declaredStatuses: [404, 409, 500, 400, 401],
}, empty: true,
requestOptions, },
), requestOptions,
revertCommit: (input: SessionRevertCommitInput, requestOptions?: RequestOptions) => ),
request<SessionRevertCommitOutput>( commit: (input: SessionRevertCommitInput, requestOptions?: RequestOptions) =>
{ request<SessionRevertCommitOutput>(
method: "POST", {
path: `/api/session/${encodeURIComponent(input.sessionID)}/revert/commit`, method: "POST",
successStatus: 204, path: `/api/session/${encodeURIComponent(input.sessionID)}/revert/commit`,
declaredStatuses: [404, 409, 400, 401], successStatus: 204,
empty: true, declaredStatuses: [404, 409, 400, 401],
}, empty: true,
requestOptions, },
), requestOptions,
),
},
context: (input: SessionContextInput, requestOptions?: RequestOptions) => context: (input: SessionContextInput, requestOptions?: RequestOptions) =>
request<{ readonly data: SessionContextOutput }>( request<{ readonly data: SessionContextOutput }>(
{ {
@@ -822,69 +838,73 @@ export function make(options: ClientOptions) {
}, },
requestOptions, requestOptions,
), ),
connectKey: (input: IntegrationConnectKeyInput, requestOptions?: RequestOptions) => connect: {
request<IntegrationConnectKeyOutput>( key: (input: IntegrationConnectKeyInput, requestOptions?: RequestOptions) =>
{ request<IntegrationConnectKeyOutput>(
method: "POST", {
path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/key`, method: "POST",
query: { location: input["location"] }, path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/key`,
body: { key: input["key"], label: input["label"] }, query: { location: input["location"] },
successStatus: 204, body: { key: input["key"], label: input["label"] },
declaredStatuses: [400, 401], successStatus: 204,
empty: true, declaredStatuses: [400, 401],
}, empty: true,
requestOptions, },
), requestOptions,
connectOauth: (input: IntegrationConnectOauthInput, requestOptions?: RequestOptions) => ),
request<IntegrationConnectOauthOutput>( oauth: (input: IntegrationConnectOauthInput, requestOptions?: RequestOptions) =>
{ request<IntegrationConnectOauthOutput>(
method: "POST", {
path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/oauth`, method: "POST",
query: { location: input["location"] }, path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/oauth`,
body: { methodID: input["methodID"], inputs: input["inputs"], label: input["label"] }, query: { location: input["location"] },
successStatus: 200, body: { methodID: input["methodID"], inputs: input["inputs"], label: input["label"] },
declaredStatuses: [400, 401], successStatus: 200,
empty: false, declaredStatuses: [400, 401],
}, empty: false,
requestOptions, },
), requestOptions,
attemptStatus: (input: IntegrationAttemptStatusInput, requestOptions?: RequestOptions) => ),
request<IntegrationAttemptStatusOutput>( },
{ attempt: {
method: "GET", status: (input: IntegrationAttemptStatusInput, requestOptions?: RequestOptions) =>
path: `/api/integration/attempt/${encodeURIComponent(input.attemptID)}`, request<IntegrationAttemptStatusOutput>(
query: { location: input["location"] }, {
successStatus: 200, method: "GET",
declaredStatuses: [401, 400], path: `/api/integration/attempt/${encodeURIComponent(input.attemptID)}`,
empty: false, query: { location: input["location"] },
}, successStatus: 200,
requestOptions, declaredStatuses: [401, 400],
), empty: false,
attemptComplete: (input: IntegrationAttemptCompleteInput, requestOptions?: RequestOptions) => },
request<IntegrationAttemptCompleteOutput>( requestOptions,
{ ),
method: "POST", complete: (input: IntegrationAttemptCompleteInput, requestOptions?: RequestOptions) =>
path: `/api/integration/attempt/${encodeURIComponent(input.attemptID)}/complete`, request<IntegrationAttemptCompleteOutput>(
query: { location: input["location"] }, {
body: { code: input["code"] }, method: "POST",
successStatus: 204, path: `/api/integration/attempt/${encodeURIComponent(input.attemptID)}/complete`,
declaredStatuses: [400, 401], query: { location: input["location"] },
empty: true, body: { code: input["code"] },
}, successStatus: 204,
requestOptions, declaredStatuses: [400, 401],
), empty: true,
attemptCancel: (input: IntegrationAttemptCancelInput, requestOptions?: RequestOptions) => },
request<IntegrationAttemptCancelOutput>( requestOptions,
{ ),
method: "DELETE", cancel: (input: IntegrationAttemptCancelInput, requestOptions?: RequestOptions) =>
path: `/api/integration/attempt/${encodeURIComponent(input.attemptID)}`, request<IntegrationAttemptCancelOutput>(
query: { location: input["location"] }, {
successStatus: 204, method: "DELETE",
declaredStatuses: [401, 400], path: `/api/integration/attempt/${encodeURIComponent(input.attemptID)}`,
empty: true, query: { location: input["location"] },
}, successStatus: 204,
requestOptions, declaredStatuses: [401, 400],
), empty: true,
},
requestOptions,
),
},
}, },
"server.mcp": { "server.mcp": {
list: (input?: ServerMcpListInput, requestOptions?: RequestOptions) => list: (input?: ServerMcpListInput, requestOptions?: RequestOptions) =>
@@ -899,18 +919,20 @@ export function make(options: ClientOptions) {
}, },
requestOptions, requestOptions,
), ),
catalog: (input?: ServerMcpCatalogInput, requestOptions?: RequestOptions) => resource: {
request<ServerMcpCatalogOutput>( catalog: (input?: ServerMcpResourceCatalogInput, requestOptions?: RequestOptions) =>
{ request<ServerMcpResourceCatalogOutput>(
method: "GET", {
path: `/api/mcp/resource`, method: "GET",
query: { location: input?.["location"] }, path: `/api/mcp/resource`,
successStatus: 200, query: { location: input?.["location"] },
declaredStatuses: [401, 400], successStatus: 200,
empty: false, declaredStatuses: [401, 400],
}, empty: false,
requestOptions, },
), requestOptions,
),
},
}, },
credential: { credential: {
update: (input: CredentialUpdateInput, requestOptions?: RequestOptions) => update: (input: CredentialUpdateInput, requestOptions?: RequestOptions) =>
@@ -971,18 +993,20 @@ export function make(options: ClientOptions) {
), ),
}, },
form: { form: {
listRequests: (input?: FormListRequestsInput, requestOptions?: RequestOptions) => request: {
request<FormListRequestsOutput>( list: (input?: FormRequestListInput, requestOptions?: RequestOptions) =>
{ request<FormRequestListOutput>(
method: "GET", {
path: `/api/form/request`, method: "GET",
query: { location: input?.["location"] }, path: `/api/form/request`,
successStatus: 200, query: { location: input?.["location"] },
declaredStatuses: [401, 400], successStatus: 200,
empty: false, declaredStatuses: [401, 400],
}, empty: false,
requestOptions, },
), requestOptions,
),
},
list: (input: FormListInput, requestOptions?: RequestOptions) => list: (input: FormListInput, requestOptions?: RequestOptions) =>
request<{ readonly data: FormListOutput }>( request<{ readonly data: FormListOutput }>(
{ {
@@ -1060,41 +1084,45 @@ export function make(options: ClientOptions) {
), ),
}, },
permission: { permission: {
listRequests: (input?: PermissionListRequestsInput, requestOptions?: RequestOptions) => request: {
request<PermissionListRequestsOutput>( list: (input?: PermissionRequestListInput, requestOptions?: RequestOptions) =>
{ request<PermissionRequestListOutput>(
method: "GET", {
path: `/api/permission/request`, method: "GET",
query: { location: input?.["location"] }, path: `/api/permission/request`,
successStatus: 200, query: { location: input?.["location"] },
declaredStatuses: [401, 400], successStatus: 200,
empty: false, declaredStatuses: [401, 400],
}, empty: false,
requestOptions, },
), requestOptions,
listSaved: (input?: PermissionListSavedInput, requestOptions?: RequestOptions) => ),
request<{ readonly data: PermissionListSavedOutput }>( },
{ saved: {
method: "GET", list: (input?: PermissionSavedListInput, requestOptions?: RequestOptions) =>
path: `/api/permission/saved`, request<{ readonly data: PermissionSavedListOutput }>(
query: { projectID: input?.["projectID"] }, {
successStatus: 200, method: "GET",
declaredStatuses: [401, 400], path: `/api/permission/saved`,
empty: false, query: { projectID: input?.["projectID"] },
}, successStatus: 200,
requestOptions, declaredStatuses: [401, 400],
).then((value) => value.data), empty: false,
removeSaved: (input: PermissionRemoveSavedInput, requestOptions?: RequestOptions) => },
request<PermissionRemoveSavedOutput>( requestOptions,
{ ).then((value) => value.data),
method: "DELETE", remove: (input: PermissionSavedRemoveInput, requestOptions?: RequestOptions) =>
path: `/api/permission/saved/${encodeURIComponent(input.id)}`, request<PermissionSavedRemoveOutput>(
successStatus: 204, {
declaredStatuses: [401, 400], method: "DELETE",
empty: true, path: `/api/permission/saved/${encodeURIComponent(input.id)}`,
}, successStatus: 204,
requestOptions, declaredStatuses: [401, 400],
), empty: true,
},
requestOptions,
),
},
create: (input: PermissionCreateInput, requestOptions?: RequestOptions) => create: (input: PermissionCreateInput, requestOptions?: RequestOptions) =>
request<{ readonly data: PermissionCreateOutput }>( request<{ readonly data: PermissionCreateOutput }>(
{ {
@@ -1376,18 +1404,20 @@ export function make(options: ClientOptions) {
), ),
}, },
question: { question: {
listRequests: (input?: QuestionListRequestsInput, requestOptions?: RequestOptions) => request: {
request<QuestionListRequestsOutput>( list: (input?: QuestionRequestListInput, requestOptions?: RequestOptions) =>
{ request<QuestionRequestListOutput>(
method: "GET", {
path: `/api/question/request`, method: "GET",
query: { location: input?.["location"] }, path: `/api/question/request`,
successStatus: 200, query: { location: input?.["location"] },
declaredStatuses: [401, 400], successStatus: 200,
empty: false, declaredStatuses: [401, 400],
}, empty: false,
requestOptions, },
), requestOptions,
),
},
list: (input: QuestionListInput, requestOptions?: RequestOptions) => list: (input: QuestionListInput, requestOptions?: RequestOptions) =>
request<{ readonly data: QuestionListOutput }>( request<{ readonly data: QuestionListOutput }>(
{ {
@@ -1504,29 +1534,31 @@ export function make(options: ClientOptions) {
), ),
}, },
debug: { debug: {
location: (requestOptions?: RequestOptions) => location: {
request<DebugLocationOutput>( list: (requestOptions?: RequestOptions) =>
{ request<DebugLocationListOutput>(
method: "GET", {
path: `/api/debug/location`, method: "GET",
successStatus: 200, path: `/api/debug/location`,
declaredStatuses: [401, 400], successStatus: 200,
empty: false, declaredStatuses: [401, 400],
}, empty: false,
requestOptions, },
), requestOptions,
evictLocation: (input?: DebugEvictLocationInput, requestOptions?: RequestOptions) => ),
request<DebugEvictLocationOutput>( evict: (input?: DebugLocationEvictInput, requestOptions?: RequestOptions) =>
{ request<DebugLocationEvictOutput>(
method: "DELETE", {
path: `/api/debug/location`, method: "DELETE",
query: { location: input?.["location"] }, path: `/api/debug/location`,
successStatus: 204, query: { location: input?.["location"] },
declaredStatuses: [401, 400], successStatus: 204,
empty: true, declaredStatuses: [401, 400],
}, empty: true,
requestOptions, },
), requestOptions,
),
},
}, },
} }
} }
File diff suppressed because it is too large Load Diff
@@ -19,7 +19,7 @@ import { SessionMessage } from "@opencode-ai/schema/session-message"
import { Workspace } from "@opencode-ai/schema/workspace" import { Workspace } from "@opencode-ai/schema/workspace"
import { Api } from "@opencode-ai/server/api" import { Api } from "@opencode-ai/server/api"
import { compile, emitPromise } from "@opencode-ai/httpapi-codegen" import { compile, emitPromise } from "@opencode-ai/httpapi-codegen"
import { ClientApi, endpointNames, groupNames, promiseOmitEndpoints } from "../src/contract" import { ClientApi, groupNames, promiseOmitEndpoints } from "../src/contract"
const Client = await import("../src/effect") const Client = await import("../src/effect")
@@ -38,7 +38,7 @@ test("Core and Server reuse the authoritative Schema and Protocol values", () =>
expect(ProjectV2.Directory).toBe(Project.Directory) expect(ProjectV2.Directory).toBe(Project.Directory)
expect(ProjectV2.Directories).toBe(Project.Directories) expect(ProjectV2.Directories).toBe(Project.Directories)
expect(CoreSessionInput.Admitted).toBe(SessionInput.Admitted) expect(CoreSessionInput.Admitted).toBe(SessionInput.Admitted)
expect(CoreSessionMessage.Message).toBe(SessionMessage.Message) expect(CoreSessionMessage.Info).toBe(SessionMessage.Info)
expect(Api.groups["server.session"].identifier).toBe("server.session") expect(Api.groups["server.session"].identifier).toBe("server.session")
expect(Api.groups["server.project"].identifier).toBe("server.project") expect(Api.groups["server.project"].identifier).toBe("server.project")
expect(Object.keys(ClientApi.groups)).toEqual(Object.keys(Api.groups)) expect(Object.keys(ClientApi.groups)).toEqual(Object.keys(Api.groups))
@@ -49,8 +49,8 @@ test("Core and Server reuse the authoritative Schema and Protocol values", () =>
}) })
test("client and Server contracts generate identically", () => { test("client and Server contracts generate identically", () => {
const server = compile(Api, { groupNames, endpointNames, omitEndpoints: promiseOmitEndpoints }) const server = compile(Api, { groupNames, omitEndpoints: promiseOmitEndpoints })
const client = compile(ClientApi, { groupNames, endpointNames, omitEndpoints: promiseOmitEndpoints }) const client = compile(ClientApi, { groupNames, omitEndpoints: promiseOmitEndpoints })
expect(emitPromise(client)).toEqual(emitPromise(server)) expect(emitPromise(client)).toEqual(emitPromise(server))
}) })
+5 -10
View File
@@ -33,16 +33,11 @@ test("exposes every standard HTTP API group", () => {
"debug", "debug",
]) ])
expect(Object.keys(client.debug)).toEqual(["location"]) expect(Object.keys(client.debug)).toEqual(["location"])
expect(Object.keys(client.debug.location)).toEqual(["list", "evict"])
expect(Object.keys(client.message)).toEqual(["list"]) expect(Object.keys(client.message)).toEqual(["list"])
expect(Object.keys(client.integration)).toEqual([ expect(Object.keys(client.integration)).toEqual(["list", "get", "connect", "attempt"])
"list", expect(Object.keys(client.integration.connect)).toEqual(["key", "oauth"])
"get", expect(Object.keys(client.integration.attempt)).toEqual(["status", "complete", "cancel"])
"connectKey",
"connectOauth",
"attemptStatus",
"attemptComplete",
"attemptCancel",
])
expect(Object.keys(client.file)).toEqual(["read", "list", "find"]) expect(Object.keys(client.file)).toEqual(["read", "list", "find"])
expect(Object.keys(client.vcs)).toEqual(["status", "diff"]) expect(Object.keys(client.vcs)).toEqual(["status", "diff"])
expect(Object.keys(client.pty)).toEqual(["list", "create", "get", "update", "remove"]) expect(Object.keys(client.pty)).toEqual(["list", "create", "get", "update", "remove"])
@@ -66,7 +61,7 @@ test("MCP resource catalog uses the public HTTP contract", async () => {
}, },
}) })
const result = await client["server.mcp"].catalog({ location: { directory: "/tmp/project" } }) const result = await client["server.mcp"].resource.catalog({ location: { directory: "/tmp/project" } })
expect(result.data.resources[0]?.uri).toBe("docs://readme") expect(result.data.resources[0]?.uri).toBe("docs://readme")
expect(request?.method).toBe("GET") expect(request?.method).toBe("GET")
+17 -2
View File
@@ -530,17 +530,29 @@ const invokeArrayStatic = (name: string, args: Array<unknown>, node: AstNode): u
if (args[0] instanceof SandboxURLSearchParams) { if (args[0] instanceof SandboxURLSearchParams) {
return Array.from(args[0].params.entries(), ([key, value]) => [key, value]) return Array.from(args[0].params.entries(), ([key, value]) => [key, value])
} }
const source = boundedData(args[0], "Array.from input") const source = args[0]
if (source instanceof SandboxPromise) {
throw new InterpreterRuntimeError(
"Array.from received an un-awaited Promise; await it before creating the array.",
node,
"InvalidDataValue",
)
}
if (typeof source === "string") return Array.from(source) if (typeof source === "string") return Array.from(source)
if (Array.isArray(source)) return [...source] if (Array.isArray(source)) return [...source]
if ( if (
source !== null && source !== null &&
typeof source === "object" && typeof source === "object" &&
(Object.getPrototypeOf(source) === Object.prototype || Object.getPrototypeOf(source) === null) &&
typeof (source as { length?: unknown }).length === "number" typeof (source as { length?: unknown }).length === "number"
) { ) {
return Array.from(source as ArrayLike<unknown>) return Array.from(source as ArrayLike<unknown>)
} }
throw new InterpreterRuntimeError("Array.from expects an array, string, Map, Set, or array-like value.", node) throw new InterpreterRuntimeError(
"Array.from expects an array, string, Map, Set, or array-like value.",
node,
"InvalidDataValue",
)
} }
default: default:
throw new InterpreterRuntimeError(`Array.${name} is not available in CodeMode.`, node) throw new InterpreterRuntimeError(`Array.${name} is not available in CodeMode.`, node)
@@ -2005,6 +2017,9 @@ class Interpreter<R> {
if (callable.namespace === "Object" && objectMethodsPreservingIdentity.has(callable.name)) { if (callable.namespace === "Object" && objectMethodsPreservingIdentity.has(callable.name)) {
return invokeGlobalMethod(callable, args, node) return invokeGlobalMethod(callable, args, node)
} }
if (callable.namespace === "Array" && (callable.name === "from" || callable.name === "of")) {
return invokeGlobalMethod(callable, args, node)
}
return boundedData(invokeGlobalMethod(callable, args, node), `${callable.namespace}.${callable.name} result`) return boundedData(invokeGlobalMethod(callable, args, node), `${callable.namespace}.${callable.name} result`)
} }
if (callable instanceof CoercionFunction) { if (callable instanceof CoercionFunction) {
+18 -15
View File
@@ -1,6 +1,6 @@
import { type AstNode, InterpreterRuntimeError } from "../interpreter/model.js" import { type AstNode, InterpreterRuntimeError } from "../interpreter/model.js"
import { isBlockedMember } from "../tool-runtime.js" import { isBlockedMember } from "../tool-runtime.js"
import { isSandboxValue, SandboxMap, SandboxSet, SandboxURLSearchParams } from "../values.js" import { isSandboxValue, SandboxMap, SandboxPromise, SandboxSet, SandboxURLSearchParams } from "../values.js"
import { boundedData, coerceToString } from "./value.js" import { boundedData, coerceToString } from "./value.js"
export const objectStatics = new Set(["keys", "values", "entries", "hasOwn", "assign", "fromEntries"]) export const objectStatics = new Set(["keys", "values", "entries", "hasOwn", "assign", "fromEntries"])
@@ -10,13 +10,23 @@ export const invokeObjectMethod = (name: string, args: Array<unknown>, node: Ast
if (!objectStatics.has(name)) throw new InterpreterRuntimeError(`Object.${name} is not available in CodeMode.`, node) if (!objectStatics.has(name)) throw new InterpreterRuntimeError(`Object.${name} is not available in CodeMode.`, node)
const requireObject = (): Record<string, unknown> => { const requireObject = (): Record<string, unknown> => {
const input = args[0] const input = args[0]
const value = boundedData(args[0], `Object.${name} input`)
if (Array.isArray(input)) return input as unknown as Record<string, unknown> if (Array.isArray(input)) return input as unknown as Record<string, unknown>
if (isSandboxValue(value)) return {} if (isSandboxValue(input)) return {}
if (value === null || typeof value !== "object") { if (input instanceof SandboxPromise) {
throw new InterpreterRuntimeError(`Object.${name} expects a data object or array.`, node) throw new InterpreterRuntimeError(
`Object.${name} received an un-awaited Promise; await it before inspecting the result.`,
node,
"InvalidDataValue",
)
} }
return value as Record<string, unknown> if (input === null || typeof input !== "object") {
throw new InterpreterRuntimeError(`Object.${name} expects a data object or array.`, node, "InvalidDataValue")
}
const prototype = Object.getPrototypeOf(input)
if (prototype !== null && prototype !== Object.prototype) {
throw new InterpreterRuntimeError(`Object.${name} expects a data object or array.`, node, "InvalidDataValue")
}
return input as Record<string, unknown>
} }
const guardedSet = (out: Record<string, unknown>, key: string, item: unknown): void => { const guardedSet = (out: Record<string, unknown>, key: string, item: unknown): void => {
if (isBlockedMember(key)) throw new InterpreterRuntimeError(`Property '${key}' is not available in CodeMode.`, node) if (isBlockedMember(key)) throw new InterpreterRuntimeError(`Property '${key}' is not available in CodeMode.`, node)
@@ -28,15 +38,8 @@ export const invokeObjectMethod = (name: string, args: Array<unknown>, node: Ast
guardedSet(out, coerceToString(key), item) guardedSet(out, coerceToString(key), item)
} }
switch (name) { switch (name) {
case "keys": { case "keys":
const value = boundedData(args[0], "Object.keys input") return Object.keys(requireObject())
if (isSandboxValue(value)) return []
if (Array.isArray(value)) return Object.keys(value)
if (value === null || typeof value !== "object") {
throw new InterpreterRuntimeError("Object.keys expects a data object or array.", node)
}
return Object.keys(value)
}
case "values": case "values":
return Object.values(requireObject()) return Object.values(requireObject())
case "entries": case "entries":
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -177,13 +177,13 @@ describe("OpenAPI.fromSpec", () => {
const spec = await opencodeSpec() const spec = await opencodeSpec()
const result = OpenAPI.fromSpec({ spec, baseUrl }) const result = OpenAPI.fromSpec({ spec, baseUrl })
expect(result.skipped).toHaveLength(5) expect(result.skipped).toHaveLength(4)
expect(result.skipped).toContainEqual({ expect(result.skipped).toContainEqual({
method: "GET", method: "GET",
path: "/api/pty/{ptyID}/connect", path: "/api/pty/{ptyID}/connect",
reason: "WebSocket operations are not supported", reason: "WebSocket operations are not supported",
}) })
expect(result.skipped.filter((item) => item.reason === "SSE operations are not supported")).toHaveLength(3) expect(result.skipped.filter((item) => item.reason === "SSE operations are not supported")).toHaveLength(2)
expect(result.skipped).toContainEqual({ expect(result.skipped).toContainEqual({
method: "GET", method: "GET",
path: "/api/fs/read/*", path: "/api/fs/read/*",
@@ -210,11 +210,11 @@ describe("OpenAPI.fromSpec", () => {
if (!Tool.isDefinition(instructionPut)) throw new Error("v2.session.instructions.entry.put was not generated") if (!Tool.isDefinition(instructionPut)) throw new Error("v2.session.instructions.entry.put was not generated")
expect(inputTypeScript(instructionPut)).toBe("{ sessionID: string; key: string; value: unknown }") expect(inputTypeScript(instructionPut)).toBe("{ sessionID: string; key: string; value: unknown }")
expect(toolAt(result.tools, "v2_session_instructions_entry_put_2")).toBeUndefined() expect(toolAt(result.tools, "v2_session_instructions_entry_put_2")).toBeUndefined()
expect(toolAt(result.tools, "v2.pty.connect")).toBeUndefined() expect(Tool.isDefinition(toolAt(result.tools, "v2.pty.connect"))).toBe(false)
expect(toolAt(result.tools, "v2.session.log")).toBeUndefined() expect(toolAt(result.tools, "v2.session.log")).toBeUndefined()
expect(toolAt(result.tools, "v2.event.subscribe")).toBeUndefined() expect(toolAt(result.tools, "v2.event.subscribe")).toBeUndefined()
expect(toolAt(result.tools, "v2.fs.read")).toBeUndefined() expect(toolAt(result.tools, "v2.fs.read")).toBeUndefined()
expect(toolAt(result.tools, "v2.pty.connectToken")).not.toBeUndefined() expect(toolAt(result.tools, "v2.pty.connect.token")).not.toBeUndefined()
}) })
test("preserves operation path sanitization and collision handling", () => { test("preserves operation path sanitization and collision handling", () => {
+6
View File
@@ -245,6 +245,12 @@ describe("promises at data boundaries", () => {
expect(diagnostic.message).toContain("await tools.ns.tool(...)") expect(diagnostic.message).toContain("await tools.ns.tool(...)")
}) })
test("collection helpers do not let un-awaited promises cross the result boundary", async () => {
const diagnostic = await error(`return Array.from([Promise.resolve(1)])`)
expect(diagnostic.kind).toBe("InvalidDataValue")
expect(diagnostic.message).toContain("un-awaited Promise")
})
test("passing an un-awaited promise as a tool argument is a clear diagnostic", async () => { test("passing an un-awaited promise as a tool argument is a clear diagnostic", async () => {
const diagnostic = await error(`return await tools.host.sleepy({ id: tools.host.sleepy({ id: 1 }) })`) const diagnostic = await error(`return await tools.host.sleepy({ id: tools.host.sleepy({ id: 1 }) })`)
expect(diagnostic.kind).toBe("InvalidDataValue") expect(diagnostic.kind).toBe("InvalidDataValue")
+84
View File
@@ -773,6 +773,43 @@ describe("sandbox values at intra-sandbox checkpoints", () => {
) )
}) })
test("Object.values/entries preserve nested object identity", async () => {
expect(
await value(`
const child = { selected: false }
const rows = { a: child }
Object.values(rows)[0].selected = true
return child.selected
`),
).toBe(true)
expect(
await value(`
const child = { selected: false }
const rows = { a: child }
Object.entries(rows)[0][1].selected = true
return child.selected
`),
).toBe(true)
})
test("Object enumeration preserves promises and callable references", async () => {
expect(
await value(`
const pending = Promise.resolve(1)
const source = { pending }
return [Object.keys(source), Object.hasOwn(source, "pending"), await Object.values(source)[0], await Object.entries(source)[0][1]]
`),
).toEqual([["pending"], true, 1, 1])
expect(await value(`return Object.values({ max: Math.max })[0](1, 2)`)).toBe(2)
})
test("Object enumeration rejects invalid receivers and gives promises an await hint", async () => {
const diagnostic = await error(`return Object.keys(Promise.resolve({ a: 1 }))`)
expect(diagnostic.kind).toBe("InvalidDataValue")
expect(diagnostic.message).toContain("await")
expect((await error(`return Object.keys(Math)`)).kind).toBe("InvalidDataValue")
})
test("Object.assign keeps Maps usable", async () => { test("Object.assign keeps Maps usable", async () => {
expect(await value(`const merged = Object.assign({}, { m: new Map([["a", 1]]) }); return merged.m.get("a")`)).toBe( expect(await value(`const merged = Object.assign({}, { m: new Map([["a", 1]]) }); return merged.m.get("a")`)).toBe(
1, 1,
@@ -795,6 +832,53 @@ describe("sandbox values at intra-sandbox checkpoints", () => {
expect(await value(`return Array.from([new Date(5)])[0].getTime()`)).toBe(5) expect(await value(`return Array.from([new Date(5)])[0].getTime()`)).toBe(5)
}) })
test("Array.from and Array.of preserve nested object identity", async () => {
expect(
await value(`
const child = { selected: false }
Array.from([child])[0].selected = true
return child.selected
`),
).toBe(true)
expect(
await value(`
const child = { selected: false }
Array.of(child)[0].selected = true
return child.selected
`),
).toBe(true)
})
test("Array.from and Array.of preserve promises and callable references", async () => {
expect(
await value(`
const pending = Promise.resolve(1)
return [await Array.from([pending])[0], await Array.of(pending)[0]]
`),
).toEqual([1, 1])
expect(await value(`return [Array.from([Math.max])[0](1, 2), Array.of(Math.max)[0](3, 4)]`)).toEqual([2, 4])
})
test("Array.from preserves identity across supported collection shapes", async () => {
expect(
await value(`
const child = { selected: false }
const fromArrayLike = Array.from({ 0: child, length: 1 })
const fromMap = Array.from(new Map([["child", child]]))
const fromSet = Array.from(new Set([child]))
fromArrayLike[0].selected = true
return [fromMap[0][1] === child, fromSet[0] === child, child.selected]
`),
).toEqual([true, true, true])
})
test("Array.from rejects invalid receivers and gives promises an await hint", async () => {
const diagnostic = await error(`return Array.from(Promise.resolve([1]))`)
expect(diagnostic.kind).toBe("InvalidDataValue")
expect(diagnostic.message).toContain("await")
expect((await error(`return Array.from(() => 1)`)).kind).toBe("InvalidDataValue")
})
test("regexes stay callable through Object.values", async () => { test("regexes stay callable through Object.values", async () => {
expect(await value(`return Object.values({ r: /ab+/ })[0].test("abb")`)).toBe(true) expect(await value(`return Object.values({ r: /ab+/ })[0].test("abb")`)).toBe(true)
}) })
+2
View File
@@ -8,6 +8,8 @@ import { State } from "./state"
export const ID = Agent.ID export const ID = Agent.ID
export type ID = typeof ID.Type export type ID = typeof ID.Type
export const Name = Agent.Name
export type Name = Agent.Name
export const defaultID = ID.make("build") export const defaultID = ID.make("build")
export const Color = Agent.Color export const Color = Agent.Color
+7 -3
View File
@@ -6,6 +6,7 @@ import type {
JSONValue, JSONValue,
LanguageModelV3, LanguageModelV3,
LanguageModelV3CallOptions, LanguageModelV3CallOptions,
LanguageModelV3FinishReason,
LanguageModelV3FunctionTool, LanguageModelV3FunctionTool,
LanguageModelV3Message, LanguageModelV3Message,
LanguageModelV3Prompt, LanguageModelV3Prompt,
@@ -304,6 +305,7 @@ function modelFromLanguage(info: ModelV2.Info, language: LanguageModelV3) {
const route: AnyRoute = { const route: AnyRoute = {
id: `ai-sdk:${ProviderV2.packageName(info.package) ?? "unknown"}`, id: `ai-sdk:${ProviderV2.packageName(info.package) ?? "unknown"}`,
provider: ProviderID.make(info.providerID), provider: ProviderID.make(info.providerID),
providerMetadataKey: optionKey,
protocol: "ai-sdk", protocol: "ai-sdk",
endpoint: Endpoint.path("/", { baseURL: "https://ai-sdk.local" }), endpoint: Endpoint.path("/", { baseURL: "https://ai-sdk.local" }),
auth: Auth.none, auth: Auth.none,
@@ -416,7 +418,7 @@ function assistantPart(part: ContentPart): AssistantContent {
case "media": case "media":
return [{ type: "file", mediaType: part.mediaType, data: part.data, filename: part.filename }] return [{ type: "file", mediaType: part.mediaType, data: part.data, filename: part.filename }]
case "reasoning": case "reasoning":
return [{ type: "reasoning", text: part.text }] return [{ type: "reasoning", text: part.text, providerOptions: providerOptions(part.providerMetadata) }]
case "tool-call": case "tool-call":
return [ return [
{ {
@@ -425,6 +427,7 @@ function assistantPart(part: ContentPart): AssistantContent {
toolName: part.name, toolName: part.name,
input: part.input, input: part.input,
providerExecuted: part.providerExecuted, providerExecuted: part.providerExecuted,
providerOptions: providerOptions(part.providerMetadata),
}, },
] ]
case "tool-result": case "tool-result":
@@ -440,6 +443,7 @@ function toolResultPart(part: ContentPart): ToolResultContent[] {
toolCallId: part.id, toolCallId: part.id,
toolName: part.name, toolName: part.name,
output: toolOutput(part.result), output: toolOutput(part.result),
providerOptions: providerOptions(part.providerMetadata),
}, },
] ]
} }
@@ -624,8 +628,8 @@ function usage(input: Extract<LanguageModelV3StreamPart, { type: "finish" }>["us
return Object.values(output).some((value) => value !== undefined) ? output : undefined return Object.values(output).some((value) => value !== undefined) ? output : undefined
} }
function finishReason(value: unknown): FinishReason { function finishReason(value: LanguageModelV3FinishReason): FinishReason {
return Schema.is(FinishReason)(value) ? value : "unknown" return value.unified === "other" ? "unknown" : value.unified
} }
function providerMetadata(value: unknown) { function providerMetadata(value: unknown) {
+3 -2
View File
@@ -1,6 +1,7 @@
export * as ConfigProviderPlugin from "./provider" export * as ConfigProviderPlugin from "./provider"
import { define } from "@opencode-ai/plugin/v2/effect/plugin" import { define } from "@opencode-ai/plugin/v2/effect/plugin"
import { Money } from "@opencode-ai/schema/money"
import { Effect, Stream } from "effect" import { Effect, Stream } from "effect"
import { Config } from "../../config" import { Config } from "../../config"
import { ModelV2 } from "../../model" import { ModelV2 } from "../../model"
@@ -91,8 +92,8 @@ export const Plugin = define({
input: cost.input, input: cost.input,
output: cost.output, output: cost.output,
cache: { cache: {
read: cost.cache?.read ?? 0, read: cost.cache?.read ?? Money.USDPerMillionTokens.zero,
write: cost.cache?.write ?? 0, write: cost.cache?.write ?? Money.USDPerMillionTokens.zero,
}, },
})) }))
} }
+5 -4
View File
@@ -1,6 +1,7 @@
export * as ConfigProvider from "./provider" export * as ConfigProvider from "./provider"
import { Schema } from "effect" import { Schema } from "effect"
import { Money } from "@opencode-ai/schema/money"
import { ModelV2 } from "../model" import { ModelV2 } from "../model"
const JsonRecord = Schema.Record(Schema.String, Schema.Json) const JsonRecord = Schema.Record(Schema.String, Schema.Json)
@@ -17,8 +18,8 @@ export class Request extends Schema.Class<Request>("ConfigV2.Provider.Request")(
}) {} }) {}
class Cache extends Schema.Class<Cache>("ConfigV2.Model.Cost.Cache")({ class Cache extends Schema.Class<Cache>("ConfigV2.Model.Cost.Cache")({
read: Schema.Finite.pipe(Schema.optional), read: Money.USDPerMillionTokens.pipe(Schema.optional),
write: Schema.Finite.pipe(Schema.optional), write: Money.USDPerMillionTokens.pipe(Schema.optional),
}) {} }) {}
class Cost extends Schema.Class<Cost>("ConfigV2.Model.Cost")({ class Cost extends Schema.Class<Cost>("ConfigV2.Model.Cost")({
@@ -26,8 +27,8 @@ class Cost extends Schema.Class<Cost>("ConfigV2.Model.Cost")({
type: Schema.Literal("context"), type: Schema.Literal("context"),
size: Schema.Int, size: Schema.Int,
}).pipe(Schema.optional), }).pipe(Schema.optional),
input: Schema.Finite, input: Money.USDPerMillionTokens,
output: Schema.Finite, output: Money.USDPerMillionTokens,
cache: Cache.pipe(Schema.optional), cache: Cache.pipe(Schema.optional),
}) {} }) {}
+1
View File
@@ -48,5 +48,6 @@ export const migrations = (
import("./migration/20260705180000_rename_instructions"), import("./migration/20260705180000_rename_instructions"),
import("./migration/20260706223930_add-session-fork"), import("./migration/20260706223930_add-session-fork"),
import("./migration/20260707010146_durable_session_inbox"), import("./migration/20260707010146_durable_session_inbox"),
import("./migration/20260707120000_migrate_prelaunch_v2_state"),
]) ])
).map((module) => module.default) satisfies DatabaseMigration.Migration[] ).map((module) => module.default) satisfies DatabaseMigration.Migration[]
@@ -0,0 +1,227 @@
import { sql } from "drizzle-orm"
import { Effect, Schema } from "effect"
import type { DatabaseMigration } from "../migration"
const decodeJson = Schema.decodeUnknownSync(Schema.UnknownFromJsonString)
const isObject = Schema.is(Schema.Record(Schema.String, Schema.Unknown))
export default {
id: "20260707120000_migrate_prelaunch_v2_state",
up(tx) {
return Effect.gen(function* () {
yield* tx.run(
sql`DELETE FROM session_message WHERE type = 'compaction' AND json_extract(data, '$.status') = 'queued'`,
)
const messages = yield* tx.all<{ id: string; type: string; data: string }>(
sql`SELECT id, type, data FROM session_message WHERE type IN ('skill', 'shell', 'assistant', 'compaction', 'synthetic')`,
)
for (const row of messages) {
const data = object(decodeJson(row.data))
yield* tx.run(
sql`UPDATE session_message SET data = ${JSON.stringify(messageData(row.type, data))} WHERE id = ${row.id}`,
)
}
yield* tx.run(sql`DELETE FROM event WHERE type = 'session.compaction.delta.1'`)
const events = yield* tx.all<{ id: string; aggregateID: string; seq: number; type: string; data: string }>(sql`
SELECT id, aggregate_id as aggregateID, seq, type, data
FROM event
WHERE type IN (
'session.skill.activated.1',
'session.skill.activated.2',
'session.compaction.started.1',
'session.compaction.started.2',
'session.compaction.ended.1',
'session.compaction.failed.1',
'session.compaction.failed.2',
'session.revert.staged.1',
'session.revert.staged.2'
)
ORDER BY aggregate_id, seq
`)
const compactionReasons = new Map<string, "auto" | "manual">()
for (const row of events) {
const data = object(decodeJson(row.data))
if (row.type.startsWith("session.compaction.ended.")) {
compactionReasons.delete(row.aggregateID)
continue
}
const event = eventData(row.type, data, compactionReasons.get(row.aggregateID))
if (row.type.startsWith("session.compaction.started."))
compactionReasons.set(row.aggregateID, event.data.reason === "auto" ? "auto" : "manual")
if (row.type.startsWith("session.compaction.failed.")) compactionReasons.delete(row.aggregateID)
yield* tx.run(
sql`UPDATE event SET type = ${event.type}, data = ${JSON.stringify(event.data)} WHERE id = ${row.id}`,
)
}
})
},
} satisfies DatabaseMigration.Migration
function messageData(type: string, data: Record<string, unknown>) {
if (type === "skill")
return defined({
metadata: data.metadata,
time: data.time,
skill: data.skill ?? data.id ?? data.name,
name: data.name,
text: data.text,
})
if (type === "shell") {
const shell = object(data.shell)
return defined({
metadata: data.metadata,
time: data.time,
shellID: data.shellID ?? shell.id,
command: data.command ?? shell.command,
status: data.status ?? shell.status,
exit: data.exit ?? shell.exit,
output: data.output,
})
}
if (type === "assistant")
return defined({
metadata: data.metadata,
time: data.time,
agent: data.agent,
model: data.model,
content: Array.isArray(data.content) ? data.content.map(assistantContent) : data.content,
snapshot: data.snapshot,
finish: data.finish,
cost: data.cost,
tokens: data.tokens,
error: data.error,
retry: data.retry,
})
if (type === "compaction") {
if (data.status === "failed")
return defined({
metadata: data.metadata,
time: data.time,
status: data.status,
reason: data.reason,
error: data.error ?? genericCompactionError,
})
return defined({
metadata: data.metadata,
time: data.time,
status: data.status,
reason: data.reason,
summary: data.summary,
recent: data.recent,
})
}
if (type === "synthetic")
return defined({ metadata: data.metadata, time: data.time, text: data.text, description: data.description })
const { sessionID: _, ...current } = data
return current
}
function assistantContent(value: unknown) {
const content = object(value)
if (content.type === "text") return defined({ type: content.type, text: content.text })
if (content.type === "reasoning")
return defined({ type: content.type, text: content.text, state: content.state, time: content.time })
if (content.type !== "tool") return content
return defined({
type: content.type,
id: content.id,
name: content.name,
executed: content.executed,
providerState: content.providerState,
providerResultState: content.providerResultState,
state: toolState(content.state),
time: content.time,
})
}
function toolState(value: unknown) {
const state = object(value)
if (state.status === "pending" || state.status === "streaming")
return defined({ status: "streaming", input: state.input })
if (state.status === "running")
return defined({ status: state.status, input: state.input, structured: state.structured, content: state.content })
if (state.status === "completed")
return defined({
status: state.status,
input: state.input,
structured: state.structured,
content: state.content,
result: state.result,
})
if (state.status === "error")
return defined({
status: state.status,
input: state.input,
structured: state.structured,
content: state.content,
error: state.error,
result: state.result,
})
return state
}
function eventData(type: string, data: Record<string, unknown>, compactionReason?: "auto" | "manual") {
if (type.startsWith("session.skill.activated."))
return {
type: "session.skill.activated.1",
data: defined({ sessionID: data.sessionID, id: data.id ?? data.name, name: data.name, text: data.text }),
}
if (type.startsWith("session.compaction.started."))
return {
type: "session.compaction.started.1",
data: defined({
sessionID: data.sessionID,
reason: data.reason,
recent: data.recent ?? "",
inputID: data.inputID,
}),
}
if (type.startsWith("session.compaction.failed."))
return {
type: "session.compaction.failed.1",
data: defined({
sessionID: data.sessionID,
reason: data.reason ?? compactionReason ?? "manual",
error: data.error ?? genericCompactionError,
inputID: data.inputID,
}),
}
const revert = object(data.revert)
return {
type: "session.revert.staged.1",
data: defined({
sessionID: data.sessionID,
revert: defined({
messageID: revert.messageID,
partID: revert.partID,
snapshot: revert.snapshot,
files: Array.isArray(revert.files)
? revert.files.map((value) => {
const file = object(value)
return defined({
file: file.file ?? file.path,
patch: file.patch,
additions: file.additions,
deletions: file.deletions,
status: file.status,
})
})
: undefined,
}),
}),
}
}
const genericCompactionError = {
type: "compaction.failed",
message: "Compaction failed before recording an error",
}
function object(value: unknown): Record<string, unknown> {
return isObject(value) ? value : {}
}
function defined(value: Record<string, unknown>) {
return Object.fromEntries(Object.entries(value).filter((entry) => entry[1] !== undefined))
}
+1 -1
View File
@@ -200,6 +200,6 @@ export const node = makeLocationNode({ service: Service, layer, deps: [FSUtil.no
// TODO: Publish watcher/file-edit events after V2 watcher integration exists. // TODO: Publish watcher/file-edit events after V2 watcher integration exists.
// TODO: Add snapshots / undo after V2 snapshot design exists. // TODO: Add snapshots / undo after V2 snapshot design exists.
// TODO: Notify LSP and collect diagnostics after V2 LSP runtime exists. // TODO: Notify LSP and collect diagnostics after V2 LSP runtime exists.
// TODO: Design multi-file transactions / rollback if apply_patch needs atomic edits. // TODO: Design multi-file transactions / rollback if patch needs atomic edits.
// Until then, edits are sequential and report partial application. // Until then, edits are sequential and report partial application.
// TODO: Define crash recovery and idempotency for side effects between Tool.Called and durable settlement. // TODO: Define crash recovery and idempotency for side effects between Tool.Called and durable settlement.
+2 -2
View File
@@ -1,6 +1,6 @@
export * as File from "./file" export * as File from "./file"
import { Revert } from "@opencode-ai/schema/revert" import { FileDiff } from "@opencode-ai/schema/file-diff"
export const Diff = Revert.FileDiff export const Diff = FileDiff.Info
export type Diff = typeof Diff.Type export type Diff = typeof Diff.Type
+1 -1
View File
@@ -606,7 +606,7 @@ const layer = Layer.effect(
file, file,
])).text ])).text
return { return {
path: file, file,
status, status,
additions: binary ? 0 : Number(stats[0] ?? 0), additions: binary ? 0 : Number(stats[0] ?? 0),
deletions: binary ? 0 : Number(stats[1] ?? 0), deletions: binary ? 0 : Number(stats[1] ?? 0),
+14 -13
View File
@@ -2,6 +2,7 @@ import path from "path"
import { Context, Duration, Effect, Layer, Option, Schedule, Schema } from "effect" import { Context, Duration, Effect, Layer, Option, Schedule, Schema } from "effect"
import { HttpClient, HttpClientRequest } from "effect/unstable/http" import { HttpClient, HttpClientRequest } from "effect/unstable/http"
import { ModelsDev } from "@opencode-ai/schema/models-dev" import { ModelsDev } from "@opencode-ai/schema/models-dev"
import { Money } from "@opencode-ai/schema/money"
import { Global } from "./global" import { Global } from "./global"
import { Flag } from "./flag/flag" import { Flag } from "./flag/flag"
import { Flock } from "./util/flock" import { Flock } from "./util/flock"
@@ -18,10 +19,10 @@ export type CatalogModelStatus = typeof CatalogModelStatus.Type
const USER_AGENT = `opencode/${InstallationChannel}/${InstallationVersion}/${Flag.OPENCODE_CLIENT}` const USER_AGENT = `opencode/${InstallationChannel}/${InstallationVersion}/${Flag.OPENCODE_CLIENT}`
const CostTier = Schema.Struct({ const CostTier = Schema.Struct({
input: Schema.Finite, input: Money.USDPerMillionTokens,
output: Schema.Finite, output: Money.USDPerMillionTokens,
cache_read: Schema.optional(Schema.Finite), cache_read: Schema.optional(Money.USDPerMillionTokens),
cache_write: Schema.optional(Schema.Finite), cache_write: Schema.optional(Money.USDPerMillionTokens),
tier: Schema.Struct({ tier: Schema.Struct({
type: Schema.Literal("context"), type: Schema.Literal("context"),
size: Schema.Finite, size: Schema.Finite,
@@ -29,17 +30,17 @@ const CostTier = Schema.Struct({
}) })
const Cost = Schema.Struct({ const Cost = Schema.Struct({
input: Schema.Finite, input: Money.USDPerMillionTokens,
output: Schema.Finite, output: Money.USDPerMillionTokens,
cache_read: Schema.optional(Schema.Finite), cache_read: Schema.optional(Money.USDPerMillionTokens),
cache_write: Schema.optional(Schema.Finite), cache_write: Schema.optional(Money.USDPerMillionTokens),
tiers: Schema.optional(Schema.Array(CostTier)), tiers: Schema.optional(Schema.Array(CostTier)),
context_over_200k: Schema.optional( context_over_200k: Schema.optional(
Schema.Struct({ Schema.Struct({
input: Schema.Finite, input: Money.USDPerMillionTokens,
output: Schema.Finite, output: Money.USDPerMillionTokens,
cache_read: Schema.optional(Schema.Finite), cache_read: Schema.optional(Money.USDPerMillionTokens),
cache_write: Schema.optional(Schema.Finite), cache_write: Schema.optional(Money.USDPerMillionTokens),
}), }),
), ),
}) })
@@ -64,7 +65,7 @@ export const Model = Schema.Struct({
name: Schema.String, name: Schema.String,
family: Schema.optional(Schema.String), family: Schema.optional(Schema.String),
release_date: Schema.String, release_date: Schema.String,
attachment: Schema.Boolean, attachment: Schema.optional(Schema.Boolean),
reasoning: Schema.Boolean, reasoning: Schema.Boolean,
reasoning_options: Schema.optional(Schema.Array(ReasoningOption)), reasoning_options: Schema.optional(Schema.Array(ReasoningOption)),
temperature: Schema.optional(Schema.Boolean), temperature: Schema.optional(Schema.Boolean),
+13 -6
View File
@@ -1,6 +1,6 @@
export * as PluginV2 from "./plugin" export * as PluginV2 from "./plugin"
import type { Plugin } from "@opencode-ai/plugin/v2/effect" import type { Plugin } from "@opencode-ai/plugin/v2/effect/plugin"
import { Event, ID, type Info } from "@opencode-ai/schema/plugin" import { Event, ID, type Info } from "@opencode-ai/schema/plugin"
import { makeLocationNode } from "./effect/app-node" import { makeLocationNode } from "./effect/app-node"
import { Context, Effect, Exit, Layer, Scope, Semaphore } from "effect" import { Context, Effect, Exit, Layer, Scope, Semaphore } from "effect"
@@ -18,6 +18,7 @@ import { SkillV2 } from "./skill"
import { State } from "./state" import { State } from "./state"
import { ToolRegistry } from "./tool/registry" import { ToolRegistry } from "./tool/registry"
import { ToolHooks } from "./tool/hooks" import { ToolHooks } from "./tool/hooks"
import { PluginHooks } from "./plugin/hooks"
export interface Interface { export interface Interface {
readonly activate: (plugins: readonly { readonly plugin: Plugin; readonly version?: string }[]) => Effect.Effect<void> readonly activate: (plugins: readonly { readonly plugin: Plugin; readonly version?: string }[]) => Effect.Effect<void>
@@ -57,12 +58,13 @@ const layer = Layer.effect(
generation.length === definitions.length && generation.length === definitions.length &&
generation.every( generation.every(
(plugin, index) => plugin.id === definitions[index]?.id && plugin.version === definitions[index]?.version, (plugin, index) => plugin.id === definitions[index]?.id && plugin.version === definitions[index]?.version,
) ) &&
definitions.every((definition) => active.has(definition.id))
) { ) {
return return
} }
generation = undefined generation = undefined
const exit = yield* State.batch( yield* State.batch(
Effect.gen(function* () { Effect.gen(function* () {
const scopes = Array.from(active.values()).toReversed() const scopes = Array.from(active.values()).toReversed()
active.clear() active.clear()
@@ -81,13 +83,17 @@ const layer = Layer.effect(
Effect.onExit((exit) => (Exit.isFailure(exit) ? Scope.close(child, exit) : Effect.void)), Effect.onExit((exit) => (Exit.isFailure(exit) ? Scope.close(child, exit) : Effect.void)),
Effect.exit, Effect.exit,
) )
if (Exit.isFailure(loaded)) return loaded if (Exit.isFailure(loaded)) {
yield* Effect.logWarning("failed to load plugin", {
"plugin.id": definition.id,
cause: loaded.cause,
})
continue
}
active.set(definition.id, child) active.set(definition.id, child)
} }
return Exit.void
}), }),
) )
if (Exit.isFailure(exit)) return yield* exit
generation = definitions.map((definition) => ({ generation = definitions.map((definition) => ({
id: definition.id, id: definition.id,
...(definition.version === undefined ? {} : { version: definition.version }), ...(definition.version === undefined ? {} : { version: definition.version }),
@@ -131,6 +137,7 @@ export const node = makeLocationNode({
SkillV2.node, SkillV2.node,
ToolRegistry.toolsNode, ToolRegistry.toolsNode,
ToolHooks.node, ToolHooks.node,
PluginHooks.node,
PluginRuntime.node, PluginRuntime.node,
], ],
}) })
+7
View File
@@ -125,6 +125,7 @@ export const Plugin = define({
yield* ctx.agent.transform((draft) => { yield* ctx.agent.transform((draft) => {
draft.update(AgentV2.defaultID, (item) => { draft.update(AgentV2.defaultID, (item) => {
item.name = AgentV2.Name.make("Build")
item.description = "The default agent. Executes tools based on configured permissions." item.description = "The default agent. Executes tools based on configured permissions."
item.mode = "primary" item.mode = "primary"
item.permissions.push( item.permissions.push(
@@ -136,6 +137,7 @@ export const Plugin = define({
}) })
draft.update(AgentV2.ID.make("plan"), (item) => { draft.update(AgentV2.ID.make("plan"), (item) => {
item.name = AgentV2.Name.make("Plan")
item.description = "Plan mode. Disallows all edit tools." item.description = "Plan mode. Disallows all edit tools."
item.mode = "primary" item.mode = "primary"
item.permissions.push( item.permissions.push(
@@ -155,6 +157,7 @@ export const Plugin = define({
}) })
draft.update(AgentV2.ID.make("general"), (item) => { draft.update(AgentV2.ID.make("general"), (item) => {
item.name = AgentV2.Name.make("General")
item.description = item.description =
"General-purpose agent for researching complex questions and executing multi-step tasks. Use this agent to execute multiple units of work in parallel." "General-purpose agent for researching complex questions and executing multi-step tasks. Use this agent to execute multiple units of work in parallel."
item.mode = "subagent" item.mode = "subagent"
@@ -167,6 +170,7 @@ export const Plugin = define({
}) })
draft.update(AgentV2.ID.make("explore"), (item) => { draft.update(AgentV2.ID.make("explore"), (item) => {
item.name = AgentV2.Name.make("Explore")
item.description = item.description =
'Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (eg. "src/components/**/*.tsx"), search code for keywords (eg. "API endpoints"), or answer questions about the codebase (eg. "how do API endpoints work?"). When calling this agent, specify the desired thoroughness level: "quick" for basic searches, "medium" for moderate exploration, or "very thorough" for comprehensive analysis across multiple locations and naming conventions.' 'Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (eg. "src/components/**/*.tsx"), search code for keywords (eg. "API endpoints"), or answer questions about the codebase (eg. "how do API endpoints work?"). When calling this agent, specify the desired thoroughness level: "quick" for basic searches, "medium" for moderate exploration, or "very thorough" for comprehensive analysis across multiple locations and naming conventions.'
item.system = PROMPT_EXPLORE item.system = PROMPT_EXPLORE
@@ -189,6 +193,7 @@ export const Plugin = define({
}) })
draft.update(AgentV2.ID.make("compaction"), (item) => { draft.update(AgentV2.ID.make("compaction"), (item) => {
item.name = AgentV2.Name.make("Compaction")
item.mode = "primary" item.mode = "primary"
item.hidden = true item.hidden = true
item.system = PROMPT_COMPACTION item.system = PROMPT_COMPACTION
@@ -196,6 +201,7 @@ export const Plugin = define({
}) })
draft.update(AgentV2.ID.make("title"), (item) => { draft.update(AgentV2.ID.make("title"), (item) => {
item.name = AgentV2.Name.make("Title")
item.mode = "primary" item.mode = "primary"
item.hidden = true item.hidden = true
item.system = PROMPT_TITLE item.system = PROMPT_TITLE
@@ -203,6 +209,7 @@ export const Plugin = define({
}) })
draft.update(AgentV2.ID.make("summary"), (item) => { draft.update(AgentV2.ID.make("summary"), (item) => {
item.name = AgentV2.Name.make("Summary")
item.mode = "primary" item.mode = "primary"
item.hidden = true item.hidden = true
item.system = PROMPT_SUMMARY item.system = PROMPT_SUMMARY
+67
View File
@@ -0,0 +1,67 @@
export * as PluginHooks from "./hooks"
import type { AISDKHooks } from "@opencode-ai/plugin/v2/effect/aisdk"
import type { SessionHooks } from "@opencode-ai/plugin/v2/effect/session"
import type { ToolHooks } from "@opencode-ai/plugin/v2/effect/tool"
import { Context, Effect, Layer, Scope } from "effect"
import { makeLocationNode } from "../effect/app-node"
import { State } from "../state"
export interface Domains {
readonly aisdk: AISDKHooks
readonly session: SessionHooks
readonly tool: ToolHooks
}
type Callback<Event> = (event: Event) => Effect.Effect<void>
export interface Interface {
readonly register: <Domain extends keyof Domains, Name extends keyof Domains[Domain]>(
domain: Domain,
name: Name,
callback: Callback<Domains[Domain][Name]>,
) => Effect.Effect<State.Registration, never, Scope.Scope>
readonly trigger: <Domain extends keyof Domains, Name extends keyof Domains[Domain]>(
domain: Domain,
name: Name,
event: Domains[Domain][Name],
) => Effect.Effect<Domains[Domain][Name]>
}
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/PluginHooks") {}
const layer = Layer.effect(
Service,
Effect.gen(function* () {
const callbacks = new Map<string, Function[]>()
const key = (domain: keyof Domains, name: PropertyKey) => `${domain}.${String(name)}`
const register: Interface["register"] = Effect.fn("PluginHooks.register")(function* (domain, name, callback) {
const scope = yield* Scope.Scope
const id = key(domain, name)
let active = true
callbacks.set(id, [...(callbacks.get(id) ?? []), callback])
const dispose = Effect.sync(() => {
if (!active) return
active = false
const next = (callbacks.get(id) ?? []).filter((item) => item !== callback)
if (next.length === 0) callbacks.delete(id)
else callbacks.set(id, next)
})
yield* Scope.addFinalizer(scope, dispose)
return { dispose }
})
const trigger: Interface["trigger"] = Effect.fn("PluginHooks.trigger")(function* (domain, name, event) {
for (const callback of callbacks.get(key(domain, name)) ?? []) {
const result: Effect.Effect<void> = Reflect.apply(callback, undefined, [event])
yield* result
}
return event
})
return Service.of({ register, trigger })
}),
)
export const node = makeLocationNode({ service: Service, layer, deps: [] })
+74 -68
View File
@@ -1,6 +1,6 @@
export * as PluginHost from "./host" export * as PluginHost from "./host"
import type { PluginContext } from "@opencode-ai/plugin/v2/effect" import type { Plugin } from "@opencode-ai/plugin/v2/effect"
import { EventManifest } from "@opencode-ai/schema/event-manifest" import { EventManifest } from "@opencode-ai/schema/event-manifest"
import { Effect, Schema, Stream } from "effect" import { Effect, Schema, Stream } from "effect"
import { AgentV2 } from "../agent" import { AgentV2 } from "../agent"
@@ -22,6 +22,7 @@ import { Tool } from "../tool/tool"
import { Tools } from "../tool/tools" import { Tools } from "../tool/tools"
import { ToolHooks } from "../tool/hooks" import { ToolHooks } from "../tool/hooks"
import { WorkspaceV2 } from "../workspace" import { WorkspaceV2 } from "../workspace"
import { PluginHooks } from "./hooks"
const mutable = <T>(value: T) => value as DeepMutable<T> const mutable = <T>(value: T) => value as DeepMutable<T>
export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Interface) { export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Interface) {
@@ -36,6 +37,7 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
const skill = yield* SkillV2.Service const skill = yield* SkillV2.Service
const tools = yield* Tools.Service const tools = yield* Tools.Service
const toolHooks = yield* ToolHooks.Service const toolHooks = yield* ToolHooks.Service
const hooks = yield* PluginHooks.Service
const runtime = yield* PluginRuntime.Service const runtime = yield* PluginRuntime.Service
const locationInfo = () => const locationInfo = () =>
new Location.Info({ new Location.Info({
@@ -43,7 +45,7 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
workspaceID: location.workspaceID, workspaceID: location.workspaceID,
project: location.project, project: location.project,
}) })
const locationRef = (input?: Parameters<PluginContext["agent"]["list"]>[0]) => const locationRef = (input?: Parameters<Plugin.Context["agent"]["list"]>[0]) =>
input?.location === undefined input?.location === undefined
? undefined ? undefined
: Location.Ref.make({ : Location.Ref.make({
@@ -79,32 +81,32 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
}), }),
}, },
aisdk: { aisdk: {
sdk: (callback) => hook: (name, callback) => {
aisdk.hook.sdk((event) => { if (name === "sdk") {
return aisdk.hook.sdk((event) => {
const output = {
model: mutable(event.model),
package: event.package,
options: event.options,
sdk: event.sdk,
}
return Reflect.apply(callback, undefined, [output]).pipe(
Effect.tap(() => Effect.sync(() => (event.sdk = output.sdk))),
)
})
}
return aisdk.hook.language((event) => {
const output = { const output = {
model: mutable(event.model), model: mutable(event.model),
package: event.package,
options: event.options, options: event.options,
sdk: event.sdk, sdk: event.sdk,
}
const result = callback(output)
return Effect.suspend(() => (Effect.isEffect(result) ? result : Effect.void)).pipe(
Effect.tap(() => Effect.sync(() => (event.sdk = output.sdk))),
)
}),
language: (callback) =>
aisdk.hook.language((event) => {
const output = {
model: mutable(event.model),
sdk: event.sdk,
options: event.options,
language: event.language, language: event.language,
} }
const result = callback(output) return Reflect.apply(callback, undefined, [output]).pipe(
return Effect.suspend(() => (Effect.isEffect(result) ? result : Effect.void)).pipe(
Effect.tap(() => Effect.sync(() => (event.language = output.language))), Effect.tap(() => Effect.sync(() => (event.language = output.language))),
) )
}), })
},
}, },
catalog: { catalog: {
provider: { provider: {
@@ -164,25 +166,29 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
integration: { integration: {
list: () => response(integration.list()), list: () => response(integration.list()),
get: (input) => response(integration.get(Integration.ID.make(input.integrationID))), get: (input) => response(integration.get(Integration.ID.make(input.integrationID))),
connectKey: (input) => connect: {
integration.connection.key({ key: (input) =>
integrationID: Integration.ID.make(input.integrationID), integration.connection.key({
key: input.key,
label: input.label,
}),
connectOauth: (input) =>
response(
integration.connection.oauth({
integrationID: Integration.ID.make(input.integrationID), integrationID: Integration.ID.make(input.integrationID),
methodID: Integration.MethodID.make(input.methodID), key: input.key,
inputs: input.inputs,
label: input.label, label: input.label,
}), }),
), oauth: (input) =>
attemptStatus: (input) => response(integration.attempt.status(Integration.AttemptID.make(input.attemptID))), response(
attemptComplete: (input) => integration.connection.oauth({
integration.attempt.complete({ attemptID: Integration.AttemptID.make(input.attemptID), code: input.code }), integrationID: Integration.ID.make(input.integrationID),
attemptCancel: (input) => integration.attempt.cancel(Integration.AttemptID.make(input.attemptID)), methodID: Integration.MethodID.make(input.methodID),
inputs: input.inputs,
label: input.label,
}),
),
},
attempt: {
status: (input) => response(integration.attempt.status(Integration.AttemptID.make(input.attemptID))),
complete: (input) =>
integration.attempt.complete({ attemptID: Integration.AttemptID.make(input.attemptID), code: input.code }),
cancel: (input) => integration.attempt.cancel(Integration.AttemptID.make(input.attemptID)),
},
reload: integration.reload, reload: integration.reload,
connection: { connection: {
active: (id) => integration.connection.active(Integration.ID.make(id)), active: (id) => integration.connection.active(Integration.ID.make(id)),
@@ -317,11 +323,12 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
registrations, registrations,
(registration) => tools.register({ [registration.name]: registration.tool }, registration.options), (registration) => tools.register({ [registration.name]: registration.tool }, registration.options),
{ discard: true }, { discard: true },
) ).pipe(Effect.orDie)
return { dispose: Effect.void }
}), }),
execute: { hook: (name, callback) => {
before: (callback) => if (name === "execute.before") {
toolHooks.hook.before((event) => { return toolHooks.hook.before((event) => {
const output = { const output = {
tool: event.tool, tool: event.tool,
sessionID: event.sessionID, sessionID: event.sessionID,
@@ -330,38 +337,37 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
toolCallID: event.toolCallID, toolCallID: event.toolCallID,
input: event.input, input: event.input,
} }
const result = callback(output) return Reflect.apply(callback, undefined, [output]).pipe(
return Effect.suspend(() => (Effect.isEffect(result) ? result : Effect.void)).pipe(
Effect.tap(() => Effect.sync(() => (event.input = output.input))), Effect.tap(() => Effect.sync(() => (event.input = output.input))),
) )
}), })
after: (callback) => }
toolHooks.hook.after((event) => { return toolHooks.hook.after((event) => {
const output = { const output = {
tool: event.tool, tool: event.tool,
sessionID: event.sessionID, sessionID: event.sessionID,
agent: event.agent, agent: event.agent,
assistantMessageID: event.assistantMessageID, assistantMessageID: event.assistantMessageID,
toolCallID: event.toolCallID, toolCallID: event.toolCallID,
input: event.input, input: event.input,
result: event.result, result: event.result,
output: event.output, output: event.output,
outputPaths: event.outputPaths, outputPaths: event.outputPaths,
} }
const result = callback(output) return Reflect.apply(callback, undefined, [output]).pipe(
return Effect.suspend(() => (Effect.isEffect(result) ? result : Effect.void)).pipe( Effect.tap(() =>
Effect.tap(() => Effect.sync(() => {
Effect.sync(() => { event.result = output.result
event.result = output.result event.output = output.output
event.output = output.output event.outputPaths = output.outputPaths
event.outputPaths = output.outputPaths }),
}), ),
), )
) })
}),
}, },
}, },
session: { session: {
hook: (name, callback) => hooks.register("session", name, callback),
create: (input) => create: (input) =>
runtime.session.create({ runtime.session.create({
id: input?.id, id: input?.id,
@@ -375,5 +381,5 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
command: runtime.session.command, command: runtime.session.command,
interrupt: (input) => runtime.session.interrupt(input.sessionID), interrupt: (input) => runtime.session.interrupt(input.sessionID),
}, },
} satisfies PluginContext } satisfies Plugin.Context
}) })
+3 -3
View File
@@ -1,6 +1,6 @@
export * as PluginInternal from "./internal" export * as PluginInternal from "./internal"
import type { Plugin } from "@opencode-ai/plugin/v2/effect" import type { Plugin } from "@opencode-ai/plugin/v2/effect/plugin"
import { Context, Effect, Scope } from "effect" import { Context, Effect, Scope } from "effect"
import { HttpClient } from "effect/unstable/http" import { HttpClient } from "effect/unstable/http"
import { AgentV2 } from "../agent" import { AgentV2 } from "../agent"
@@ -31,7 +31,7 @@ import { SessionInstructions } from "../session/instructions"
import { SessionTodo } from "../session/todo" import { SessionTodo } from "../session/todo"
import { Shell } from "../shell" import { Shell } from "../shell"
import { SkillV2 } from "../skill" import { SkillV2 } from "../skill"
import { ApplyPatchTool } from "../tool/apply-patch" import { PatchTool } from "../tool/patch"
import { EditTool } from "../tool/edit" import { EditTool } from "../tool/edit"
import { GlobTool } from "../tool/glob" import { GlobTool } from "../tool/glob"
import { GrepTool } from "../tool/grep" import { GrepTool } from "../tool/grep"
@@ -127,7 +127,7 @@ const pre = [
SkillPlugin.Plugin, SkillPlugin.Plugin,
ModelsDevPlugin, ModelsDevPlugin,
...ProviderPlugins, ...ProviderPlugins,
ApplyPatchTool.Plugin, PatchTool.Plugin,
EditTool.Plugin, EditTool.Plugin,
GlobTool.Plugin, GlobTool.Plugin,
GrepTool.Plugin, GrepTool.Plugin,
+39 -24
View File
@@ -1,5 +1,6 @@
import { define } from "@opencode-ai/plugin/v2/effect/plugin" import { define } from "@opencode-ai/plugin/v2/effect/plugin"
import type { ModelV2Info } from "@opencode-ai/sdk/v2/types" import { Money } from "@opencode-ai/schema/money"
import type { ModelInfo } from "@opencode-ai/sdk/v2/types"
import { Effect, Stream } from "effect" import { Effect, Stream } from "effect"
import { EventV2 } from "../event" import { EventV2 } from "../event"
import { ModelV2 } from "../model" import { ModelV2 } from "../model"
@@ -11,13 +12,13 @@ function released(date: string) {
return Number.isFinite(time) ? time : 0 return Number.isFinite(time) ? time : 0
} }
function cost(input: ModelsDev.Model["cost"]): ModelV2Info["cost"] { function cost(input: ModelsDev.Model["cost"]): ModelInfo["cost"] {
const base = { const base = {
input: input?.input ?? 0, input: input?.input ?? Money.USDPerMillionTokens.zero,
output: input?.output ?? 0, output: input?.output ?? Money.USDPerMillionTokens.zero,
cache: { cache: {
read: input?.cache_read ?? 0, read: input?.cache_read ?? Money.USDPerMillionTokens.zero,
write: input?.cache_write ?? 0, write: input?.cache_write ?? Money.USDPerMillionTokens.zero,
}, },
} }
return [ return [
@@ -27,8 +28,8 @@ function cost(input: ModelsDev.Model["cost"]): ModelV2Info["cost"] {
input: item.input, input: item.input,
output: item.output, output: item.output,
cache: { cache: {
read: item.cache_read ?? 0, read: item.cache_read ?? Money.USDPerMillionTokens.zero,
write: item.cache_write ?? 0, write: item.cache_write ?? Money.USDPerMillionTokens.zero,
}, },
})) ?? []), })) ?? []),
...(input?.context_over_200k ...(input?.context_over_200k
@@ -41,8 +42,8 @@ function cost(input: ModelsDev.Model["cost"]): ModelV2Info["cost"] {
input: input.context_over_200k.input, input: input.context_over_200k.input,
output: input.context_over_200k.output, output: input.context_over_200k.output,
cache: { cache: {
read: input.context_over_200k.cache_read ?? 0, read: input.context_over_200k.cache_read ?? Money.USDPerMillionTokens.zero,
write: input.context_over_200k.cache_write ?? 0, write: input.context_over_200k.cache_write ?? Money.USDPerMillionTokens.zero,
}, },
}, },
] ]
@@ -50,13 +51,13 @@ function cost(input: ModelsDev.Model["cost"]): ModelV2Info["cost"] {
] ]
} }
function mergeCost(base: ModelV2Info["cost"], override: ModelsDev.Model["cost"] | undefined) { function mergeCost(base: ModelInfo["cost"], override: ModelsDev.Model["cost"] | undefined) {
if (!override) return base if (!override) return base
const next = cost(override) const next = cost(override)
const [baseDefault, ...baseTiers] = base const [baseDefault, ...baseTiers] = base
const [nextDefault, ...nextTiers] = next const [nextDefault, ...nextTiers] = next
const tierKey = (item: ModelV2Info["cost"][number]) => `${item.tier?.type ?? "base"}:${item.tier?.size ?? 0}` const tierKey = (item: ModelInfo["cost"][number]) => `${item.tier?.type ?? "base"}:${item.tier?.size ?? 0}`
const merge = (left: ModelV2Info["cost"][number], right: ModelV2Info["cost"][number]) => ({ const merge = (left: ModelInfo["cost"][number], right: ModelInfo["cost"][number]) => ({
...left, ...left,
...right, ...right,
tier: right.tier ?? left.tier, tier: right.tier ?? left.tier,
@@ -67,12 +68,25 @@ function mergeCost(base: ModelV2Info["cost"], override: ModelsDev.Model["cost"]
const current = tiers.get(tierKey(item)) const current = tiers.get(tierKey(item))
tiers.set(tierKey(item), current ? merge(current, item) : item) tiers.set(tierKey(item), current ? merge(current, item) : item)
} }
return [merge(baseDefault ?? { input: 0, output: 0, cache: { read: 0, write: 0 } }, nextDefault), ...tiers.values()] return [
merge(
baseDefault ?? {
input: Money.USDPerMillionTokens.zero,
output: Money.USDPerMillionTokens.zero,
cache: {
read: Money.USDPerMillionTokens.zero,
write: Money.USDPerMillionTokens.zero,
},
},
nextDefault,
),
...tiers.values(),
]
} }
const OPENAI_INCLUDE_ENCRYPTED_REASONING = ["reasoning.encrypted_content"] const OPENAI_INCLUDE_ENCRYPTED_REASONING = ["reasoning.encrypted_content"]
function reasoningVariants(provider: ModelsDev.Provider, model: ModelsDev.Model): NonNullable<ModelV2Info["variants"]> { function reasoningVariants(provider: ModelsDev.Provider, model: ModelsDev.Model): NonNullable<ModelInfo["variants"]> {
const npm = model.provider?.npm ?? provider.npm const npm = model.provider?.npm ?? provider.npm
const options = model.reasoning_options ?? [] const options = model.reasoning_options ?? []
const effort = options.find((option) => option.type === "effort") const effort = options.find((option) => option.type === "effort")
@@ -117,7 +131,7 @@ function settingsForEffort(npm: string | undefined, effort: string): ProviderV2.
function budgetVariants( function budgetVariants(
npm: string | undefined, npm: string | undefined,
option: Extract<NonNullable<ModelsDev.Model["reasoning_options"]>[number], { type: "budget_tokens" }>, option: Extract<NonNullable<ModelsDev.Model["reasoning_options"]>[number], { type: "budget_tokens" }>,
): NonNullable<ModelV2Info["variants"]> { ): NonNullable<ModelInfo["variants"]> {
const max = option.max const max = option.max
const high = const high =
option.max === undefined option.max === undefined
@@ -146,7 +160,7 @@ function modeName(model: ModelsDev.Model, mode: string) {
return `${model.name} ${mode.charAt(0).toUpperCase()}${mode.slice(1)}` return `${model.name} ${mode.charAt(0).toUpperCase()}${mode.slice(1)}`
} }
function mergeVariants(model: ModelV2Info, next: NonNullable<ModelV2Info["variants"]>) { function mergeVariants(model: ModelInfo, next: NonNullable<ModelInfo["variants"]>) {
const variants = model.variants ?? [] const variants = model.variants ?? []
const existing = new Map(variants.map((variant) => [variant.id, variant])) const existing = new Map(variants.map((variant) => [variant.id, variant]))
const nextIDs = new Set(next.map((variant) => variant.id)) const nextIDs = new Set(next.map((variant) => variant.id))
@@ -157,13 +171,13 @@ function mergeVariants(model: ModelV2Info, next: NonNullable<ModelV2Info["varian
} }
function applyModel( function applyModel(
draft: ModelV2Info, draft: ModelInfo,
model: ModelsDev.Model, model: ModelsDev.Model,
input: { input: {
readonly name?: string readonly name?: string
readonly cost?: ModelV2Info["cost"] readonly cost?: ModelInfo["cost"]
readonly request?: NonNullable<NonNullable<ModelsDev.Model["experimental"]>["modes"]>[string]["provider"] readonly request?: NonNullable<NonNullable<ModelsDev.Model["experimental"]>["modes"]>[string]["provider"]
readonly variants?: NonNullable<ModelV2Info["variants"]> readonly variants?: NonNullable<ModelInfo["variants"]>
} = {}, } = {},
) { ) {
draft.name = input.name ?? model.name draft.name = input.name ?? model.name
@@ -171,11 +185,12 @@ function applyModel(
draft.family = model.family ? ModelV2.Family.make(model.family) : undefined draft.family = model.family ? ModelV2.Family.make(model.family) : undefined
draft.package = model.provider?.npm ? ProviderV2.aisdk(model.provider.npm) : undefined draft.package = model.provider?.npm ? ProviderV2.aisdk(model.provider.npm) : undefined
draft.settings = model.provider?.api ? { ...draft.settings, baseURL: model.provider.api } : draft.settings draft.settings = model.provider?.api ? { ...draft.settings, baseURL: model.provider.api } : draft.settings
draft.capabilities = { const capabilities = ModelV2.Capabilities.defaults({
tools: model.tool_call, tools: model.tool_call,
input: [...(model.modalities?.input ?? [])], input: model.modalities?.input ?? (model.attachment === false ? ["text"] : undefined),
output: [...(model.modalities?.output ?? [])], output: model.modalities?.output,
} })
draft.capabilities = { ...capabilities, input: [...capabilities.input], output: [...capabilities.output] }
mergeVariants(draft, input.variants ?? []) mergeVariants(draft, input.variants ?? [])
draft.time.released = released(model.release_date) draft.time.released = released(model.release_date)
draft.cost = (input.cost ?? cost(model.cost)).map((item) => ({ draft.cost = (input.cost ?? cost(model.cost)).map((item) => ({
+24 -14
View File
@@ -1,11 +1,12 @@
export * as PluginPromise from "./promise" export * as PluginPromise from "./promise"
import { define } from "@opencode-ai/plugin/v2/effect" import { Plugin } from "@opencode-ai/plugin/v2/effect"
import type { Plugin, PluginContext } from "@opencode-ai/plugin/v2/promise"
import { Effect, Scope, Stream } from "effect" import { Effect, Scope, Stream } from "effect"
type HostRegistration = { readonly dispose: Effect.Effect<void> } type HostRegistration = { readonly dispose: Effect.Effect<void> }
type Registration = { readonly dispose: () => Promise<void> } type Registration = { readonly dispose: () => Promise<void> }
type PromisePlugin = import("@opencode-ai/plugin/v2/plugin").Plugin
type PromisePluginContext = import("@opencode-ai/plugin/v2/plugin").Context
/** /**
* Adapts a Promise plugin into an Effect plugin so the existing Effect-only * Adapts a Promise plugin into an Effect plugin so the existing Effect-only
@@ -16,8 +17,8 @@ type Registration = { readonly dispose: () => Promise<void> }
* preserves boot-time batching, so Promise-plugin transforms still coalesce * preserves boot-time batching, so Promise-plugin transforms still coalesce
* into one reload per domain. * into one reload per domain.
*/ */
export function fromPromise(plugin: Plugin) { export function fromPromise(plugin: PromisePlugin) {
return define({ return Plugin.define({
id: plugin.id, id: plugin.id,
effect: (host) => effect: (host) =>
Effect.gen(function* () { Effect.gen(function* () {
@@ -43,7 +44,7 @@ export function fromPromise(plugin: Plugin) {
}), }),
) )
const context2: PluginContext = { const context2: PromisePluginContext = {
options: host.options, options: host.options,
agent: { agent: {
list: (input) => run(host.agent.list(input)), list: (input) => run(host.agent.list(input)),
@@ -51,10 +52,8 @@ export function fromPromise(plugin: Plugin) {
reload: () => run(host.agent.reload()), reload: () => run(host.agent.reload()),
}, },
aisdk: { aisdk: {
sdk: (callback) => hook: (name, callback) =>
register(host.aisdk.sdk((event) => Effect.promise(() => Promise.resolve(callback(event))))), register(host.aisdk.hook(name, (event) => Effect.promise(() => Promise.resolve(callback(event))))),
language: (callback) =>
register(host.aisdk.language((event) => Effect.promise(() => Promise.resolve(callback(event))))),
}, },
catalog: { catalog: {
provider: { provider: {
@@ -79,11 +78,15 @@ export function fromPromise(plugin: Plugin) {
integration: { integration: {
list: (input) => run(host.integration.list(input)), list: (input) => run(host.integration.list(input)),
get: (input) => run(host.integration.get(input)), get: (input) => run(host.integration.get(input)),
connectKey: (input) => run(host.integration.connectKey(input)), connect: {
connectOauth: (input) => run(host.integration.connectOauth(input)), key: (input) => run(host.integration.connect.key(input)),
attemptStatus: (input) => run(host.integration.attemptStatus(input)), oauth: (input) => run(host.integration.connect.oauth(input)),
attemptComplete: (input) => run(host.integration.attemptComplete(input)), },
attemptCancel: (input) => run(host.integration.attemptCancel(input)), attempt: {
status: (input) => run(host.integration.attempt.status(input)),
complete: (input) => run(host.integration.attempt.complete(input)),
cancel: (input) => run(host.integration.attempt.cancel(input)),
},
transform: transform(host.integration), transform: transform(host.integration),
reload: () => run(host.integration.reload()), reload: () => run(host.integration.reload()),
connection: { connection: {
@@ -104,12 +107,19 @@ export function fromPromise(plugin: Plugin) {
transform: transform(host.skill), transform: transform(host.skill),
reload: () => run(host.skill.reload()), reload: () => run(host.skill.reload()),
}, },
tool: {
transform: transform(host.tool),
hook: (name, callback) =>
register(host.tool.hook(name, (event) => Effect.promise(() => Promise.resolve(callback(event))))),
},
session: { session: {
create: (input) => run(host.session.create(input)), create: (input) => run(host.session.create(input)),
get: (input) => run(host.session.get(input)), get: (input) => run(host.session.get(input)),
prompt: (input) => run(host.session.prompt(input)), prompt: (input) => run(host.session.prompt(input)),
command: (input) => run(host.session.command(input)), command: (input) => run(host.session.command(input)),
interrupt: (input) => run(host.session.interrupt(input)), interrupt: (input) => run(host.session.interrupt(input)),
hook: (name, callback) =>
register(host.session.hook(name, (event) => Effect.promise(() => Promise.resolve(callback(event))))),
}, },
} }
+2 -1
View File
@@ -4,7 +4,8 @@ import { define } from "@opencode-ai/plugin/v2/effect/plugin"
export const AlibabaPlugin = define({ export const AlibabaPlugin = define({
id: "opencode.provider.alibaba", id: "opencode.provider.alibaba",
effect: Effect.fn(function* (ctx) { effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.sdk( yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/alibaba") return if (evt.package !== "@ai-sdk/alibaba") return
const mod = yield* Effect.promise(() => import("@ai-sdk/alibaba")) const mod = yield* Effect.promise(() => import("@ai-sdk/alibaba"))
@@ -75,7 +75,8 @@ export const AmazonBedrockPlugin = define({
}) })
} }
}) })
yield* ctx.aisdk.sdk( yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (!["@ai-sdk/amazon-bedrock", "@ai-sdk/amazon-bedrock/mantle"].includes(evt.package)) return if (!["@ai-sdk/amazon-bedrock", "@ai-sdk/amazon-bedrock/mantle"].includes(evt.package)) return
const options = { ...evt.options } const options = { ...evt.options }
@@ -108,7 +109,8 @@ export const AmazonBedrockPlugin = define({
evt.sdk = mod.createAmazonBedrock(options) evt.sdk = mod.createAmazonBedrock(options)
}), }),
) )
yield* ctx.aisdk.language( yield* ctx.aisdk.hook(
"language",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.model.providerID !== ProviderV2.ID.amazonBedrock) return if (evt.model.providerID !== ProviderV2.ID.amazonBedrock) return
if ( if (
@@ -17,7 +17,8 @@ export const AnthropicPlugin = define({
}) })
} }
}) })
yield* ctx.aisdk.sdk( yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/anthropic") return if (evt.package !== "@ai-sdk/anthropic") return
const mod = yield* Effect.promise(() => import("@ai-sdk/anthropic")) const mod = yield* Effect.promise(() => import("@ai-sdk/anthropic"))
+6 -3
View File
@@ -26,7 +26,8 @@ export const AzurePlugin = define({
}) })
} }
}) })
yield* ctx.aisdk.sdk( yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/azure") return if (evt.package !== "@ai-sdk/azure") return
if (evt.model.providerID === ProviderV2.ID.azure) { if (evt.model.providerID === ProviderV2.ID.azure) {
@@ -44,7 +45,8 @@ export const AzurePlugin = define({
evt.sdk = mod.createAzure(evt.options) evt.sdk = mod.createAzure(evt.options)
}), }),
) )
yield* ctx.aisdk.language( yield* ctx.aisdk.hook(
"language",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.model.providerID !== ProviderV2.ID.azure) return if (evt.model.providerID !== ProviderV2.ID.azure) return
evt.language = selectLanguage( evt.language = selectLanguage(
@@ -75,7 +77,8 @@ export const AzureCognitiveServicesPlugin = define({
}) })
} }
}) })
yield* ctx.aisdk.language( yield* ctx.aisdk.hook(
"language",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.model.providerID !== ProviderV2.ID.make("azure-cognitive-services")) return if (evt.model.providerID !== ProviderV2.ID.make("azure-cognitive-services")) return
evt.language = selectLanguage( evt.language = selectLanguage(
@@ -14,7 +14,8 @@ export const CerebrasPlugin = define({
}) })
} }
}) })
yield* ctx.aisdk.sdk( yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/cerebras") return if (evt.package !== "@ai-sdk/cerebras") return
const mod = yield* Effect.promise(() => import("@ai-sdk/cerebras")) const mod = yield* Effect.promise(() => import("@ai-sdk/cerebras"))
@@ -6,7 +6,8 @@ import { define } from "@opencode-ai/plugin/v2/effect/plugin"
export const CloudflareAIGatewayPlugin = define({ export const CloudflareAIGatewayPlugin = define({
id: "opencode.provider.cloudflare-ai-gateway", id: "opencode.provider.cloudflare-ai-gateway",
effect: Effect.fn(function* (ctx) { effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.sdk( yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.package !== "ai-gateway-provider") return if (evt.package !== "ai-gateway-provider") return
if (evt.options.baseURL) return if (evt.options.baseURL) return
@@ -19,7 +19,8 @@ export const CloudflareWorkersAIPlugin = define({
if (accountId) provider.settings = { ...provider.settings, baseURL: workersEndpoint(accountId) } if (accountId) provider.settings = { ...provider.settings, baseURL: workersEndpoint(accountId) }
}) })
}) })
yield* ctx.aisdk.sdk( yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.model.providerID !== providerID) return if (evt.model.providerID !== providerID) return
if (evt.package !== "@ai-sdk/openai-compatible") return if (evt.package !== "@ai-sdk/openai-compatible") return
@@ -35,7 +36,8 @@ export const CloudflareWorkersAIPlugin = define({
) )
}), }),
) )
yield* ctx.aisdk.language( yield* ctx.aisdk.hook(
"language",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.model.providerID !== providerID) return if (evt.model.providerID !== providerID) return
evt.language = evt.sdk.languageModel(evt.model.modelID ?? evt.model.id) evt.language = evt.sdk.languageModel(evt.model.modelID ?? evt.model.id)
+2 -1
View File
@@ -4,7 +4,8 @@ import { define } from "@opencode-ai/plugin/v2/effect/plugin"
export const CoherePlugin = define({ export const CoherePlugin = define({
id: "opencode.provider.cohere", id: "opencode.provider.cohere",
effect: Effect.fn(function* (ctx) { effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.sdk( yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/cohere") return if (evt.package !== "@ai-sdk/cohere") return
const mod = yield* Effect.promise(() => import("@ai-sdk/cohere")) const mod = yield* Effect.promise(() => import("@ai-sdk/cohere"))
@@ -4,7 +4,8 @@ import { define } from "@opencode-ai/plugin/v2/effect/plugin"
export const DeepInfraPlugin = define({ export const DeepInfraPlugin = define({
id: "opencode.provider.deepinfra", id: "opencode.provider.deepinfra",
effect: Effect.fn(function* (ctx) { effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.sdk( yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/deepinfra") return if (evt.package !== "@ai-sdk/deepinfra") return
const mod = yield* Effect.promise(() => import("@ai-sdk/deepinfra")) const mod = yield* Effect.promise(() => import("@ai-sdk/deepinfra"))
+2 -1
View File
@@ -7,7 +7,8 @@ export const DynamicProviderPlugin = define({
id: "opencode.provider.dynamic", id: "opencode.provider.dynamic",
effect: Effect.fn(function* (ctx) { effect: Effect.fn(function* (ctx) {
const npm = yield* Npm.Service const npm = yield* Npm.Service
yield* ctx.aisdk.sdk( yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.sdk) return if (evt.sdk) return
+2 -1
View File
@@ -4,7 +4,8 @@ import { define } from "@opencode-ai/plugin/v2/effect/plugin"
export const GatewayPlugin = define({ export const GatewayPlugin = define({
id: "opencode.provider.gateway", id: "opencode.provider.gateway",
effect: Effect.fn(function* (ctx) { effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.sdk( yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/gateway") return if (evt.package !== "@ai-sdk/gateway") return
const mod = yield* Effect.promise(() => import("@ai-sdk/gateway")) const mod = yield* Effect.promise(() => import("@ai-sdk/gateway"))
@@ -23,14 +23,16 @@ export const GithubCopilotPlugin = define({
model.enabled = false model.enabled = false
}) })
}) })
yield* ctx.aisdk.sdk( yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/github-copilot") return if (evt.package !== "@ai-sdk/github-copilot") return
const mod = yield* Effect.promise(() => import("../../github-copilot/copilot-provider")) const mod = yield* Effect.promise(() => import("../../github-copilot/copilot-provider"))
evt.sdk = mod.createOpenaiCompatible(evt.options) evt.sdk = mod.createOpenaiCompatible(evt.options)
}), }),
) )
yield* ctx.aisdk.language( yield* ctx.aisdk.hook(
"language",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.model.providerID !== ProviderV2.ID.githubCopilot) return if (evt.model.providerID !== ProviderV2.ID.githubCopilot) return
if (evt.sdk.responses === undefined && evt.sdk.chat === undefined) { if (evt.sdk.responses === undefined && evt.sdk.chat === undefined) {
+4 -2
View File
@@ -7,7 +7,8 @@ import { ProviderV2 } from "../../provider"
export const GitLabPlugin = define({ export const GitLabPlugin = define({
id: "opencode.provider.gitlab", id: "opencode.provider.gitlab",
effect: Effect.fn(function* (ctx) { effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.sdk( yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.package !== "gitlab-ai-provider") return if (evt.package !== "gitlab-ai-provider") return
const mod = yield* Effect.promise(() => import("gitlab-ai-provider")) const mod = yield* Effect.promise(() => import("gitlab-ai-provider"))
@@ -31,7 +32,8 @@ export const GitLabPlugin = define({
}) })
}), }),
) )
yield* ctx.aisdk.language( yield* ctx.aisdk.hook(
"language",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.model.providerID !== ProviderV2.ID.gitlab) return if (evt.model.providerID !== ProviderV2.ID.gitlab) return
const featureFlags = const featureFlags =
@@ -85,7 +85,8 @@ export const GoogleVertexPlugin = define({
}) })
} }
}) })
yield* ctx.aisdk.sdk( yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.model.providerID === ProviderV2.ID.googleVertex && evt.package.includes("@ai-sdk/openai-compatible")) { if (evt.model.providerID === ProviderV2.ID.googleVertex && evt.package.includes("@ai-sdk/openai-compatible")) {
evt.options.fetch = authFetch(evt.options.fetch) evt.options.fetch = authFetch(evt.options.fetch)
@@ -104,7 +105,8 @@ export const GoogleVertexPlugin = define({
}) })
}), }),
) )
yield* ctx.aisdk.language( yield* ctx.aisdk.hook(
"language",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.model.providerID !== ProviderV2.ID.googleVertex) return if (evt.model.providerID !== ProviderV2.ID.googleVertex) return
evt.language = evt.sdk.languageModel(String(evt.model.modelID ?? evt.model.id).trim()) evt.language = evt.sdk.languageModel(String(evt.model.modelID ?? evt.model.id).trim())
@@ -135,7 +137,8 @@ export const GoogleVertexAnthropicPlugin = define({
}) })
} }
}) })
yield* ctx.aisdk.sdk( yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/google-vertex/anthropic") return if (evt.package !== "@ai-sdk/google-vertex/anthropic") return
const mod = yield* Effect.promise(() => import("@ai-sdk/google-vertex/anthropic")) const mod = yield* Effect.promise(() => import("@ai-sdk/google-vertex/anthropic"))
@@ -161,7 +164,8 @@ export const GoogleVertexAnthropicPlugin = define({
}) })
}), }),
) )
yield* ctx.aisdk.language( yield* ctx.aisdk.hook(
"language",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.model.providerID !== ProviderV2.ID.make("google-vertex-anthropic")) return if (evt.model.providerID !== ProviderV2.ID.make("google-vertex-anthropic")) return
evt.language = evt.sdk.languageModel(String(evt.model.modelID ?? evt.model.id).trim()) evt.language = evt.sdk.languageModel(String(evt.model.modelID ?? evt.model.id).trim())
+2 -1
View File
@@ -4,7 +4,8 @@ import { define } from "@opencode-ai/plugin/v2/effect/plugin"
export const GooglePlugin = define({ export const GooglePlugin = define({
id: "opencode.provider.google", id: "opencode.provider.google",
effect: Effect.fn(function* (ctx) { effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.sdk( yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/google") return if (evt.package !== "@ai-sdk/google") return
const mod = yield* Effect.promise(() => import("@ai-sdk/google")) const mod = yield* Effect.promise(() => import("@ai-sdk/google"))
+2 -1
View File
@@ -4,7 +4,8 @@ import { define } from "@opencode-ai/plugin/v2/effect/plugin"
export const GroqPlugin = define({ export const GroqPlugin = define({
id: "opencode.provider.groq", id: "opencode.provider.groq",
effect: Effect.fn(function* (ctx) { effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.sdk( yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/groq") return if (evt.package !== "@ai-sdk/groq") return
const mod = yield* Effect.promise(() => import("@ai-sdk/groq")) const mod = yield* Effect.promise(() => import("@ai-sdk/groq"))
+2 -1
View File
@@ -4,7 +4,8 @@ import { define } from "@opencode-ai/plugin/v2/effect/plugin"
export const MistralPlugin = define({ export const MistralPlugin = define({
id: "opencode.provider.mistral", id: "opencode.provider.mistral",
effect: Effect.fn(function* (ctx) { effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.sdk( yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/mistral") return if (evt.package !== "@ai-sdk/mistral") return
const mod = yield* Effect.promise(() => import("@ai-sdk/mistral")) const mod = yield* Effect.promise(() => import("@ai-sdk/mistral"))
@@ -4,7 +4,8 @@ import { define } from "@opencode-ai/plugin/v2/effect/plugin"
export const OpenAICompatiblePlugin = define({ export const OpenAICompatiblePlugin = define({
id: "opencode.provider.openai-compatible", id: "opencode.provider.openai-compatible",
effect: Effect.fn(function* (ctx) { effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.sdk( yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.sdk) return if (evt.sdk) return
if (!evt.package.includes("@ai-sdk/openai-compatible")) return if (!evt.package.includes("@ai-sdk/openai-compatible")) return
+4 -2
View File
@@ -210,14 +210,16 @@ export const OpenAIPlugin = define({
Effect.forkScoped({ startImmediately: true }), Effect.forkScoped({ startImmediately: true }),
) )
yield* refresh().pipe(Effect.forkScoped) yield* refresh().pipe(Effect.forkScoped)
yield* ctx.aisdk.sdk( yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/openai") return if (evt.package !== "@ai-sdk/openai") return
const mod = yield* Effect.promise(() => import("@ai-sdk/openai")) const mod = yield* Effect.promise(() => import("@ai-sdk/openai"))
evt.sdk = mod.createOpenAI(evt.options) evt.sdk = mod.createOpenAI(evt.options)
}), }),
) )
yield* ctx.aisdk.language( yield* ctx.aisdk.hook(
"language",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.model.providerID !== ProviderV2.ID.openai) return if (evt.model.providerID !== ProviderV2.ID.openai) return
evt.language = evt.sdk.responses(evt.model.modelID ?? evt.model.id) evt.language = evt.sdk.responses(evt.model.modelID ?? evt.model.id)
+11 -7
View File
@@ -10,6 +10,7 @@ import { Integration } from "../../integration"
import { ModelV2 } from "../../model" import { ModelV2 } from "../../model"
import { ProviderV2 } from "../../provider" import { ProviderV2 } from "../../provider"
import { ConfigProviderV1 } from "../../v1/config/provider" import { ConfigProviderV1 } from "../../v1/config/provider"
import { Money } from "@opencode-ai/schema/money"
import { ConfigProviderOptionsV1 } from "../../v1/config/provider-options" import { ConfigProviderOptionsV1 } from "../../v1/config/provider-options"
import { ConfigV1 } from "../../v1/config/config" import { ConfigV1 } from "../../v1/config/config"
@@ -220,20 +221,23 @@ function withoutCredentials(body: Readonly<Record<string, unknown>> | undefined)
function remoteCost(input: NonNullable<(typeof ConfigProviderV1.Model.Type)["cost"]>) { function remoteCost(input: NonNullable<(typeof ConfigProviderV1.Model.Type)["cost"]>) {
const base = { const base = {
input: input.input, input: Money.USDPerMillionTokens.make(input.input),
output: input.output, output: Money.USDPerMillionTokens.make(input.output),
cache: { read: input.cache_read ?? 0, write: input.cache_write ?? 0 }, cache: {
read: Money.USDPerMillionTokens.make(input.cache_read ?? 0),
write: Money.USDPerMillionTokens.make(input.cache_write ?? 0),
},
} }
if (!input.context_over_200k) return [base] if (!input.context_over_200k) return [base]
return [ return [
base, base,
{ {
tier: { type: "context" as const, size: 200_000 }, tier: { type: "context" as const, size: 200_000 },
input: input.context_over_200k.input, input: Money.USDPerMillionTokens.make(input.context_over_200k.input),
output: input.context_over_200k.output, output: Money.USDPerMillionTokens.make(input.context_over_200k.output),
cache: { cache: {
read: input.context_over_200k.cache_read ?? 0, read: Money.USDPerMillionTokens.make(input.context_over_200k.cache_read ?? 0),
write: input.context_over_200k.cache_write ?? 0, write: Money.USDPerMillionTokens.make(input.context_over_200k.cache_write ?? 0),
}, },
}, },
] ]
@@ -23,7 +23,8 @@ export const OpenRouterPlugin = define({
} }
} }
}) })
yield* ctx.aisdk.sdk( yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.package !== "@openrouter/ai-sdk-provider") return if (evt.package !== "@openrouter/ai-sdk-provider") return
const mod = yield* Effect.promise(() => import("@openrouter/ai-sdk-provider")) const mod = yield* Effect.promise(() => import("@openrouter/ai-sdk-provider"))
@@ -4,7 +4,8 @@ import { define } from "@opencode-ai/plugin/v2/effect/plugin"
export const PerplexityPlugin = define({ export const PerplexityPlugin = define({
id: "opencode.provider.perplexity", id: "opencode.provider.perplexity",
effect: Effect.fn(function* (ctx) { effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.sdk( yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/perplexity") return if (evt.package !== "@ai-sdk/perplexity") return
const mod = yield* Effect.promise(() => import("@ai-sdk/perplexity")) const mod = yield* Effect.promise(() => import("@ai-sdk/perplexity"))
@@ -8,7 +8,8 @@ export const SapAICorePlugin = define({
id: "opencode.provider.sap-ai-core", id: "opencode.provider.sap-ai-core",
effect: Effect.fn(function* (ctx) { effect: Effect.fn(function* (ctx) {
const npm = yield* Npm.Service const npm = yield* Npm.Service
yield* ctx.aisdk.sdk( yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.model.providerID !== ProviderV2.ID.make("sap-ai-core")) return if (evt.model.providerID !== ProviderV2.ID.make("sap-ai-core")) return
const serviceKey = const serviceKey =
@@ -37,7 +38,8 @@ export const SapAICorePlugin = define({
) )
}), }),
) )
yield* ctx.aisdk.language( yield* ctx.aisdk.hook(
"language",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.model.providerID !== ProviderV2.ID.make("sap-ai-core")) return if (evt.model.providerID !== ProviderV2.ID.make("sap-ai-core")) return
evt.language = evt.sdk(evt.model.modelID ?? evt.model.id) evt.language = evt.sdk(evt.model.modelID ?? evt.model.id)
@@ -67,7 +67,8 @@ export function cortexFetch(upstream: FetchLike = fetch) {
export const SnowflakeCortexPlugin = define({ export const SnowflakeCortexPlugin = define({
id: "opencode.provider.snowflake-cortex", id: "opencode.provider.snowflake-cortex",
effect: Effect.fn(function* (ctx) { effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.sdk( yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.model.providerID !== ProviderV2.ID.make("snowflake-cortex")) return if (evt.model.providerID !== ProviderV2.ID.make("snowflake-cortex")) return
const token = const token =
@@ -4,7 +4,8 @@ import { define } from "@opencode-ai/plugin/v2/effect/plugin"
export const TogetherAIPlugin = define({ export const TogetherAIPlugin = define({
id: "opencode.provider.togetherai", id: "opencode.provider.togetherai",
effect: Effect.fn(function* (ctx) { effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.sdk( yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/togetherai") return if (evt.package !== "@ai-sdk/togetherai") return
const mod = yield* Effect.promise(() => import("@ai-sdk/togetherai")) const mod = yield* Effect.promise(() => import("@ai-sdk/togetherai"))
+2 -1
View File
@@ -4,7 +4,8 @@ import { define } from "@opencode-ai/plugin/v2/effect/plugin"
export const VenicePlugin = define({ export const VenicePlugin = define({
id: "opencode.provider.venice", id: "opencode.provider.venice",
effect: Effect.fn(function* (ctx) { effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.sdk( yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.package !== "venice-ai-sdk-provider") return if (evt.package !== "venice-ai-sdk-provider") return
const mod = yield* Effect.promise(() => import("venice-ai-sdk-provider")) const mod = yield* Effect.promise(() => import("venice-ai-sdk-provider"))
+2 -1
View File
@@ -14,7 +14,8 @@ export const VercelPlugin = define({
}) })
} }
}) })
yield* ctx.aisdk.sdk( yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/vercel") return if (evt.package !== "@ai-sdk/vercel") return
const mod = yield* Effect.promise(() => import("@ai-sdk/vercel")) const mod = yield* Effect.promise(() => import("@ai-sdk/vercel"))
+4 -2
View File
@@ -5,14 +5,16 @@ import { ProviderV2 } from "../../provider"
export const XAIPlugin = define({ export const XAIPlugin = define({
id: "opencode.provider.xai", id: "opencode.provider.xai",
effect: Effect.fn(function* (ctx) { effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.sdk( yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/xai") return if (evt.package !== "@ai-sdk/xai") return
const mod = yield* Effect.promise(() => import("@ai-sdk/xai")) const mod = yield* Effect.promise(() => import("@ai-sdk/xai"))
evt.sdk = mod.createXai(evt.options) evt.sdk = mod.createXai(evt.options)
}), }),
) )
yield* ctx.aisdk.language( yield* ctx.aisdk.hook(
"language",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.model.providerID !== ProviderV2.ID.make("xai")) return if (evt.model.providerID !== ProviderV2.ID.make("xai")) return
evt.language = evt.sdk.responses(evt.model.modelID ?? evt.model.id) evt.language = evt.sdk.responses(evt.model.modelID ?? evt.model.id)
+1 -1
View File
@@ -1,6 +1,6 @@
export * as SdkPlugins from "./sdk" export * as SdkPlugins from "./sdk"
import type { Plugin } from "@opencode-ai/plugin/v2/effect" import type { Plugin } from "@opencode-ai/plugin/v2/effect/plugin"
import { Context, Effect, Layer } from "effect" import { Context, Effect, Layer } from "effect"
import { makeGlobalNode } from "../effect/app-node" import { makeGlobalNode } from "../effect/app-node"
import { EventV2 } from "../event" import { EventV2 } from "../event"
+19 -10
View File
@@ -33,7 +33,8 @@ export const Plugin = define({
SkillV2.EmbeddedSource.make({ SkillV2.EmbeddedSource.make({
type: "embedded", type: "embedded",
skill: SkillV2.Info.make({ skill: SkillV2.Info.make({
name: "opencode", id: SkillV2.ID.make("opencode"),
name: SkillV2.Name.make("OpenCode"),
description: OpencodeDescription, description: OpencodeDescription,
location: AbsolutePath.make("/builtin/opencode.md"), location: AbsolutePath.make("/builtin/opencode.md"),
content: OpencodeContent, content: OpencodeContent,
@@ -44,7 +45,8 @@ export const Plugin = define({
SkillV2.EmbeddedSource.make({ SkillV2.EmbeddedSource.make({
type: "embedded", type: "embedded",
skill: SkillV2.Info.make({ skill: SkillV2.Info.make({
name: "report", id: SkillV2.ID.make("report"),
name: SkillV2.Name.make("Report"),
description: REPORT_DESCRIPTION, description: REPORT_DESCRIPTION,
slash: true, slash: true,
location: AbsolutePath.make("/builtin/report.md"), location: AbsolutePath.make("/builtin/report.md"),
@@ -103,15 +105,22 @@ const configuredPlugins = Effect.fn("SkillPlugin.configuredPlugins")(function* (
}) })
function terminal() { function terminal() {
return [ return (
process.env.TERM_PROGRAM ? `TERM_PROGRAM=${process.env.TERM_PROGRAM}` : undefined, [
process.env.TERM ? `TERM=${process.env.TERM}` : undefined, process.env.TERM_PROGRAM ? `TERM_PROGRAM=${process.env.TERM_PROGRAM}` : undefined,
process.env.COLORTERM ? `COLORTERM=${process.env.COLORTERM}` : undefined, process.env.TERM ? `TERM=${process.env.TERM}` : undefined,
] process.env.COLORTERM ? `COLORTERM=${process.env.COLORTERM}` : undefined,
.filter((item): item is string => item !== undefined) ]
.join(", ") || "Unavailable: terminal environment variables are not set" .filter((item): item is string => item !== undefined)
.join(", ") || "Unavailable: terminal environment variables are not set"
)
} }
function shell() { function shell() {
return process.env.SHELL ?? process.env.ComSpec ?? process.env.COMSPEC ?? "Unavailable: shell environment variable is not set" return (
process.env.SHELL ??
process.env.ComSpec ??
process.env.COMSPEC ??
"Unavailable: shell environment variable is not set"
)
} }
+1 -1
View File
@@ -1,6 +1,6 @@
export * as PluginSupervisor from "./supervisor" export * as PluginSupervisor from "./supervisor"
import type { Plugin } from "@opencode-ai/plugin/v2/effect" import type { Plugin } from "@opencode-ai/plugin/v2/effect/plugin"
import { Event } from "@opencode-ai/schema/config" import { Event } from "@opencode-ai/schema/config"
import { Context, Effect, Fiber, Layer, Option, Schema, Semaphore, Stream } from "effect" import { Context, Effect, Fiber, Layer, Option, Schema, Semaphore, Stream } from "effect"
import path from "path" import path from "path"
+21 -16
View File
@@ -1,7 +1,7 @@
export * as SessionV2 from "./session" export * as SessionV2 from "./session"
export * from "./session/schema" export * from "./session/schema"
import { DateTime, Effect, Layer, Schema, Context, Stream, Scope } from "effect" import { Effect, Layer, Schema, Context, Stream, Scope } from "effect"
import { ListAnchor } from "@opencode-ai/schema/session" import { ListAnchor } from "@opencode-ai/schema/session"
import { and, asc, desc, eq, gt, isNull, like, lt, or, type SQL } from "drizzle-orm" import { and, asc, desc, eq, gt, isNull, like, lt, or, type SQL } from "drizzle-orm"
import { ProjectV2 } from "./project" import { ProjectV2 } from "./project"
@@ -19,6 +19,7 @@ import { SessionSchema } from "./session/schema"
import { AbsolutePath, PositiveInt, RelativePath } from "./schema" import { AbsolutePath, PositiveInt, RelativePath } from "./schema"
import { AgentV2 } from "./agent" import { AgentV2 } from "./agent"
import { SessionV1 } from "./v1/session" import { SessionV1 } from "./v1/session"
import { Money } from "@opencode-ai/schema/money"
import { InstallationVersion } from "./installation/version" import { InstallationVersion } from "./installation/version"
import { Slug } from "./util/slug" import { Slug } from "./util/slug"
import { ProjectTable } from "./project/sql" import { ProjectTable } from "./project/sql"
@@ -34,7 +35,7 @@ import { SessionEvent } from "./session/event"
import { SessionInput } from "./session/input" import { SessionInput } from "./session/input"
import { Snapshot } from "./snapshot" import { Snapshot } from "./snapshot"
import { SessionRevert } from "./session/revert" import { SessionRevert } from "./session/revert"
import { Revert } from "@opencode-ai/schema/revert" import { Session } from "@opencode-ai/schema/session"
import { FSUtil } from "./fs-util" import { FSUtil } from "./fs-util"
import { Mime } from "./mime" import { Mime } from "./mime"
import type { EventLog } from "@opencode-ai/schema/event-log" import type { EventLog } from "@opencode-ai/schema/event-log"
@@ -46,8 +47,8 @@ import { Shell as ShellSchema } from "@opencode-ai/schema/shell"
import { KeyedMutex } from "./effect/keyed-mutex" import { KeyedMutex } from "./effect/keyed-mutex"
import { fileURLToPath } from "url" import { fileURLToPath } from "url"
export const RevertState = Revert.State export const RevertState = Session.Revert
export type RevertState = Revert.State export type RevertState = Session.Revert
// get project -> project.locations // get project -> project.locations
// //
@@ -136,7 +137,7 @@ export class BusyError extends Schema.TaggedErrorClass<BusyError>()("Session.Bus
sessionID: SessionSchema.ID, sessionID: SessionSchema.ID,
}) {} }) {}
export class SkillNotFoundError extends Schema.TaggedErrorClass<SkillNotFoundError>()("Session.SkillNotFoundError", { export class SkillNotFoundError extends Schema.TaggedErrorClass<SkillNotFoundError>()("Session.SkillNotFoundError", {
skill: Schema.String, skill: SkillV2.ID,
}) {} }) {}
export const MessageNotFoundError = SessionRevert.MessageNotFoundError export const MessageNotFoundError = SessionRevert.MessageNotFoundError
export type MessageNotFoundError = SessionRevert.MessageNotFoundError export type MessageNotFoundError = SessionRevert.MessageNotFoundError
@@ -170,14 +171,14 @@ export interface Interface {
id: SessionMessage.ID id: SessionMessage.ID
direction: "previous" | "next" direction: "previous" | "next"
} }
}) => Effect.Effect<SessionMessage.Message[], NotFoundError | MessageDecodeError> }) => Effect.Effect<SessionMessage.Info[], NotFoundError | MessageDecodeError>
readonly message: (input: { readonly message: (input: {
sessionID: SessionSchema.ID sessionID: SessionSchema.ID
messageID: SessionMessage.ID messageID: SessionMessage.ID
}) => Effect.Effect<SessionMessage.Message | undefined> }) => Effect.Effect<SessionMessage.Info | undefined>
readonly context: ( readonly context: (
sessionID: SessionSchema.ID, sessionID: SessionSchema.ID,
) => Effect.Effect<SessionMessage.Message[], NotFoundError | MessageDecodeError> ) => Effect.Effect<SessionMessage.Info[], NotFoundError | MessageDecodeError>
/** /**
* Durable, ordered, gap-free session log read. Replays public durable * Durable, ordered, gap-free session log read. Replays public durable
* session events after the exclusive `after` cursor, emits a `Synced` * session events after the exclusive `after` cursor, emits a `Synced`
@@ -191,7 +192,10 @@ export interface Interface {
after?: number after?: number
follow?: boolean follow?: boolean
}) => Stream.Stream<SessionEvent.DurableEvent | EventLog.Synced, NotFoundError> }) => Stream.Stream<SessionEvent.DurableEvent | EventLog.Synced, NotFoundError>
readonly switchAgent: (input: { sessionID: SessionSchema.ID; agent: string }) => Effect.Effect<void, NotFoundError> readonly switchAgent: (input: {
sessionID: SessionSchema.ID
agent: AgentV2.ID
}) => Effect.Effect<void, NotFoundError>
readonly switchModel: (input: { readonly switchModel: (input: {
sessionID: SessionSchema.ID sessionID: SessionSchema.ID
model: ModelV2.Ref model: ModelV2.Ref
@@ -209,7 +213,7 @@ export interface Interface {
sessionID: SessionSchema.ID sessionID: SessionSchema.ID
command: string command: string
arguments?: string arguments?: string
agent?: string agent?: AgentV2.ID
model?: ModelV2.Ref model?: ModelV2.Ref
files?: PromptInput.Prompt["files"] files?: PromptInput.Prompt["files"]
agents?: PromptInput.Prompt["agents"] agents?: PromptInput.Prompt["agents"]
@@ -227,7 +231,7 @@ export interface Interface {
readonly skill: (input: { readonly skill: (input: {
id?: SessionMessage.ID id?: SessionMessage.ID
sessionID: SessionSchema.ID sessionID: SessionSchema.ID
skill: string skill: SkillV2.ID
resume?: boolean resume?: boolean
}) => Effect.Effect<void, NotFoundError | SkillNotFoundError> }) => Effect.Effect<void, NotFoundError | SkillNotFoundError>
readonly compact: ( readonly compact: (
@@ -250,7 +254,7 @@ export interface Interface {
sessionID: SessionSchema.ID sessionID: SessionSchema.ID
messageID: SessionMessage.ID messageID: SessionMessage.ID
files?: boolean files?: boolean
}) => Effect.Effect<Revert.State, NotFoundError | MessageNotFoundError | BusyError | Snapshot.Error> }) => Effect.Effect<Session.Revert, NotFoundError | MessageNotFoundError | BusyError | Snapshot.Error>
readonly clear: (sessionID: SessionSchema.ID) => Effect.Effect<void, NotFoundError | BusyError | Snapshot.Error> readonly clear: (sessionID: SessionSchema.ID) => Effect.Effect<void, NotFoundError | BusyError | Snapshot.Error>
readonly commit: (sessionID: SessionSchema.ID) => Effect.Effect<void, NotFoundError | BusyError> readonly commit: (sessionID: SessionSchema.ID) => Effect.Effect<void, NotFoundError | BusyError>
} }
@@ -273,7 +277,7 @@ const layer = Layer.effect(
const scope = yield* Scope.Scope const scope = yield* Scope.Scope
const activeShells = new Set<SessionSchema.ID>() const activeShells = new Set<SessionSchema.ID>()
const shellLocks = KeyedMutex.makeUnsafe<SessionSchema.ID>() const shellLocks = KeyedMutex.makeUnsafe<SessionSchema.ID>()
const decodeMessage = Schema.decodeUnknownEffect(SessionMessage.Message) const decodeMessage = Schema.decodeUnknownEffect(SessionMessage.Info)
const isDurableSessionEvent = Schema.is(SessionEvent.Durable) const isDurableSessionEvent = Schema.is(SessionEvent.Durable)
const decode = (row: typeof SessionMessageTable.$inferSelect) => const decode = (row: typeof SessionMessageTable.$inferSelect) =>
decodeMessage({ ...row.data, id: row.id, type: row.type }).pipe( decodeMessage({ ...row.data, id: row.id, type: row.type }).pipe(
@@ -322,7 +326,7 @@ const layer = Layer.effect(
variant: input.model.variant, variant: input.model.variant,
} }
: undefined, : undefined,
cost: 0, cost: Money.USD.zero,
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
time: { created: now, updated: now }, time: { created: now, updated: now },
}) })
@@ -529,7 +533,7 @@ const layer = Layer.effect(
}) })
const model = command.model ?? commandAgent?.model ?? input.model const model = command.model ?? commandAgent?.model ?? input.model
if (agent !== undefined && session.agent !== AgentV2.ID.make(agent)) if (agent !== undefined && session.agent !== AgentV2.ID.make(agent))
yield* result.switchAgent({ sessionID: input.sessionID, agent }) yield* result.switchAgent({ sessionID: input.sessionID, agent: AgentV2.ID.make(agent) })
if (model !== undefined) yield* result.switchModel({ sessionID: input.sessionID, model }) if (model !== undefined) yield* result.switchModel({ sessionID: input.sessionID, model })
return yield* result.prompt({ return yield* result.prompt({
@@ -591,12 +595,13 @@ const layer = Layer.effect(
skill: Effect.fn("V2Session.skill")(function* (input) { skill: Effect.fn("V2Session.skill")(function* (input) {
const session = yield* result.get(input.sessionID) const session = yield* result.get(input.sessionID)
const skills = yield* SkillV2.Service.pipe(Effect.provide(locations.get(session.location))) const skills = yield* SkillV2.Service.pipe(Effect.provide(locations.get(session.location)))
const skill = (yield* skills.list()).find((item) => item.name === input.skill) const skill = (yield* skills.list()).find((item) => item.id === input.skill)
if (!skill) return yield* new SkillNotFoundError({ skill: input.skill }) if (!skill) return yield* new SkillNotFoundError({ skill: input.skill })
yield* events.publish( yield* events.publish(
SessionEvent.Skill.Activated, SessionEvent.Skill.Activated,
{ {
sessionID: input.sessionID, sessionID: input.sessionID,
id: skill.id,
name: skill.name, name: skill.name,
text: skill.content, text: skill.content,
}, },
+32 -7
View File
@@ -64,19 +64,21 @@ type Dependencies = {
export type AutoInput = { export type AutoInput = {
readonly sessionID: SessionSchema.ID readonly sessionID: SessionSchema.ID
readonly messages: readonly SessionMessage.Message[] readonly messages: readonly SessionMessage.Info[]
readonly request: LLMRequest readonly request: LLMRequest
} }
type CompactInput = { type CompactInput = {
readonly sessionID: SessionSchema.ID readonly sessionID: SessionSchema.ID
readonly messages: readonly SessionMessage.Message[] readonly messages: readonly SessionMessage.Info[]
readonly model: Model readonly model: Model
readonly inputID?: SessionMessage.ID
} }
export type ManualInput = { export type ManualInput = {
readonly session: SessionSchema.Info readonly session: SessionSchema.Info
readonly messages: readonly SessionMessage.Message[] readonly messages: readonly SessionMessage.Info[]
readonly inputID: SessionMessage.ID
} }
export interface Interface { export interface Interface {
@@ -99,7 +101,7 @@ export const serializeToolContent = (content: SessionMessage.ToolStateCompleted[
) )
.join("\n") .join("\n")
const serialize = (message: SessionMessage.Message) => { const serialize = (message: SessionMessage.Info) => {
if (message.type === "user") { if (message.type === "user") {
const files = const files =
message.files?.map( message.files?.map(
@@ -128,7 +130,7 @@ const serialize = (message: SessionMessage.Message) => {
if (message.type === "system") return `[System update]: ${message.text}` if (message.type === "system") return `[System update]: ${message.text}`
if (message.type === "synthetic") return `[Synthetic context]: ${message.text}` if (message.type === "synthetic") return `[Synthetic context]: ${message.text}`
if (message.type === "skill") return `[Skill activated: ${message.name}]\n${message.text}` if (message.type === "skill") return `[Skill activated: ${message.name}]\n${message.text}`
if (message.type === "shell") return `[Shell]: ${message.shell.command}\n${truncate(message.output?.output ?? "")}` if (message.type === "shell") return `[Shell]: ${message.command}\n${truncate(message.output?.output ?? "")}`
return "" return ""
} }
@@ -147,7 +149,7 @@ const settings = (documents: readonly Config.Entry[]) => {
} }
const select = ( const select = (
messages: readonly SessionMessage.Message[], messages: readonly SessionMessage.Info[],
tokens: number, tokens: number,
): { readonly head: string; readonly recent: string } | undefined => { ): { readonly head: string; readonly recent: string } | undefined => {
const conversation = messages const conversation = messages
@@ -198,6 +200,7 @@ const make = (dependencies: Dependencies) => {
readonly context: readonly string[] readonly context: readonly string[]
readonly recent: string readonly recent: string
readonly output?: number readonly output?: number
readonly inputID?: SessionMessage.ID
}) { }) {
const context = input.model.route.defaults.limits?.context const context = input.model.route.defaults.limits?.context
if (context === undefined || context <= 0) return false if (context === undefined || context <= 0) return false
@@ -208,6 +211,8 @@ const make = (dependencies: Dependencies) => {
yield* dependencies.events.publish(SessionEvent.Compaction.Started, { yield* dependencies.events.publish(SessionEvent.Compaction.Started, {
sessionID: input.sessionID, sessionID: input.sessionID,
reason: input.reason, reason: input.reason,
recent: input.recent,
inputID: input.inputID,
}) })
const chunks: string[] = [] const chunks: string[] = []
@@ -235,9 +240,27 @@ const make = (dependencies: Dependencies) => {
}), }),
Effect.as(true), Effect.as(true),
Effect.catchTag("LLM.Error", () => Effect.succeed(false)), Effect.catchTag("LLM.Error", () => Effect.succeed(false)),
Effect.onInterrupt(() =>
input.reason === "auto"
? dependencies.events.publish(SessionEvent.Compaction.Failed, {
sessionID: input.sessionID,
reason: input.reason,
error: { type: "compaction.interrupted", message: "Compaction was interrupted" },
inputID: input.inputID,
})
: Effect.void,
),
) )
const summary = chunks.join("") const summary = chunks.join("")
if (!summarized || failed || !summary.trim()) return false if (!summarized || failed || !summary.trim()) {
yield* dependencies.events.publish(SessionEvent.Compaction.Failed, {
sessionID: input.sessionID,
reason: input.reason,
error: { type: "compaction.failed", message: "Compaction produced no summary" },
inputID: input.inputID,
})
return false
}
yield* dependencies.events.publish(SessionEvent.Compaction.Ended, { yield* dependencies.events.publish(SessionEvent.Compaction.Ended, {
sessionID: input.sessionID, sessionID: input.sessionID,
reason: input.reason, reason: input.reason,
@@ -284,6 +307,7 @@ const make = (dependencies: Dependencies) => {
), ),
recent: forcedShortContext ? "" : selected.recent, recent: forcedShortContext ? "" : selected.recent,
output: input.output, output: input.output,
inputID: input.inputID,
}) })
}) })
const compactManual = Effect.fn("SessionCompaction.compactManual")(function* (input: CompactInput) { const compactManual = Effect.fn("SessionCompaction.compactManual")(function* (input: CompactInput) {
@@ -327,6 +351,7 @@ export const layer = Layer.effect(
sessionID: input.session.id, sessionID: input.session.id,
messages: input.messages, messages: input.messages,
model: resolved.model, model: resolved.model,
inputID: input.inputID,
}) })
}), }),
}) })

Some files were not shown because too many files have changed in this diff Show More