fix linter errors

This commit is contained in:
Simon Klee
2026-04-20 14:28:11 +02:00
parent 3b7cccd870
commit 2de253726c
18 changed files with 271 additions and 201 deletions

View File

@@ -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: "",

View File

@@ -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}>

View File

@@ -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()

View File

@@ -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))

View File

@@ -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)

View File

@@ -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)

View File

@@ -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,

View File

@@ -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

View File

@@ -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

View File

@@ -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") {

View File

@@ -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,
}
}

View File

@@ -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
}

View File

@@ -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) =>

View File

@@ -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
}

View File

@@ -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(

View File

@@ -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)

View File

@@ -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()

View 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) {