feat(aircoding): AirCoding V2 baseline — deterministic multi-agent architecture
Forked from OpenCode v1.17.4 with multi-agent system: - 5 agents: aircoding, scheduler, worker, architect, reviewer - Deterministic DAG scheduling engine (coordinator_tick) - Tool whitelists as hard enforcement - AirCoding validation plugin - V1 requirements: C4 docs, ADR, AGENTS.md, debug-log.md - Design documents in docs/
This commit is contained in:
245
packages/core/test/tool-output-store.test.ts
Normal file
245
packages/core/test/tool-output-store.test.ts
Normal file
@@ -0,0 +1,245 @@
|
||||
import { describe, expect } from "bun:test"
|
||||
import path from "path"
|
||||
import { Cause, Effect, Exit, Fiber, Layer, Option } from "effect"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { Config } from "@opencode-ai/core/config"
|
||||
import { ConfigToolOutput } from "@opencode-ai/core/config/tool-output"
|
||||
import { SessionV2 } from "@opencode-ai/core/session"
|
||||
import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
|
||||
import { testEffect } from "./lib/effect"
|
||||
import { tmpdir } from "./fixture/tmpdir"
|
||||
|
||||
const sessionID = SessionV2.ID.make("ses_tool_output_store")
|
||||
|
||||
const withStore = <A, E, R>(
|
||||
body: (input: { root: string; store: ToolOutputStore.Interface; fs: FSUtil.Interface }) => Effect.Effect<A, E, R>,
|
||||
config?: Config.Info,
|
||||
) =>
|
||||
Effect.acquireUseRelease(
|
||||
Effect.promise(() => tmpdir()),
|
||||
(tmp) => {
|
||||
const global = Global.layerWith({ data: tmp.path })
|
||||
const configured = config
|
||||
? Layer.succeed(
|
||||
Config.Service,
|
||||
Config.Service.of({
|
||||
entries: () => Effect.succeed([new Config.Document({ type: "document", info: config })]),
|
||||
}),
|
||||
)
|
||||
: Layer.empty
|
||||
const store = ToolOutputStore.layer.pipe(
|
||||
Layer.provide(FSUtil.defaultLayer),
|
||||
Layer.provide(global),
|
||||
Layer.provide(configured),
|
||||
)
|
||||
return Effect.gen(function* () {
|
||||
return yield* body({ root: tmp.path, store: yield* ToolOutputStore.Service, fs: yield* FSUtil.Service })
|
||||
}).pipe(Effect.provide(Layer.mergeAll(store, FSUtil.defaultLayer)))
|
||||
},
|
||||
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
|
||||
)
|
||||
|
||||
const it = testEffect(Layer.empty)
|
||||
|
||||
describe("ToolOutputStore", () => {
|
||||
it.live("bounds the provider-facing text channel with one managed file", () =>
|
||||
withStore(({ store, fs }) =>
|
||||
Effect.gen(function* () {
|
||||
const first = "HEAD-" + "x".repeat(30_000)
|
||||
const second = "y".repeat(30_000) + "-TAIL"
|
||||
const result = yield* store.bound({
|
||||
sessionID,
|
||||
toolCallID: "call-aggregate",
|
||||
output: {
|
||||
structured: { kind: "report" },
|
||||
content: [
|
||||
{ type: "text", text: first },
|
||||
{ type: "text", text: second },
|
||||
],
|
||||
},
|
||||
})
|
||||
expect(result.output.structured).toEqual({ kind: "report" })
|
||||
expect(result.outputPaths).toHaveLength(1)
|
||||
expect(yield* fs.readFileString(result.outputPaths[0])).toBe(first + second)
|
||||
if (result.output.content[0]?.type !== "text") throw new Error("expected text preview")
|
||||
expect(Buffer.byteLength(result.output.content[0].text)).toBeLessThanOrEqual(ToolOutputStore.MAX_BYTES)
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
it.live("uses bounded text for oversized structured-only output", () =>
|
||||
withStore(({ store, fs }) =>
|
||||
Effect.gen(function* () {
|
||||
const structured = { text: "x".repeat(ToolOutputStore.MAX_BYTES) }
|
||||
const result = yield* store.bound({ sessionID, toolCallID: "call-json", output: { structured, content: [] } })
|
||||
expect(result.output.structured).toEqual(structured)
|
||||
expect(result.outputPaths).toHaveLength(1)
|
||||
expect(JSON.parse(yield* fs.readFileString(result.outputPaths[0]))).toEqual(structured)
|
||||
expect(result.output.content).toHaveLength(1)
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
it.live("preserves native media and structured metadata without applying a settlement media limit", () =>
|
||||
withStore(({ store }) =>
|
||||
Effect.gen(function* () {
|
||||
const data = "a".repeat(6 * 1024 * 1024)
|
||||
const result = yield* store.bound({
|
||||
sessionID,
|
||||
toolCallID: "call-file",
|
||||
output: {
|
||||
structured: { caption: "pixel" },
|
||||
content: [{ type: "file", uri: `data:image/png;base64,${data}`, mime: "image/png", name: "pixel.png" }],
|
||||
},
|
||||
})
|
||||
expect(result.outputPaths).toEqual([])
|
||||
expect(result.output.structured).toEqual({ caption: "pixel" })
|
||||
expect(result.output.content).toHaveLength(1)
|
||||
expect(result.output.content[0]).toEqual({
|
||||
type: "file",
|
||||
uri: `data:image/png;base64,${data}`,
|
||||
mime: "image/png",
|
||||
name: "pixel.png",
|
||||
})
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
it.live("preserves structured metadata and native media when bounding text", () =>
|
||||
withStore(({ store, fs }) =>
|
||||
Effect.gen(function* () {
|
||||
const text = "x".repeat(ToolOutputStore.MAX_BYTES + 1)
|
||||
const media = {
|
||||
type: "file" as const,
|
||||
uri: "data:image/png;base64,aGVsbG8=",
|
||||
mime: "image/png",
|
||||
name: "pixel.png",
|
||||
}
|
||||
const result = yield* store.bound({
|
||||
sessionID,
|
||||
toolCallID: "call-text-and-media",
|
||||
output: { structured: { caption: "pixel" }, content: [{ type: "text", text }, media] },
|
||||
})
|
||||
|
||||
expect(result.output.structured).toEqual({ caption: "pixel" })
|
||||
expect(result.output.content[1]).toEqual(media)
|
||||
expect(yield* fs.readFileString(result.outputPaths[0])).toBe(text)
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
it.live("does not double-count structured data duplicated in projected text", () =>
|
||||
withStore(({ store }) =>
|
||||
Effect.gen(function* () {
|
||||
const text = "x".repeat(30_000)
|
||||
const output = { structured: { output: text }, content: [{ type: "text" as const, text }] }
|
||||
expect(yield* store.bound({ sessionID, toolCallID: "call-duplicated", output })).toEqual({
|
||||
output,
|
||||
outputPaths: [],
|
||||
})
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
it.live("fails oversized settlement when complete retention cannot be written", () =>
|
||||
withStore(({ root, store, fs }) =>
|
||||
Effect.gen(function* () {
|
||||
yield* fs.writeFileString(path.join(root, "tool-output"), "not a directory")
|
||||
const exit = yield* store
|
||||
.bound({
|
||||
sessionID,
|
||||
toolCallID: "call-lossy",
|
||||
output: { structured: {}, content: [{ type: "text", text: "x".repeat(ToolOutputStore.MAX_BYTES + 1) }] },
|
||||
})
|
||||
.pipe(Effect.exit)
|
||||
expect(Exit.isFailure(exit)).toBe(true)
|
||||
if (Exit.isFailure(exit))
|
||||
expect(Option.getOrUndefined(Cause.findErrorOption(exit.cause))?._tag).toBe("ToolOutputStore.StorageError")
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
it.live("does not encode ignored structured metadata when projected content exists", () =>
|
||||
withStore(({ store }) =>
|
||||
Effect.gen(function* () {
|
||||
const output = { structured: { value: 1n }, content: [{ type: "text" as const, text: "readable text" }] }
|
||||
expect(yield* store.bound({ sessionID, toolCallID: "call-unencodable", output })).toEqual({
|
||||
output,
|
||||
outputPaths: [],
|
||||
})
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
it.live("preserves interruption while retaining complete output", () =>
|
||||
Effect.gen(function* () {
|
||||
const root = yield* Effect.promise(() => tmpdir())
|
||||
const blockedFilesystem = Layer.effect(
|
||||
FSUtil.Service,
|
||||
Effect.gen(function* () {
|
||||
const fs = yield* FSUtil.Service
|
||||
return FSUtil.Service.of({
|
||||
...fs,
|
||||
ensureDir: () => Effect.void,
|
||||
writeFileString: () => Effect.never,
|
||||
})
|
||||
}),
|
||||
).pipe(Layer.provide(FSUtil.defaultLayer))
|
||||
const store = ToolOutputStore.layer.pipe(
|
||||
Layer.provide(blockedFilesystem),
|
||||
Layer.provide(Global.layerWith({ data: root.path })),
|
||||
)
|
||||
const exit = yield* Effect.gen(function* () {
|
||||
const service = yield* ToolOutputStore.Service
|
||||
const fiber = yield* service
|
||||
.bound({
|
||||
sessionID,
|
||||
toolCallID: "call-interrupted",
|
||||
output: { structured: {}, content: [{ type: "text", text: "x".repeat(ToolOutputStore.MAX_BYTES + 1) }] },
|
||||
})
|
||||
.pipe(Effect.forkChild)
|
||||
yield* Fiber.interrupt(fiber)
|
||||
return yield* Fiber.await(fiber)
|
||||
}).pipe(Effect.provide(store))
|
||||
expect(Exit.isFailure(exit) && Cause.hasInterrupts(exit.cause)).toBe(true)
|
||||
yield* Effect.promise(() => root[Symbol.asyncDispose]())
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("honors configured limits", () =>
|
||||
withStore(
|
||||
({ store }) =>
|
||||
Effect.gen(function* () {
|
||||
expect(yield* store.limits()).toEqual({ maxLines: 2, maxBytes: 1_000 })
|
||||
const result = yield* store.bound({
|
||||
sessionID,
|
||||
toolCallID: "call-config",
|
||||
output: { structured: {}, content: [{ type: "text", text: "one\ntwo\nthree" }] },
|
||||
})
|
||||
expect(result.outputPaths).toHaveLength(1)
|
||||
}),
|
||||
new Config.Info({ tool_output: new ConfigToolOutput.Info({ max_lines: 2, max_bytes: 1_000 }) }),
|
||||
),
|
||||
)
|
||||
|
||||
it.live("cleans expired managed files and preserves unrelated files", () =>
|
||||
withStore(({ root, store, fs }) =>
|
||||
Effect.gen(function* () {
|
||||
const old = path.join(root, "tool-output", "tool_old")
|
||||
const recent = path.join(root, "tool-output", "tool_recent")
|
||||
const unrelated = path.join(root, "tool-output", "keep.txt")
|
||||
yield* fs.ensureDir(path.join(root, "tool-output"))
|
||||
yield* fs.writeFileString(old, "old")
|
||||
yield* fs.writeFileString(recent, "recent")
|
||||
yield* fs.writeFileString(unrelated, "keep")
|
||||
const expired = new Date(Date.now() - 8 * 24 * 60 * 60 * 1_000)
|
||||
yield* fs.utimes(old, expired, expired)
|
||||
yield* store.cleanup()
|
||||
expect(yield* fs.exists(old)).toBe(false)
|
||||
expect(yield* fs.exists(recent)).toBe(true)
|
||||
expect(yield* fs.exists(unrelated)).toBe(true)
|
||||
}),
|
||||
),
|
||||
)
|
||||
})
|
||||
Reference in New Issue
Block a user