mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-27 05:35:46 -04:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4c1c374a41 | |||
| b9fa63a0bc |
@@ -1,6 +1,7 @@
|
||||
import { describe, expect } from "bun:test"
|
||||
import { Effect, Exit, Scope } from "effect"
|
||||
import { AgentV2 } from "@opencode-ai/core/agent"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { Location } from "@opencode-ai/core/location"
|
||||
import { AgentPlugin } from "@opencode-ai/core/plugin/agent"
|
||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||
@@ -8,7 +9,7 @@ import { location } from "./fixture/location"
|
||||
import { testEffect } from "./lib/effect"
|
||||
import { agentHost, host } from "./plugin/host"
|
||||
|
||||
const it = testEffect(AgentV2.locationLayer)
|
||||
const it = testEffect(AppNodeBuilder.build(AgentV2.node))
|
||||
|
||||
describe("AgentV2", () => {
|
||||
it.effect("starts without agents", () =>
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { describe, expect } from "bun:test"
|
||||
import { BackgroundJob } from "@opencode-ai/core/background-job"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { Deferred, Effect, Exit, Scope } from "effect"
|
||||
import { it } from "./lib/effect"
|
||||
|
||||
const jobsLayer = LayerNode.compile(BackgroundJob.node)
|
||||
|
||||
describe("BackgroundJob", () => {
|
||||
it.live("tracks process-local work through explicit observation", () =>
|
||||
Effect.gen(function* () {
|
||||
@@ -25,7 +28,7 @@ describe("BackgroundJob", () => {
|
||||
timedOut: false,
|
||||
info: { status: "completed", output: "done" },
|
||||
})
|
||||
}).pipe(Effect.provide(BackgroundJob.layer)),
|
||||
}).pipe(Effect.provide(jobsLayer)),
|
||||
)
|
||||
|
||||
it.live("publishes jobs before starting immediately settling work", () =>
|
||||
@@ -55,7 +58,7 @@ describe("BackgroundJob", () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
}).pipe(Effect.provide(BackgroundJob.layer)),
|
||||
}).pipe(Effect.provide(jobsLayer)),
|
||||
)
|
||||
|
||||
it.live("increments pending work before starting immediately settling extensions", () =>
|
||||
@@ -80,7 +83,7 @@ describe("BackgroundJob", () => {
|
||||
})
|
||||
}),
|
||||
)
|
||||
}).pipe(Effect.provide(BackgroundJob.layer)),
|
||||
}).pipe(Effect.provide(jobsLayer)),
|
||||
)
|
||||
|
||||
it.live("interrupts live work without promising settlement after the owning process-local scope closes", () =>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { describe, expect } from "bun:test"
|
||||
import { Effect } from "effect"
|
||||
import { CommandV2 } from "@opencode-ai/core/command"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { ModelV2 } from "@opencode-ai/core/model"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { testEffect } from "./lib/effect"
|
||||
|
||||
const it = testEffect(CommandV2.locationLayer)
|
||||
const it = testEffect(AppNodeBuilder.build(CommandV2.node))
|
||||
|
||||
describe("CommandV2", () => {
|
||||
it.effect("applies command transforms and preserves later overrides", () =>
|
||||
|
||||
@@ -69,8 +69,8 @@ void invalidNodeReplacement
|
||||
// @ts-expect-error Replacement cannot introduce a new error
|
||||
LayerNode.compile(a, [[a, Layer.effect(A, Effect.fail(new OtherError()))]])
|
||||
|
||||
// @ts-expect-error Node replacement cannot introduce a new error
|
||||
const invalidNodeErrorReplacement = () =>
|
||||
// @ts-expect-error Node replacement cannot introduce a new error
|
||||
LayerNode.compile(a, [[a, make({ service: A, layer: Layer.effect(A, Effect.fail(new OtherError())), deps: [] })]])
|
||||
void invalidNodeErrorReplacement
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect } from "bun:test"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { Location } from "@opencode-ai/core/location"
|
||||
import { Project } from "@opencode-ai/core/project"
|
||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||
@@ -21,7 +22,7 @@ const projectLayer = Layer.succeed(
|
||||
commit: () => Effect.void,
|
||||
}),
|
||||
)
|
||||
const it = testEffect(Location.layer(ref).pipe(Layer.provide(projectLayer)))
|
||||
const it = testEffect(AppNodeBuilder.build(Location.boundNode(ref), [[Project.node, projectLayer]]))
|
||||
|
||||
describe("Location", () => {
|
||||
it.effect("resolves the current project and vcs information", () =>
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { NodeFileSystem } from "@effect/platform-node"
|
||||
import { Effect, Layer, Option } from "effect"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { Effect, Option } from "effect"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { Npm } from "@opencode-ai/core/npm"
|
||||
import { EffectFlock } from "@opencode-ai/core/util/effect-flock"
|
||||
import { tmpdir } from "./fixture/tmpdir"
|
||||
|
||||
const win = process.platform === "win32"
|
||||
@@ -21,12 +19,7 @@ const writePackage = (dir: string, pkg: Record<string, unknown>) =>
|
||||
)
|
||||
|
||||
const npmLayer = (cache: string) =>
|
||||
Npm.layer.pipe(
|
||||
Layer.provide(EffectFlock.layer),
|
||||
Layer.provide(FSUtil.layer),
|
||||
Layer.provide(Global.layerWith({ cache, state: path.join(cache, "state") })),
|
||||
Layer.provide(NodeFileSystem.layer),
|
||||
)
|
||||
AppNodeBuilder.build(Npm.node, [[Global.node, Global.layerWith({ cache, state: path.join(cache, "state") })]])
|
||||
|
||||
describe("Npm.sanitize", () => {
|
||||
test("keeps normal scoped package specs unchanged", () => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect } from "bun:test"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { CommandV2 } from "@opencode-ai/core/command"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { Location } from "@opencode-ai/core/location"
|
||||
import { CommandPlugin } from "@opencode-ai/core/plugin/command"
|
||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||
@@ -10,12 +11,9 @@ import { host } from "./host"
|
||||
|
||||
const directory = AbsolutePath.make("/repo/packages/app")
|
||||
const project = AbsolutePath.make("/repo")
|
||||
const locationLayer = Layer.succeed(Location.Service, Location.Service.of(location({ directory }, { projectDirectory: project })))
|
||||
const it = testEffect(
|
||||
CommandV2.locationLayer.pipe(
|
||||
Layer.provide(
|
||||
Layer.succeed(Location.Service, Location.Service.of(location({ directory }, { projectDirectory: project }))),
|
||||
),
|
||||
),
|
||||
AppNodeBuilder.build(CommandV2.node, [[Location.node, locationLayer]]),
|
||||
)
|
||||
|
||||
describe("CommandPlugin.Plugin", () => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect } from "bun:test"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { Location } from "@opencode-ai/core/location"
|
||||
import { Policy } from "@opencode-ai/core/policy"
|
||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||
@@ -7,11 +8,9 @@ import { location } from "./fixture/location"
|
||||
import { testEffect } from "./lib/effect"
|
||||
|
||||
const it = testEffect(
|
||||
Policy.locationLayer.pipe(
|
||||
Layer.provide(
|
||||
Layer.succeed(Location.Service, Location.Service.of(location({ directory: AbsolutePath.make("test") }))),
|
||||
),
|
||||
),
|
||||
AppNodeBuilder.build(Policy.node, [
|
||||
[Location.node, Layer.succeed(Location.Service, Location.Service.of(location({ directory: AbsolutePath.make("test") })))],
|
||||
]),
|
||||
)
|
||||
|
||||
describe("Policy", () => {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { describe, expect } from "bun:test"
|
||||
import { Cause, Deferred, Effect, Exit, Layer, Queue } from "effect"
|
||||
import { Config } from "@opencode-ai/core/config"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { Location } from "@opencode-ai/core/location"
|
||||
import { Pty } from "@opencode-ai/core/pty"
|
||||
@@ -17,11 +19,10 @@ const locationLayer = Layer.succeed(
|
||||
)
|
||||
const configLayer = Layer.mock(Config.Service)({ entries: () => Effect.succeed([]) })
|
||||
const it = testEffect(
|
||||
Pty.layer.pipe(
|
||||
Layer.provide(configLayer),
|
||||
Layer.provideMerge(EventV2.defaultLayer),
|
||||
Layer.provideMerge(locationLayer),
|
||||
),
|
||||
AppNodeBuilder.build(LayerNode.group([Pty.node, EventV2.node]), [
|
||||
[Config.node, configLayer],
|
||||
[Location.node, locationLayer],
|
||||
]),
|
||||
)
|
||||
const ptyTest = process.platform === "win32" ? it.live.skip : it.live
|
||||
|
||||
@@ -205,8 +206,9 @@ describe("pty", () => {
|
||||
|
||||
const configuredShell = process.platform === "win32" ? undefined : Bun.which("bash")
|
||||
const configuredIt = testEffect(
|
||||
Pty.layer.pipe(
|
||||
Layer.provide(
|
||||
AppNodeBuilder.build(LayerNode.group([Pty.node, EventV2.node]), [
|
||||
[
|
||||
Config.node,
|
||||
Layer.mock(Config.Service)({
|
||||
entries: () =>
|
||||
Effect.succeed(
|
||||
@@ -215,10 +217,9 @@ const configuredIt = testEffect(
|
||||
: [],
|
||||
),
|
||||
}),
|
||||
),
|
||||
Layer.provideMerge(EventV2.defaultLayer),
|
||||
Layer.provideMerge(locationLayer),
|
||||
),
|
||||
],
|
||||
[Location.node, locationLayer],
|
||||
]),
|
||||
)
|
||||
const configuredTest = process.platform === "win32" ? configuredIt.live.skip : configuredIt.live
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { describe, expect } from "bun:test"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { PtyID } from "@opencode-ai/core/pty/schema"
|
||||
import { PtyTicket } from "@opencode-ai/core/pty/ticket"
|
||||
import { WorkspaceV2 } from "@opencode-ai/core/workspace"
|
||||
import { testEffect } from "../lib/effect"
|
||||
|
||||
const it = testEffect(PtyTicket.layer)
|
||||
const itExpiring = testEffect(Layer.effect(PtyTicket.Service, PtyTicket.make(5)))
|
||||
const it = testEffect(LayerNode.compile(PtyTicket.node))
|
||||
const itExpiring = testEffect(
|
||||
LayerNode.compile(PtyTicket.node, [[PtyTicket.node, Layer.effect(PtyTicket.Service, PtyTicket.make(5))]]),
|
||||
)
|
||||
|
||||
describe("PTY websocket tickets", () => {
|
||||
it.live("consumes tickets once", () =>
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { describe, expect } from "bun:test"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||
import { Reference } from "@opencode-ai/core/reference"
|
||||
import { ReferenceGuidance } from "@opencode-ai/core/reference/guidance"
|
||||
import { SystemContext } from "@opencode-ai/core/system-context/index"
|
||||
import { it } from "./lib/effect"
|
||||
|
||||
const guidanceLayer = (referenceLayer: Layer.Layer<Reference.Service>) =>
|
||||
AppNodeBuilder.build(ReferenceGuidance.node, [[Reference.node, referenceLayer]])
|
||||
|
||||
describe("ReferenceGuidance", () => {
|
||||
it.effect("lists available references in the system context", () =>
|
||||
Effect.gen(function* () {
|
||||
@@ -17,23 +21,24 @@ describe("ReferenceGuidance", () => {
|
||||
expect(generation.baseline).toContain("<path>/docs</path>")
|
||||
expect(generation.baseline).toContain("<description>Use for product documentation</description>")
|
||||
}).pipe(
|
||||
Effect.provide(ReferenceGuidance.layer),
|
||||
Effect.provide(
|
||||
Layer.mock(Reference.Service, {
|
||||
list: () =>
|
||||
Effect.succeed([
|
||||
new Reference.Info({
|
||||
name: "docs",
|
||||
path: AbsolutePath.make("/docs"),
|
||||
description: "Use for product documentation",
|
||||
source: Reference.LocalSource.make({
|
||||
type: "local",
|
||||
guidanceLayer(
|
||||
Layer.mock(Reference.Service, {
|
||||
list: () =>
|
||||
Effect.succeed([
|
||||
new Reference.Info({
|
||||
name: "docs",
|
||||
path: AbsolutePath.make("/docs"),
|
||||
description: "Use for product documentation",
|
||||
source: Reference.LocalSource.make({
|
||||
type: "local",
|
||||
path: AbsolutePath.make("/docs"),
|
||||
description: "Use for product documentation",
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -43,10 +48,7 @@ describe("ReferenceGuidance", () => {
|
||||
const guidance = yield* ReferenceGuidance.Service
|
||||
const generation = yield* SystemContext.initialize(yield* guidance.load())
|
||||
expect(generation.baseline).toBe("")
|
||||
}).pipe(
|
||||
Effect.provide(ReferenceGuidance.layer),
|
||||
Effect.provide(Layer.mock(Reference.Service, { list: () => Effect.succeed([]) })),
|
||||
),
|
||||
}).pipe(Effect.provide(guidanceLayer(Layer.mock(Reference.Service, { list: () => Effect.succeed([]) })))),
|
||||
)
|
||||
|
||||
it.effect("omits references without descriptions", () =>
|
||||
@@ -55,18 +57,19 @@ describe("ReferenceGuidance", () => {
|
||||
const generation = yield* SystemContext.initialize(yield* guidance.load())
|
||||
expect(generation.baseline).toBe("")
|
||||
}).pipe(
|
||||
Effect.provide(ReferenceGuidance.layer),
|
||||
Effect.provide(
|
||||
Layer.mock(Reference.Service, {
|
||||
list: () =>
|
||||
Effect.succeed([
|
||||
new Reference.Info({
|
||||
name: "docs",
|
||||
path: AbsolutePath.make("/docs"),
|
||||
source: Reference.LocalSource.make({ type: "local", path: AbsolutePath.make("/docs") }),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
guidanceLayer(
|
||||
Layer.mock(Reference.Service, {
|
||||
list: () =>
|
||||
Effect.succeed([
|
||||
new Reference.Info({
|
||||
name: "docs",
|
||||
path: AbsolutePath.make("/docs"),
|
||||
source: Reference.LocalSource.make({ type: "local", path: AbsolutePath.make("/docs") }),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -4,6 +4,8 @@ import { Effect, Layer, Stream } from "effect"
|
||||
import { AgentV2 } from "@opencode-ai/core/agent"
|
||||
import { asc, eq } from "drizzle-orm"
|
||||
import { Database } from "@opencode-ai/core/database/database"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { EventTable } from "@opencode-ai/core/event/sql"
|
||||
import { Location } from "@opencode-ai/core/location"
|
||||
@@ -13,7 +15,6 @@ import { ProjectTable } from "@opencode-ai/core/project/sql"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||
import { SessionV2 } from "@opencode-ai/core/session"
|
||||
import { locationServiceMapLayer } from "@opencode-ai/core/location-services"
|
||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||
import { Prompt } from "@opencode-ai/core/session/prompt"
|
||||
import { SessionProjector } from "@opencode-ai/core/session/projector"
|
||||
@@ -34,24 +35,11 @@ const projects = Layer.succeed(
|
||||
commit: () => Effect.void,
|
||||
}),
|
||||
)
|
||||
const sessions = SessionV2.layer.pipe(
|
||||
Layer.provide(locationServiceMapLayer),
|
||||
Layer.provide(EventV2.defaultLayer),
|
||||
Layer.provide(Database.defaultLayer),
|
||||
Layer.provide(SessionStore.defaultLayer),
|
||||
Layer.provide(projects),
|
||||
Layer.provide(SessionExecution.noopLayer),
|
||||
)
|
||||
const it = testEffect(
|
||||
Layer.mergeAll(
|
||||
Database.defaultLayer,
|
||||
EventV2.defaultLayer,
|
||||
projects,
|
||||
SessionProjector.defaultLayer,
|
||||
SessionStore.defaultLayer,
|
||||
SessionExecution.noopLayer,
|
||||
sessions,
|
||||
),
|
||||
AppNodeBuilder.build(LayerNode.group([Database.node, EventV2.node, SessionProjector.node, SessionStore.node, SessionV2.node]), [
|
||||
[ProjectV2.node, projects],
|
||||
[SessionExecution.node, SessionExecution.noopLayer],
|
||||
]),
|
||||
)
|
||||
const location = Location.Ref.make({ directory: AbsolutePath.make("/project") })
|
||||
const id = SessionV2.ID.create()
|
||||
@@ -248,9 +236,10 @@ describe("SessionV2.create", () => {
|
||||
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
|
||||
)
|
||||
const targetDatabase = Database.layerFromPath(path.join(tmp.path, "target.sqlite"))
|
||||
const targetEvents = EventV2.layer.pipe(Layer.provide(targetDatabase))
|
||||
const targetProjector = SessionProjector.layer.pipe(Layer.provide(targetEvents), Layer.provide(targetDatabase))
|
||||
const targetStore = SessionStore.layer.pipe(Layer.provide(targetDatabase))
|
||||
const targetLayer = AppNodeBuilder.build(
|
||||
LayerNode.group([Database.node, EventV2.node, SessionProjector.node, SessionStore.node]),
|
||||
[[Database.node, targetDatabase]],
|
||||
)
|
||||
|
||||
yield* Effect.gen(function* () {
|
||||
const db = (yield* Database.Service).db
|
||||
@@ -298,7 +287,7 @@ describe("SessionV2.create", () => {
|
||||
[1, EventV2.versionedType(SessionEvent.PromptAdmitted.type, 1)],
|
||||
[2, EventV2.versionedType(SessionEvent.Prompted.type, 1)],
|
||||
])
|
||||
}).pipe(Effect.provide(Layer.fresh(Layer.mergeAll(targetDatabase, targetEvents, targetProjector, targetStore))))
|
||||
}).pipe(Effect.provide(Layer.fresh(targetLayer)))
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { describe, expect } from "bun:test"
|
||||
import { Effect, Layer, Schema } from "effect"
|
||||
import { Database } from "@opencode-ai/core/database/database"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { Location } from "@opencode-ai/core/location"
|
||||
import { locationServiceMapLayer } from "@opencode-ai/core/location-services"
|
||||
import { ProjectV2 } from "@opencode-ai/core/project"
|
||||
import { ProjectTable } from "@opencode-ai/core/project/sql"
|
||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||
@@ -22,24 +23,11 @@ const projects = Layer.succeed(
|
||||
commit: () => Effect.void,
|
||||
}),
|
||||
)
|
||||
const sessions = SessionV2.layer.pipe(
|
||||
Layer.provide(locationServiceMapLayer),
|
||||
Layer.provide(EventV2.defaultLayer),
|
||||
Layer.provide(Database.defaultLayer),
|
||||
Layer.provide(SessionStore.defaultLayer),
|
||||
Layer.provide(projects),
|
||||
Layer.provide(SessionExecution.noopLayer),
|
||||
)
|
||||
const it = testEffect(
|
||||
Layer.mergeAll(
|
||||
Database.defaultLayer,
|
||||
EventV2.defaultLayer,
|
||||
projects,
|
||||
SessionProjector.defaultLayer,
|
||||
SessionStore.defaultLayer,
|
||||
SessionExecution.noopLayer,
|
||||
sessions,
|
||||
),
|
||||
AppNodeBuilder.build(LayerNode.group([Database.node, EventV2.node, SessionProjector.node, SessionStore.node, SessionV2.node]), [
|
||||
[ProjectV2.node, projects],
|
||||
[SessionExecution.node, SessionExecution.noopLayer],
|
||||
]),
|
||||
)
|
||||
const location = Location.Ref.make({ directory: AbsolutePath.make("/project") })
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ import { describe, expect } from "bun:test"
|
||||
import { DateTime, Effect, Fiber, Layer, Stream } from "effect"
|
||||
import { eq } from "drizzle-orm"
|
||||
import { Database } from "@opencode-ai/core/database/database"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { EventTable } from "@opencode-ai/core/event/sql"
|
||||
import { SessionEvent } from "@opencode-ai/core/session/event"
|
||||
@@ -9,7 +11,6 @@ import { Project } from "@opencode-ai/core/project"
|
||||
import { ProjectTable } from "@opencode-ai/core/project/sql"
|
||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||
import { SessionV2 } from "@opencode-ai/core/session"
|
||||
import { locationServiceMapLayer } from "@opencode-ai/core/location-services"
|
||||
import { Prompt } from "@opencode-ai/core/session/prompt"
|
||||
import { SessionMessage } from "@opencode-ai/core/session/message"
|
||||
import { SessionProjector } from "@opencode-ai/core/session/projector"
|
||||
@@ -41,23 +42,10 @@ const execution = Layer.succeed(
|
||||
}),
|
||||
}),
|
||||
)
|
||||
const sessions = SessionV2.layer.pipe(
|
||||
Layer.provide(locationServiceMapLayer),
|
||||
Layer.provide(EventV2.defaultLayer),
|
||||
Layer.provide(Database.defaultLayer),
|
||||
Layer.provide(SessionStore.defaultLayer),
|
||||
Layer.provide(Project.defaultLayer),
|
||||
Layer.provide(execution),
|
||||
)
|
||||
const it = testEffect(
|
||||
Layer.mergeAll(
|
||||
Database.defaultLayer,
|
||||
EventV2.defaultLayer,
|
||||
SessionProjector.defaultLayer,
|
||||
SessionStore.defaultLayer,
|
||||
execution,
|
||||
sessions,
|
||||
),
|
||||
AppNodeBuilder.build(LayerNode.group([Database.node, EventV2.node, SessionProjector.node, SessionStore.node, SessionV2.node]), [
|
||||
[SessionExecution.node, execution],
|
||||
]),
|
||||
)
|
||||
const sessionID = SessionV2.ID.make("ses_prompt_test")
|
||||
const messageID = SessionMessage.ID.create()
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { describe, expect } from "bun:test"
|
||||
import { Tool } from "@opencode-ai/core/tool/tool"
|
||||
import { AgentV2 } from "@opencode-ai/core/agent"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { ApplicationTools } from "@opencode-ai/core/tool/application-tools"
|
||||
import { SessionV2 } from "@opencode-ai/core/session"
|
||||
import { SessionMessage } from "@opencode-ai/core/session/message"
|
||||
@@ -27,9 +29,11 @@ const outputStore = Layer.mock(ToolOutputStore.Service, {
|
||||
)
|
||||
},
|
||||
})
|
||||
const registry = ToolRegistry.layer.pipe(Layer.provide(ApplicationTools.layer), Layer.provide(outputStore))
|
||||
const it = testEffect(registry)
|
||||
const integrated = testEffect(Layer.mergeAll(ApplicationTools.layer, registry))
|
||||
const registryLayer = AppNodeBuilder.build(ToolRegistry.node, [[ToolOutputStore.node, outputStore]])
|
||||
const it = testEffect(registryLayer)
|
||||
const integrated = testEffect(
|
||||
AppNodeBuilder.build(LayerNode.group([ApplicationTools.node, ToolRegistry.node]), [[ToolOutputStore.node, outputStore]]),
|
||||
)
|
||||
const identity = {
|
||||
agent: AgentV2.ID.make("build"),
|
||||
assistantMessageID: SessionMessage.ID.make("msg_registry"),
|
||||
|
||||
@@ -2,6 +2,7 @@ import path from "path"
|
||||
import { describe, expect } from "bun:test"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { AgentV2 } from "@opencode-ai/core/agent"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||
import { SkillV2 } from "@opencode-ai/core/skill"
|
||||
import { SystemContext } from "@opencode-ai/core/system-context"
|
||||
@@ -28,7 +29,9 @@ const denied = SkillV2.Info.make({
|
||||
})
|
||||
|
||||
const layer = (list: () => SkillV2.Info[]) =>
|
||||
SkillGuidance.layer.pipe(Layer.provide(Layer.mock(SkillV2.Service, { list: () => Effect.succeed(list()) })))
|
||||
AppNodeBuilder.build(SkillGuidance.node, [
|
||||
[SkillV2.node, Layer.mock(SkillV2.Service, { list: () => Effect.succeed(list()) })],
|
||||
])
|
||||
|
||||
describe("SkillGuidance", () => {
|
||||
it.effect("renders described agent skills and reconciles the complete available list", () => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect } from "bun:test"
|
||||
import { Cause, Effect, Exit, Schema, Scope } from "effect"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { SystemContext } from "@opencode-ai/core/system-context"
|
||||
import { SystemContextRegistry } from "@opencode-ai/core/system-context/registry"
|
||||
import { testEffect } from "../lib/effect"
|
||||
@@ -17,7 +18,7 @@ const entry = (key: string, text: string, sourceKey = key) => ({
|
||||
),
|
||||
})
|
||||
|
||||
const it = testEffect(SystemContextRegistry.layer)
|
||||
const it = testEffect(AppNodeBuilder.build(SystemContextRegistry.node))
|
||||
|
||||
describe("SystemContextRegistry", () => {
|
||||
it.effect("loads empty system context when there are no entries", () =>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect } from "bun:test"
|
||||
import { Effect, Exit, Fiber, Layer } from "effect"
|
||||
import { makeLocationNode } from "@opencode-ai/core/effect/app-node"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { PermissionV2 } from "@opencode-ai/core/permission"
|
||||
@@ -31,9 +32,6 @@ const permission = Layer.succeed(
|
||||
list: () => Effect.die("unused"),
|
||||
}),
|
||||
)
|
||||
const registry = AppNodeBuilder.build(LayerNode.group([ToolRegistry.node, ToolRegistry.toolsNode]), [
|
||||
[ToolOutputStore.node, ToolOutputStore.nodeWithoutConfig],
|
||||
])
|
||||
const question = Layer.succeed(
|
||||
QuestionV2.Service,
|
||||
QuestionV2.Service.of({
|
||||
@@ -46,8 +44,18 @@ const question = Layer.succeed(
|
||||
list: () => Effect.die("unused"),
|
||||
}),
|
||||
)
|
||||
const tool = QuestionTool.layer.pipe(Layer.provide(registry), Layer.provide(permission), Layer.provide(question))
|
||||
const it = testEffect(Layer.mergeAll(permission, registry, question, tool))
|
||||
const toolNode = makeLocationNode({
|
||||
name: "test/question-tool",
|
||||
layer: QuestionTool.layer,
|
||||
deps: [ToolRegistry.node, PermissionV2.node, QuestionV2.node],
|
||||
})
|
||||
const it = testEffect(
|
||||
AppNodeBuilder.build(LayerNode.group([ToolRegistry.node, ToolRegistry.toolsNode, toolNode]), [
|
||||
[PermissionV2.node, permission],
|
||||
[QuestionV2.node, question],
|
||||
[ToolOutputStore.node, ToolOutputStore.nodeWithoutConfig],
|
||||
]),
|
||||
)
|
||||
|
||||
describe("QuestionTool", () => {
|
||||
it.effect("omits a denied built-in question and terminally settles a stale call", () =>
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { describe, expect } from "bun:test"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { Database } from "@opencode-ai/core/database/database"
|
||||
import { makeLocationNode } from "@opencode-ai/core/effect/app-node"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { PermissionV2 } from "@opencode-ai/core/permission"
|
||||
import { Project } from "@opencode-ai/core/project"
|
||||
@@ -11,6 +14,7 @@ import { SessionTable } from "@opencode-ai/core/session/sql"
|
||||
import { SessionTodo } from "@opencode-ai/core/session/todo"
|
||||
import { TodoWriteTool } from "@opencode-ai/core/tool/todowrite"
|
||||
import { ToolRegistry } from "@opencode-ai/core/tool/registry"
|
||||
import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
|
||||
import { testEffect } from "./lib/effect"
|
||||
import { toolIdentity, executeTool, settleTool, toolDefinitions } from "./lib/tool"
|
||||
|
||||
@@ -32,14 +36,19 @@ const permission = Layer.succeed(
|
||||
list: () => Effect.die("unused"),
|
||||
}),
|
||||
)
|
||||
const registry = ToolRegistry.defaultLayer.pipe(Layer.provide(permission))
|
||||
const tool = TodoWriteTool.layer.pipe(
|
||||
Layer.provide(registry),
|
||||
Layer.provide(permission),
|
||||
Layer.provide(SessionTodo.defaultLayer),
|
||||
)
|
||||
const toolNode = makeLocationNode({
|
||||
name: "test/todowrite-tool",
|
||||
layer: TodoWriteTool.layer,
|
||||
deps: [ToolRegistry.node, PermissionV2.node, SessionTodo.node],
|
||||
})
|
||||
const it = testEffect(
|
||||
Layer.mergeAll(Database.defaultLayer, EventV2.defaultLayer, SessionTodo.defaultLayer, permission, registry, tool),
|
||||
AppNodeBuilder.build(
|
||||
LayerNode.group([Database.node, EventV2.node, SessionTodo.node, ToolRegistry.node, ToolRegistry.toolsNode, toolNode]),
|
||||
[
|
||||
[PermissionV2.node, permission],
|
||||
[ToolOutputStore.node, ToolOutputStore.nodeWithoutConfig],
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
const setup = Effect.gen(function* () {
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { Duration, Effect, Fiber, Layer, Schema } from "effect"
|
||||
import * as TestClock from "effect/testing/TestClock"
|
||||
import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
|
||||
import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
|
||||
import { makeLocationNode } from "@opencode-ai/core/effect/app-node"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { LayerNodePlatform } from "@opencode-ai/core/effect/app-node-platform"
|
||||
import { PermissionV2 } from "@opencode-ai/core/permission"
|
||||
import { SessionV2 } from "@opencode-ai/core/session"
|
||||
import { ToolRegistry } from "@opencode-ai/core/tool/registry"
|
||||
import { WebFetchTool } from "@opencode-ai/core/tool/webfetch"
|
||||
import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
|
||||
import { testEffect } from "./lib/effect"
|
||||
import { toolIdentity, executeTool, settleTool, toolDefinitions } from "./lib/tool"
|
||||
|
||||
@@ -35,15 +40,19 @@ const permission = Layer.succeed(
|
||||
list: () => Effect.die("unused"),
|
||||
}),
|
||||
)
|
||||
const registry = ToolRegistry.defaultLayer.pipe(Layer.provide(permission))
|
||||
const webfetch = WebFetchTool.layer.pipe(Layer.provide(registry), Layer.provide(permission), Layer.provide(http))
|
||||
const it = testEffect(Layer.mergeAll(registry, permission, http, webfetch))
|
||||
const fetchWebfetch = WebFetchTool.layer.pipe(
|
||||
Layer.provide(registry),
|
||||
Layer.provide(permission),
|
||||
Layer.provide(FetchHttpClient.layer),
|
||||
)
|
||||
const live = testEffect(Layer.mergeAll(registry, permission, FetchHttpClient.layer, fetchWebfetch))
|
||||
const toolNode = makeLocationNode({
|
||||
name: "test/webfetch-tool",
|
||||
layer: WebFetchTool.layer,
|
||||
deps: [ToolRegistry.node, PermissionV2.node, LayerNodePlatform.httpClient],
|
||||
})
|
||||
const toolLayer = (replacements: LayerNode.Replacements = []) =>
|
||||
AppNodeBuilder.build(LayerNode.group([ToolRegistry.node, ToolRegistry.toolsNode, toolNode]), [
|
||||
[PermissionV2.node, permission],
|
||||
[ToolOutputStore.node, ToolOutputStore.nodeWithoutConfig],
|
||||
...replacements,
|
||||
])
|
||||
const it = testEffect(toolLayer([[LayerNodePlatform.httpClient, http]]))
|
||||
const live = testEffect(toolLayer())
|
||||
|
||||
const reset = () => {
|
||||
requests.length = 0
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import { beforeEach, describe, expect, test } from "bun:test"
|
||||
import { Effect, Layer, Schema } from "effect"
|
||||
import { HttpClient, HttpClientResponse } from "effect/unstable/http"
|
||||
import { makeLocationNode } from "@opencode-ai/core/effect/app-node"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { LayerNodePlatform } from "@opencode-ai/core/effect/app-node-platform"
|
||||
import { PermissionV2 } from "@opencode-ai/core/permission"
|
||||
import { SessionV2 } from "@opencode-ai/core/session"
|
||||
import { ToolRegistry } from "@opencode-ai/core/tool/registry"
|
||||
import { WebSearchTool } from "@opencode-ai/core/tool/websearch"
|
||||
import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
|
||||
import { testEffect } from "./lib/effect"
|
||||
import { toolIdentity, executeTool, settleTool, toolDefinitions } from "./lib/tool"
|
||||
|
||||
@@ -99,7 +104,6 @@ const permission = Layer.succeed(
|
||||
list: () => Effect.die("unused"),
|
||||
}),
|
||||
)
|
||||
const registry = ToolRegistry.defaultLayer.pipe(Layer.provide(permission))
|
||||
const websearchConfig = Layer.succeed(
|
||||
WebSearchTool.ConfigService,
|
||||
WebSearchTool.ConfigService.of({
|
||||
@@ -120,13 +124,23 @@ const websearchConfig = Layer.succeed(
|
||||
},
|
||||
}),
|
||||
)
|
||||
const websearch = WebSearchTool.layer.pipe(
|
||||
Layer.provide(registry),
|
||||
Layer.provide(permission),
|
||||
Layer.provide(http),
|
||||
Layer.provide(websearchConfig),
|
||||
const websearchConfigNode = makeLocationNode({
|
||||
service: WebSearchTool.ConfigService,
|
||||
layer: websearchConfig,
|
||||
deps: [],
|
||||
})
|
||||
const toolNode = makeLocationNode({
|
||||
name: "test/websearch-tool",
|
||||
layer: WebSearchTool.layer,
|
||||
deps: [ToolRegistry.node, PermissionV2.node, LayerNodePlatform.httpClient, websearchConfigNode],
|
||||
})
|
||||
const it = testEffect(
|
||||
AppNodeBuilder.build(LayerNode.group([ToolRegistry.node, ToolRegistry.toolsNode, websearchConfigNode, toolNode]), [
|
||||
[PermissionV2.node, permission],
|
||||
[LayerNodePlatform.httpClient, http],
|
||||
[ToolOutputStore.node, ToolOutputStore.nodeWithoutConfig],
|
||||
]),
|
||||
)
|
||||
const it = testEffect(Layer.mergeAll(registry, permission, http, websearchConfig, websearch))
|
||||
|
||||
describe("WebSearchTool registration", () => {
|
||||
it.effect("registers websearch, asserts query permission, and calls Exa", () =>
|
||||
|
||||
Reference in New Issue
Block a user