mirror of
https://fastgit.cc/https://github.com/anomalyco/opencode
synced 2026-04-20 21:00:29 +08:00
fix linter errors
This commit is contained in:
@@ -14,7 +14,7 @@
|
||||
// Demo mode also handles permission and question replies locally, completing
|
||||
// or failing the synthetic tool parts as appropriate.
|
||||
import path from "path"
|
||||
import type { Event } from "@opencode-ai/sdk/v2"
|
||||
import type { Event, ToolPart } from "@opencode-ai/sdk/v2"
|
||||
import { createSessionData, reduceSessionData, type SessionData } from "./session-data"
|
||||
import { writeSessionOutput } from "./stream"
|
||||
import type {
|
||||
@@ -48,6 +48,16 @@ const QUESTIONS = ["multi", "single", "checklist", "custom"] as const
|
||||
type PermissionKind = (typeof PERMISSIONS)[number]
|
||||
type QuestionKind = (typeof QUESTIONS)[number]
|
||||
|
||||
function permissionKind(value: string | undefined): PermissionKind | undefined {
|
||||
const next = (value || "edit").toLowerCase()
|
||||
return PERMISSIONS.find((item) => item === next)
|
||||
}
|
||||
|
||||
function questionKind(value: string | undefined): QuestionKind | undefined {
|
||||
const next = (value || "multi").toLowerCase()
|
||||
return QUESTIONS.find((item) => item === next)
|
||||
}
|
||||
|
||||
const SAMPLE_MARKDOWN = [
|
||||
"# Direct Mode Demo",
|
||||
"",
|
||||
@@ -565,16 +575,19 @@ function failTool(state: State, ref: Ref, error: string): void {
|
||||
}
|
||||
|
||||
function emitError(state: State, text: string): void {
|
||||
feed(state, {
|
||||
const event = {
|
||||
type: "session.error",
|
||||
properties: {
|
||||
sessionID: state.id,
|
||||
error: {
|
||||
name: "DemoError",
|
||||
message: text,
|
||||
name: "UnknownError",
|
||||
data: {
|
||||
message: text,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as Event)
|
||||
} satisfies Event
|
||||
feed(state, event)
|
||||
}
|
||||
|
||||
async function emitBash(state: State, signal?: AbortSignal): Promise<void> {
|
||||
@@ -663,6 +676,25 @@ function emitTask(state: State): void {
|
||||
sessionId: "sub_demo_1",
|
||||
},
|
||||
})
|
||||
const part = {
|
||||
id: "sub_demo_tool_1",
|
||||
type: "tool",
|
||||
sessionID: "sub_demo_1",
|
||||
messageID: "sub_demo_msg_tool",
|
||||
callID: "sub_demo_call_1",
|
||||
tool: "read",
|
||||
state: {
|
||||
status: "running",
|
||||
input: {
|
||||
filePath: "packages/opencode/src/cli/cmd/run/stream.ts",
|
||||
offset: 1,
|
||||
limit: 200,
|
||||
},
|
||||
time: {
|
||||
start: Date.now(),
|
||||
},
|
||||
},
|
||||
} satisfies ToolPart
|
||||
showSubagent(state, {
|
||||
sessionID: "sub_demo_1",
|
||||
partID: ref.part,
|
||||
@@ -695,25 +727,7 @@ function emitTask(state: State): void {
|
||||
messageID: "sub_demo_msg_tool",
|
||||
partID: "sub_demo_tool_1",
|
||||
tool: "read",
|
||||
part: {
|
||||
id: "sub_demo_tool_1",
|
||||
type: "tool",
|
||||
sessionID: "sub_demo_1",
|
||||
messageID: "sub_demo_msg_tool",
|
||||
callID: "sub_demo_call_1",
|
||||
tool: "read",
|
||||
state: {
|
||||
status: "running",
|
||||
input: {
|
||||
filePath: "packages/opencode/src/cli/cmd/run/stream.ts",
|
||||
offset: 1,
|
||||
limit: 200,
|
||||
},
|
||||
time: {
|
||||
start: Date.now(),
|
||||
},
|
||||
},
|
||||
} as never,
|
||||
part,
|
||||
},
|
||||
{
|
||||
kind: "assistant",
|
||||
@@ -1160,8 +1174,8 @@ export function createRunDemo(input: Input) {
|
||||
}
|
||||
|
||||
if (cmd === "/permission") {
|
||||
const kind = (list[1] || "edit").toLowerCase() as PermissionKind
|
||||
if (!PERMISSIONS.includes(kind)) {
|
||||
const kind = permissionKind(list[1])
|
||||
if (!kind) {
|
||||
note(state.footer, `Pick a permission kind: ${PERMISSIONS.join(", ")}`)
|
||||
return true
|
||||
}
|
||||
@@ -1171,8 +1185,8 @@ export function createRunDemo(input: Input) {
|
||||
}
|
||||
|
||||
if (cmd === "/question") {
|
||||
const kind = (list[1] || "multi").toLowerCase() as QuestionKind
|
||||
if (!QUESTIONS.includes(kind)) {
|
||||
const kind = questionKind(list[1])
|
||||
if (!kind) {
|
||||
note(state.footer, `Pick a question kind: ${QUESTIONS.join(", ")}`)
|
||||
return true
|
||||
}
|
||||
@@ -1194,7 +1208,7 @@ export function createRunDemo(input: Input) {
|
||||
return true
|
||||
}
|
||||
|
||||
note(state.footer, `Unknown kind \"${kind}\". Use: ${KINDS.join(", ")}`)
|
||||
note(state.footer, `Unknown kind "${kind}". Use: ${KINDS.join(", ")}`)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1203,19 +1217,20 @@ export function createRunDemo(input: Input) {
|
||||
|
||||
const permission = (input: PermissionReply): boolean => {
|
||||
const item = state.perms.get(input.requestID)
|
||||
if (!item) {
|
||||
if (!item || !input.reply) {
|
||||
return false
|
||||
}
|
||||
|
||||
state.perms.delete(input.requestID)
|
||||
feed(state, {
|
||||
const event = {
|
||||
type: "permission.replied",
|
||||
properties: {
|
||||
sessionID: state.id,
|
||||
requestID: input.requestID,
|
||||
reply: input.reply,
|
||||
},
|
||||
} as Event)
|
||||
} satisfies Event
|
||||
feed(state, event)
|
||||
|
||||
if (input.reply === "reject") {
|
||||
failTool(state, item.ref, input.message || "permission rejected")
|
||||
@@ -1228,19 +1243,20 @@ export function createRunDemo(input: Input) {
|
||||
|
||||
const questionReply = (input: QuestionReply): boolean => {
|
||||
const ask = state.asks.get(input.requestID)
|
||||
if (!ask) {
|
||||
if (!ask || !input.answers) {
|
||||
return false
|
||||
}
|
||||
|
||||
state.asks.delete(input.requestID)
|
||||
feed(state, {
|
||||
const event = {
|
||||
type: "question.replied",
|
||||
properties: {
|
||||
sessionID: state.id,
|
||||
requestID: input.requestID,
|
||||
answers: input.answers,
|
||||
},
|
||||
} as Event)
|
||||
} satisfies Event
|
||||
feed(state, event)
|
||||
doneTool(state, ask.ref, {
|
||||
title: "question",
|
||||
output: "",
|
||||
|
||||
@@ -173,7 +173,7 @@ export function RunFooterSubagentBody(props: {
|
||||
stickyStart="bottom"
|
||||
verticalScrollbarOptions={scrollbar()}
|
||||
ref={(item) => {
|
||||
scroll = item as ScrollBoxRenderable
|
||||
scroll = item
|
||||
}}
|
||||
>
|
||||
<box width="100%" flexDirection="column" gap={0}>
|
||||
|
||||
@@ -136,6 +136,8 @@ function eventPatch(next: FooterEvent): FooterPatch | undefined {
|
||||
if (next.type === "stream.patch") {
|
||||
return next.patch
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
export class RunFooter implements FooterApi {
|
||||
@@ -187,14 +189,14 @@ export class RunFooter implements FooterApi {
|
||||
const [view, setView] = createSignal<FooterView>({ type: "prompt" })
|
||||
this.view = view
|
||||
this.setView = setView
|
||||
const [agents, setAgents] = createSignal<RunAgent[]>(options.agents)
|
||||
const [agents, setAgents] = createSignal(options.agents)
|
||||
this.agents = agents
|
||||
this.setAgents = setAgents
|
||||
const [resources, setResources] = createSignal<RunResource[]>(options.resources)
|
||||
const [resources, setResources] = createSignal(options.resources)
|
||||
this.resources = resources
|
||||
this.setResources = setResources
|
||||
const [subagent, setSubagent] = createStore<FooterSubagentState>(createEmptySubagentState())
|
||||
this.subagent = () => subagent as FooterSubagentState
|
||||
this.subagent = () => subagent
|
||||
this.setSubagent = (next) => {
|
||||
setSubagent("tabs", reconcile(next.tabs, { key: "sessionID" }))
|
||||
setSubagent("details", reconcile(next.details))
|
||||
@@ -239,7 +241,7 @@ export class RunFooter implements FooterApi {
|
||||
onStatus: this.setStatus,
|
||||
onSubagentSelect: options.onSubagentSelect,
|
||||
}),
|
||||
this.renderer as unknown as Parameters<typeof render>[1],
|
||||
this.renderer,
|
||||
).catch(() => {
|
||||
if (!this.isGone) {
|
||||
this.close()
|
||||
|
||||
@@ -84,11 +84,11 @@ function subagentShortcut(event: {
|
||||
super?: boolean
|
||||
}): number | undefined {
|
||||
if (!event.ctrl || event.meta || event.super) {
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (!/^[0-9]$/.test(event.name)) {
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
const slot = Number(event.name)
|
||||
@@ -121,11 +121,7 @@ export function RunFooterView(props: RunFooterViewProps) {
|
||||
const showTabs = createMemo(() => active().type === "prompt" && tabs().length > 0)
|
||||
const detail = createMemo(() => {
|
||||
const current = route()
|
||||
if (current.type !== "subagent") {
|
||||
return
|
||||
}
|
||||
|
||||
return subagent().details[current.sessionID]
|
||||
return current.type === "subagent" ? subagent().details[current.sessionID] : undefined
|
||||
})
|
||||
const variant = createMemo(() => printableBinding(props.keybinds.variantCycle, props.keybinds.leader))
|
||||
const interrupt = createMemo(() => printableBinding(props.keybinds.interrupt, props.keybinds.leader))
|
||||
|
||||
@@ -12,14 +12,14 @@ const tracer = trace.getTracer("opencode.run")
|
||||
const runtime = ManagedRuntime.make(Observability.layer, { memoMap })
|
||||
let ready: Promise<void> | undefined
|
||||
|
||||
function attributes(input?: RunSpanAttributes) {
|
||||
function attributes(input?: RunSpanAttributes): Record<string, string | number | boolean> | undefined {
|
||||
if (!input) {
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
const out = Object.entries(input).flatMap(([key, value]) => (value === undefined ? [] : [[key, value] as const]))
|
||||
if (out.length === 0) {
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
return Object.fromEntries(out)
|
||||
|
||||
@@ -48,7 +48,7 @@ function dict(v: unknown): Dict {
|
||||
return {}
|
||||
}
|
||||
|
||||
return v as Dict
|
||||
return { ...v }
|
||||
}
|
||||
|
||||
function text(v: unknown): string {
|
||||
@@ -225,7 +225,7 @@ export function permissionRun(state: PermissionBodyState, requestID: string, opt
|
||||
|
||||
export function permissionReject(state: PermissionBodyState, requestID: string): PermissionReply | undefined {
|
||||
if (state.submitting) {
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
return permissionReply(requestID, "reject", state.message)
|
||||
|
||||
@@ -56,7 +56,7 @@ function defer<T = void>(): Deferred<T> {
|
||||
// the queue depth so the user knows how many are pending.
|
||||
export async function runPromptQueue(input: QueueInput): Promise<void> {
|
||||
const stop = defer<{ type: "closed" }>()
|
||||
const done = defer<void>()
|
||||
const done = defer()
|
||||
const state: State = {
|
||||
queue: [],
|
||||
closed: input.footer.isClosed,
|
||||
|
||||
@@ -96,9 +96,13 @@ function eagerStream(input: RunRuntimeInput, ctx: BootContext) {
|
||||
return ctx.resume === true || !input.resolveSession || !!input.demo
|
||||
}
|
||||
|
||||
async function resolveExitTitle(ctx: BootContext, input: RunRuntimeInput, state: RuntimeState) {
|
||||
async function resolveExitTitle(
|
||||
ctx: BootContext,
|
||||
input: RunRuntimeInput,
|
||||
state: RuntimeState,
|
||||
): Promise<string | undefined> {
|
||||
if (!state.shown || !hasSession(input, state)) {
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
return ctx.sdk.session
|
||||
@@ -267,36 +271,35 @@ async function runInteractiveRuntime(input: RunRuntimeInput): Promise<void> {
|
||||
})
|
||||
const footer = shell.footer
|
||||
|
||||
const loadCatalog = async (): Promise<void> => {
|
||||
if (footer.isClosed) {
|
||||
return
|
||||
}
|
||||
|
||||
const [agents, resources] = await Promise.all([
|
||||
ctx.sdk.app
|
||||
.agents({ directory: ctx.directory })
|
||||
.then((x) => x.data ?? [])
|
||||
.catch(() => []),
|
||||
ctx.sdk.experimental.resource
|
||||
.list({ directory: ctx.directory })
|
||||
.then((x) => Object.values(x.data ?? {}))
|
||||
.catch(() => []),
|
||||
])
|
||||
if (footer.isClosed) {
|
||||
return
|
||||
}
|
||||
|
||||
footer.event({
|
||||
type: "catalog",
|
||||
agents,
|
||||
resources,
|
||||
})
|
||||
}
|
||||
|
||||
void footer
|
||||
.idle()
|
||||
.then(() => {
|
||||
if (footer.isClosed) {
|
||||
return
|
||||
}
|
||||
|
||||
return Promise.all([
|
||||
ctx.sdk.app
|
||||
.agents({ directory: ctx.directory })
|
||||
.then((x) => x.data ?? [])
|
||||
.catch(() => []),
|
||||
ctx.sdk.experimental.resource
|
||||
.list({ directory: ctx.directory })
|
||||
.then((x) => Object.values(x.data ?? {}))
|
||||
.catch(() => []),
|
||||
])
|
||||
.then(([agents, resources]) => {
|
||||
if (footer.isClosed) {
|
||||
return
|
||||
}
|
||||
|
||||
footer.event({
|
||||
type: "catalog",
|
||||
agents,
|
||||
resources,
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
})
|
||||
.then(loadCatalog)
|
||||
.catch(() => {})
|
||||
|
||||
if (Flag.OPENCODE_SHOW_TTFD) {
|
||||
@@ -379,7 +382,7 @@ async function runInteractiveRuntime(input: RunRuntimeInput): Promise<void> {
|
||||
throw new Error("runtime closed")
|
||||
}
|
||||
|
||||
state.selectSubagent = handle.selectSubagent
|
||||
state.selectSubagent = (sessionID) => handle.selectSubagent(sessionID)
|
||||
return { mod, handle }
|
||||
})()
|
||||
state.stream = next
|
||||
|
||||
@@ -172,7 +172,11 @@ export class RunScrollbackStream {
|
||||
}
|
||||
|
||||
if (active.body.type === "text") {
|
||||
const renderable = active.renderable as TextRenderable
|
||||
if (!(active.renderable instanceof TextRenderable)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const renderable = active.renderable
|
||||
renderable.content = active.content
|
||||
active.surface.render()
|
||||
const targetRows = done ? active.surface.height : Math.max(active.committedRows, active.surface.height - 1)
|
||||
@@ -190,7 +194,11 @@ export class RunScrollbackStream {
|
||||
}
|
||||
|
||||
if (active.body.type === "code") {
|
||||
const renderable = active.renderable as CodeRenderable
|
||||
if (!(active.renderable instanceof CodeRenderable)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const renderable = active.renderable
|
||||
renderable.content = active.content
|
||||
renderable.streaming = !done
|
||||
await active.surface.settle()
|
||||
@@ -208,7 +216,11 @@ export class RunScrollbackStream {
|
||||
return true
|
||||
}
|
||||
|
||||
const renderable = active.renderable as MarkdownRenderable
|
||||
if (!(active.renderable instanceof MarkdownRenderable)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const renderable = active.renderable
|
||||
renderable.content = active.content
|
||||
renderable.streaming = !done
|
||||
await active.surface.settle()
|
||||
@@ -237,7 +249,7 @@ export class RunScrollbackStream {
|
||||
|
||||
private async finishActive(trailingNewline: boolean): Promise<StreamCommit | undefined> {
|
||||
if (!this.active) {
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
const active = this.active
|
||||
|
||||
@@ -31,7 +31,7 @@ function todoColor(theme: RunTheme, status: string) {
|
||||
|
||||
export function entryGroupKey(commit: StreamCommit): string | undefined {
|
||||
if (!commit.partID) {
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (toolStructuredFinal(commit)) {
|
||||
@@ -93,35 +93,19 @@ export function RunEntryContent(props: {
|
||||
const body = createMemo(() => entryBody(props.commit))
|
||||
const text = () => {
|
||||
const value = body()
|
||||
if (value.type !== "text") {
|
||||
return
|
||||
}
|
||||
|
||||
return value
|
||||
return value.type === "text" ? value : undefined
|
||||
}
|
||||
const code = () => {
|
||||
const value = body()
|
||||
if (value.type !== "code") {
|
||||
return
|
||||
}
|
||||
|
||||
return value
|
||||
return value.type === "code" ? value : undefined
|
||||
}
|
||||
const snapshot = () => {
|
||||
const value = body()
|
||||
if (value.type !== "structured") {
|
||||
return
|
||||
}
|
||||
|
||||
return value.snapshot
|
||||
return value.type === "structured" ? value.snapshot : undefined
|
||||
}
|
||||
const markdown = () => {
|
||||
const value = body()
|
||||
if (value.type !== "markdown") {
|
||||
return
|
||||
}
|
||||
|
||||
return value
|
||||
return value.type === "markdown" ? value : undefined
|
||||
}
|
||||
|
||||
if (body().type === "none") {
|
||||
|
||||
@@ -136,7 +136,7 @@ function formatUsage(
|
||||
if (typeof cost === "number" && cost > 0) {
|
||||
return money.format(cost)
|
||||
}
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
const text =
|
||||
@@ -157,15 +157,15 @@ export function formatError(error: {
|
||||
}
|
||||
}): string {
|
||||
if (error.data?.message) {
|
||||
return String(error.data.message)
|
||||
return error.data.message
|
||||
}
|
||||
|
||||
if (error.message) {
|
||||
return String(error.message)
|
||||
return error.message
|
||||
}
|
||||
|
||||
if (error.name) {
|
||||
return String(error.name)
|
||||
return error.name
|
||||
}
|
||||
|
||||
return "unknown error"
|
||||
@@ -181,7 +181,7 @@ function msgErr(id: string): string {
|
||||
|
||||
function patch(patch?: FooterPatch, view?: FooterView): FooterOutput | undefined {
|
||||
if (!patch && !view) {
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -262,7 +262,7 @@ function upsert<T extends { id: string }>(list: T[], item: T) {
|
||||
list[idx] = item
|
||||
}
|
||||
|
||||
function remove<T extends { id: string }>(list: T[], id: string): boolean {
|
||||
function remove(list: Array<{ id: string }>, id: string): boolean {
|
||||
const idx = list.findIndex((entry) => entry.id === id)
|
||||
if (idx === -1) {
|
||||
return false
|
||||
@@ -334,7 +334,7 @@ function enrichPermission(data: SessionData, request: PermissionRequest): Permis
|
||||
function syncPermission(data: SessionData, part: ToolPart): FooterOutput | undefined {
|
||||
data.call.set(key(part.messageID, part.callID), part.state.input)
|
||||
if (data.permissions.length === 0) {
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
let changed = false
|
||||
@@ -355,7 +355,7 @@ function syncPermission(data: SessionData, part: ToolPart): FooterOutput | undef
|
||||
})
|
||||
|
||||
if (!changed || !active) {
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -437,7 +437,7 @@ function stashEcho(data: SessionData, part: ToolPart) {
|
||||
return
|
||||
}
|
||||
|
||||
const output = (part.state as { output?: unknown }).output
|
||||
const output = "output" in part.state ? part.state.output : undefined
|
||||
if (typeof output !== "string") {
|
||||
return
|
||||
}
|
||||
@@ -547,7 +547,7 @@ function drop(data: SessionData, partID: string) {
|
||||
// buffered text parts that were waiting on role confirmation. User-role
|
||||
// parts are silently dropped.
|
||||
function replay(data: SessionData, commits: SessionCommit[], messageID: string, role: MessageRole, thinking: boolean) {
|
||||
for (const [partID, msg] of [...data.msg.entries()]) {
|
||||
for (const [partID, msg] of data.msg.entries()) {
|
||||
if (msg !== messageID || data.ids.has(partID)) {
|
||||
continue
|
||||
}
|
||||
@@ -628,7 +628,7 @@ function failTool(part: ToolPart, text: string): SessionCommit {
|
||||
|
||||
// Emits "interrupted" final entries for all in-flight parts. Called when a turn is aborted.
|
||||
export function flushInterrupted(data: SessionData, commits: SessionCommit[]) {
|
||||
for (const partID of [...data.part.keys()]) {
|
||||
for (const partID of data.part.keys()) {
|
||||
if (data.ids.has(partID)) {
|
||||
continue
|
||||
}
|
||||
@@ -689,7 +689,7 @@ export function reduceSessionData(input: SessionDataInput): SessionDataOutput {
|
||||
)
|
||||
if (usage) {
|
||||
next = {
|
||||
...(next ?? {}),
|
||||
...next,
|
||||
usage,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,12 +68,12 @@ function prompt(msg: SessionMessages[number]): RunPrompt {
|
||||
let cursor = Bun.stringWidth(text)
|
||||
const used: Array<{ start: number; end: number }> = []
|
||||
|
||||
const take = (value: string) => {
|
||||
const take = (value: string): { start: number; end: number; value: string } | undefined => {
|
||||
let from = 0
|
||||
while (true) {
|
||||
const idx = text.indexOf(value, from)
|
||||
if (idx === -1) {
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
const start = Bun.stringWidth(text.slice(0, idx))
|
||||
@@ -128,7 +128,7 @@ function prompt(msg: SessionMessages[number]): RunPrompt {
|
||||
|
||||
function turn(msg: SessionMessages[number]): Turn | undefined {
|
||||
if (msg.info.role !== "user") {
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -177,7 +177,7 @@ export function sessionHistory(session: RunSession, limit = LIMIT): RunPrompt[]
|
||||
|
||||
export function sessionVariant(session: RunSession, model: RunInput["model"]): string | undefined {
|
||||
if (!model) {
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
for (let idx = session.turns.length - 1; idx >= 0; idx -= 1) {
|
||||
@@ -188,4 +188,6 @@ export function sessionVariant(session: RunSession, model: RunInput["model"]): s
|
||||
|
||||
return turn.variant
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ import {
|
||||
reduceSubagentData,
|
||||
sameSubagentTab,
|
||||
snapshotSelectedSubagentData,
|
||||
snapshotSubagentData,
|
||||
SUBAGENT_BOOTSTRAP_LIMIT,
|
||||
SUBAGENT_CALL_BOOTSTRAP_LIMIT,
|
||||
type SubagentData,
|
||||
@@ -135,6 +134,18 @@ function sid(event: Event): string | undefined {
|
||||
) {
|
||||
return event.properties.sessionID
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
function isEvent(value: unknown): value is Event {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const type = Reflect.get(value, "type")
|
||||
const properties = Reflect.get(value, "properties")
|
||||
return typeof type === "string" && !!properties && typeof properties === "object"
|
||||
}
|
||||
|
||||
function active(event: Event, sessionID: string): boolean {
|
||||
@@ -156,7 +167,7 @@ function waitTurn(done: Wait["done"], signal: AbortSignal) {
|
||||
Effect.callback<"abort">((resume) => {
|
||||
if (signal.aborted) {
|
||||
resume(Effect.succeed("abort"))
|
||||
return
|
||||
return Effect.void
|
||||
}
|
||||
|
||||
const onAbort = () => {
|
||||
@@ -243,23 +254,23 @@ function composeFooter(input: {
|
||||
|
||||
if (input.subagent) {
|
||||
footer = {
|
||||
...(footer ?? {}),
|
||||
...footer,
|
||||
subagent: input.subagent,
|
||||
}
|
||||
}
|
||||
|
||||
if (!sameView(input.previous, input.current)) {
|
||||
footer = {
|
||||
...(footer ?? {}),
|
||||
...footer,
|
||||
view: input.current,
|
||||
}
|
||||
}
|
||||
|
||||
if (input.current.type !== "prompt") {
|
||||
footer = {
|
||||
...(footer ?? {}),
|
||||
...footer,
|
||||
patch: {
|
||||
...(input.patch ?? {}),
|
||||
...input.patch,
|
||||
status: blockerStatus(input.current),
|
||||
},
|
||||
}
|
||||
@@ -268,7 +279,7 @@ function composeFooter(input: {
|
||||
|
||||
if (input.patch) {
|
||||
footer = {
|
||||
...(footer ?? {}),
|
||||
...footer,
|
||||
patch: input.patch,
|
||||
}
|
||||
return footer
|
||||
@@ -276,7 +287,7 @@ function composeFooter(input: {
|
||||
|
||||
if (input.previous.type !== "prompt") {
|
||||
footer = {
|
||||
...(footer ?? {}),
|
||||
...footer,
|
||||
patch: {
|
||||
status: "",
|
||||
},
|
||||
@@ -622,7 +633,11 @@ function createLayer(input: StreamInput) {
|
||||
return
|
||||
}
|
||||
|
||||
const event = item as Event
|
||||
if (!isEvent(item)) {
|
||||
return
|
||||
}
|
||||
|
||||
const event = item
|
||||
input.trace?.write("recv.event", event)
|
||||
trackBlocker(event)
|
||||
|
||||
@@ -675,11 +690,13 @@ function createLayer(input: StreamInput) {
|
||||
}
|
||||
|
||||
if (state.fault) {
|
||||
return yield* Effect.fail(state.fault)
|
||||
yield* Effect.fail(state.fault)
|
||||
return
|
||||
}
|
||||
|
||||
if (state.wait) {
|
||||
return yield* Effect.fail(new Error("prompt already running"))
|
||||
yield* Effect.fail(new Error("prompt already running"))
|
||||
return
|
||||
}
|
||||
|
||||
const prev = listSubagentTabs(state.subagent)
|
||||
@@ -733,7 +750,7 @@ function createLayer(input: StreamInput) {
|
||||
),
|
||||
)
|
||||
|
||||
return yield* send.pipe(
|
||||
yield* send.pipe(
|
||||
Effect.flatMap(() => {
|
||||
if (turn.signal.aborted || next.signal?.aborted || input.footer.isClosed || closed) {
|
||||
if (state.wait === item) {
|
||||
@@ -805,6 +822,7 @@ function createLayer(input: StreamInput) {
|
||||
}),
|
||||
),
|
||||
)
|
||||
return
|
||||
})
|
||||
|
||||
const selectSubagent = Effect.fn("RunStreamTransport.selectSubagent")((sessionID: string | undefined) =>
|
||||
|
||||
@@ -117,24 +117,24 @@ function sameCommit(left: StreamCommit, right: StreamCommit) {
|
||||
)
|
||||
}
|
||||
|
||||
function text(value: unknown) {
|
||||
function text(value: unknown): string | undefined {
|
||||
if (typeof value !== "string") {
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
const next = value.trim()
|
||||
return next || undefined
|
||||
}
|
||||
|
||||
function num(value: unknown) {
|
||||
function num(value: unknown): number | undefined {
|
||||
if (typeof value === "number" && Number.isFinite(value)) {
|
||||
return value
|
||||
}
|
||||
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
function inputLabel(input: Record<string, unknown>) {
|
||||
function inputLabel(input: Record<string, unknown>): string | undefined {
|
||||
const description = text(input.description)
|
||||
if (description) {
|
||||
return description
|
||||
@@ -175,21 +175,60 @@ function inputLabel(input: Record<string, unknown>) {
|
||||
return prompt
|
||||
}
|
||||
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
function stateTitle(part: ToolPart) {
|
||||
return text("title" in part.state ? part.state.title : undefined)
|
||||
}
|
||||
|
||||
function callKey(messageID: string | undefined, callID: string | undefined) {
|
||||
function callKey(messageID: string | undefined, callID: string | undefined): string | undefined {
|
||||
if (!messageID || !callID) {
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
return `${messageID}:${callID}`
|
||||
}
|
||||
|
||||
function compactToolState(part: ToolPart): ToolPart["state"] {
|
||||
if (part.state.status === "pending") {
|
||||
return {
|
||||
status: "pending",
|
||||
input: part.state.input,
|
||||
raw: part.state.raw,
|
||||
}
|
||||
}
|
||||
|
||||
if (part.state.status === "running") {
|
||||
return {
|
||||
status: "running",
|
||||
input: part.state.input,
|
||||
time: part.state.time,
|
||||
...(part.state.metadata ? { metadata: part.state.metadata } : {}),
|
||||
...(part.state.title ? { title: part.state.title } : {}),
|
||||
}
|
||||
}
|
||||
|
||||
if (part.state.status === "completed") {
|
||||
return {
|
||||
status: "completed",
|
||||
input: part.state.input,
|
||||
output: part.state.output,
|
||||
title: part.state.title,
|
||||
metadata: part.state.metadata,
|
||||
time: part.state.time,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status: "error",
|
||||
input: part.state.input,
|
||||
error: part.state.error,
|
||||
time: part.state.time,
|
||||
...(part.state.metadata ? { metadata: part.state.metadata } : {}),
|
||||
}
|
||||
}
|
||||
|
||||
function recent<T>(input: Iterable<T>, limit: number) {
|
||||
const list = [...input]
|
||||
return list.slice(Math.max(0, list.length - limit))
|
||||
@@ -215,15 +254,9 @@ function compactToolPart(part: ToolPart): ToolPart {
|
||||
messageID: part.messageID,
|
||||
callID: part.callID,
|
||||
tool: part.tool,
|
||||
state: {
|
||||
status: part.state.status,
|
||||
input: part.state.input,
|
||||
metadata: "metadata" in part.state ? part.state.metadata : undefined,
|
||||
time: "time" in part.state ? part.state.time : undefined,
|
||||
title: "title" in part.state ? part.state.title : undefined,
|
||||
error: "error" in part.state ? part.state.error : undefined,
|
||||
},
|
||||
} as ToolPart
|
||||
state: compactToolState(part),
|
||||
...(part.metadata ? { metadata: part.metadata } : {}),
|
||||
}
|
||||
}
|
||||
|
||||
function compactCommit(commit: StreamCommit): StreamCommit {
|
||||
@@ -623,7 +656,7 @@ export function bootstrapSubagentCalls(input: { data: SubagentData; sessionID: s
|
||||
export function clearFinishedSubagents(data: SubagentData) {
|
||||
let changed = false
|
||||
|
||||
for (const [sessionID, tab] of [...data.tabs.entries()]) {
|
||||
for (const [sessionID, tab] of data.tabs.entries()) {
|
||||
if (tab.status === "running") {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ function blend(color: RGBA, bg: RGBA): RGBA {
|
||||
|
||||
export function opaqueSyntaxStyle(style: SyntaxStyle | undefined, bg: RGBA): SyntaxStyle | undefined {
|
||||
if (!style) {
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
return SyntaxStyle.fromStyles(
|
||||
|
||||
@@ -130,28 +130,28 @@ type ToolRegistry = {
|
||||
[K in ToolName]: ToolRule<ToolDefs[K]>
|
||||
}
|
||||
|
||||
type AnyToolRule = ToolRule<Tool.Info>
|
||||
type AnyToolRule = ToolRule
|
||||
|
||||
function dict(v: unknown): ToolDict {
|
||||
if (!v || typeof v !== "object" || Array.isArray(v)) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return v as ToolDict
|
||||
return { ...v }
|
||||
}
|
||||
|
||||
function props<T = Tool.Info>(frame: ToolFrame): ToolProps<T> {
|
||||
return {
|
||||
input: frame.input as Partial<Tool.InferParameters<T>>,
|
||||
metadata: frame.meta as Partial<Tool.InferMetadata<T>>,
|
||||
input: Object.assign(Object.create(null), frame.input),
|
||||
metadata: Object.assign(Object.create(null), frame.meta),
|
||||
frame,
|
||||
}
|
||||
}
|
||||
|
||||
function permission<T = Tool.Info>(ctx: ToolPermissionCtx): ToolPermissionProps<T> {
|
||||
return {
|
||||
input: ctx.input as Partial<Tool.InferParameters<T>>,
|
||||
metadata: ctx.meta as Partial<Tool.InferMetadata<T>>,
|
||||
input: Object.assign(Object.create(null), ctx.input),
|
||||
metadata: Object.assign(Object.create(null), ctx.meta),
|
||||
patterns: ctx.patterns,
|
||||
}
|
||||
}
|
||||
@@ -162,14 +162,18 @@ function text(v: unknown): string {
|
||||
|
||||
function num(v: unknown): number | undefined {
|
||||
if (typeof v !== "number" || !Number.isFinite(v)) {
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
function list<T>(v: unknown): T[] {
|
||||
return Array.isArray(v) ? (v as T[]) : []
|
||||
if (!Array.isArray(v)) {
|
||||
return []
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
function done(name: string, time: string): string {
|
||||
@@ -193,7 +197,7 @@ function info(data: ToolDict, skip: string[] = []): string {
|
||||
return ""
|
||||
}
|
||||
|
||||
return `[${list.map(([key, val]) => `${key}=${val}`).join(", ")}]`
|
||||
return `[${list.map(([key, val]) => `${key}=${String(val)}`).join(", ")}]`
|
||||
}
|
||||
|
||||
function span(state: ToolDict): string {
|
||||
@@ -506,7 +510,7 @@ function snapWrite(p: ToolProps<typeof WriteTool>): ToolSnapshot | undefined {
|
||||
const file = p.input.filePath || ""
|
||||
const content = p.input.content || ""
|
||||
if (!file && !content) {
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -521,7 +525,7 @@ function snapEdit(p: ToolProps<typeof EditTool>): ToolSnapshot | undefined {
|
||||
const file = p.input.filePath || ""
|
||||
const diff = p.metadata.diff || ""
|
||||
if (!file || !diff.trim()) {
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -539,31 +543,31 @@ function snapEdit(p: ToolProps<typeof EditTool>): ToolSnapshot | undefined {
|
||||
function snapPatch(p: ToolProps<typeof ApplyPatchTool>): ToolSnapshot | undefined {
|
||||
const files = list<PatchFile>(p.frame.meta.files)
|
||||
if (files.length === 0) {
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
return {
|
||||
kind: "diff",
|
||||
items: files
|
||||
.map((file) => {
|
||||
if (!file || typeof file !== "object") {
|
||||
return
|
||||
}
|
||||
items: files.flatMap((file) => {
|
||||
if (!file || typeof file !== "object") {
|
||||
return []
|
||||
}
|
||||
|
||||
const diff = typeof file.patch === "string" ? file.patch : ""
|
||||
if (!diff.trim()) {
|
||||
return
|
||||
}
|
||||
const diff = typeof file.patch === "string" ? file.patch : ""
|
||||
if (!diff.trim()) {
|
||||
return []
|
||||
}
|
||||
|
||||
const name = file.movePath || file.filePath || file.relativePath
|
||||
return {
|
||||
const name = file.movePath || file.filePath || file.relativePath
|
||||
return [
|
||||
{
|
||||
title: patchTitle(file),
|
||||
diff,
|
||||
file: name,
|
||||
deletions: typeof file.deletions === "number" ? file.deletions : 0,
|
||||
}
|
||||
})
|
||||
.filter((item): item is NonNullable<typeof item> => Boolean(item)),
|
||||
},
|
||||
]
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -746,9 +750,9 @@ function scrollTaskStart(_: ToolProps<typeof TaskTool>): string {
|
||||
return ""
|
||||
}
|
||||
|
||||
function taskResult(output: string) {
|
||||
function taskResult(output: string): string | undefined {
|
||||
if (!output.trim()) {
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
const match = output.match(/<task_result>\s*([\s\S]*?)\s*<\/task_result>/)
|
||||
@@ -1236,10 +1240,10 @@ function key(name: string): name is ToolName {
|
||||
|
||||
function rule(name?: string): AnyToolRule | undefined {
|
||||
if (!name || !key(name)) {
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
return TOOL_RULES[name] as AnyToolRule
|
||||
return TOOL_RULES[name]
|
||||
}
|
||||
|
||||
function frame(part: ToolPart): ToolFrame {
|
||||
@@ -1345,13 +1349,13 @@ export function toolPermissionInfo(
|
||||
): ToolPermissionInfo | undefined {
|
||||
const draw = rule(name)?.permission
|
||||
if (!draw) {
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
try {
|
||||
return draw(permission({ input, meta, patterns }))
|
||||
} catch {
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1359,19 +1363,19 @@ export function toolSnapshot(commit: StreamCommit, raw: string): ToolSnapshot |
|
||||
const ctx = toolFrame(commit, raw)
|
||||
const draw = rule(ctx.name)?.snap
|
||||
if (!draw) {
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
try {
|
||||
return draw(props(ctx))
|
||||
} catch {
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
function textBody(content: string): RunEntryBody | undefined {
|
||||
if (!content) {
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -1382,7 +1386,7 @@ function textBody(content: string): RunEntryBody | undefined {
|
||||
|
||||
function markdownBody(content: string): RunEntryBody | undefined {
|
||||
if (!content) {
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -1394,7 +1398,7 @@ function markdownBody(content: string): RunEntryBody | undefined {
|
||||
function structuredBody(commit: StreamCommit, raw: string): RunEntryBody | undefined {
|
||||
const snap = toolSnapshot(commit, raw)
|
||||
if (!snap) {
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -1409,7 +1413,7 @@ export function toolEntryBody(commit: StreamCommit, raw: string): RunEntryBody |
|
||||
|
||||
if (ctx.name === "task") {
|
||||
if (commit.phase === "start") {
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (commit.phase === "final" && ctx.status === "completed") {
|
||||
@@ -1421,7 +1425,7 @@ export function toolEntryBody(commit: StreamCommit, raw: string): RunEntryBody |
|
||||
}
|
||||
|
||||
if (commit.phase === "progress" && !view.output) {
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (commit.phase === "final") {
|
||||
@@ -1430,7 +1434,7 @@ export function toolEntryBody(commit: StreamCommit, raw: string): RunEntryBody |
|
||||
}
|
||||
|
||||
if (!view.final) {
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (ctx.status && ctx.status !== "completed") {
|
||||
@@ -1447,7 +1451,7 @@ export function toolEntryBody(commit: StreamCommit, raw: string): RunEntryBody |
|
||||
|
||||
export function toolFiletype(input?: string): string | undefined {
|
||||
if (!input) {
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
const ext = path.extname(input)
|
||||
|
||||
@@ -50,14 +50,14 @@ function text(data: unknown) {
|
||||
)
|
||||
}
|
||||
|
||||
export function trace() {
|
||||
export function trace(): Trace | undefined {
|
||||
if (state !== undefined) {
|
||||
return state || undefined
|
||||
}
|
||||
|
||||
if (!process.env.OPENCODE_DIRECT_TRACE) {
|
||||
state = false
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
|
||||
const target = file()
|
||||
|
||||
@@ -132,7 +132,7 @@ function createLayer(fs = AppFileSystem.defaultLayer) {
|
||||
const read = Effect.fn("RunVariant.read")(function* () {
|
||||
return yield* file.readJson(MODEL_FILE).pipe(
|
||||
Effect.map(state),
|
||||
Effect.catchCause(() => Effect.succeed({})),
|
||||
Effect.catchCause(() => Effect.succeed(state(undefined))),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -154,7 +154,7 @@ function createLayer(fs = AppFileSystem.defaultLayer) {
|
||||
|
||||
const current = yield* read()
|
||||
const next = {
|
||||
...(current.variant ?? {}),
|
||||
...current.variant,
|
||||
}
|
||||
const key = variantKey(model)
|
||||
if (variant) {
|
||||
|
||||
Reference in New Issue
Block a user