diff --git a/packages/core/src/session/runner/publish-llm-event.ts b/packages/core/src/session/runner/publish-llm-event.ts index 9116e32091a7..755fbd71c918 100644 --- a/packages/core/src/session/runner/publish-llm-event.ts +++ b/packages/core/src/session/runner/publish-llm-event.ts @@ -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): 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) }] } @@ -563,12 +558,12 @@ export const createLLMEventPublisher = (bus: Pick, 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, }) diff --git a/packages/core/src/session/transfer.ts b/packages/core/src/session/transfer.ts index 4a9b11b6f8ba..1cfcb7ef72a2 100644 --- a/packages/core/src/session/transfer.ts +++ b/packages/core/src/session/transfer.ts @@ -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" @@ -309,21 +310,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, } } diff --git a/packages/util/src/cross-spawn-spawner.ts b/packages/util/src/cross-spawn-spawner.ts index 920fcf2223a9..0ca2cb156fc6 100644 --- a/packages/util/src/cross-spawn-spawner.ts +++ b/packages/util/src/cross-spawn-spawner.ts @@ -1,4 +1,4 @@ -import type { NonEmptyReadonlyArray } from "effect/Array" +import { isArrayNonEmpty } from "effect/Array" import * as NodeSink from "@effect/platform-node/NodeSink" import * as NodeStream from "@effect/platform-node/NodeStream" import { Deferred, Effect, Exit, FileSystem, Layer, Path, PlatformError, Predicate, Sink, Stream } from "effect" @@ -60,10 +60,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, + commands, opts, } }