Compare commits

...
Author SHA1 Message Date
Kit Langton b91eb7daf9 refactor(core): use nonempty array guards 2026-08-27 15:45:25 -04:00
3 changed files with 10 additions and 23 deletions
@@ -1,5 +1,6 @@
import { type LLMEvent, type ProviderMetadata, type ToolResultValue } from "@opencode-ai/ai"
import { Clock, Effect, Iterable } from "effect"
import { isArrayNonEmpty, isReadonlyArrayNonEmpty } from "effect/Array"
import { Bus } from "../../bus.js"
import { Model } from "../../model.js"
import { SessionEvent } from "../event.js"
@@ -45,9 +46,6 @@ export interface StepRecord {
/** Derives canonical model content from a provider-hosted tool result. */
type NonEmptyContent = readonly [Tool.Content, ...Tool.Content[]]
const nonEmpty = (content: ReadonlyArray<Tool.Content>): NonEmptyContent | undefined =>
content.length > 0 ? (content as NonEmptyContent) : undefined
const stringify = (value: unknown) => {
if (typeof value === "string") return value
try {
@@ -58,10 +56,7 @@ const stringify = (value: unknown) => {
}
const hostedContent = (result: ToolResultValue): NonEmptyContent => {
if (result.type === "content") {
const content = nonEmpty(result.value)
if (content !== undefined) return content
}
if (result.type === "content" && isReadonlyArrayNonEmpty(result.value)) return result.value
return [{ type: "text", text: stringify(result.value) }]
}
@@ -561,12 +556,12 @@ export const createLLMEventPublisher = (bus: Pick<Bus.Interface, "publish">, inp
: result.content === undefined
? []
: [...result.content]
if (content.length === 0) return yield* Effect.die(new Error(`Tool execution has no content: ${id}`))
if (!isArrayNonEmpty(content)) return yield* Effect.die(new Error(`Tool execution has no content: ${id}`))
yield* bus.publish(SessionEvent.Tool.Success, {
sessionID: input.sessionID,
assistantMessageID,
id,
content: [content[0], ...content.slice(1)],
content,
...(result.metadata === undefined ? {} : { metadata: result.metadata }),
executed: tool.providerExecuted,
})
+3 -10
View File
@@ -5,6 +5,7 @@ import { Tool } from "@opencode-ai/schema/tool"
import { Skill } from "@opencode-ai/schema/skill"
import { eq } from "drizzle-orm"
import { Context, DateTime, Effect, Layer, Schema } from "effect"
import { map } from "effect/Array"
import path from "path"
import { makeGlobalNode } from "@opencode-ai/util/effect/app-node"
import { App } from "../app.js"
@@ -304,21 +305,13 @@ function sanitizeToolState(id: string, state: SessionMessage.ToolState): Session
return {
...state,
input: { redacted: `tool-input:${id}` },
content: [
sanitizeToolContent(id, state.content[0]),
...state.content.slice(1).map((item) => sanitizeToolContent(id, item)),
],
content: map(state.content, (item) => sanitizeToolContent(id, item)),
metadata: meta,
}
return {
...state,
input: { redacted: `tool-input:${id}` },
content: state.content
? [
sanitizeToolContent(id, state.content[0]),
...state.content.slice(1).map((item) => sanitizeToolContent(id, item)),
]
: undefined,
content: state.content ? map(state.content, (item) => sanitizeToolContent(id, item)) : undefined,
metadata: meta,
}
}
+3 -4
View File
@@ -1,4 +1,4 @@
import type { NonEmptyReadonlyArray } from "effect/Array"
import { isArrayNonEmpty } from "effect/Array"
import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem"
import * as NodePath from "@effect/platform-node/NodePath"
import * as NodeSink from "@effect/platform-node/NodeSink"
@@ -62,10 +62,9 @@ const flatten = (command: ChildProcess.Command) => {
}
walk(command)
if (commands.length === 0) throw new Error("flatten produced empty commands array")
const [head, ...tail] = commands
if (!isArrayNonEmpty(commands)) throw new Error("flatten produced empty commands array")
return {
commands: [head, ...tail] as NonEmptyReadonlyArray<ChildProcess.StandardCommand>,
commands,
opts,
}
}