refactor(core): move v1 schemas into core (#30473)
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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."),
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user