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