refactor(core): move v1 schemas into core (#30473)

This commit is contained in:
Dax
2026-06-02 22:42:13 -04:00
committed by GitHub
parent 0543fd29c8
commit 83452558f7
129 changed files with 1578 additions and 1227 deletions

View File

@@ -9,7 +9,7 @@ import { Config } from "../../src/config/config"
import { RuntimeFlags } from "../../src/effect/runtime-flags"
import { Global } from "@opencode-ai/core/global"
import { Permission } from "../../src/permission"
import { PermissionLegacy } from "@opencode-ai/core/permission/legacy"
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
import { Plugin } from "../../src/plugin"
import { Provider } from "../../src/provider/provider"
import { Skill } from "../../src/skill"
@@ -28,7 +28,7 @@ const agentLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
const it = testEffect(agentLayer())
// Helper to evaluate permission for a tool with wildcard pattern
function evalPerm(agent: Agent.Info | undefined, permission: string): PermissionLegacy.Action | undefined {
function evalPerm(agent: Agent.Info | undefined, permission: string): PermissionV1.Action | undefined {
if (!agent) return undefined
return Permission.evaluate(permission, "*", agent.permission).action
}

View File

@@ -1,4 +1,4 @@
import { PermissionLegacy } from "@opencode-ai/core/permission/legacy"
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
/**
* Reproducer for opencode issue #26514:
*
@@ -61,7 +61,7 @@ it.instance("[#26514] subagent spawned from plan mode inherits read-only restric
// session's `permission` field is empty (Plan Mode lives on the agent
// ruleset, not the session). So we pass [] through as the parent
// session permission, exactly like the actual code path.
const parentSessionPermission: PermissionLegacy.Ruleset = []
const parentSessionPermission: PermissionV1.Ruleset = []
const subagentSessionPermission = deriveSubagentSessionPermission({
parentSessionPermission,
@@ -89,7 +89,7 @@ it.instance("[#26514] explore subagent launched from plan mode also stays read-o
expect(planAgent).toBeDefined()
expect(explore).toBeDefined()
const parentSessionPermission: PermissionLegacy.Ruleset = []
const parentSessionPermission: PermissionV1.Ruleset = []
const subagentSessionPermission = deriveSubagentSessionPermission({
parentSessionPermission,
parentAgent: planAgent,
@@ -114,7 +114,7 @@ it.instance(
expect(planAgent).toBeDefined()
expect(my).toBeDefined()
const parentSessionPermission: PermissionLegacy.Ruleset = []
const parentSessionPermission: PermissionV1.Ruleset = []
const subagentSessionPermission = deriveSubagentSessionPermission({
parentSessionPermission,
parentAgent: planAgent,

View File

@@ -5,7 +5,7 @@
*/
import { describe, expect, test } from "bun:test"
import { aggregateFailures } from "@/cli/cmd/tui/context/aggregate-failures"
import { ConfigError } from "@/config/error"
import { ConfigErrorV1 } from "@opencode-ai/core/v1/config/error"
describe("aggregateFailures", () => {
test("returns null when every result is fulfilled", () => {
@@ -43,7 +43,7 @@ describe("aggregateFailures", () => {
})
test("formats structured config errors hidden inside SDK error causes", () => {
const configError = new ConfigError.InvalidError({
const configError = new ConfigErrorV1.InvalidError({
path: "/tmp/opencode.json",
issues: [{ message: "Expected object", path: ["provider", "anthropic", "options"] }],
})

View File

@@ -1,11 +1,11 @@
import { test, expect, describe } from "bun:test"
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
import { SessionV1 } from "@opencode-ai/core/v1/session"
import { extractResponseText, formatPromptTooLargeError } from "../../src/cli/cmd/github"
import type { MessageV2 } from "../../src/session/message-v2"
import { SessionID, MessageID, PartID } from "../../src/session/schema"
// Helper to create minimal valid parts
function createTextPart(text: string): SessionLegacy.Part {
function createTextPart(text: string): SessionV1.Part {
return {
id: PartID.ascending(),
sessionID: SessionID.make("ses_test"),
@@ -15,7 +15,7 @@ function createTextPart(text: string): SessionLegacy.Part {
}
}
function createReasoningPart(text: string): SessionLegacy.Part {
function createReasoningPart(text: string): SessionV1.Part {
return {
id: PartID.ascending(),
sessionID: SessionID.make("ses_test"),
@@ -30,7 +30,7 @@ function createToolPart(
tool: string,
title: string,
status: "completed" | "running" = "completed",
): SessionLegacy.Part {
): SessionV1.Part {
if (status === "completed") {
return {
id: PartID.ascending(),
@@ -64,7 +64,7 @@ function createToolPart(
}
}
function createStepStartPart(): SessionLegacy.Part {
function createStepStartPart(): SessionV1.Part {
return {
id: PartID.ascending(),
sessionID: SessionID.make("ses_test"),
@@ -73,7 +73,7 @@ function createStepStartPart(): SessionLegacy.Part {
}
}
function createStepFinishPart(): SessionLegacy.Part {
function createStepFinishPart(): SessionV1.Part {
return {
id: PartID.ascending(),
sessionID: SessionID.make("ses_test"),

View File

@@ -1,4 +1,5 @@
import { test, expect, describe, afterEach, beforeEach, spyOn } from "bun:test"
import { ConfigV1 } from "@opencode-ai/core/v1/config/config"
import { Effect, Exit, Layer, Option } from "effect"
import { FetchHttpClient, HttpClient, HttpClientResponse } from "effect/unstable/http"
import { NodeFileSystem, NodePath } from "@effect/platform-node"
@@ -34,6 +35,7 @@ import { Global } from "@opencode-ai/core/global"
import { ProjectV2 } from "@opencode-ai/core/project"
import { Filesystem } from "@/util/filesystem"
import { ConfigPlugin } from "@/config/plugin"
import { ConfigPluginV1 } from "@opencode-ai/core/v1/config/plugin"
import { AccountTest } from "../fake/account"
import { AuthTest } from "../fake/auth"
import { NpmTest } from "../fake/npm"
@@ -360,7 +362,7 @@ it.instance("updates config and preserves empty shell sentinel", () =>
"config.json",
)
yield* Config.Service.use((svc) => svc.update(ConfigParse.schema(Config.Info, { shell: "" }, "test:config")))
yield* Config.Service.use((svc) => svc.update(ConfigParse.schema(ConfigV1.Info, { shell: "" }, "test:config")))
const writtenConfig = yield* FSUtil.use.readJson(path.join(test.directory, "config.json"))
expect(writtenConfig).toMatchObject({ shell: "" })
@@ -385,7 +387,7 @@ it.effect("updates global config and omits empty shell key in jsonc", () =>
const file = path.join(dir, "opencode.jsonc")
const writtenConfig = yield* FSUtil.use.readFileString(file)
const parsed = ConfigParse.schema(Config.Info, ConfigParse.jsonc(writtenConfig, file), file)
const parsed = ConfigParse.schema(ConfigV1.Info, ConfigParse.jsonc(writtenConfig, file), file)
expect(writtenConfig).not.toContain('"shell"')
expect(parsed.shell).toBeUndefined()
expect(parsed.model).toBe("test/model")
@@ -870,7 +872,7 @@ it.instance("updates config and writes to file", () =>
Effect.gen(function* () {
const test = yield* TestInstance
yield* Config.Service.use((svc) =>
svc.update(ConfigParse.schema(Config.Info, { model: "updated/model" }, "test:config")),
svc.update(ConfigParse.schema(ConfigV1.Info, { model: "updated/model" }, "test:config")),
)
const writtenConfig = yield* FSUtil.use.readJson(path.join(test.directory, "config.json"))
@@ -1284,7 +1286,7 @@ it.instance("permission config preserves user key order", () =>
test("config parser preserves permission order while rejecting unknown top-level keys", () => {
const config = ConfigParse.schema(
Config.Info,
ConfigV1.Info,
{
permission: {
bash: "allow",
@@ -1297,7 +1299,7 @@ test("config parser preserves permission order while rejecting unknown top-level
expect(Object.keys(config.permission!)).toEqual(["bash", "*", "edit"])
try {
ConfigParse.schema(Config.Info, { invalid_field: true }, "test")
ConfigParse.schema(ConfigV1.Info, { invalid_field: true }, "test")
throw new Error("expected config parse to fail")
} catch (err) {
const error = err as { data?: { issues?: Array<{ code?: string; keys?: string[]; path?: string[] }> } }
@@ -1684,7 +1686,7 @@ describe("resolvePluginSpec", () => {
})
describe("deduplicatePluginOrigins", () => {
const dedupe = (plugins: ConfigPlugin.Spec[]) =>
const dedupe = (plugins: ConfigPluginV1.Spec[]) =>
ConfigPlugin.deduplicatePluginOrigins(
plugins.map((spec) => ({
spec,
@@ -1883,7 +1885,7 @@ describe("OPENCODE_CONFIG_CONTENT token substitution", () => {
test("parseManagedPlist strips MDM metadata keys", async () => {
const config = ConfigParse.schema(
Config.Info,
ConfigV1.Info,
ConfigParse.jsonc(
await ConfigManaged.parseManagedPlist(
JSON.stringify({
@@ -1911,7 +1913,7 @@ test("parseManagedPlist strips MDM metadata keys", async () => {
test("parseManagedPlist parses server settings", async () => {
const config = ConfigParse.schema(
Config.Info,
ConfigV1.Info,
ConfigParse.jsonc(
await ConfigManaged.parseManagedPlist(
JSON.stringify({
@@ -1931,7 +1933,7 @@ test("parseManagedPlist parses server settings", async () => {
test("parseManagedPlist parses permission rules", async () => {
const config = ConfigParse.schema(
Config.Info,
ConfigV1.Info,
ConfigParse.jsonc(
await ConfigManaged.parseManagedPlist(
JSON.stringify({
@@ -1961,7 +1963,7 @@ test("parseManagedPlist parses permission rules", async () => {
test("parseManagedPlist parses enabled_providers", async () => {
const config = ConfigParse.schema(
Config.Info,
ConfigV1.Info,
ConfigParse.jsonc(
await ConfigManaged.parseManagedPlist(
JSON.stringify({
@@ -1978,7 +1980,7 @@ test("parseManagedPlist parses enabled_providers", async () => {
test("parseManagedPlist handles empty config", async () => {
const config = ConfigParse.schema(
Config.Info,
ConfigV1.Info,
ConfigParse.jsonc(
await ConfigManaged.parseManagedPlist(JSON.stringify({ $schema: "https://opencode.ai/config.json" })),
"test:mobileconfig",

View File

@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test"
import { Schema } from "effect"
import { ConfigLSP } from "../../src/config/lsp"
import { ConfigLSPV1 } from "@opencode-ai/core/v1/config/lsp"
// The LSP config refinement enforces: any custom (non-builtin) LSP server
// entry must declare an `extensions` array so the client knows which files
@@ -8,8 +8,8 @@ import { ConfigLSP } from "../../src/config/lsp"
// entries are exempt.
//
// `typescript` is a builtin server id (see src/lsp/server.ts).
describe("ConfigLSP.Info refinement", () => {
const decodeEffect = Schema.decodeUnknownSync(ConfigLSP.Info)
describe("ConfigLSPV1.Info refinement", () => {
const decodeEffect = Schema.decodeUnknownSync(ConfigLSPV1.Info)
describe("accepted inputs", () => {
test("true and false pass (top-level toggle)", () => {

View File

@@ -1,5 +1,5 @@
import { Config } from "@/config/config"
import { emptyConsoleState } from "@/config/console-state"
import { emptyConsoleState } from "@opencode-ai/core/v1/config/console-state"
import { Effect, Layer } from "effect"
export function make(overrides: Partial<Config.Interface> = {}) {

View File

@@ -1,4 +1,5 @@
import { $ } from "bun"
import { ConfigV1 } from "@opencode-ai/core/v1/config/config"
import * as fs from "fs/promises"
import os from "os"
import path from "path"
@@ -72,7 +73,7 @@ async function stop(dir: string) {
type TmpDirOptions<T> = {
git?: boolean
config?: Partial<Config.Info>
config?: Partial<ConfigV1.Info>
init?: (dir: string) => Promise<T>
dispose?: (dir: string) => Promise<T>
}
@@ -116,7 +117,7 @@ export async function tmpdir<T>(options?: TmpDirOptions<T>) {
/** Effectful scoped tmpdir. Cleaned up when the scope closes. Make sure these stay in sync */
export function tmpdirScoped<E = never, R = never>(options?: {
git?: boolean
config?: Partial<Config.Info> | (() => Partial<Config.Info>)
config?: Partial<ConfigV1.Info> | (() => Partial<ConfigV1.Info>)
init?: (directory: string) => Effect.Effect<void, E, R>
}) {
return Effect.gen(function* () {
@@ -177,7 +178,7 @@ export const disposeAllInstancesEffect = InstanceStore.Service.use((store) => st
export function provideTmpdirInstance<A, E, R>(
self: (path: string) => Effect.Effect<A, E, R>,
options?: { git?: boolean; config?: Partial<Config.Info> | (() => Partial<Config.Info>) },
options?: { git?: boolean; config?: Partial<ConfigV1.Info> | (() => Partial<ConfigV1.Info>) },
) {
return Effect.gen(function* () {
const path = yield* tmpdirScoped(options)
@@ -196,7 +197,7 @@ export const requireInstance = Effect.gen(function* () {
export const withTmpdirInstance =
<E2 = never, R2 = never>(options?: {
git?: boolean
config?: Partial<Config.Info> | (() => Partial<Config.Info>)
config?: Partial<ConfigV1.Info> | (() => Partial<ConfigV1.Info>)
init?: (directory: string) => Effect.Effect<void, E2, R2>
}) =>
<A, E, R>(self: Effect.Effect<A, E, R>) =>
@@ -207,7 +208,7 @@ export const withTmpdirInstance =
export function provideTmpdirServer<A, E, R>(
self: (input: { dir: string; llm: TestLLMServer["Service"] }) => Effect.Effect<A, E, R>,
options?: { git?: boolean; config?: (url: string) => Partial<Config.Info> },
options?: { git?: boolean; config?: (url: string) => Partial<ConfigV1.Info> },
): Effect.Effect<
A,
E | PlatformError.PlatformError,

View File

@@ -1,4 +1,5 @@
import { test, type TestOptions } from "bun:test"
import { ConfigV1 } from "@opencode-ai/core/v1/config/config"
import { Cause, Duration, Effect, Exit, Layer } from "effect"
import * as Scope from "effect/Scope"
import * as TestClock from "effect/testing/TestClock"
@@ -11,7 +12,7 @@ import { InstanceStore } from "@/project/instance-store"
type Body<A, E, R> = Effect.Effect<A, E, R> | (() => Effect.Effect<A, E, R>)
type InstanceOptions<E, R> = {
git?: boolean
config?: Partial<Config.Info> | (() => Partial<Config.Info>)
config?: Partial<ConfigV1.Info> | (() => Partial<ConfigV1.Info>)
init?: (directory: string) => Effect.Effect<void, E, R>
}

View File

@@ -1,4 +1,4 @@
import { PermissionLegacy } from "@opencode-ai/core/permission/legacy"
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
import { describe, test, expect } from "bun:test"
import { Effect } from "effect"
import { Permission } from "../src/permission"
@@ -10,7 +10,7 @@ const it = testEffect(Config.defaultLayer)
const load = Config.use.get()
describe("Permission.evaluate for permission.task", () => {
const createRuleset = (rules: Record<string, "allow" | "deny" | "ask">): PermissionLegacy.Ruleset =>
const createRuleset = (rules: Record<string, "allow" | "deny" | "ask">): PermissionV1.Ruleset =>
Object.entries(rules).map(([pattern, action]) => ({
permission: "task",
pattern,
@@ -76,7 +76,7 @@ describe("Permission.disabled for task tool", () => {
// Note: The `disabled` function checks if a TOOL should be completely removed from the tool list.
// It only disables a tool when there's a rule with `pattern: "*"` and `action: "deny"`.
// It does NOT evaluate complex subagent patterns - those are handled at runtime by `evaluate`.
const createRuleset = (rules: Record<string, "allow" | "deny" | "ask">): PermissionLegacy.Ruleset =>
const createRuleset = (rules: Record<string, "allow" | "deny" | "ask">): PermissionV1.Ruleset =>
Object.entries(rules).map(([pattern, action]) => ({
permission: "task",
pattern,

View File

@@ -1,4 +1,4 @@
import { PermissionLegacy } from "@opencode-ai/core/permission/legacy"
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
import { test, expect } from "bun:test"
import os from "os"
import { Cause, Deferred, Effect, Exit, Fiber, Layer } from "effect"
@@ -261,8 +261,8 @@ test("merge - preserves rule order", () => {
})
test("merge - config permission overrides default ask", () => {
const defaults: PermissionLegacy.Ruleset = [{ permission: "*", pattern: "*", action: "ask" }]
const config: PermissionLegacy.Ruleset = [{ permission: "bash", pattern: "*", action: "allow" }]
const defaults: PermissionV1.Ruleset = [{ permission: "*", pattern: "*", action: "ask" }]
const config: PermissionV1.Ruleset = [{ permission: "bash", pattern: "*", action: "allow" }]
const merged = Permission.merge(defaults, config)
expect(Permission.evaluate("bash", "ls", merged).action).toBe("allow")
@@ -270,8 +270,8 @@ test("merge - config permission overrides default ask", () => {
})
test("merge - config ask overrides default allow", () => {
const defaults: PermissionLegacy.Ruleset = [{ permission: "bash", pattern: "*", action: "allow" }]
const config: PermissionLegacy.Ruleset = [{ permission: "bash", pattern: "*", action: "ask" }]
const defaults: PermissionV1.Ruleset = [{ permission: "bash", pattern: "*", action: "allow" }]
const config: PermissionV1.Ruleset = [{ permission: "bash", pattern: "*", action: "ask" }]
const merged = Permission.merge(defaults, config)
expect(Permission.evaluate("bash", "ls", merged).action).toBe("ask")
@@ -443,8 +443,8 @@ test("evaluate - later wildcard permission can override earlier specific permiss
})
test("evaluate - merges multiple rulesets", () => {
const config: PermissionLegacy.Ruleset = [{ permission: "bash", pattern: "*", action: "allow" }]
const approved: PermissionLegacy.Ruleset = [{ permission: "bash", pattern: "rm", action: "deny" }]
const config: PermissionV1.Ruleset = [{ permission: "bash", pattern: "*", action: "allow" }]
const approved: PermissionV1.Ruleset = [{ permission: "bash", pattern: "rm", action: "deny" }]
const result = Permission.evaluate("bash", "rm", config, approved)
expect(result.action).toBe("deny")
})
@@ -588,7 +588,7 @@ it.instance(
ruleset: [{ permission: "bash", pattern: "*", action: "deny" }],
}),
)
expect(err).toBeInstanceOf(PermissionLegacy.DeniedError)
expect(err).toBeInstanceOf(PermissionV1.DeniedError)
}),
{ git: true },
)
@@ -655,10 +655,10 @@ it.instance(
() =>
Effect.gen(function* () {
const events = yield* EventV2Bridge.Service
const seen = yield* Deferred.make<PermissionLegacy.Request>()
const seen = yield* Deferred.make<PermissionV1.Request>()
const unsub = yield* events.listen((event) => {
if (event.type === Permission.Event.Asked.type)
Deferred.doneUnsafe(seen, Effect.succeed(event.data as PermissionLegacy.Request))
Deferred.doneUnsafe(seen, Effect.succeed(event.data as PermissionV1.Request))
return Effect.void
})
yield* Effect.addFinalizer(() => unsub)
@@ -703,7 +703,7 @@ it.instance(
() =>
Effect.gen(function* () {
const fiber = yield* ask({
id: PermissionLegacy.ID.make("per_test1"),
id: PermissionV1.ID.make("per_test1"),
sessionID: SessionID.make("session_test"),
permission: "bash",
patterns: ["ls"],
@@ -713,7 +713,7 @@ it.instance(
}).pipe(Effect.forkScoped)
yield* waitForPending(1)
yield* reply({ requestID: PermissionLegacy.ID.make("per_test1"), reply: "once" })
yield* reply({ requestID: PermissionV1.ID.make("per_test1"), reply: "once" })
yield* Fiber.join(fiber)
}),
{ git: true },
@@ -724,7 +724,7 @@ it.instance(
() =>
Effect.gen(function* () {
const fiber = yield* ask({
id: PermissionLegacy.ID.make("per_test2"),
id: PermissionV1.ID.make("per_test2"),
sessionID: SessionID.make("session_test"),
permission: "bash",
patterns: ["ls"],
@@ -734,11 +734,11 @@ it.instance(
}).pipe(Effect.forkScoped)
yield* waitForPending(1)
yield* reply({ requestID: PermissionLegacy.ID.make("per_test2"), reply: "reject" })
yield* reply({ requestID: PermissionV1.ID.make("per_test2"), reply: "reject" })
const exit = yield* Fiber.await(fiber)
expect(Exit.isFailure(exit)).toBe(true)
if (Exit.isFailure(exit)) expect(Cause.squash(exit.cause)).toBeInstanceOf(PermissionLegacy.RejectedError)
if (Exit.isFailure(exit)) expect(Cause.squash(exit.cause)).toBeInstanceOf(PermissionV1.RejectedError)
}),
{ git: true },
)
@@ -748,7 +748,7 @@ it.instance(
() =>
Effect.gen(function* () {
const fiber = yield* ask({
id: PermissionLegacy.ID.make("per_test2b"),
id: PermissionV1.ID.make("per_test2b"),
sessionID: SessionID.make("session_test"),
permission: "bash",
patterns: ["ls"],
@@ -759,7 +759,7 @@ it.instance(
yield* waitForPending(1)
yield* reply({
requestID: PermissionLegacy.ID.make("per_test2b"),
requestID: PermissionV1.ID.make("per_test2b"),
reply: "reject",
message: "Use a safer command",
})
@@ -768,7 +768,7 @@ it.instance(
expect(Exit.isFailure(exit)).toBe(true)
if (Exit.isFailure(exit)) {
const err = Cause.squash(exit.cause)
expect(err).toBeInstanceOf(PermissionLegacy.CorrectedError)
expect(err).toBeInstanceOf(PermissionV1.CorrectedError)
expect(String(err)).toContain("Use a safer command")
}
}),
@@ -780,7 +780,7 @@ it.instance(
() =>
Effect.gen(function* () {
const fiber = yield* ask({
id: PermissionLegacy.ID.make("per_test3"),
id: PermissionV1.ID.make("per_test3"),
sessionID: SessionID.make("session_test"),
permission: "bash",
patterns: ["ls"],
@@ -790,7 +790,7 @@ it.instance(
}).pipe(Effect.forkScoped)
yield* waitForPending(1)
yield* reply({ requestID: PermissionLegacy.ID.make("per_test3"), reply: "always" })
yield* reply({ requestID: PermissionV1.ID.make("per_test3"), reply: "always" })
yield* Fiber.join(fiber)
const result = yield* ask({
@@ -811,7 +811,7 @@ it.instance(
() =>
Effect.gen(function* () {
const a = yield* ask({
id: PermissionLegacy.ID.make("per_test4a"),
id: PermissionV1.ID.make("per_test4a"),
sessionID: SessionID.make("session_same"),
permission: "bash",
patterns: ["ls"],
@@ -821,7 +821,7 @@ it.instance(
}).pipe(Effect.forkScoped)
const b = yield* ask({
id: PermissionLegacy.ID.make("per_test4b"),
id: PermissionV1.ID.make("per_test4b"),
sessionID: SessionID.make("session_same"),
permission: "edit",
patterns: ["foo.ts"],
@@ -831,13 +831,13 @@ it.instance(
}).pipe(Effect.forkScoped)
yield* waitForPending(2)
yield* reply({ requestID: PermissionLegacy.ID.make("per_test4a"), reply: "reject" })
yield* reply({ requestID: PermissionV1.ID.make("per_test4a"), reply: "reject" })
const [ea, eb] = yield* Effect.all([Fiber.await(a), Fiber.await(b)])
expect(Exit.isFailure(ea)).toBe(true)
expect(Exit.isFailure(eb)).toBe(true)
if (Exit.isFailure(ea)) expect(Cause.squash(ea.cause)).toBeInstanceOf(PermissionLegacy.RejectedError)
if (Exit.isFailure(eb)) expect(Cause.squash(eb.cause)).toBeInstanceOf(PermissionLegacy.RejectedError)
if (Exit.isFailure(ea)) expect(Cause.squash(ea.cause)).toBeInstanceOf(PermissionV1.RejectedError)
if (Exit.isFailure(eb)) expect(Cause.squash(eb.cause)).toBeInstanceOf(PermissionV1.RejectedError)
}),
{ git: true },
)
@@ -847,7 +847,7 @@ it.instance(
() =>
Effect.gen(function* () {
const a = yield* ask({
id: PermissionLegacy.ID.make("per_test5a"),
id: PermissionV1.ID.make("per_test5a"),
sessionID: SessionID.make("session_same"),
permission: "bash",
patterns: ["ls"],
@@ -857,7 +857,7 @@ it.instance(
}).pipe(Effect.forkScoped)
const b = yield* ask({
id: PermissionLegacy.ID.make("per_test5b"),
id: PermissionV1.ID.make("per_test5b"),
sessionID: SessionID.make("session_same"),
permission: "bash",
patterns: ["ls"],
@@ -867,7 +867,7 @@ it.instance(
}).pipe(Effect.forkScoped)
yield* waitForPending(2)
yield* reply({ requestID: PermissionLegacy.ID.make("per_test5a"), reply: "always" })
yield* reply({ requestID: PermissionV1.ID.make("per_test5a"), reply: "always" })
yield* Fiber.join(a)
yield* Fiber.join(b)
@@ -881,7 +881,7 @@ it.instance(
() =>
Effect.gen(function* () {
const a = yield* ask({
id: PermissionLegacy.ID.make("per_test6a"),
id: PermissionV1.ID.make("per_test6a"),
sessionID: SessionID.make("session_a"),
permission: "bash",
patterns: ["ls"],
@@ -891,7 +891,7 @@ it.instance(
}).pipe(Effect.forkScoped)
const b = yield* ask({
id: PermissionLegacy.ID.make("per_test6b"),
id: PermissionV1.ID.make("per_test6b"),
sessionID: SessionID.make("session_b"),
permission: "bash",
patterns: ["ls"],
@@ -901,10 +901,10 @@ it.instance(
}).pipe(Effect.forkScoped)
yield* waitForPending(2)
yield* reply({ requestID: PermissionLegacy.ID.make("per_test6a"), reply: "always" })
yield* reply({ requestID: PermissionV1.ID.make("per_test6a"), reply: "always" })
yield* Fiber.join(a)
expect((yield* list()).map((item) => item.id)).toEqual([PermissionLegacy.ID.make("per_test6b")])
expect((yield* list()).map((item) => item.id)).toEqual([PermissionV1.ID.make("per_test6b")])
yield* rejectAll()
yield* Fiber.await(b)
@@ -919,12 +919,12 @@ it.instance(
const events = yield* EventV2Bridge.Service
const seen = yield* Deferred.make<{
sessionID: SessionID
requestID: PermissionLegacy.ID
reply: PermissionLegacy.Reply
requestID: PermissionV1.ID
reply: PermissionV1.Reply
}>()
const fiber = yield* ask({
id: PermissionLegacy.ID.make("per_test7"),
id: PermissionV1.ID.make("per_test7"),
sessionID: SessionID.make("session_test"),
permission: "bash",
patterns: ["ls"],
@@ -940,14 +940,14 @@ it.instance(
Deferred.doneUnsafe(
seen,
Effect.succeed(
event.data as { sessionID: SessionID; requestID: PermissionLegacy.ID; reply: PermissionLegacy.Reply },
event.data as { sessionID: SessionID; requestID: PermissionV1.ID; reply: PermissionV1.Reply },
),
)
return Effect.void
})
yield* Effect.addFinalizer(() => unsub)
yield* reply({ requestID: PermissionLegacy.ID.make("per_test7"), reply: "once" })
yield* reply({ requestID: PermissionV1.ID.make("per_test7"), reply: "once" })
yield* Fiber.join(fiber)
expect(
yield* Deferred.await(seen).pipe(
@@ -958,7 +958,7 @@ it.instance(
),
).toEqual({
sessionID: SessionID.make("session_test"),
requestID: PermissionLegacy.ID.make("per_test7"),
requestID: PermissionV1.ID.make("per_test7"),
reply: "once",
})
}),
@@ -975,7 +975,7 @@ it.live("permission requests stay isolated by directory", () =>
.provide(
{ directory: one },
ask({
id: PermissionLegacy.ID.make("per_dir_a"),
id: PermissionV1.ID.make("per_dir_a"),
sessionID: SessionID.make("session_dir_a"),
permission: "bash",
patterns: ["ls"],
@@ -990,7 +990,7 @@ it.live("permission requests stay isolated by directory", () =>
.provide(
{ directory: two },
ask({
id: PermissionLegacy.ID.make("per_dir_b"),
id: PermissionV1.ID.make("per_dir_b"),
sessionID: SessionID.make("session_dir_b"),
permission: "bash",
patterns: ["pwd"],
@@ -1006,8 +1006,8 @@ it.live("permission requests stay isolated by directory", () =>
expect(onePending).toHaveLength(1)
expect(twoPending).toHaveLength(1)
expect(onePending[0].id).toBe(PermissionLegacy.ID.make("per_dir_a"))
expect(twoPending[0].id).toBe(PermissionLegacy.ID.make("per_dir_b"))
expect(onePending[0].id).toBe(PermissionV1.ID.make("per_dir_a"))
expect(twoPending[0].id).toBe(PermissionV1.ID.make("per_dir_b"))
yield* store.provide({ directory: one }, reply({ requestID: onePending[0].id, reply: "reject" }))
yield* store.provide({ directory: two }, reply({ requestID: twoPending[0].id, reply: "reject" }))
@@ -1024,7 +1024,7 @@ it.instance(
const test = yield* TestInstance
const store = yield* InstanceStore.Service
const fiber = yield* ask({
id: PermissionLegacy.ID.make("per_dispose"),
id: PermissionV1.ID.make("per_dispose"),
sessionID: SessionID.make("session_dispose"),
permission: "bash",
patterns: ["ls"],
@@ -1039,7 +1039,7 @@ it.instance(
const exit = yield* Fiber.await(fiber)
expect(Exit.isFailure(exit)).toBe(true)
if (Exit.isFailure(exit)) expect(Cause.squash(exit.cause)).toBeInstanceOf(PermissionLegacy.RejectedError)
if (Exit.isFailure(exit)) expect(Cause.squash(exit.cause)).toBeInstanceOf(PermissionV1.RejectedError)
}),
{ git: true },
)
@@ -1051,7 +1051,7 @@ it.instance(
const test = yield* TestInstance
const store = yield* InstanceStore.Service
const fiber = yield* ask({
id: PermissionLegacy.ID.make("per_reload"),
id: PermissionV1.ID.make("per_reload"),
sessionID: SessionID.make("session_reload"),
permission: "bash",
patterns: ["ls"],
@@ -1065,7 +1065,7 @@ it.instance(
const exit = yield* Fiber.await(fiber)
expect(Exit.isFailure(exit)).toBe(true)
if (Exit.isFailure(exit)) expect(Cause.squash(exit.cause)).toBeInstanceOf(PermissionLegacy.RejectedError)
if (Exit.isFailure(exit)) expect(Cause.squash(exit.cause)).toBeInstanceOf(PermissionV1.RejectedError)
}),
{ git: true },
)
@@ -1074,7 +1074,7 @@ it.instance(
"reply - fails for unknown requestID",
() =>
Effect.gen(function* () {
const exit = yield* reply({ requestID: PermissionLegacy.ID.make("per_unknown"), reply: "once" }).pipe(Effect.exit)
const exit = yield* reply({ requestID: PermissionV1.ID.make("per_unknown"), reply: "once" }).pipe(Effect.exit)
expect(Exit.isFailure(exit)).toBe(true)
if (Exit.isFailure(exit)) {
expect(Cause.squash(exit.cause)).toMatchObject({ _tag: "Permission.NotFoundError", requestID: "per_unknown" })
@@ -1101,7 +1101,7 @@ it.instance(
],
}),
)
expect(err).toBeInstanceOf(PermissionLegacy.DeniedError)
expect(err).toBeInstanceOf(PermissionV1.DeniedError)
}),
{ git: true },
)
@@ -1141,7 +1141,7 @@ it.instance(
}),
)
expect(err).toBeInstanceOf(PermissionLegacy.DeniedError)
expect(err).toBeInstanceOf(PermissionV1.DeniedError)
expect(yield* list()).toHaveLength(0)
}),
{ git: true },
@@ -1155,7 +1155,7 @@ it.instance(
const store = yield* InstanceStore.Service
const fiber = yield* ask({
id: PermissionLegacy.ID.make("per_reload"),
id: PermissionV1.ID.make("per_reload"),
sessionID: SessionID.make("session_reload"),
permission: "bash",
patterns: ["ls"],
@@ -1170,7 +1170,7 @@ it.instance(
const exit = yield* Fiber.await(fiber)
expect(Exit.isFailure(exit)).toBe(true)
if (Exit.isFailure(exit)) expect(Cause.squash(exit.cause)).toBeInstanceOf(PermissionLegacy.RejectedError)
if (Exit.isFailure(exit)) expect(Cause.squash(exit.cause)).toBeInstanceOf(PermissionV1.RejectedError)
}),
{ git: true },
)

View File

@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test"
import { Schema } from "effect"
import { ConfigProvider } from "@/config/provider"
import { ConfigProviderV1 } from "@opencode-ai/core/v1/config/provider"
import { CatalogModelStatus, ModelStatus } from "@/provider/model-status"
import { ModelsDev } from "@opencode-ai/core/models-dev"
import { Provider } from "@/provider/provider"
@@ -13,7 +13,7 @@ describe("provider model status schemas", () => {
})
test("accepts active status across public provider schemas", () => {
expect(Schema.decodeUnknownSync(ConfigProvider.Model)({ status: "active" }).status).toBe("active")
expect(Schema.decodeUnknownSync(ConfigProviderV1.Model)({ status: "active" }).status).toBe("active")
expect(
Schema.decodeUnknownSync(ModelsDev.Model)({
id: "test-model",

View File

@@ -1,7 +1,7 @@
import { NodeHttpServer, NodeServices } from "@effect/platform-node"
import { NamedError } from "@opencode-ai/core/util/error"
import { describe, expect } from "bun:test"
import { ConfigError } from "../../src/config/error"
import { ConfigErrorV1 } from "@opencode-ai/core/v1/config/error"
import { Effect, Layer } from "effect"
import { HttpClient, HttpClientRequest, HttpRouter } from "effect/unstable/http"
import { errorLayer } from "../../src/server/routes/instance/httpapi/middleware/error"
@@ -55,7 +55,7 @@ describe("HttpApi error middleware", () => {
it.live("does not expose config defects from generic middleware", () =>
Effect.gen(function* () {
const configError = new ConfigError.InvalidError({
const configError = new ConfigErrorV1.InvalidError({
path: "/tmp/opencode.json",
issues: [{ message: "Expected object", path: ["provider", "anthropic", "options"] }],
})

View File

@@ -1,5 +1,6 @@
import { Flag } from "@opencode-ai/core/flag/flag"
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
import { ConfigV1 } from "@opencode-ai/core/v1/config/config"
import { SessionV1 } from "@opencode-ai/core/v1/session"
import { Cause, Duration, Effect, Layer, Scope } from "effect"
import { TestLLMServer } from "../../lib/llm-server"
import type { Config } from "../../../src/config/config"
@@ -144,7 +145,7 @@ function withContext<A, E>(
}),
message: (sessionID, input) =>
Effect.gen(function* () {
const info: SessionLegacy.User = {
const info: SessionV1.User = {
id: MessageID.ascending(),
sessionID,
role: "user",
@@ -155,7 +156,7 @@ function withContext<A, E>(
modelID: ProviderV2.ModelID.make("test"),
},
}
const part: SessionLegacy.TextPart = {
const part: SessionV1.TextPart = {
id: PartID.ascending(),
sessionID,
messageID: info.id,
@@ -205,7 +206,7 @@ function trace(options: Options, scenario: ActiveScenario, phase: string) {
function projectOptions(
project: ProjectOptions,
llmUrl: string | undefined,
): { git?: boolean; config?: Partial<Config.Info> } {
): { git?: boolean; config?: Partial<ConfigV1.Info> } {
if (!project.llm || !llmUrl) return { git: project.git, config: project.config }
const fake = fakeLlmConfig(llmUrl)
return {
@@ -221,7 +222,7 @@ function projectOptions(
}
}
function fakeLlmConfig(url: string): Partial<Config.Info> {
function fakeLlmConfig(url: string): Partial<ConfigV1.Info> {
return {
model: "test/test-model",
small_model: "test/test-model",

View File

@@ -1,5 +1,6 @@
import type { Duration, Effect } from "effect"
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
import { ConfigV1 } from "@opencode-ai/core/v1/config/config"
import { SessionV1 } from "@opencode-ai/core/v1/session"
import type { Config } from "../../../src/config/config"
import type { Project } from "../../../src/project/project"
import type { Worktree } from "../../../src/worktree"
@@ -15,7 +16,7 @@ export type Mode = "effect" | "coverage" | "auth"
export type Comparison = "none" | "status" | "json"
export type CaptureMode = "full" | "stream"
export type AuthPolicy = "protected" | "public" | "public-bypass" | "ticket-bypass"
export type ProjectOptions = { git?: boolean; config?: Partial<Config.Info>; llm?: boolean }
export type ProjectOptions = { git?: boolean; config?: Partial<ConfigV1.Info>; llm?: boolean }
export type OpenApiSpec = { paths?: Record<string, Partial<Record<OpenApiMethod, unknown>>> }
export type JsonObject = Record<string, unknown>
@@ -58,7 +59,7 @@ export type ScenarioContext = {
sessionGet: (sessionID: SessionID) => Effect.Effect<SessionInfo | undefined>
project: () => Effect.Effect<Project.Info>
message: (sessionID: SessionID, input?: { text?: string }) => Effect.Effect<MessageSeed>
messages: (sessionID: SessionID) => Effect.Effect<SessionLegacy.WithParts[]>
messages: (sessionID: SessionID) => Effect.Effect<SessionV1.WithParts[]>
todos: (sessionID: SessionID, todos: TodoInfo[]) => Effect.Effect<void>
worktree: (input?: { name?: string }) => Effect.Effect<Worktree.Info>
worktreeRemove: (directory: string) => Effect.Effect<void>
@@ -119,4 +120,4 @@ export type Result =
export type SessionInfo = { id: SessionID; title: string; parentID?: SessionID }
export type TodoInfo = { content: string; status: string; priority: string }
export type MessageSeed = { info: SessionLegacy.User; part: SessionLegacy.TextPart }
export type MessageSeed = { info: SessionV1.User; part: SessionV1.TextPart }

View File

@@ -1,4 +1,4 @@
import { PermissionLegacy } from "@opencode-ai/core/permission/legacy"
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
import { NodeHttpServer, NodeServices } from "@effect/platform-node"
import { Flag } from "@opencode-ai/core/flag/flag"
import { describe, expect } from "bun:test"
@@ -167,7 +167,7 @@ describe("instance HttpApi", () => {
handlerContext,
),
)
const permissionID = PermissionLegacy.ID.ascending()
const permissionID = PermissionV1.ID.ascending()
const questionReplyID = QuestionID.ascending()
const questionRejectID = QuestionID.ascending()
const [permission, questionReply, questionReject] = yield* Effect.all(

View File

@@ -1,5 +1,6 @@
import { afterEach, describe, expect } from "bun:test"
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
import { ConfigV1 } from "@opencode-ai/core/v1/config/config"
import { SessionV1 } from "@opencode-ai/core/v1/session"
import { Deferred, Effect, Layer } from "effect"
import type * as Scope from "effect/Scope"
import { HttpServer } from "effect/unstable/http"
@@ -190,7 +191,7 @@ function httpapiInstance<A, E>(
options: {
serverPath: ServerPath
git?: boolean
config?: Partial<Config.Info>
config?: Partial<ConfigV1.Info>
setup?: (dir: string) => Effect.Effect<void, E, TestServices>
},
run: (input: ProjectFixture) => Effect.Effect<A, E, TestScope>,
@@ -214,7 +215,7 @@ function withProject<A, E, E2 = never>(
serverPath: ServerPath,
options: {
git?: boolean
config?: Partial<Config.Info>
config?: Partial<ConfigV1.Info>
setup?: (dir: string) => Effect.Effect<void, E2, TestServices>
},
run: (input: ProjectFixture) => Effect.Effect<A, E, TestScope>,
@@ -300,7 +301,7 @@ function seedMessage(directory: string, sessionID: string) {
agent: "test",
model: { providerID: ProviderV2.ID.make("test"), modelID: ProviderV2.ModelID.make("test") },
tools: {},
} satisfies SessionLegacy.User)
} satisfies SessionV1.User)
const part = yield* svc.updatePart({
id: PartID.ascending(),
sessionID: id,

View File

@@ -1,7 +1,7 @@
import { PermissionLegacy } from "@opencode-ai/core/permission/legacy"
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
import { afterEach, describe, expect } from "bun:test"
import { NodeHttpServer, NodeServices } from "@effect/platform-node"
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
import { SessionV1 } from "@opencode-ai/core/v1/session"
import { mkdir } from "node:fs/promises"
import path from "node:path"
import { Cause, Config, Effect, Exit, Layer } from "effect"
@@ -362,7 +362,7 @@ describe("session HttpApi", () => {
const messages = yield* request(`${pathFor(SessionPaths.messages, { sessionID: parent.id })}?limit=1`, {
headers,
})
const messagePage = yield* json<SessionLegacy.WithParts[]>(messages)
const messagePage = yield* json<SessionV1.WithParts[]>(messages)
const nextCursor = messages.headers["x-next-cursor"]
expect(nextCursor).toBeTruthy()
expect(messagePage[0]?.parts[0]).toMatchObject({ type: "text" })
@@ -379,7 +379,7 @@ describe("session HttpApi", () => {
).toBe(400)
expect(
yield* requestJson<SessionLegacy.WithParts>(
yield* requestJson<SessionV1.WithParts>(
pathFor(SessionPaths.message, { sessionID: parent.id, messageID: message.info.id }),
{ headers },
),
@@ -829,7 +829,7 @@ describe("session HttpApi", () => {
const first = yield* createTextMessage(session.id, "first")
const second = yield* createTextMessage(session.id, "second")
const updated = yield* requestJson<SessionLegacy.Part>(
const updated = yield* requestJson<SessionV1.Part>(
pathFor(SessionPaths.updatePart, {
sessionID: session.id,
messageID: first.info.id,
@@ -913,7 +913,7 @@ describe("session HttpApi", () => {
}),
).toMatchObject({ id: session.id })
const permissionID = String(PermissionLegacy.ID.ascending())
const permissionID = String(PermissionV1.ID.ascending())
const permission = yield* request(
pathFor(SessionPaths.permissions, {
sessionID: session.id,

View File

@@ -15,7 +15,7 @@ import { Effect, Layer } from "effect"
import { SessionPaths } from "@/server/routes/instance/httpapi/groups/session"
import { Session } from "@/session/session"
import { Storage } from "@/storage/storage"
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
import { SessionV1 } from "@opencode-ai/core/v1/session"
import { MessageID } from "@/session/schema"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { resetDatabase } from "../fixture/db"
@@ -83,7 +83,7 @@ describe("session diff with missing patch (#26574)", () => {
summary: {
diffs: [{ file: "turn.ts", additions: 1, deletions: 0, status: "modified" }],
},
} satisfies SessionLegacy.User)
} satisfies SessionV1.User)
const response = yield* requestInDirectory(
`${pathFor(SessionPaths.diff, { sessionID: session.id })}?messageID=${messageID}`,

View File

@@ -1,5 +1,5 @@
import { afterEach, describe, expect } from "bun:test"
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
import { SessionV1 } from "@opencode-ai/core/v1/session"
import { Effect, Layer } from "effect"
import { HttpClientResponse } from "effect/unstable/http"
import { Session as SessionNs } from "@/session/session"
@@ -65,14 +65,14 @@ const fill = Effect.fn("SessionMessagesTest.fill")(function* (
agent: "test",
model,
tools: {},
} satisfies SessionLegacy.User)
} satisfies SessionV1.User)
yield* session.updatePart({
id: PartID.ascending(),
sessionID,
messageID: id,
type: "text",
text: `m${i}`,
} satisfies SessionLegacy.TextPart)
} satisfies SessionV1.TextPart)
return id
}),
)
@@ -96,7 +96,7 @@ describe("session messages endpoint", () => {
const a = yield* request(`/session/${session.id}/message?limit=2`)
expect(a.status).toBe(200)
const aBody = yield* json<SessionLegacy.WithParts[]>(a)
const aBody = yield* json<SessionV1.WithParts[]>(a)
expect(aBody.map((item) => item.info.id)).toEqual(ids.slice(-2))
const cursor = a.headers["x-next-cursor"]
expect(cursor).toBeTruthy()
@@ -104,7 +104,7 @@ describe("session messages endpoint", () => {
const b = yield* request(`/session/${session.id}/message?limit=2&before=${encodeURIComponent(cursor!)}`)
expect(b.status).toBe(200)
const bBody = yield* json<SessionLegacy.WithParts[]>(b)
const bBody = yield* json<SessionV1.WithParts[]>(b)
expect(bBody.map((item) => item.info.id)).toEqual(ids.slice(-4, -2))
}),
),
@@ -120,7 +120,7 @@ describe("session messages endpoint", () => {
const res = yield* request(`/session/${session.id}/message`)
expect(res.status).toBe(200)
const body = yield* json<SessionLegacy.WithParts[]>(res)
const body = yield* json<SessionV1.WithParts[]>(res)
expect(body.map((item) => item.info.id)).toEqual(ids)
}),
),
@@ -152,7 +152,7 @@ describe("session messages endpoint", () => {
const res = yield* request(`/session/${session.id}/message?limit=510`)
expect(res.status).toBe(200)
const body = yield* json<SessionLegacy.WithParts[]>(res)
const body = yield* json<SessionV1.WithParts[]>(res)
expect(body).toHaveLength(510)
}),
),

View File

@@ -1,5 +1,6 @@
import { afterEach, describe, expect, mock, test } from "bun:test"
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
import { ConfigV1 } from "@opencode-ai/core/v1/config/config"
import { SessionV1 } from "@opencode-ai/core/v1/session"
import { Database } from "@opencode-ai/core/database/database"
import { EventV2Bridge } from "@/event-v2-bridge"
import { APICallError } from "ai"
@@ -218,8 +219,8 @@ function layer(result: "continue" | "compact") {
)
}
function cfg(compaction?: Config.Info["compaction"]) {
const base = Schema.decodeUnknownSync(Config.Info)({}) as Config.Info
function cfg(compaction?: ConfigV1.Info["compaction"]) {
const base = Schema.decodeUnknownSync(ConfigV1.Info)({}) as ConfigV1.Info
return TestConfig.layer({
get: () => Effect.succeed({ ...base, compaction }),
})
@@ -303,7 +304,7 @@ function readCompactionPart(sessionID: SessionID) {
.messages({ sessionID })
.pipe(
Effect.map((messages) =>
messages.at(-2)?.parts.find((item): item is SessionLegacy.CompactionPart => item.type === "compaction"),
messages.at(-2)?.parts.find((item): item is SessionV1.CompactionPart => item.type === "compaction"),
),
)
}
@@ -649,7 +650,7 @@ describe("session.compaction.prune", () => {
type: "text",
text: "first",
})
const b: SessionLegacy.Assistant = {
const b: SessionV1.Assistant = {
id: MessageID.ascending(),
role: "assistant",
sessionID: info.id,
@@ -745,7 +746,7 @@ describe("session.compaction.prune", () => {
type: "text",
text: "first",
})
const b: SessionLegacy.Assistant = {
const b: SessionV1.Assistant = {
id: MessageID.ascending(),
role: "assistant",
sessionID: info.id,

View File

@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test"
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
import { SessionV1 } from "@opencode-ai/core/v1/session"
import path from "path"
import { Effect, FileSystem, Layer } from "effect"
import { FetchHttpClient } from "effect/unstable/http"
@@ -63,7 +63,7 @@ const tmpWithFiles = (files: Record<string, string>) =>
return dir
})
function loaded(filepath: string): SessionLegacy.WithParts[] {
function loaded(filepath: string): SessionV1.WithParts[] {
const sessionID = SessionID.make("session-loaded-1")
const messageID = MessageID.make("msg_message-loaded-1")

View File

@@ -1,5 +1,6 @@
import { NodeFileSystem } from "@effect/platform-node"
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
import { ConfigV1 } from "@opencode-ai/core/v1/config/config"
import { SessionV1 } from "@opencode-ai/core/v1/session"
import { FSUtil } from "@opencode-ai/core/fs-util"
import { ModelsDev } from "@opencode-ai/core/models-dev"
import { LocationServiceMap } from "@opencode-ai/core/location-layer"
@@ -52,7 +53,7 @@ type RecordedScenario = {
readonly recordAuth?: () => Auth.Info | undefined
readonly replayAuth?: Auth.Info
readonly stableID?: string
readonly config: (model: ModelsDev.Provider["models"][string]) => Partial<Config.Info>
readonly config: (model: ModelsDev.Provider["models"][string]) => Partial<ConfigV1.Info>
}
const cloneModel = (model: ModelsDev.Provider["models"][string]) => {
@@ -60,9 +61,9 @@ const cloneModel = (model: ModelsDev.Provider["models"][string]) => {
const { experimental, ...rest } = cloned
// oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- The config schema accepts the same model shape except object-valued experimental metadata.
if (typeof experimental === "boolean")
return cloned as NonNullable<NonNullable<Config.Info["provider"]>[string]["models"]>[string]
return cloned as NonNullable<NonNullable<ConfigV1.Info["provider"]>[string]["models"]>[string]
// oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- Dropping non-boolean experimental metadata makes the fixture model match config input.
return rest as NonNullable<NonNullable<Config.Info["provider"]>[string]["models"]>[string]
return rest as NonNullable<NonNullable<ConfigV1.Info["provider"]>[string]["models"]>[string]
}
const envValue = (...names: string[]) => names.map((name) => process.env[name]).find(Boolean)
@@ -97,7 +98,7 @@ const providerConfig = (input: {
readonly api: string
readonly model: ModelsDev.Provider["models"][string]
readonly options: Record<string, unknown>
}): Partial<Config.Info> => ({
}): Partial<ConfigV1.Info> => ({
enabled_providers: [input.providerID],
provider: {
[input.providerID]: {
@@ -396,7 +397,7 @@ const driveToolLoop = (scenario: RecordedScenario) =>
time: { created: 0 },
agent: agent.name,
model: { providerID: scenario.providerID, modelID },
} satisfies SessionLegacy.User,
} satisfies SessionV1.User,
sessionID,
model: resolved,
agent,

View File

@@ -1,6 +1,7 @@
import { PermissionLegacy } from "@opencode-ai/core/permission/legacy"
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
import { ConfigV1 } from "@opencode-ai/core/v1/config/config"
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test"
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
import { SessionV1 } from "@opencode-ai/core/v1/session"
import path from "path"
import { tool, type ModelMessage } from "ai"
import { Cause, Effect, Exit, Fiber, Layer, Stream } from "effect"
@@ -26,9 +27,9 @@ import { LLMAISDK } from "@/session/llm/ai-sdk"
import { Session as SessionNs } from "@/session/session"
import { ProviderV2 } from "@opencode-ai/core/provider"
type ConfigModel = NonNullable<NonNullable<Config.Info["provider"]>[string]["models"]>[string]
type ConfigModel = NonNullable<NonNullable<ConfigV1.Info["provider"]>[string]["models"]>[string]
const openAIConfig = (model: ModelsDev.Provider["models"][string], baseURL: string): Partial<Config.Info> => {
const openAIConfig = (model: ModelsDev.Provider["models"][string], baseURL: string): Partial<ConfigV1.Info> => {
const { experimental: _experimental, ...configModel } = model
return {
enabled_providers: ["openai"],
@@ -333,7 +334,7 @@ describe("session.llm.ai-sdk adapter", () => {
})
test("preserves tool-error cause", async () => {
const error = new PermissionLegacy.RejectedError()
const error = new PermissionV1.RejectedError()
const events = await Effect.runPromise(
LLMAISDK.toLLMEvents(LLMAISDK.adapterState(), {
type: "tool-error",
@@ -786,7 +787,7 @@ describe("session.llm.stream", () => {
time: { created: Date.now() },
agent: agent.name,
model: { providerID: ProviderV2.ID.make(vivgridFixture.providerID), modelID: resolved.id, variant: "high" },
} satisfies SessionLegacy.User
} satisfies SessionV1.User
yield* drain({
user,
@@ -857,7 +858,7 @@ describe("session.llm.stream", () => {
time: { created: Date.now() },
agent: agent.name,
model: { providerID: ProviderV2.ID.make(alibabaQwenFixture.providerID), modelID: resolved.id },
} satisfies SessionLegacy.User
} satisfies SessionV1.User
const fiber = yield* drain({
user,
@@ -927,7 +928,7 @@ describe("session.llm.stream", () => {
agent: agent.name,
model: { providerID: ProviderV2.ID.make(alibabaQwenFixture.providerID), modelID: resolved.id },
tools: { question: true },
} satisfies SessionLegacy.User
} satisfies SessionV1.User
yield* drain({
user,
@@ -1029,7 +1030,7 @@ describe("session.llm.stream", () => {
time: { created: Date.now() },
agent: agent.name,
model: { providerID: ProviderV2.ID.make("openai"), modelID: resolved.id, variant: "high" },
} satisfies SessionLegacy.User
} satisfies SessionV1.User
yield* drain({
user,
@@ -1143,7 +1144,7 @@ describe("session.llm.stream", () => {
time: { created: Date.now() },
agent: agent.name,
model: { providerID: ProviderV2.ID.make("openai"), modelID: resolved.id, variant: "high" },
} satisfies SessionLegacy.User,
} satisfies SessionV1.User,
sessionID,
model: resolved,
agent,
@@ -1205,7 +1206,7 @@ describe("session.llm.stream", () => {
time: { created: Date.now() },
agent: agent.name,
model: { providerID: ProviderV2.ID.make("openai"), modelID: resolved.id, variant: "high" },
} satisfies SessionLegacy.User,
} satisfies SessionV1.User,
sessionID,
model: resolved,
agent,
@@ -1288,7 +1289,7 @@ describe("session.llm.stream", () => {
time: { created: Date.now() },
agent: agent.name,
model: { providerID: ProviderV2.ID.make("openai"), modelID: resolved.id },
} satisfies SessionLegacy.User,
} satisfies SessionV1.User,
sessionID,
model: resolved,
agent,
@@ -1376,7 +1377,7 @@ describe("session.llm.stream", () => {
time: { created: Date.now() },
agent: agent.name,
model: { providerID: ProviderV2.ID.make("openai"), modelID: resolved.id },
} satisfies SessionLegacy.User,
} satisfies SessionV1.User,
sessionID,
model: resolved,
agent,
@@ -1501,7 +1502,7 @@ describe("session.llm.stream", () => {
time: { created: Date.now() },
agent: agent.name,
model: { providerID: ProviderV2.ID.make("openai"), modelID: resolved.id },
} satisfies SessionLegacy.User
} satisfies SessionV1.User
yield* drain({
user,
@@ -1593,7 +1594,7 @@ describe("session.llm.stream", () => {
time: { created: Date.now() },
agent: agent.name,
model: { providerID: ProviderV2.ID.make("minimax"), modelID: ProviderV2.ModelID.make("MiniMax-M2.5") },
} satisfies SessionLegacy.User
} satisfies SessionV1.User
yield* drain({
user,
@@ -1687,7 +1688,7 @@ describe("session.llm.stream", () => {
time: { created: Date.now() },
agent: agent.name,
model: { providerID: ProviderV2.ID.make("anthropic"), modelID: resolved.id, variant: "max" },
} satisfies SessionLegacy.User
} satisfies SessionV1.User
const input = [
{
@@ -1892,7 +1893,7 @@ describe("session.llm.stream", () => {
time: { created: Date.now() },
agent: agent.name,
model: { providerID: ProviderV2.ID.make(geminiFixture.providerID), modelID: resolved.id },
} satisfies SessionLegacy.User
} satisfies SessionV1.User
yield* drain({
user,

View File

@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test"
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
import { SessionV1 } from "@opencode-ai/core/v1/session"
import { APICallError } from "ai"
import { MessageV2 } from "../../src/session/message-v2"
import { ProviderTransform } from "@/provider/transform"
@@ -60,7 +60,7 @@ const model: Provider.Model = {
release_date: "2026-01-01",
}
function userInfo(id: string): SessionLegacy.User {
function userInfo(id: string): SessionV1.User {
return {
id,
sessionID,
@@ -70,15 +70,15 @@ function userInfo(id: string): SessionLegacy.User {
model: { providerID, modelID: ProviderV2.ModelID.make("test") },
tools: {},
mode: "",
} as unknown as SessionLegacy.User
} as unknown as SessionV1.User
}
function assistantInfo(
id: string,
parentID: string,
error?: SessionLegacy.Assistant["error"],
error?: SessionV1.Assistant["error"],
meta?: { providerID: string; modelID: string },
): SessionLegacy.Assistant {
): SessionV1.Assistant {
const infoModel = meta ?? { providerID: model.providerID, modelID: model.api.id }
return {
id,
@@ -99,7 +99,7 @@ function assistantInfo(
reasoning: 0,
cache: { read: 0, write: 0 },
},
} as unknown as SessionLegacy.Assistant
} as unknown as SessionV1.Assistant
}
function basePart(messageID: string, id: string) {
@@ -112,7 +112,7 @@ function basePart(messageID: string, id: string) {
describe("session.message-v2.toModelMessage", () => {
test("filters out messages with no parts", async () => {
const input: SessionLegacy.WithParts[] = [
const input: SessionV1.WithParts[] = [
{
info: userInfo("m-empty"),
parts: [],
@@ -125,7 +125,7 @@ describe("session.message-v2.toModelMessage", () => {
type: "text",
text: "hello",
},
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
]
@@ -140,7 +140,7 @@ describe("session.message-v2.toModelMessage", () => {
test("filters out messages with only ignored parts", async () => {
const messageID = "m-user"
const input: SessionLegacy.WithParts[] = [
const input: SessionV1.WithParts[] = [
{
info: userInfo(messageID),
parts: [
@@ -150,7 +150,7 @@ describe("session.message-v2.toModelMessage", () => {
text: "ignored",
ignored: true,
},
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
]
@@ -160,7 +160,7 @@ describe("session.message-v2.toModelMessage", () => {
test("filters out user messages with only empty text parts", async () => {
const messageID = "m-user"
const input: SessionLegacy.WithParts[] = [
const input: SessionV1.WithParts[] = [
{
info: userInfo(messageID),
parts: [
@@ -169,7 +169,7 @@ describe("session.message-v2.toModelMessage", () => {
type: "text",
text: "",
},
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
]
@@ -179,7 +179,7 @@ describe("session.message-v2.toModelMessage", () => {
test("filters empty user text parts while keeping non-empty parts", async () => {
const messageID = "m-user"
const input: SessionLegacy.WithParts[] = [
const input: SessionV1.WithParts[] = [
{
info: userInfo(messageID),
parts: [
@@ -193,7 +193,7 @@ describe("session.message-v2.toModelMessage", () => {
type: "text",
text: "hello",
},
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
]
@@ -208,7 +208,7 @@ describe("session.message-v2.toModelMessage", () => {
test("includes synthetic text parts", async () => {
const messageID = "m-user"
const input: SessionLegacy.WithParts[] = [
const input: SessionV1.WithParts[] = [
{
info: userInfo(messageID),
parts: [
@@ -218,7 +218,7 @@ describe("session.message-v2.toModelMessage", () => {
text: "hello",
synthetic: true,
},
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
{
info: assistantInfo("m-assistant", messageID),
@@ -229,7 +229,7 @@ describe("session.message-v2.toModelMessage", () => {
text: "assistant",
synthetic: true,
},
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
]
@@ -248,7 +248,7 @@ describe("session.message-v2.toModelMessage", () => {
test("converts user text/file parts and injects compaction/subtask prompts", async () => {
const messageID = "m-user"
const input: SessionLegacy.WithParts[] = [
const input: SessionV1.WithParts[] = [
{
info: userInfo(messageID),
parts: [
@@ -296,7 +296,7 @@ describe("session.message-v2.toModelMessage", () => {
description: "desc",
agent: "agent",
},
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
]
@@ -322,7 +322,7 @@ describe("session.message-v2.toModelMessage", () => {
const userID = "m-user"
const assistantID = "m-assistant"
const input: SessionLegacy.WithParts[] = [
const input: SessionV1.WithParts[] = [
{
info: userInfo(userID),
parts: [
@@ -331,7 +331,7 @@ describe("session.message-v2.toModelMessage", () => {
type: "text",
text: "run tool",
},
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
{
info: assistantInfo(assistantID, userID),
@@ -366,7 +366,7 @@ describe("session.message-v2.toModelMessage", () => {
},
metadata: { openai: { tool: "meta" } },
},
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
]
@@ -435,7 +435,7 @@ describe("session.message-v2.toModelMessage", () => {
)
const userID = "m-user-anthropic"
const assistantID = "m-assistant-anthropic"
const input: SessionLegacy.WithParts[] = [
const input: SessionV1.WithParts[] = [
{
info: userInfo(userID),
parts: [
@@ -444,7 +444,7 @@ describe("session.message-v2.toModelMessage", () => {
type: "text",
text: "run tool",
},
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
{
info: assistantInfo(assistantID, userID),
@@ -472,7 +472,7 @@ describe("session.message-v2.toModelMessage", () => {
],
},
},
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
]
@@ -516,7 +516,7 @@ describe("session.message-v2.toModelMessage", () => {
const pdf = Buffer.from("%PDF-1.4\n").toString("base64")
const userID = "m-user-bedrock-pdf"
const assistantID = "m-assistant-bedrock-pdf"
const input: SessionLegacy.WithParts[] = [
const input: SessionV1.WithParts[] = [
{
info: userInfo(userID),
parts: [
@@ -525,7 +525,7 @@ describe("session.message-v2.toModelMessage", () => {
type: "text",
text: "run tool",
},
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
{
info: assistantInfo(assistantID, userID),
@@ -553,7 +553,7 @@ describe("session.message-v2.toModelMessage", () => {
],
},
},
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
]
@@ -604,7 +604,7 @@ describe("session.message-v2.toModelMessage", () => {
const userID = "m-user"
const assistantID = "m-assistant"
const input: SessionLegacy.WithParts[] = [
const input: SessionV1.WithParts[] = [
{
info: userInfo(userID),
parts: [
@@ -613,7 +613,7 @@ describe("session.message-v2.toModelMessage", () => {
type: "text",
text: "run tool",
},
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
{
info: assistantInfo(assistantID, userID, undefined, { providerID: "other", modelID: "other" }),
@@ -646,7 +646,7 @@ describe("session.message-v2.toModelMessage", () => {
},
metadata: { openai: { tool: "meta" } },
},
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
]
@@ -687,7 +687,7 @@ describe("session.message-v2.toModelMessage", () => {
const userID = "m-user"
const assistantID = "m-assistant"
const input: SessionLegacy.WithParts[] = [
const input: SessionV1.WithParts[] = [
{
info: userInfo(userID),
parts: [
@@ -696,7 +696,7 @@ describe("session.message-v2.toModelMessage", () => {
type: "text",
text: "run tool",
},
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
{
info: assistantInfo(assistantID, userID),
@@ -715,7 +715,7 @@ describe("session.message-v2.toModelMessage", () => {
time: { start: 0, end: 1, compacted: 1 },
},
},
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
]
@@ -754,7 +754,7 @@ describe("session.message-v2.toModelMessage", () => {
const userID = "m-user"
const assistantID = "m-assistant"
const input: SessionLegacy.WithParts[] = [
const input: SessionV1.WithParts[] = [
{
info: userInfo(userID),
parts: [
@@ -763,7 +763,7 @@ describe("session.message-v2.toModelMessage", () => {
type: "text",
text: "run tool",
},
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
{
info: assistantInfo(assistantID, userID),
@@ -782,7 +782,7 @@ describe("session.message-v2.toModelMessage", () => {
time: { start: 0, end: 1 },
},
},
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
]
@@ -824,7 +824,7 @@ describe("session.message-v2.toModelMessage", () => {
const userID = "m-user"
const assistantID = "m-assistant"
const input: SessionLegacy.WithParts[] = [
const input: SessionV1.WithParts[] = [
{
info: userInfo(userID),
parts: [
@@ -833,7 +833,7 @@ describe("session.message-v2.toModelMessage", () => {
type: "text",
text: "run tool",
},
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
{
info: assistantInfo(assistantID, userID),
@@ -852,7 +852,7 @@ describe("session.message-v2.toModelMessage", () => {
},
metadata: { openai: { tool: "meta" } },
},
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
]
@@ -902,7 +902,7 @@ describe("session.message-v2.toModelMessage", () => {
"</shell_metadata>",
].join("\n")
const input: SessionLegacy.WithParts[] = [
const input: SessionV1.WithParts[] = [
{
info: userInfo(userID),
parts: [
@@ -911,7 +911,7 @@ describe("session.message-v2.toModelMessage", () => {
type: "text",
text: "run tool",
},
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
{
info: assistantInfo(assistantID, userID),
@@ -929,7 +929,7 @@ describe("session.message-v2.toModelMessage", () => {
time: { start: 0, end: 1 },
},
},
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
]
@@ -967,12 +967,12 @@ describe("session.message-v2.toModelMessage", () => {
test("filters assistant messages with non-abort errors", async () => {
const assistantID = "m-assistant"
const input: SessionLegacy.WithParts[] = [
const input: SessionV1.WithParts[] = [
{
info: assistantInfo(
assistantID,
"m-parent",
new SessionLegacy.APIError({ message: "boom", isRetryable: true }).toObject() as SessionLegacy.APIError,
new SessionV1.APIError({ message: "boom", isRetryable: true }).toObject() as SessionV1.APIError,
),
parts: [
{
@@ -980,7 +980,7 @@ describe("session.message-v2.toModelMessage", () => {
type: "text",
text: "should not render",
},
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
]
@@ -991,11 +991,11 @@ describe("session.message-v2.toModelMessage", () => {
const assistantID1 = "m-assistant-1"
const assistantID2 = "m-assistant-2"
const aborted = new SessionLegacy.AbortedError({
const aborted = new SessionV1.AbortedError({
message: "aborted",
}).toObject() as SessionLegacy.Assistant["error"]
}).toObject() as SessionV1.Assistant["error"]
const input: SessionLegacy.WithParts[] = [
const input: SessionV1.WithParts[] = [
{
info: assistantInfo(assistantID1, "m-parent", aborted),
parts: [
@@ -1010,7 +1010,7 @@ describe("session.message-v2.toModelMessage", () => {
type: "text",
text: "partial answer",
},
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
{
info: assistantInfo(assistantID2, "m-parent", aborted),
@@ -1025,7 +1025,7 @@ describe("session.message-v2.toModelMessage", () => {
text: "thinking",
time: { start: 0 },
},
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
]
@@ -1065,7 +1065,7 @@ describe("session.message-v2.toModelMessage", () => {
index: 0,
},
]
const input: SessionLegacy.WithParts[] = [
const input: SessionV1.WithParts[] = [
{
info: assistantInfo(assistantID, "m-parent", undefined, {
providerID: openrouterModel.providerID,
@@ -1088,7 +1088,7 @@ describe("session.message-v2.toModelMessage", () => {
type: "text",
text: "answer",
},
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
]
@@ -1116,7 +1116,7 @@ describe("session.message-v2.toModelMessage", () => {
test("splits assistant messages on step-start boundaries", async () => {
const assistantID = "m-assistant"
const input: SessionLegacy.WithParts[] = [
const input: SessionV1.WithParts[] = [
{
info: assistantInfo(assistantID, "m-parent"),
parts: [
@@ -1134,7 +1134,7 @@ describe("session.message-v2.toModelMessage", () => {
type: "text",
text: "second",
},
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
]
@@ -1153,7 +1153,7 @@ describe("session.message-v2.toModelMessage", () => {
test("drops messages that only contain step-start parts", async () => {
const assistantID = "m-assistant"
const input: SessionLegacy.WithParts[] = [
const input: SessionV1.WithParts[] = [
{
info: assistantInfo(assistantID, "m-parent"),
parts: [
@@ -1161,7 +1161,7 @@ describe("session.message-v2.toModelMessage", () => {
...basePart(assistantID, "p1"),
type: "step-start",
},
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
]
@@ -1172,7 +1172,7 @@ describe("session.message-v2.toModelMessage", () => {
const userID = "m-user"
const assistantID = "m-assistant"
const input: SessionLegacy.WithParts[] = [
const input: SessionV1.WithParts[] = [
{
info: userInfo(userID),
parts: [
@@ -1181,7 +1181,7 @@ describe("session.message-v2.toModelMessage", () => {
type: "text",
text: "run tool",
},
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
{
info: assistantInfo(assistantID, userID),
@@ -1208,7 +1208,7 @@ describe("session.message-v2.toModelMessage", () => {
time: { start: 0 },
},
},
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
]
@@ -1261,7 +1261,7 @@ describe("session.message-v2.toModelMessage", () => {
test("substitutes space for empty text between signed reasoning blocks", async () => {
// Reproduces the bug pattern: [reasoning(sig), text(""), reasoning(sig), text(full)]
const assistantID = "m-assistant"
const input: SessionLegacy.WithParts[] = [
const input: SessionV1.WithParts[] = [
{
info: assistantInfo(assistantID, "m-parent"),
parts: [
@@ -1281,7 +1281,7 @@ describe("session.message-v2.toModelMessage", () => {
metadata: { anthropic: { signature: "sig2" } },
},
{ ...basePart(assistantID, "p6"), type: "text", text: "the answer" },
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
]
@@ -1297,7 +1297,7 @@ describe("session.message-v2.toModelMessage", () => {
// Bedrock signed reasoning is preserved as reasoning metadata, but unlike the
// direct Anthropic path we do not preserve empty text separators for Bedrock.
const assistantID = "m-assistant-bedrock"
const input: SessionLegacy.WithParts[] = [
const input: SessionV1.WithParts[] = [
{
info: assistantInfo(assistantID, "m-parent"),
parts: [
@@ -1309,7 +1309,7 @@ describe("session.message-v2.toModelMessage", () => {
},
{ ...basePart(assistantID, "p2"), type: "text", text: "" },
{ ...basePart(assistantID, "p3"), type: "text", text: "answer" },
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
]
@@ -1324,14 +1324,14 @@ describe("session.message-v2.toModelMessage", () => {
// Non-Anthropic providers' reasoning doesn't position-validate, so empty text
// should be filtered normally rather than substituted.
const assistantID = "m-assistant-unsigned"
const input: SessionLegacy.WithParts[] = [
const input: SessionV1.WithParts[] = [
{
info: assistantInfo(assistantID, "m-parent"),
parts: [
{ ...basePart(assistantID, "p1"), type: "reasoning", text: "thinking" },
{ ...basePart(assistantID, "p2"), type: "text", text: "" },
{ ...basePart(assistantID, "p3"), type: "text", text: "answer" },
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
]
@@ -1344,13 +1344,13 @@ describe("session.message-v2.toModelMessage", () => {
test("leaves empty text alone in assistant messages without reasoning", async () => {
const assistantID = "m-assistant-no-reasoning"
const input: SessionLegacy.WithParts[] = [
const input: SessionV1.WithParts[] = [
{
info: assistantInfo(assistantID, "m-parent"),
parts: [
{ ...basePart(assistantID, "p1"), type: "text", text: "" },
{ ...basePart(assistantID, "p2"), type: "text", text: "hello" },
] as SessionLegacy.Part[],
] as SessionV1.Part[],
},
]
@@ -1462,7 +1462,7 @@ describe("session.message-v2.fromError", () => {
isRetryable: false,
})
const result = MessageV2.fromError(error, { providerID })
expect(SessionLegacy.ContextOverflowError.isInstance(result)).toBe(true)
expect(SessionV1.ContextOverflowError.isInstance(result)).toBe(true)
})
})
@@ -1483,7 +1483,7 @@ describe("session.message-v2.fromError", () => {
isRetryable: false,
})
const result = MessageV2.fromError(error, { providerID })
expect(SessionLegacy.ContextOverflowError.isInstance(result)).toBe(true)
expect(SessionV1.ContextOverflowError.isInstance(result)).toBe(true)
})
test("does not classify 429 no body as context overflow", () => {
@@ -1498,8 +1498,8 @@ describe("session.message-v2.fromError", () => {
}),
{ providerID },
)
expect(SessionLegacy.ContextOverflowError.isInstance(result)).toBe(false)
expect(SessionLegacy.APIError.isInstance(result)).toBe(true)
expect(SessionV1.ContextOverflowError.isInstance(result)).toBe(false)
expect(SessionV1.APIError.isInstance(result)).toBe(true)
})
test("serializes unknown inputs", () => {
@@ -1534,9 +1534,9 @@ describe("session.message-v2.fromError", () => {
const result = MessageV2.fromError(zlibError, { providerID })
expect(SessionLegacy.APIError.isInstance(result)).toBe(true)
expect((result as SessionLegacy.APIError).data.isRetryable).toBe(true)
expect((result as SessionLegacy.APIError).data.message).toInclude("decompression")
expect(SessionV1.APIError.isInstance(result)).toBe(true)
expect((result as SessionV1.APIError).data.isRetryable).toBe(true)
expect((result as SessionV1.APIError).data.message).toInclude("decompression")
})
test("classifies ZlibError as AbortedError when abort context is provided", () => {
@@ -1560,21 +1560,21 @@ describe("session.message-v2.latest", () => {
const CONTINUE_USER = MessageID.make("msg_005")
const NEW_COMPACTION_USER = MessageID.make("msg_006")
const tailUser: SessionLegacy.WithParts = {
const tailUser: SessionV1.WithParts = {
info: userInfo(TAIL_USER),
parts: [{ ...basePart(TAIL_USER, "p1"), type: "text", text: "original prompt" }] as SessionLegacy.Part[],
parts: [{ ...basePart(TAIL_USER, "p1"), type: "text", text: "original prompt" }] as SessionV1.Part[],
}
const overflowAssistant: SessionLegacy.WithParts = {
const overflowAssistant: SessionV1.WithParts = {
info: {
...assistantInfo(OVERFLOW_ASSISTANT, TAIL_USER),
finish: "tool-calls",
tokens: { input: 280_000, output: 200, reasoning: 0, cache: { read: 0, write: 0 }, total: 280_200 },
} as SessionLegacy.Assistant,
} as SessionV1.Assistant,
parts: [],
}
const compactionUser: SessionLegacy.WithParts = {
const compactionUser: SessionV1.WithParts = {
info: userInfo(COMPACTION_USER),
parts: [
{
@@ -1583,20 +1583,20 @@ describe("session.message-v2.latest", () => {
auto: true,
tail_start_id: TAIL_USER,
},
] as SessionLegacy.Part[],
] as SessionV1.Part[],
}
const summaryAssistant: SessionLegacy.WithParts = {
const summaryAssistant: SessionV1.WithParts = {
info: {
...assistantInfo(SUMMARY_ASSISTANT, COMPACTION_USER),
summary: true,
finish: "stop",
tokens: { input: 150_000, output: 1_500, reasoning: 0, cache: { read: 0, write: 0 }, total: 151_500 },
} as SessionLegacy.Assistant,
} as SessionV1.Assistant,
parts: [],
}
const continueUser: SessionLegacy.WithParts = {
const continueUser: SessionV1.WithParts = {
info: userInfo(CONTINUE_USER),
parts: [
{
@@ -1606,7 +1606,7 @@ describe("session.message-v2.latest", () => {
synthetic: true,
metadata: { compaction_continue: true },
},
] as SessionLegacy.Part[],
] as SessionV1.Part[],
}
// Regression for double auto-compaction. The reorder in filterCompacted
@@ -1632,7 +1632,7 @@ describe("session.message-v2.latest", () => {
})
test("a fresh compaction-user newer than the latest summary surfaces in tasks", () => {
const newCompactionUser: SessionLegacy.WithParts = {
const newCompactionUser: SessionV1.WithParts = {
info: userInfo(NEW_COMPACTION_USER),
parts: [
{
@@ -1640,7 +1640,7 @@ describe("session.message-v2.latest", () => {
type: "compaction",
auto: true,
},
] as SessionLegacy.Part[],
] as SessionV1.Part[],
}
const state = MessageV2.latest([

View File

@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test"
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
import { SessionV1 } from "@opencode-ai/core/v1/session"
import { Database } from "@opencode-ai/core/database/database"
import { Effect, Layer, Option } from "effect"
import { Session as SessionNs } from "@/session/session"
@@ -48,7 +48,7 @@ const fill = Effect.fn("Test.fill")(function* (
model: { providerID: "test", modelID: "test" },
tools: {},
mode: "",
} as unknown as SessionLegacy.Info)
} as unknown as SessionV1.Info)
yield* session.updatePart({
id: PartID.ascending(),
sessionID,
@@ -72,7 +72,7 @@ const addUser = Effect.fn("Test.addUser")(function* (sessionID: SessionID, text?
model: { providerID: "test", modelID: "test" },
tools: {},
mode: "",
} as unknown as SessionLegacy.Info)
} as unknown as SessionV1.Info)
if (text) {
yield* session.updatePart({
id: PartID.ascending(),
@@ -88,7 +88,7 @@ const addUser = Effect.fn("Test.addUser")(function* (sessionID: SessionID, text?
const addAssistant = Effect.fn("Test.addAssistant")(function* (
sessionID: SessionID,
parentID: MessageID,
opts?: { summary?: boolean; finish?: string; error?: SessionLegacy.Assistant["error"] },
opts?: { summary?: boolean; finish?: string; error?: SessionV1.Assistant["error"] },
) {
const session = yield* SessionNs.Service
const id = MessageID.ascending()
@@ -108,7 +108,7 @@ const addAssistant = Effect.fn("Test.addAssistant")(function* (
summary: opts?.summary,
finish: opts?.finish,
error: opts?.error,
} as unknown as SessionLegacy.Info)
} as unknown as SessionV1.Info)
return id
})
@@ -388,7 +388,7 @@ describe("MessageV2.parts", () => {
const result = yield* MessageV2.parts(id)
expect(result).toHaveLength(1)
expect(result[0].type).toBe("text")
expect((result[0] as SessionLegacy.TextPart).text).toBe("m0")
expect((result[0] as SessionV1.TextPart).text).toBe("m0")
}),
),
)
@@ -426,9 +426,9 @@ describe("MessageV2.parts", () => {
const result = yield* MessageV2.parts(id)
expect(result).toHaveLength(3)
expect((result[0] as SessionLegacy.TextPart).text).toBe("m0")
expect((result[1] as SessionLegacy.TextPart).text).toBe("second")
expect((result[2] as SessionLegacy.TextPart).text).toBe("third")
expect((result[0] as SessionV1.TextPart).text).toBe("m0")
expect((result[1] as SessionV1.TextPart).text).toBe("second")
expect((result[2] as SessionV1.TextPart).text).toBe("third")
}),
),
)
@@ -465,7 +465,7 @@ describe("MessageV2.get", () => {
expect(result.info.sessionID).toBe(sessionID)
expect(result.info.role).toBe("user")
expect(result.parts).toHaveLength(1)
expect((result.parts[0] as SessionLegacy.TextPart).text).toBe("m0")
expect((result.parts[0] as SessionV1.TextPart).text).toBe("m0")
}),
),
)
@@ -535,7 +535,7 @@ describe("MessageV2.get", () => {
const result = yield* MessageV2.get({ sessionID, messageID: aid })
expect(result.info.role).toBe("assistant")
expect(result.parts).toHaveLength(1)
expect((result.parts[0] as SessionLegacy.TextPart).text).toBe("response")
expect((result.parts[0] as SessionV1.TextPart).text).toBe("response")
}),
),
)
@@ -671,10 +671,10 @@ describe("MessageV2.filterCompacted", () => {
const u1 = yield* addUser(sessionID, "hello")
yield* addCompactionPart(sessionID, u1)
const error = new SessionLegacy.APIError({
const error = new SessionV1.APIError({
message: "boom",
isRetryable: true,
}).toObject() as SessionLegacy.Assistant["error"]
}).toObject() as SessionV1.Assistant["error"]
yield* addAssistant(sessionID, u1, { summary: true, finish: "end_turn", error })
yield* addUser(sessionID, "retry")
@@ -950,7 +950,7 @@ describe("MessageV2.filterCompacted", () => {
test("works with array input", () => {
// filterCompacted accepts any Iterable, not just generators
const id = MessageID.ascending()
const items: SessionLegacy.WithParts[] = [
const items: SessionV1.WithParts[] = [
{
info: {
id,
@@ -959,8 +959,8 @@ describe("MessageV2.filterCompacted", () => {
time: { created: 1 },
agent: "test",
model: { providerID: "test", modelID: "test" },
} as unknown as SessionLegacy.Info,
parts: [{ type: "text", text: "hello" }] as unknown as SessionLegacy.Part[],
} as unknown as SessionV1.Info,
parts: [{ type: "text", text: "hello" }] as unknown as SessionV1.Part[],
},
]
const result = MessageV2.filterCompacted(items)
@@ -1026,7 +1026,7 @@ describe("MessageV2 consistency", () => {
const streamed = yield* MessageV2.stream(sessionID)
const paged = [] as SessionLegacy.WithParts[]
const paged = [] as SessionV1.WithParts[]
let cursor: string | undefined
while (true) {
const result = yield* MessageV2.page({ sessionID, limit: 3, before: cursor })

View File

@@ -1,5 +1,5 @@
import { NodeFileSystem } from "@effect/platform-node"
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
import { SessionV1 } from "@opencode-ai/core/v1/session"
import { Database } from "@opencode-ai/core/database/database"
import { EventV2Bridge } from "@/event-v2-bridge"
import { expect } from "bun:test"
@@ -146,7 +146,7 @@ const assistant = Effect.fn("TestSession.assistant")(function* (
root: string,
) {
const session = yield* Session.Service
const msg: SessionLegacy.Assistant = {
const msg: SessionV1.Assistant = {
id: MessageID.ascending(),
role: "assistant",
sessionID,
@@ -236,7 +236,7 @@ it.live("session.processor effect tests capture llm input cleanly", () =>
time: parent.time,
agent: parent.agent,
model: { providerID: ref.providerID, modelID: ref.modelID },
} satisfies SessionLegacy.User,
} satisfies SessionV1.User,
sessionID: chat.id,
model: mdl,
agent: agent(),
@@ -309,7 +309,7 @@ it.live("session.processor effect tests preserve text start time", () =>
time: parent.time,
agent: parent.agent,
model: { providerID: ref.providerID, modelID: ref.modelID },
} satisfies SessionLegacy.User,
} satisfies SessionV1.User,
sessionID: chat.id,
model: mdl,
agent: agent(),
@@ -321,7 +321,7 @@ it.live("session.processor effect tests preserve text start time", () =>
yield* waitFor(
MessageV2.parts(msg.id).pipe(
Effect.map((parts) => parts.find((part): part is SessionLegacy.TextPart => part.type === "text")),
Effect.map((parts) => parts.find((part): part is SessionV1.TextPart => part.type === "text")),
Effect.provideService(Database.Service, database),
),
"timed out waiting for text part",
@@ -331,7 +331,7 @@ it.live("session.processor effect tests preserve text start time", () =>
const exit = yield* Fiber.await(run)
const text = (yield* MessageV2.parts(msg.id)).find(
(part): part is SessionLegacy.TextPart => part.type === "text",
(part): part is SessionV1.TextPart => part.type === "text",
)
expect(Exit.isSuccess(exit)).toBe(true)
@@ -373,7 +373,7 @@ it.live("session.processor effect tests stop after token overflow requests compa
time: parent.time,
agent: parent.agent,
model: { providerID: ref.providerID, modelID: ref.modelID },
} satisfies SessionLegacy.User,
} satisfies SessionV1.User,
sessionID: chat.id,
model: mdl,
agent: agent(),
@@ -419,7 +419,7 @@ it.live("session.processor effect tests capture reasoning from http mock", () =>
time: parent.time,
agent: parent.agent,
model: { providerID: ref.providerID, modelID: ref.modelID },
} satisfies SessionLegacy.User,
} satisfies SessionV1.User,
sessionID: chat.id,
model: mdl,
agent: agent(),
@@ -429,8 +429,8 @@ it.live("session.processor effect tests capture reasoning from http mock", () =>
})
const parts = yield* MessageV2.parts(msg.id)
const reasoning = parts.find((part): part is SessionLegacy.ReasoningPart => part.type === "reasoning")
const text = parts.find((part): part is SessionLegacy.TextPart => part.type === "text")
const reasoning = parts.find((part): part is SessionV1.ReasoningPart => part.type === "reasoning")
const text = parts.find((part): part is SessionV1.TextPart => part.type === "text")
expect(value).toBe("continue")
expect(yield* llm.calls).toBe(1)
@@ -467,7 +467,7 @@ it.live("session.processor effect tests reset reasoning state across retries", (
time: parent.time,
agent: parent.agent,
model: { providerID: ref.providerID, modelID: ref.modelID },
} satisfies SessionLegacy.User,
} satisfies SessionV1.User,
sessionID: chat.id,
model: mdl,
agent: agent(),
@@ -477,7 +477,7 @@ it.live("session.processor effect tests reset reasoning state across retries", (
})
const parts = yield* MessageV2.parts(msg.id)
const reasoning = parts.filter((part): part is SessionLegacy.ReasoningPart => part.type === "reasoning")
const reasoning = parts.filter((part): part is SessionV1.ReasoningPart => part.type === "reasoning")
expect(value).toBe("continue")
expect(yield* llm.calls).toBe(2)
@@ -514,7 +514,7 @@ it.live("session.processor effect tests do not retry unknown json errors", () =>
time: parent.time,
agent: parent.agent,
model: { providerID: ref.providerID, modelID: ref.modelID },
} satisfies SessionLegacy.User,
} satisfies SessionV1.User,
sessionID: chat.id,
model: mdl,
agent: agent(),
@@ -558,7 +558,7 @@ it.live("session.processor effect tests retry recognized structured json errors"
time: parent.time,
agent: parent.agent,
model: { providerID: ref.providerID, modelID: ref.modelID },
} satisfies SessionLegacy.User,
} satisfies SessionV1.User,
sessionID: chat.id,
model: mdl,
agent: agent(),
@@ -613,7 +613,7 @@ it.live("session.processor effect tests publish retry status updates", () =>
time: parent.time,
agent: parent.agent,
model: { providerID: ref.providerID, modelID: ref.modelID },
} satisfies SessionLegacy.User,
} satisfies SessionV1.User,
sessionID: chat.id,
model: mdl,
agent: agent(),
@@ -658,7 +658,7 @@ it.live("session.processor effect tests compact on structured context overflow",
time: parent.time,
agent: parent.agent,
model: { providerID: ref.providerID, modelID: ref.modelID },
} satisfies SessionLegacy.User,
} satisfies SessionV1.User,
sessionID: chat.id,
model: mdl,
agent: agent(),
@@ -701,7 +701,7 @@ it.live("session.processor effect tests complete AI SDK tool calls when native f
time: parent.time,
agent: parent.agent,
model: { providerID: ref.providerID, modelID: ref.modelID },
} satisfies SessionLegacy.User,
} satisfies SessionV1.User,
sessionID: chat.id,
model: mdl,
agent: agent(),
@@ -721,7 +721,7 @@ it.live("session.processor effect tests complete AI SDK tool calls when native f
})
const parts = yield* MessageV2.parts(msg.id)
const call = parts.find((part): part is SessionLegacy.ToolPart => part.type === "tool")
const call = parts.find((part): part is SessionV1.ToolPart => part.type === "tool")
expect(value).toBe("continue")
expect(yield* llm.calls).toBe(1)
@@ -768,7 +768,7 @@ it.live("session.processor effect tests mark pending tools as aborted on cleanup
time: parent.time,
agent: parent.agent,
model: { providerID: ref.providerID, modelID: ref.modelID },
} satisfies SessionLegacy.User,
} satisfies SessionV1.User,
sessionID: chat.id,
model: mdl,
agent: agent(),
@@ -781,7 +781,7 @@ it.live("session.processor effect tests mark pending tools as aborted on cleanup
yield* llm.wait(1)
yield* waitFor(
MessageV2.parts(msg.id).pipe(
Effect.map((parts) => parts.find((part): part is SessionLegacy.ToolPart => part.type === "tool")),
Effect.map((parts) => parts.find((part): part is SessionV1.ToolPart => part.type === "tool")),
Effect.provideService(Database.Service, database),
),
"timed out waiting for tool part",
@@ -790,7 +790,7 @@ it.live("session.processor effect tests mark pending tools as aborted on cleanup
const exit = yield* Fiber.await(run)
const parts = yield* MessageV2.parts(msg.id)
const call = parts.find((part): part is SessionLegacy.ToolPart => part.type === "tool")
const call = parts.find((part): part is SessionV1.ToolPart => part.type === "tool")
expect(Exit.isFailure(exit)).toBe(true)
if (Exit.isFailure(exit)) {
@@ -847,7 +847,7 @@ it.live("session.processor effect tests record aborted errors and idle state", (
time: parent.time,
agent: parent.agent,
model: { providerID: ref.providerID, modelID: ref.modelID },
} satisfies SessionLegacy.User,
} satisfies SessionV1.User,
sessionID: chat.id,
model: mdl,
agent: agent(),
@@ -910,7 +910,7 @@ it.live("session.processor effect tests mark interruptions aborted without manua
time: parent.time,
agent: parent.agent,
model: { providerID: ref.providerID, modelID: ref.modelID },
} satisfies SessionLegacy.User,
} satisfies SessionV1.User,
sessionID: chat.id,
model: mdl,
agent: agent(),

View File

@@ -1,5 +1,6 @@
import { NodeFileSystem } from "@effect/platform-node"
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
import { ConfigV1 } from "@opencode-ai/core/v1/config/config"
import { SessionV1 } from "@opencode-ai/core/v1/session"
import { Database } from "@opencode-ai/core/database/database"
import { eq } from "drizzle-orm"
import { EventV2Bridge } from "@/event-v2-bridge"
@@ -91,20 +92,20 @@ function withSh<A, E, R>(fx: () => Effect.Effect<A, E, R>) {
)
}
function toolPart(parts: SessionLegacy.Part[]) {
return parts.find((part): part is SessionLegacy.ToolPart => part.type === "tool")
function toolPart(parts: SessionV1.Part[]) {
return parts.find((part): part is SessionV1.ToolPart => part.type === "tool")
}
type CompletedToolPart = SessionLegacy.ToolPart & { state: SessionLegacy.ToolStateCompleted }
type ErrorToolPart = SessionLegacy.ToolPart & { state: SessionLegacy.ToolStateError }
type CompletedToolPart = SessionV1.ToolPart & { state: SessionV1.ToolStateCompleted }
type ErrorToolPart = SessionV1.ToolPart & { state: SessionV1.ToolStateError }
function completedTool(parts: SessionLegacy.Part[]) {
function completedTool(parts: SessionV1.Part[]) {
const part = toolPart(parts)
expect(part?.state.status).toBe("completed")
return part?.state.status === "completed" ? (part as CompletedToolPart) : undefined
}
function errorTool(parts: SessionLegacy.Part[]) {
function errorTool(parts: SessionV1.Part[]) {
const part = toolPart(parts)
expect(part?.state.status).toBe("error")
return part?.state.status === "error" ? (part as ErrorToolPart) : undefined
@@ -305,14 +306,14 @@ const ensureDir = Effect.fn("test.ensureDir")(function* (dir: string) {
yield* fs.ensureDir(dir)
})
const writeConfig = Effect.fn("test.writeConfig")(function* (dir: string, config: Partial<Config.Info>) {
const writeConfig = Effect.fn("test.writeConfig")(function* (dir: string, config: Partial<ConfigV1.Info>) {
yield* writeText(
path.join(dir, "opencode.json"),
JSON.stringify({ $schema: "https://opencode.ai/config.json", ...config }),
)
})
const useServerConfig = Effect.fn("test.useServerConfig")(function* (config: (url: string) => Partial<Config.Info>) {
const useServerConfig = Effect.fn("test.useServerConfig")(function* (config: (url: string) => Partial<ConfigV1.Info>) {
const { directory: dir } = yield* TestInstance
const llm = yield* TestLLMServer
yield* writeConfig(dir, config(llm.url))
@@ -389,7 +390,7 @@ const user = Effect.fn("test.user")(function* (sessionID: SessionID, text: strin
const seed = Effect.fn("test.seed")(function* (sessionID: SessionID, opts?: { finish?: string }) {
const session = yield* Session.Service
const msg = yield* user(sessionID, "hello")
const assistant: SessionLegacy.Assistant = {
const assistant: SessionV1.Assistant = {
id: MessageID.ascending(),
role: "assistant",
parentID: msg.id,
@@ -782,7 +783,7 @@ it.instance(
Effect.gen(function* () {
const msgs = yield* MessageV2.filterCompactedEffect(chat.id)
const taskMsg = msgs.find((item) => item.info.role === "assistant" && item.info.agent === "general")
const tool = taskMsg?.parts.find((part): part is SessionLegacy.ToolPart => part.type === "tool")
const tool = taskMsg?.parts.find((part): part is SessionV1.ToolPart => part.type === "tool")
if (tool?.state.status === "running" && tool.state.metadata?.sessionId) return tool
}),
"timed out waiting for running subtask metadata",
@@ -825,7 +826,7 @@ it.instance(
const msgs = yield* MessageV2.filterCompactedEffect(chat.id)
const assistant = msgs.findLast((item) => item.info.role === "assistant" && item.info.agent === "build")
const tool = assistant?.parts.find(
(part): part is SessionLegacy.ToolPart => part.type === "tool" && part.tool === "task",
(part): part is SessionV1.ToolPart => part.type === "tool" && part.tool === "task",
)
if (tool?.state.status === "running" && tool.state.metadata?.sessionId) return tool
}),
@@ -1946,11 +1947,11 @@ noLLMServer.instance(
"Use @docs and @docs/README.md and @docs/guide and @docs/missing.md and @docs/README.md and @build",
)
const references = parts.filter(
(part): part is SessionLegacy.TextPartInput =>
(part): part is SessionV1.TextPartInput =>
part.type === "text" && part.synthetic === true && part.text.startsWith("Referenced configured reference "),
)
const files = parts.filter((part): part is SessionLegacy.FilePartInput => part.type === "file")
const agents = parts.filter((part): part is SessionLegacy.AgentPartInput => part.type === "agent")
const files = parts.filter((part): part is SessionV1.FilePartInput => part.type === "file")
const agents = parts.filter((part): part is SessionV1.AgentPartInput => part.type === "agent")
const bare = references.find((part) => part.text.includes("@docs."))
const missing = references.find((part) => part.text.includes("@docs/missing.md"))
const guide = files.find((part) => part.filename === "docs/guide")
@@ -2003,7 +2004,7 @@ noLLMServer.instance(
const stored = yield* MessageV2.get({ sessionID: session.id, messageID: message.info.id })
const synthetic = stored.parts.filter(
(part): part is SessionLegacy.TextPart => part.type === "text" && part.synthetic === true,
(part): part is SessionV1.TextPart => part.type === "text" && part.synthetic === true,
)
const reference = synthetic.find((part) => part.text.startsWith("Referenced configured reference @docs."))
@@ -2058,7 +2059,7 @@ noLLMServer.instance(
const stored = yield* MessageV2.get({ sessionID: session.id, messageID: message.info.id })
const synthetic = stored.parts.filter(
(part): part is SessionLegacy.TextPart => part.type === "text" && part.synthetic === true,
(part): part is SessionV1.TextPart => part.type === "text" && part.synthetic === true,
)
const reference = synthetic.find((part) =>
part.text.startsWith("Referenced configured reference @docs/README.md."),

View File

@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test"
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
import { SessionV1 } from "@opencode-ai/core/v1/session"
import type { NamedError } from "@opencode-ai/core/util/error"
import { APICallError } from "ai"
import { setTimeout as sleep } from "node:timers/promises"
@@ -17,9 +17,9 @@ const providerID = ProviderV2.ID.make("test")
const retryProvider = "test"
const it = testEffect(Layer.mergeAll(SessionStatus.defaultLayer, CrossSpawnSpawner.defaultLayer))
function apiError(headers?: Record<string, string>): SessionLegacy.APIError {
return Schema.decodeUnknownSync(SessionLegacy.APIError.Schema)(
new SessionLegacy.APIError({
function apiError(headers?: Record<string, string>): SessionV1.APIError {
return Schema.decodeUnknownSync(SessionV1.APIError.Schema)(
new SessionV1.APIError({
message: "boom",
isRetryable: true,
responseHeaders: headers,
@@ -94,7 +94,7 @@ describe("session.retry.delay", () => {
const step = yield* Schedule.toStepWithMetadata(
SessionRetry.policy({
provider: "test",
parse: Schema.decodeUnknownSync(SessionLegacy.APIError.Schema),
parse: Schema.decodeUnknownSync(SessionV1.APIError.Schema),
set: (info) =>
status.set(sessionID, {
type: "retry",
@@ -164,7 +164,7 @@ describe("session.retry.retryable", () => {
test("retries transport timeout errors", () => {
const request = MessageV2.fromError(new ProviderError.HeaderTimeoutError(10000), { providerID })
expect(SessionLegacy.APIError.isInstance(request)).toBe(true)
expect(SessionV1.APIError.isInstance(request)).toBe(true)
expect(SessionRetry.retryable(request, retryProvider)).toEqual({
message: "Provider response headers timed out after 10000ms",
})
@@ -175,14 +175,14 @@ describe("session.retry.retryable", () => {
new ProviderError.ResponseStreamError("WebSocket closed before response.completed (code 1006: Connection ended)"),
{ providerID },
)
expect(SessionLegacy.APIError.isInstance(request)).toBe(true)
expect(SessionV1.APIError.isInstance(request)).toBe(true)
expect(SessionRetry.retryable(request, retryProvider)).toEqual({
message: "WebSocket closed before response.completed (code 1006: Connection ended)",
})
})
test("does not retry context overflow errors", () => {
const error = new SessionLegacy.ContextOverflowError({
const error = new SessionV1.ContextOverflowError({
message: "Input exceeds context window of this model",
responseBody: '{"error":{"code":"context_length_exceeded"}}',
}).toObject()
@@ -191,8 +191,8 @@ describe("session.retry.retryable", () => {
})
test("retries 500 errors even when isRetryable is false", () => {
const error = Schema.decodeUnknownSync(SessionLegacy.APIError.Schema)(
new SessionLegacy.APIError({
const error = Schema.decodeUnknownSync(SessionV1.APIError.Schema)(
new SessionV1.APIError({
message: "Internal server error",
isRetryable: false,
statusCode: 500,
@@ -204,8 +204,8 @@ describe("session.retry.retryable", () => {
})
test("retries 502 bad gateway errors", () => {
const error = Schema.decodeUnknownSync(SessionLegacy.APIError.Schema)(
new SessionLegacy.APIError({
const error = Schema.decodeUnknownSync(SessionV1.APIError.Schema)(
new SessionV1.APIError({
message: "Bad gateway",
isRetryable: false,
statusCode: 502,
@@ -216,8 +216,8 @@ describe("session.retry.retryable", () => {
})
test("retries 503 service unavailable errors", () => {
const error = Schema.decodeUnknownSync(SessionLegacy.APIError.Schema)(
new SessionLegacy.APIError({
const error = Schema.decodeUnknownSync(SessionV1.APIError.Schema)(
new SessionV1.APIError({
message: "Service unavailable",
isRetryable: false,
statusCode: 503,
@@ -228,8 +228,8 @@ describe("session.retry.retryable", () => {
})
test("does not retry 4xx errors when isRetryable is false", () => {
const error = Schema.decodeUnknownSync(SessionLegacy.APIError.Schema)(
new SessionLegacy.APIError({
const error = Schema.decodeUnknownSync(SessionV1.APIError.Schema)(
new SessionV1.APIError({
message: "Bad request",
isRetryable: false,
statusCode: 400,
@@ -240,8 +240,8 @@ describe("session.retry.retryable", () => {
})
test("retries ZlibError decompression failures", () => {
const error = Schema.decodeUnknownSync(SessionLegacy.APIError.Schema)(
new SessionLegacy.APIError({
const error = Schema.decodeUnknownSync(SessionV1.APIError.Schema)(
new SessionV1.APIError({
message: "Response decompression failed",
isRetryable: true,
metadata: { code: "ZlibError" },
@@ -254,8 +254,8 @@ describe("session.retry.retryable", () => {
})
test("maps free limits to Go upsell action", () => {
const error = Schema.decodeUnknownSync(SessionLegacy.APIError.Schema)(
new SessionLegacy.APIError({
const error = Schema.decodeUnknownSync(SessionV1.APIError.Schema)(
new SessionV1.APIError({
message: "Free usage exceeded",
isRetryable: true,
statusCode: 429,
@@ -280,8 +280,8 @@ describe("session.retry.retryable", () => {
})
test("maps Go subscription limits to workspace PAYG upsell", () => {
const error = Schema.decodeUnknownSync(SessionLegacy.APIError.Schema)(
new SessionLegacy.APIError({
const error = Schema.decodeUnknownSync(SessionV1.APIError.Schema)(
new SessionV1.APIError({
message: "Subscription quota exceeded. You can continue using free models.",
isRetryable: true,
statusCode: 429,
@@ -318,8 +318,8 @@ describe("session.retry.retryable", () => {
})
test("maps Go subscription limits without limit metadata", () => {
const error = Schema.decodeUnknownSync(SessionLegacy.APIError.Schema)(
new SessionLegacy.APIError({
const error = Schema.decodeUnknownSync(SessionV1.APIError.Schema)(
new SessionV1.APIError({
message: "Subscription quota exceeded. You can continue using free models.",
isRetryable: true,
statusCode: 429,
@@ -373,8 +373,8 @@ describe("session.message-v2.fromError", () => {
const result = MessageV2.fromError(error, { providerID })
expect(SessionLegacy.APIError.isInstance(result)).toBe(true)
if (!SessionLegacy.APIError.isInstance(result)) throw new Error("expected APIError")
expect(SessionV1.APIError.isInstance(result)).toBe(true)
if (!SessionV1.APIError.isInstance(result)) throw new Error("expected APIError")
expect(result.data.isRetryable).toBe(true)
expect(result.data.message).toBe("Connection reset by server")
expect(result.data.metadata?.code).toBe("ECONNRESET")
@@ -384,8 +384,8 @@ describe("session.message-v2.fromError", () => {
)
test("ECONNRESET socket error is retryable", () => {
const error = Schema.decodeUnknownSync(SessionLegacy.APIError.Schema)(
new SessionLegacy.APIError({
const error = Schema.decodeUnknownSync(SessionV1.APIError.Schema)(
new SessionV1.APIError({
message: "Connection reset by server",
isRetryable: true,
metadata: { code: "ECONNRESET", message: "The socket connection was closed unexpectedly" },
@@ -408,7 +408,7 @@ describe("session.message-v2.fromError", () => {
isRetryable: false,
})
const result = MessageV2.fromError(error, { providerID: ProviderV2.ID.make("openai") })
if (!SessionLegacy.APIError.isInstance(result)) throw new Error("expected APIError")
if (!SessionV1.APIError.isInstance(result)) throw new Error("expected APIError")
expect(result.data.isRetryable).toBe(true)
})
@@ -429,8 +429,8 @@ describe("session.message-v2.fromError", () => {
{ providerID: ProviderV2.ID.make("openai") },
)
expect(SessionLegacy.APIError.isInstance(result)).toBe(true)
if (!SessionLegacy.APIError.isInstance(result)) throw new Error("expected APIError")
expect(SessionV1.APIError.isInstance(result)).toBe(true)
if (!SessionV1.APIError.isInstance(result)) throw new Error("expected APIError")
expect(result.data.isRetryable).toBe(true)
expect(SessionRetry.retryable(result, retryProvider)).toEqual({
message: "An error occurred while processing your request.",

View File

@@ -1,5 +1,5 @@
import { describe, expect } from "bun:test"
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
import { SessionV1 } from "@opencode-ai/core/v1/session"
import fs from "fs/promises"
import path from "path"
import { Effect, Layer } from "effect"
@@ -132,7 +132,7 @@ describe("revert + compact workflow", () => {
text: "Hello, please help me",
})
const assistantMsg1: SessionLegacy.Assistant = {
const assistantMsg1: SessionV1.Assistant = {
id: MessageID.ascending(),
role: "assistant",
sessionID,
@@ -189,7 +189,7 @@ describe("revert + compact workflow", () => {
text: "What's the capital of France?",
})
const assistantMsg2: SessionLegacy.Assistant = {
const assistantMsg2: SessionV1.Assistant = {
id: MessageID.ascending(),
role: "assistant",
sessionID,
@@ -294,7 +294,7 @@ describe("revert + compact workflow", () => {
text: "Hello",
})
const assistantMsg: SessionLegacy.Assistant = {
const assistantMsg: SessionV1.Assistant = {
id: MessageID.ascending(),
role: "assistant",
sessionID,

View File

@@ -1,5 +1,5 @@
import { describe, expect } from "bun:test"
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
import { SessionV1 } from "@opencode-ai/core/v1/session"
import { Database } from "@opencode-ai/core/database/database"
import { SessionProjector } from "@opencode-ai/core/session/projector"
import { Deferred, Effect, Exit, Layer } from "effect"
@@ -122,17 +122,17 @@ describe("step-finish token propagation via event", () => {
model: { providerID: "test", modelID: "test" },
tools: {},
mode: "",
} as unknown as SessionLegacy.Info)
} as unknown as SessionV1.Info)
// Event subscribers receive readonly Schema.Type payloads; `SessionLegacy.Part`
// Event subscribers receive readonly Schema.Type payloads; `SessionV1.Part`
// is the mutable domain type. Cast bridges the two — safe because the
// test only reads the value afterwards.
const received = yield* Deferred.make<SessionLegacy.Part>()
const received = yield* Deferred.make<SessionV1.Part>()
const unsub = yield* events.listen((event) => {
if (event.type === MessageV2.Event.PartUpdated.type)
Deferred.doneUnsafe(
received,
Effect.succeed((event.data as typeof MessageV2.Event.PartUpdated.data.Type).part as SessionLegacy.Part),
Effect.succeed((event.data as typeof MessageV2.Event.PartUpdated.data.Type).part as SessionV1.Part),
)
return Effect.void
})
@@ -160,7 +160,7 @@ describe("step-finish token propagation via event", () => {
const receivedPart = yield* awaitDeferred(received, "timed out waiting for message.part.updated")
expect(receivedPart.type).toBe("step-finish")
const finish = receivedPart as SessionLegacy.StepFinishPart
const finish = receivedPart as SessionV1.StepFinishPart
expect(finish.tokens.input).toBe(500)
expect(finish.tokens.output).toBe(800)
expect(finish.tokens.reasoning).toBe(200)

View File

@@ -22,7 +22,7 @@ import { SessionPrompt } from "../../src/session/prompt"
import { SessionRevert } from "../../src/session/revert"
import { SessionSummary } from "../../src/session/summary"
import { MessageV2 } from "../../src/session/message-v2"
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
import { SessionV1 } from "@opencode-ai/core/v1/session"
import * as Log from "@opencode-ai/core/util/log"
import { provideTmpdirServer } from "../fixture/fixture"
import { testEffect } from "../lib/effect"
@@ -258,11 +258,11 @@ it.live("tool execution produces non-empty session diff (snapshot race)", () =>
// Verify the tool call completed (in the first assistant message)
const allMsgs = yield* MessageV2.filterCompactedEffect(session.id)
const user = allMsgs.find(
(msg): msg is SessionLegacy.WithParts & { info: SessionLegacy.User } => msg.info.role === "user",
(msg): msg is SessionV1.WithParts & { info: SessionV1.User } => msg.info.role === "user",
)
const tool = allMsgs
.flatMap((m) => m.parts)
.find((p): p is SessionLegacy.ToolPart => p.type === "tool" && p.tool === "bash")
.find((p): p is SessionV1.ToolPart => p.type === "tool" && p.tool === "bash")
expect(tool?.state.status).toBe("completed")
if (!user) throw new Error("Expected user message")

View File

@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test"
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
import { SessionV1 } from "@opencode-ai/core/v1/session"
import { Effect, Layer } from "effect"
import { Session } from "@/session/session"
import { SessionPrompt } from "../../src/session/prompt"
@@ -219,7 +219,7 @@ describe("StructuredOutput Integration", () => {
)
test("unit test: StructuredOutputError is properly structured", () => {
const error = new SessionLegacy.StructuredOutputError({
const error = new SessionV1.StructuredOutputError({
message: "Failed to produce valid structured output after 3 attempts",
retries: 3,
})

View File

@@ -1,13 +1,13 @@
import { describe, expect, test } from "bun:test"
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
import { SessionV1 } from "@opencode-ai/core/v1/session"
import { Exit, Schema } from "effect"
import { MessageV2 } from "../../src/session/message-v2"
import { SessionPrompt } from "../../src/session/prompt"
import { SessionID, MessageID } from "../../src/session/schema"
const decodeFormat = Schema.decodeUnknownExit(SessionLegacy.Format)
const decodeUser = Schema.decodeUnknownExit(SessionLegacy.User)
const decodeAssistant = Schema.decodeUnknownExit(SessionLegacy.Assistant)
const decodeFormat = Schema.decodeUnknownExit(SessionV1.Format)
const decodeUser = Schema.decodeUnknownExit(SessionV1.User)
const decodeAssistant = Schema.decodeUnknownExit(SessionV1.Assistant)
describe("structured-output.OutputFormat", () => {
test("parses text format", () => {
@@ -66,7 +66,7 @@ describe("structured-output.OutputFormat", () => {
describe("structured-output.StructuredOutputError", () => {
test("creates error with message and retries", () => {
const error = new SessionLegacy.StructuredOutputError({
const error = new SessionV1.StructuredOutputError({
message: "Failed to validate",
retries: 3,
})
@@ -77,7 +77,7 @@ describe("structured-output.StructuredOutputError", () => {
})
test("converts to object correctly", () => {
const error = new SessionLegacy.StructuredOutputError({
const error = new SessionV1.StructuredOutputError({
message: "Test error",
retries: 2,
})
@@ -89,13 +89,13 @@ describe("structured-output.StructuredOutputError", () => {
})
test("isInstance correctly identifies error", () => {
const error = new SessionLegacy.StructuredOutputError({
const error = new SessionV1.StructuredOutputError({
message: "Test",
retries: 1,
})
expect(SessionLegacy.StructuredOutputError.isInstance(error)).toBe(true)
expect(SessionLegacy.StructuredOutputError.isInstance({ name: "other" })).toBe(false)
expect(SessionV1.StructuredOutputError.isInstance(error)).toBe(true)
expect(SessionV1.StructuredOutputError.isInstance({ name: "other" })).toBe(false)
})
})

View File

@@ -1,4 +1,4 @@
import { PermissionLegacy } from "@opencode-ai/core/permission/legacy"
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
import { describe, expect } from "bun:test"
import path from "path"
import { Effect } from "effect"
@@ -27,7 +27,7 @@ const glob = (p: string) =>
process.platform === "win32" ? Filesystem.normalizePathPattern(p) : p.replaceAll("\\", "/")
function makeCtx() {
const requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
const ctx: Tool.Context = {
...baseCtx,
ask: (req) =>

View File

@@ -1,4 +1,4 @@
import { PermissionLegacy } from "@opencode-ai/core/permission/legacy"
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
import { describe, expect } from "bun:test"
import path from "path"
import { Cause, Effect, Exit, Layer } from "effect"
@@ -53,12 +53,12 @@ const ctx = {
}
const asks = () => {
const items: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const items: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
return {
items,
next: {
...ctx,
ask: (req: Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">) =>
ask: (req: Omit<PermissionV1.Request, "id" | "sessionID" | "tool">) =>
Effect.sync(() => {
items.push(req)
}),

View File

@@ -1,4 +1,4 @@
import { PermissionLegacy } from "@opencode-ai/core/permission/legacy"
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
import { describe, expect } from "bun:test"
import fs from "fs/promises"
import os from "os"
@@ -187,7 +187,7 @@ describe("tool.grep", () => {
[path.join(alias, "*")]: "allow",
},
})
const requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
const next: Tool.Context = {
...ctx,
ask: (req) =>
@@ -235,7 +235,7 @@ describe("tool.grep", () => {
yield* appfs.makeDirectory(remoteDir, { recursive: true }).pipe(Effect.orDie)
yield* git(remoteRoot, ["clone", "--bare", source, remoteRepo])
const requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
const next: Tool.Context = {
...ctx,
ask: (req) =>

View File

@@ -1,4 +1,4 @@
import { PermissionLegacy } from "@opencode-ai/core/permission/legacy"
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
import { afterEach, describe, expect } from "bun:test"
import { Effect, Layer } from "effect"
import path from "path"
@@ -78,12 +78,12 @@ const put = Effect.fn("LspToolTest.put")(function* (file: string) {
})
const asks = () => {
const items: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const items: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
return {
items,
next: {
...ctx,
ask: (req: Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">) =>
ask: (req: Omit<PermissionV1.Request, "id" | "sessionID" | "tool">) =>
Effect.sync(() => {
items.push(req)
}),

View File

@@ -1,4 +1,4 @@
import { PermissionLegacy } from "@opencode-ai/core/permission/legacy"
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
import { afterEach, describe, expect } from "bun:test"
import { Cause, Effect, Exit, Layer, Stream } from "effect"
import path from "path"
@@ -141,12 +141,12 @@ const load = Effect.fn("ReadToolTest.load")(function* (p: string) {
return yield* fs.readFileString(p)
})
const asks = () => {
const items: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const items: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
return {
items,
next: {
...ctx,
ask: (req: Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">) =>
ask: (req: Omit<PermissionV1.Request, "id" | "sessionID" | "tool">) =>
Effect.sync(() => {
items.push(req)
}),
@@ -329,7 +329,7 @@ describe("tool.read env file permissions", () => {
let asked = false
const next = {
...ctx,
ask: (req: Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">) =>
ask: (req: Omit<PermissionV1.Request, "id" | "sessionID" | "tool">) =>
Effect.sync(() => {
for (const pattern of req.patterns) {
const rule = Permission.evaluate(req.permission, pattern, info.permission)
@@ -337,7 +337,7 @@ describe("tool.read env file permissions", () => {
asked = true
}
if (rule.action === "deny") {
throw new PermissionLegacy.DeniedError({ ruleset: info.permission })
throw new PermissionV1.DeniedError({ ruleset: info.permission })
}
}
}),

View File

@@ -1,4 +1,4 @@
import { PermissionLegacy } from "@opencode-ai/core/permission/legacy"
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
import { describe, expect } from "bun:test"
import { Cause, Effect, Exit, Layer } from "effect"
import type * as Scope from "effect/Scope"
@@ -156,9 +156,9 @@ const each = (
}
}
const capture = (requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">>, stop?: Error) => ({
const capture = (requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">>, stop?: Error) => ({
...ctx,
ask: (req: Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">) =>
ask: (req: Omit<PermissionV1.Request, "id" | "sessionID" | "tool">) =>
Effect.sync(() => {
requests.push(req)
if (stop) throw stop
@@ -223,7 +223,7 @@ describe("tool.shell permissions", () => {
yield* runIn(
tmp,
Effect.gen(function* () {
const requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
yield* run(
{
command: "echo hello",
@@ -245,7 +245,7 @@ describe("tool.shell permissions", () => {
yield* runIn(
tmp,
Effect.gen(function* () {
const requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
yield* run(
{
command: "echo foo && echo bar",
@@ -269,7 +269,7 @@ describe("tool.shell permissions", () => {
runIn(
projectRoot,
Effect.gen(function* () {
const requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
yield* run(
{
command: "Write-Host foo; if ($?) { Write-Host bar }",
@@ -298,7 +298,7 @@ describe("tool.shell permissions", () => {
tmp,
Effect.gen(function* () {
const err = new Error("stop after permission")
const requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
expect(
yield* fail(
{
@@ -324,7 +324,7 @@ describe("tool.shell permissions", () => {
projectRoot,
Effect.gen(function* () {
const err = new Error("stop after permission")
const requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
const file = process.platform === "win32" ? `${process.env.WINDIR!.replaceAll("\\", "/")}/*` : "/etc/*"
const want = process.platform === "win32" ? glob(path.join(process.env.WINDIR!, "*")) : "/etc/*"
expect(
@@ -355,7 +355,7 @@ describe("tool.shell permissions", () => {
projectRoot,
Effect.gen(function* () {
const file = path.join(outerTmp, "outside.txt").replaceAll("\\", "/")
const requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
yield* run(
{
command: `echo $(cat "${file}")`,
@@ -384,7 +384,7 @@ describe("tool.shell permissions", () => {
projectRoot,
Effect.gen(function* () {
const err = new Error("stop after permission")
const requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
expect(
yield* fail(
{
@@ -410,7 +410,7 @@ describe("tool.shell permissions", () => {
runIn(
projectRoot,
Effect.gen(function* () {
const requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
const file = `${process.env.WINDIR!.replaceAll("\\", "/")}/win.ini`
yield* run(
{
@@ -441,7 +441,7 @@ describe("tool.shell permissions", () => {
tmp,
Effect.gen(function* () {
const err = new Error("stop after permission")
const requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
expect(
yield* fail(
{
@@ -469,7 +469,7 @@ describe("tool.shell permissions", () => {
projectRoot,
Effect.gen(function* () {
const err = new Error("stop after permission")
const requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
expect(
yield* fail(
{
@@ -498,7 +498,7 @@ describe("tool.shell permissions", () => {
tmp,
Effect.gen(function* () {
const err = new Error("stop after permission")
const requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
expect(
yield* fail(
{
@@ -526,7 +526,7 @@ describe("tool.shell permissions", () => {
projectRoot,
Effect.gen(function* () {
const err = new Error("stop after permission")
const requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
expect(
yield* fail(
{
@@ -561,7 +561,7 @@ describe("tool.shell permissions", () => {
projectRoot,
Effect.gen(function* () {
const err = new Error("stop after permission")
const requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
const root = path.parse(process.env.WINDIR!).root.replace(/[\\/]+$/, "")
expect(
yield* fail(
@@ -594,7 +594,7 @@ describe("tool.shell permissions", () => {
runIn(
projectRoot,
Effect.gen(function* () {
const requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
yield* run(
{
command: "Get-Content $env:WINDIR/win.ini",
@@ -621,7 +621,7 @@ describe("tool.shell permissions", () => {
projectRoot,
Effect.gen(function* () {
const err = new Error("stop after permission")
const requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
expect(
yield* fail(
{
@@ -650,7 +650,7 @@ describe("tool.shell permissions", () => {
projectRoot,
Effect.gen(function* () {
const err = new Error("stop after permission")
const requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
expect(
yield* fail(
{
@@ -678,7 +678,7 @@ describe("tool.shell permissions", () => {
runIn(
projectRoot,
Effect.gen(function* () {
const requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
yield* run(
{
command: "Set-Location C:/Windows",
@@ -706,7 +706,7 @@ describe("tool.shell permissions", () => {
runIn(
projectRoot,
Effect.gen(function* () {
const requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
yield* run(
{
command: "Write-Output ('a' * 3)",
@@ -732,7 +732,7 @@ describe("tool.shell permissions", () => {
runIn(
projectRoot,
Effect.gen(function* () {
const requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
yield* run(
{
command: `TYPE "${path.join(process.env.WINDIR!, "win.ini")}"`,
@@ -756,7 +756,7 @@ describe("tool.shell permissions", () => {
tmp,
Effect.gen(function* () {
const err = new Error("stop after permission")
const requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
expect(
yield* fail(
{
@@ -780,7 +780,7 @@ describe("tool.shell permissions", () => {
tmp,
Effect.gen(function* () {
const err = new Error("stop after permission")
const requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
expect(
yield* fail(
{
@@ -811,7 +811,7 @@ describe("tool.shell permissions", () => {
const want = Filesystem.normalizePathPattern(path.join(outerTmp, "*"))
for (const dir of forms(outerTmp)) {
const requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
expect(
yield* fail(
{
@@ -843,7 +843,7 @@ describe("tool.shell permissions", () => {
projectRoot,
Effect.gen(function* () {
const err = new Error("stop after permission")
const requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
const want = glob(path.join(os.tmpdir(), "*"))
expect(
yield* fail(
@@ -872,7 +872,7 @@ describe("tool.shell permissions", () => {
projectRoot,
Effect.gen(function* () {
const err = new Error("stop after permission")
const requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
const want = glob(path.join(os.tmpdir(), "*"))
expect(
yield* fail(
@@ -904,7 +904,7 @@ describe("tool.shell permissions", () => {
tmp,
Effect.gen(function* () {
const err = new Error("stop after permission")
const requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
const filepath = path.join(outerTmp, "outside.txt")
expect(
yield* fail(
@@ -932,7 +932,7 @@ describe("tool.shell permissions", () => {
yield* runIn(
tmp,
Effect.gen(function* () {
const requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
yield* run(
{
command: `rm -rf ${path.join(tmp, "nested")}`,
@@ -953,7 +953,7 @@ describe("tool.shell permissions", () => {
yield* runIn(
tmp,
Effect.gen(function* () {
const requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
yield* run(
{
command: "git log --oneline -5",
@@ -975,7 +975,7 @@ describe("tool.shell permissions", () => {
yield* runIn(
tmp,
Effect.gen(function* () {
const requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
yield* run(
{
command: "cd .",
@@ -997,7 +997,7 @@ describe("tool.shell permissions", () => {
tmp,
Effect.gen(function* () {
const err = new Error("stop after permission")
const requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
expect(
yield* fail(
{ command: "echo test > output.txt", description: "Redirect test output" },
@@ -1018,7 +1018,7 @@ describe("tool.shell permissions", () => {
yield* runIn(
tmp,
Effect.gen(function* () {
const requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
yield* run({ command: "ls -la", description: "List" }, capture(requests))
const bashReq = requests.find((r) => r.permission === "bash")
expect(bashReq).toBeDefined()

View File

@@ -1,4 +1,4 @@
import { PermissionLegacy } from "@opencode-ai/core/permission/legacy"
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
import { Cause, Effect, Exit, Layer } from "effect"
import { afterEach, describe, expect } from "bun:test"
@@ -68,7 +68,7 @@ Use this skill.
})).find((tool) => tool.id === SkillTool.id)
if (!tool) throw new Error("Skill tool not found")
const requests: Array<Omit<PermissionLegacy.Request, "id" | "sessionID" | "tool">> = []
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
const ctx: Tool.Context = {
...baseCtx,
ask: (req) =>

View File

@@ -1,5 +1,5 @@
import { afterEach, describe, expect } from "bun:test"
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
import { SessionV1 } from "@opencode-ai/core/v1/session"
import { Database } from "@opencode-ai/core/database/database"
import { Effect, Exit, Fiber, Layer } from "effect"
import { Agent } from "../../src/agent/agent"
@@ -69,7 +69,7 @@ const seed = Effect.fn("TaskToolTest.seed")(function* (title = "Pinned") {
model: ref,
time: { created: Date.now() },
})
const assistant: SessionLegacy.Assistant = {
const assistant: SessionV1.Assistant = {
id: MessageID.ascending(),
role: "assistant",
parentID: user.id,
@@ -99,7 +99,7 @@ function stubOps(opts?: { onPrompt?: (input: SessionPrompt.PromptInput) => void;
}
}
function reply(input: SessionPrompt.PromptInput, text: string): SessionLegacy.WithParts {
function reply(input: SessionPrompt.PromptInput, text: string): SessionV1.WithParts {
const id = MessageID.ascending()
return {
info: {

View File

@@ -1,4 +1,5 @@
import { describe, test, expect } from "bun:test"
import { ConfigV1 } from "@opencode-ai/core/v1/config/config"
import { NodeFileSystem } from "@effect/platform-node"
import { FSUtil } from "@opencode-ai/core/fs-util"
import { Effect, FileSystem, Layer } from "effect"
@@ -16,14 +17,14 @@ const ROOT = path.resolve(import.meta.dir, "..", "..")
const it = testEffect(Layer.mergeAll(Truncate.defaultLayer, NodeFileSystem.layer, FSUtil.defaultLayer))
const configuredLayer = (cfg: Config.Info) =>
const configuredLayer = (cfg: ConfigV1.Info) =>
Layer.mergeAll(
Truncate.defaultLayer,
NodeFileSystem.layer,
FSUtil.defaultLayer,
TestConfig.layer({ get: () => Effect.succeed(cfg) }),
)
const configuredIt = (cfg: Config.Info) => testEffect(configuredLayer(cfg))
const configuredIt = (cfg: ConfigV1.Info) => testEffect(configuredLayer(cfg))
describe("Truncate", () => {
describe("output", () => {