From aa8ad494b8fb613cb73d8041860a25178dbb78dc Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sun, 24 May 2026 15:14:29 -0400 Subject: [PATCH] refactor(core): centralize legacy session schemas --- packages/core/src/database/database.ts | 8 +- packages/core/src/session/legacy.ts | 496 +++++++++++++++++- packages/core/src/session/sql.ts | 8 +- packages/opencode/src/cli/cmd/debug/agent.ts | 3 +- packages/opencode/src/cli/cmd/export.ts | 9 +- packages/opencode/src/cli/cmd/github.ts | 3 +- packages/opencode/src/cli/cmd/import.ts | 9 +- packages/opencode/src/image/image.ts | 5 +- .../routes/instance/httpapi/groups/session.ts | 15 +- .../instance/httpapi/handlers/session.ts | 5 +- packages/opencode/src/session/compaction.ts | 37 +- packages/opencode/src/session/instruction.ts | 9 +- packages/opencode/src/session/llm.ts | 3 +- packages/opencode/src/session/llm/request.ts | 3 +- packages/opencode/src/session/message-v2.ts | 493 +---------------- packages/opencode/src/session/overflow.ts | 3 +- packages/opencode/src/session/processor.ts | 37 +- packages/opencode/src/session/projectors.ts | 9 +- packages/opencode/src/session/prompt.ts | 85 +-- .../opencode/src/session/prompt/reference.ts | 3 +- packages/opencode/src/session/reminders.ts | 3 +- packages/opencode/src/session/retry.ts | 9 +- packages/opencode/src/session/revert.ts | 7 +- packages/opencode/src/session/run-state.ts | 27 +- packages/opencode/src/session/session.ts | 31 +- packages/opencode/src/session/summary.ts | 5 +- packages/opencode/src/session/tools.ts | 5 +- packages/opencode/src/storage/db.ts | 4 +- packages/opencode/src/tool/plan.ts | 5 +- packages/opencode/src/tool/task.ts | 5 +- packages/opencode/src/tool/task_status.ts | 5 +- packages/opencode/src/tool/tool.ts | 5 +- .../opencode/test/cli/github-action.test.ts | 11 +- .../test/server/httpapi-exercise/runner.ts | 5 +- .../test/server/httpapi-exercise/types.ts | 5 +- .../opencode/test/server/httpapi-sdk.test.ts | 3 +- .../test/server/httpapi-session.test.ts | 7 +- .../test/server/session-messages.test.ts | 13 +- .../opencode/test/session/compaction.test.ts | 7 +- .../opencode/test/session/instruction.test.ts | 3 +- .../test/session/llm-native-recorded.test.ts | 3 +- packages/opencode/test/session/llm.test.ts | 25 +- .../opencode/test/session/message-v2.test.ts | 171 +++--- .../test/session/messages-pagination.test.ts | 33 +- .../test/session/processor-effect.test.ts | 45 +- packages/opencode/test/session/prompt.test.ts | 29 +- packages/opencode/test/session/retry.test.ts | 57 +- .../test/session/revert-compact.test.ts | 7 +- .../opencode/test/session/session.test.ts | 11 +- .../test/session/snapshot-tool-race.test.ts | 3 +- .../structured-output-integration.test.ts | 3 +- .../test/session/structured-output.test.ts | 17 +- packages/opencode/test/tool/task.test.ts | 5 +- 53 files changed, 958 insertions(+), 859 deletions(-) diff --git a/packages/core/src/database/database.ts b/packages/core/src/database/database.ts index 830814619..a8fcdfd73 100644 --- a/packages/core/src/database/database.ts +++ b/packages/core/src/database/database.ts @@ -8,6 +8,7 @@ import { Flag } from "../flag/flag" import { isAbsolute, join } from "path" import { DatabaseMigration } from "./migration" import { InstallationChannel } from "../installation/version" +import { makeRuntime } from "../effect/runtime" const makeDatabase = EffectDrizzleSqlite.makeWithDefaults() type DatabaseShape = Effect.Success @@ -58,7 +59,6 @@ export const defaultLayer = Layer.unwrap( }), ).pipe(Layer.provide(Global.defaultLayer)) -export function init(options: { path?: string } = {}) { - const filename = options.path ?? path() - return Effect.runSync(Service.use(() => Effect.void).pipe(Effect.provide(layerFromPath(filename)))) -} +const { runSync } = makeRuntime(Service, defaultLayer) + +export const init = () => runSync(() => Effect.void) diff --git a/packages/core/src/session/legacy.ts b/packages/core/src/session/legacy.ts index 3210bc7c3..5b804d9ae 100644 --- a/packages/core/src/session/legacy.ts +++ b/packages/core/src/session/legacy.ts @@ -1,8 +1,11 @@ export * as SessionLegacy from "./legacy" -import { Schema } from "effect" +import { Effect, Schema, Types } from "effect" import { withStatics } from "../schema" import { Identifier } from "../util/identifier" +import { NonNegativeInt } from "../schema" +import { NamedError } from "../util/error" +import { SessionSchema } from "./schema" export const MessageID = Schema.String.check(Schema.isStartsWith("msg")).pipe( Schema.brand("MessageID"), @@ -15,3 +18,494 @@ export const PartID = Schema.String.check(Schema.isStartsWith("prt")).pipe( withStatics((schema) => ({ ascending: (id?: string) => schema.make(id ?? "prt_" + Identifier.ascending()) })), ) export type PartID = typeof PartID.Type + +export const ProviderID = Schema.String.pipe( + Schema.brand("ProviderID"), + withStatics((schema) => ({ + opencode: schema.make("opencode"), + anthropic: schema.make("anthropic"), + openai: schema.make("openai"), + google: schema.make("google"), + googleVertex: schema.make("google-vertex"), + githubCopilot: schema.make("github-copilot"), + amazonBedrock: schema.make("amazon-bedrock"), + azure: schema.make("azure"), + openrouter: schema.make("openrouter"), + mistral: schema.make("mistral"), + gitlab: schema.make("gitlab"), + })), +) +export type ProviderID = typeof ProviderID.Type + +export const ModelID = Schema.String.pipe(Schema.brand("ModelID")) +export type ModelID = typeof ModelID.Type + +export const OutputLengthError = NamedError.create("MessageOutputLengthError", {}) + +export const AuthError = NamedError.create("ProviderAuthError", { + providerID: Schema.String, + message: Schema.String, +}) + +export const AbortedError = NamedError.create("MessageAbortedError", { message: Schema.String }) +export const StructuredOutputError = NamedError.create("StructuredOutputError", { + message: Schema.String, + retries: NonNegativeInt, +}) +export const APIError = NamedError.create("APIError", { + message: Schema.String, + statusCode: Schema.optional(NonNegativeInt), + isRetryable: Schema.Boolean, + responseHeaders: Schema.optional(Schema.Record(Schema.String, Schema.String)), + responseBody: Schema.optional(Schema.String), + metadata: Schema.optional(Schema.Record(Schema.String, Schema.String)), +}) +export type APIError = Schema.Schema.Type +export const ContextOverflowError = NamedError.create("ContextOverflowError", { + message: Schema.String, + responseBody: Schema.optional(Schema.String), +}) + +export class OutputFormatText extends Schema.Class("OutputFormatText")({ + type: Schema.Literal("text"), +}) {} + +export class OutputFormatJsonSchema extends Schema.Class("OutputFormatJsonSchema")({ + type: Schema.Literal("json_schema"), + schema: Schema.Record(Schema.String, Schema.Any).annotate({ identifier: "JSONSchema" }), + retryCount: NonNegativeInt.pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed(2))), +}) {} + +export const Format = Schema.Union([OutputFormatText, OutputFormatJsonSchema]).annotate({ + discriminator: "type", + identifier: "OutputFormat", +}) +export type OutputFormat = Schema.Schema.Type + +const partBase = { + id: PartID, + sessionID: SessionSchema.ID, + messageID: MessageID, +} + +export const SnapshotPart = Schema.Struct({ + ...partBase, + type: Schema.Literal("snapshot"), + snapshot: Schema.String, +}).annotate({ identifier: "SnapshotPart" }) +export type SnapshotPart = Types.DeepMutable> + +export const PatchPart = Schema.Struct({ + ...partBase, + type: Schema.Literal("patch"), + hash: Schema.String, + files: Schema.Array(Schema.String), +}).annotate({ identifier: "PatchPart" }) +export type PatchPart = Types.DeepMutable> + +export const TextPart = Schema.Struct({ + ...partBase, + type: Schema.Literal("text"), + text: Schema.String, + synthetic: Schema.optional(Schema.Boolean), + ignored: Schema.optional(Schema.Boolean), + time: Schema.optional( + Schema.Struct({ + start: NonNegativeInt, + end: Schema.optional(NonNegativeInt), + }), + ), + metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)), +}).annotate({ identifier: "TextPart" }) +export type TextPart = Types.DeepMutable> + +export const ReasoningPart = Schema.Struct({ + ...partBase, + type: Schema.Literal("reasoning"), + text: Schema.String, + metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)), + time: Schema.Struct({ + start: NonNegativeInt, + end: Schema.optional(NonNegativeInt), + }), +}).annotate({ identifier: "ReasoningPart" }) +export type ReasoningPart = Types.DeepMutable> + +const filePartSourceBase = { + text: Schema.Struct({ + value: Schema.String, + start: Schema.Finite, + end: Schema.Finite, + }).annotate({ identifier: "FilePartSourceText" }), +} + +export const Range = Schema.Struct({ + start: Schema.Struct({ line: NonNegativeInt, character: NonNegativeInt }), + end: Schema.Struct({ line: NonNegativeInt, character: NonNegativeInt }), +}).annotate({ identifier: "Range" }) +export type Range = typeof Range.Type + +export const FileSource = Schema.Struct({ + ...filePartSourceBase, + type: Schema.Literal("file"), + path: Schema.String, +}).annotate({ identifier: "FileSource" }) + +export const SymbolSource = Schema.Struct({ + ...filePartSourceBase, + type: Schema.Literal("symbol"), + path: Schema.String, + range: Range, + name: Schema.String, + kind: NonNegativeInt, +}).annotate({ identifier: "SymbolSource" }) + +export const ResourceSource = Schema.Struct({ + ...filePartSourceBase, + type: Schema.Literal("resource"), + clientName: Schema.String, + uri: Schema.String, +}).annotate({ identifier: "ResourceSource" }) + +export const FilePartSource = Schema.Union([FileSource, SymbolSource, ResourceSource]).annotate({ + discriminator: "type", + identifier: "FilePartSource", +}) + +export const FilePart = Schema.Struct({ + ...partBase, + type: Schema.Literal("file"), + mime: Schema.String, + filename: Schema.optional(Schema.String), + url: Schema.String, + source: Schema.optional(FilePartSource), +}).annotate({ identifier: "FilePart" }) +export type FilePart = Types.DeepMutable> + +export const AgentPart = Schema.Struct({ + ...partBase, + type: Schema.Literal("agent"), + name: Schema.String, + source: Schema.optional( + Schema.Struct({ + value: Schema.String, + start: NonNegativeInt, + end: NonNegativeInt, + }), + ), +}).annotate({ identifier: "AgentPart" }) +export type AgentPart = Types.DeepMutable> + +export const CompactionPart = Schema.Struct({ + ...partBase, + type: Schema.Literal("compaction"), + auto: Schema.Boolean, + overflow: Schema.optional(Schema.Boolean), + tail_start_id: Schema.optional(MessageID), +}).annotate({ identifier: "CompactionPart" }) +export type CompactionPart = Types.DeepMutable> + +export const SubtaskPart = Schema.Struct({ + ...partBase, + type: Schema.Literal("subtask"), + prompt: Schema.String, + description: Schema.String, + agent: Schema.String, + model: Schema.optional( + Schema.Struct({ + providerID: ProviderID, + modelID: ModelID, + }), + ), + command: Schema.optional(Schema.String), +}).annotate({ identifier: "SubtaskPart" }) +export type SubtaskPart = Types.DeepMutable> + +export const RetryPart = Schema.Struct({ + ...partBase, + type: Schema.Literal("retry"), + attempt: NonNegativeInt, + error: APIError.EffectSchema, + time: Schema.Struct({ + created: NonNegativeInt, + }), +}).annotate({ identifier: "RetryPart" }) +export type RetryPart = Omit>, "error"> & { + error: APIError +} + +export const StepStartPart = Schema.Struct({ + ...partBase, + type: Schema.Literal("step-start"), + snapshot: Schema.optional(Schema.String), +}).annotate({ identifier: "StepStartPart" }) +export type StepStartPart = Types.DeepMutable> + +export const StepFinishPart = Schema.Struct({ + ...partBase, + type: Schema.Literal("step-finish"), + reason: Schema.String, + snapshot: Schema.optional(Schema.String), + cost: Schema.Finite, + tokens: Schema.Struct({ + total: Schema.optional(Schema.Finite), + input: Schema.Finite, + output: Schema.Finite, + reasoning: Schema.Finite, + cache: Schema.Struct({ + read: Schema.Finite, + write: Schema.Finite, + }), + }), +}).annotate({ identifier: "StepFinishPart" }) +export type StepFinishPart = Types.DeepMutable> + +export const ToolStatePending = Schema.Struct({ + status: Schema.Literal("pending"), + input: Schema.Record(Schema.String, Schema.Any), + raw: Schema.String, +}).annotate({ identifier: "ToolStatePending" }) +export type ToolStatePending = Types.DeepMutable> + +export const ToolStateRunning = Schema.Struct({ + status: Schema.Literal("running"), + input: Schema.Record(Schema.String, Schema.Any), + title: Schema.optional(Schema.String), + metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)), + time: Schema.Struct({ + start: NonNegativeInt, + }), +}).annotate({ identifier: "ToolStateRunning" }) +export type ToolStateRunning = Types.DeepMutable> + +export const ToolStateCompleted = Schema.Struct({ + status: Schema.Literal("completed"), + input: Schema.Record(Schema.String, Schema.Any), + output: Schema.String, + title: Schema.String, + metadata: Schema.Record(Schema.String, Schema.Any), + time: Schema.Struct({ + start: NonNegativeInt, + end: NonNegativeInt, + compacted: Schema.optional(NonNegativeInt), + }), + attachments: Schema.optional(Schema.Array(FilePart)), +}).annotate({ identifier: "ToolStateCompleted" }) +export type ToolStateCompleted = Types.DeepMutable> + +export const ToolStateError = Schema.Struct({ + status: Schema.Literal("error"), + input: Schema.Record(Schema.String, Schema.Any), + error: Schema.String, + metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)), + time: Schema.Struct({ + start: NonNegativeInt, + end: NonNegativeInt, + }), +}).annotate({ identifier: "ToolStateError" }) +export type ToolStateError = Types.DeepMutable> + +export const ToolState = Schema.Union([ + ToolStatePending, + ToolStateRunning, + ToolStateCompleted, + ToolStateError, +]).annotate({ + discriminator: "status", + identifier: "ToolState", +}) +export type ToolState = ToolStatePending | ToolStateRunning | ToolStateCompleted | ToolStateError + +export const ToolPart = Schema.Struct({ + ...partBase, + type: Schema.Literal("tool"), + callID: Schema.String, + tool: Schema.String, + state: ToolState, + metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)), +}).annotate({ identifier: "ToolPart" }) +export type ToolPart = Omit>, "state"> & { + state: ToolState +} + +const messageBase = { + id: MessageID, + sessionID: partBase.sessionID, +} + +const FileDiff = Schema.Struct({ + file: Schema.optional(Schema.String), + patch: Schema.optional(Schema.String), + additions: Schema.Finite, + deletions: Schema.Finite, + status: Schema.optional(Schema.Literals(["added", "deleted", "modified"])), +}).annotate({ identifier: "SnapshotFileDiff" }) + +export const User = Schema.Struct({ + ...messageBase, + role: Schema.Literal("user"), + time: Schema.Struct({ + created: NonNegativeInt, + }), + format: Schema.optional(Format), + summary: Schema.optional( + Schema.Struct({ + title: Schema.optional(Schema.String), + body: Schema.optional(Schema.String), + diffs: Schema.Array(FileDiff), + }), + ), + agent: Schema.String, + model: Schema.Struct({ + providerID: ProviderID, + modelID: ModelID, + variant: Schema.optional(Schema.String), + }), + system: Schema.optional(Schema.String), + tools: Schema.optional(Schema.Record(Schema.String, Schema.Boolean)), +}).annotate({ identifier: "UserMessage" }) +export type User = Types.DeepMutable> + +export const Part = Schema.Union([ + TextPart, + SubtaskPart, + ReasoningPart, + FilePart, + ToolPart, + StepStartPart, + StepFinishPart, + SnapshotPart, + PatchPart, + AgentPart, + RetryPart, + CompactionPart, +]).annotate({ discriminator: "type", identifier: "Part" }) +export type Part = + | TextPart + | SubtaskPart + | ReasoningPart + | FilePart + | ToolPart + | StepStartPart + | StepFinishPart + | SnapshotPart + | PatchPart + | AgentPart + | RetryPart + | CompactionPart + +const AssistantErrorSchema = Schema.Union([ + AuthError.EffectSchema, + NamedError.Unknown.EffectSchema, + OutputLengthError.EffectSchema, + AbortedError.EffectSchema, + StructuredOutputError.EffectSchema, + ContextOverflowError.EffectSchema, + APIError.EffectSchema, +]).annotate({ discriminator: "name" }) +type AssistantError = Schema.Schema.Type + +export const TextPartInput = Schema.Struct({ + id: Schema.optional(PartID), + type: Schema.Literal("text"), + text: Schema.String, + synthetic: Schema.optional(Schema.Boolean), + ignored: Schema.optional(Schema.Boolean), + time: Schema.optional( + Schema.Struct({ + start: NonNegativeInt, + end: Schema.optional(NonNegativeInt), + }), + ), + metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)), +}).annotate({ identifier: "TextPartInput" }) +export type TextPartInput = Types.DeepMutable> + +export const FilePartInput = Schema.Struct({ + id: Schema.optional(PartID), + type: Schema.Literal("file"), + mime: Schema.String, + filename: Schema.optional(Schema.String), + url: Schema.String, + source: Schema.optional(FilePartSource), +}).annotate({ identifier: "FilePartInput" }) +export type FilePartInput = Types.DeepMutable> + +export const AgentPartInput = Schema.Struct({ + id: Schema.optional(PartID), + type: Schema.Literal("agent"), + name: Schema.String, + source: Schema.optional( + Schema.Struct({ + value: Schema.String, + start: NonNegativeInt, + end: NonNegativeInt, + }), + ), +}).annotate({ identifier: "AgentPartInput" }) +export type AgentPartInput = Types.DeepMutable> + +export const SubtaskPartInput = Schema.Struct({ + id: Schema.optional(PartID), + type: Schema.Literal("subtask"), + prompt: Schema.String, + description: Schema.String, + agent: Schema.String, + model: Schema.optional( + Schema.Struct({ + providerID: ProviderID, + modelID: ModelID, + }), + ), + command: Schema.optional(Schema.String), +}).annotate({ identifier: "SubtaskPartInput" }) +export type SubtaskPartInput = Types.DeepMutable> + +export const Assistant = Schema.Struct({ + ...messageBase, + role: Schema.Literal("assistant"), + time: Schema.Struct({ + created: NonNegativeInt, + completed: Schema.optional(NonNegativeInt), + }), + error: Schema.optional(AssistantErrorSchema), + parentID: MessageID, + modelID: ModelID, + providerID: ProviderID, + mode: Schema.String, + agent: Schema.String, + path: Schema.Struct({ + cwd: Schema.String, + root: Schema.String, + }), + summary: Schema.optional(Schema.Boolean), + cost: Schema.Finite, + tokens: Schema.Struct({ + total: Schema.optional(Schema.Finite), + input: Schema.Finite, + output: Schema.Finite, + reasoning: Schema.Finite, + cache: Schema.Struct({ + read: Schema.Finite, + write: Schema.Finite, + }), + }), + structured: Schema.optional(Schema.Any), + variant: Schema.optional(Schema.String), + finish: Schema.optional(Schema.String), +}).annotate({ identifier: "AssistantMessage" }) +export type Assistant = Omit>, "error"> & { + error?: AssistantError +} + +export const Info = Schema.Union([User, Assistant]).annotate({ discriminator: "role", identifier: "Message" }) +export type Info = User | Assistant + +export const WithParts = Schema.Struct({ + info: Info, + parts: Schema.Array(Part), +}) +export type WithParts = { + info: Info + parts: Part[] +} diff --git a/packages/core/src/session/sql.ts b/packages/core/src/session/sql.ts index f99187e9f..aa3c4c215 100644 --- a/packages/core/src/session/sql.ts +++ b/packages/core/src/session/sql.ts @@ -5,11 +5,13 @@ import type { Snapshot } from "../snapshot" import { PermissionV2 } from "../permission" import { ProjectV2 } from "../project" import type { SessionSchema } from "./schema" -import type { MessageID, PartID } from "./legacy" +import type { MessageID, PartID, Info as LegacyMessageInfo, Part as LegacyMessagePart } from "./legacy" import { WorkspaceV2 } from "../workspace" import { Timestamps } from "../database/schema.sql" type SessionMessageData = Omit<(typeof SessionMessage.Message)["Encoded"], "type" | "id"> +type LegacyMessageData = Omit +type LegacyPartData = Omit export const SessionTable = sqliteTable( "session", @@ -65,7 +67,7 @@ export const MessageTable = sqliteTable( .notNull() .references(() => SessionTable.id, { onDelete: "cascade" }), ...Timestamps, - data: text({ mode: "json" }).notNull(), + data: text({ mode: "json" }).notNull().$type(), }, (table) => [index("message_session_time_created_id_idx").on(table.session_id, table.time_created, table.id)], ) @@ -80,7 +82,7 @@ export const PartTable = sqliteTable( .references(() => MessageTable.id, { onDelete: "cascade" }), session_id: text().$type().notNull(), ...Timestamps, - data: text({ mode: "json" }).notNull(), + data: text({ mode: "json" }).notNull().$type(), }, (table) => [ index("part_message_id_id_idx").on(table.message_id, table.id), diff --git a/packages/opencode/src/cli/cmd/debug/agent.ts b/packages/opencode/src/cli/cmd/debug/agent.ts index c74c1c907..0c310474e 100644 --- a/packages/opencode/src/cli/cmd/debug/agent.ts +++ b/packages/opencode/src/cli/cmd/debug/agent.ts @@ -1,4 +1,5 @@ import { EOL } from "os" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { basename } from "path" import { Cause, Effect } from "effect" import { Agent } from "../../../agent/agent" @@ -163,7 +164,7 @@ const createToolContext = Effect.fn("Cli.debug.agent.createToolContext")(functio ) }) const now = Date.now() - const message: MessageV2.Assistant = { + const message: SessionLegacy.Assistant = { id: messageID, sessionID: session.id, role: "assistant", diff --git a/packages/opencode/src/cli/cmd/export.ts b/packages/opencode/src/cli/cmd/export.ts index 9eb1faffe..e6bff506c 100644 --- a/packages/opencode/src/cli/cmd/export.ts +++ b/packages/opencode/src/cli/cmd/export.ts @@ -1,4 +1,5 @@ import { Session } from "@/session/session" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { MessageV2 } from "../../session/message-v2" import { SessionID } from "../../session/schema" import { effectCmd, fail } from "../effect-cmd" @@ -31,7 +32,7 @@ function diff(kind: string, diffs: { file?: string; patch?: string }[] | undefin })) } -function source(part: MessageV2.FilePart) { +function source(part: SessionLegacy.FilePart) { if (!part.source) return part.source if (part.source.type === "symbol") { return { @@ -56,7 +57,7 @@ function source(part: MessageV2.FilePart) { } } -function filepart(part: MessageV2.FilePart): MessageV2.FilePart { +function filepart(part: SessionLegacy.FilePart): SessionLegacy.FilePart { return { ...part, url: redact("file-url", part.id, part.url), @@ -65,7 +66,7 @@ function filepart(part: MessageV2.FilePart): MessageV2.FilePart { } } -function part(part: MessageV2.Part): MessageV2.Part { +function part(part: SessionLegacy.Part): SessionLegacy.Part { switch (part.type) { case "text": return { @@ -159,7 +160,7 @@ function part(part: MessageV2.Part): MessageV2.Part { const partFn = part -function sanitize(data: { info: Session.Info; messages: MessageV2.WithParts[] }) { +function sanitize(data: { info: Session.Info; messages: SessionLegacy.WithParts[] }) { return { info: { ...data.info, diff --git a/packages/opencode/src/cli/cmd/github.ts b/packages/opencode/src/cli/cmd/github.ts index 9ac605f46..13c7dd30b 100644 --- a/packages/opencode/src/cli/cmd/github.ts +++ b/packages/opencode/src/cli/cmd/github.ts @@ -1,4 +1,5 @@ import path from "path" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { exec } from "child_process" import { Filesystem } from "@/util/filesystem" import * as prompts from "@clack/prompts" @@ -159,7 +160,7 @@ export { parseGitHubRemote } * Returns null for non-text responses (signals summary needed). * Throws only for truly empty responses. */ -export function extractResponseText(parts: MessageV2.Part[]): string | null { +export function extractResponseText(parts: SessionLegacy.Part[]): string | null { const textPart = parts.findLast((p) => p.type === "text") if (textPart) return textPart.text diff --git a/packages/opencode/src/cli/cmd/import.ts b/packages/opencode/src/cli/cmd/import.ts index c5a701419..44bd41119 100644 --- a/packages/opencode/src/cli/cmd/import.ts +++ b/packages/opencode/src/cli/cmd/import.ts @@ -1,4 +1,5 @@ import type { Session as SDKSession, Message, Part } from "@opencode-ai/sdk/v2" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { Session } from "@/session/session" import { MessageV2 } from "../../session/message-v2" import { CliError, effectCmd } from "../effect-cmd" @@ -12,8 +13,8 @@ import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Effect, Schema } from "effect" import type { InstanceContext } from "@/project/instance-context" -const decodeMessageInfo = Schema.decodeUnknownSync(MessageV2.Info) -const decodePart = Schema.decodeUnknownSync(MessageV2.Part) +const decodeMessageInfo = Schema.decodeUnknownSync(SessionLegacy.Info) +const decodePart = Schema.decodeUnknownSync(SessionLegacy.Part) /** Discriminated union returned by the ShareNext API (GET /api/shares/:id/data) */ export type ShareData = @@ -187,7 +188,7 @@ const runImport = Effect.fn("Cli.import.body")(function* (file: string, ctx: Ins ) for (const msg of exportData.messages) { - const msgInfo = decodeMessageInfo(msg.info) as MessageV2.Info + const msgInfo = decodeMessageInfo(msg.info) as SessionLegacy.Info const { id, sessionID: _, ...msgData } = msgInfo Database.use((db) => db @@ -203,7 +204,7 @@ const runImport = Effect.fn("Cli.import.body")(function* (file: string, ctx: Ins ) for (const part of msg.parts) { - const partInfo = decodePart(part) as MessageV2.Part + const partInfo = decodePart(part) as SessionLegacy.Part const { id: partId, sessionID: _s, messageID, ...partData } = partInfo Database.use((db) => db diff --git a/packages/opencode/src/image/image.ts b/packages/opencode/src/image/image.ts index 2a3c4fa5c..8ecf0dcfc 100644 --- a/packages/opencode/src/image/image.ts +++ b/packages/opencode/src/image/image.ts @@ -1,4 +1,5 @@ import { Config } from "@/config/config" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import type { MessageV2 } from "@/session/message-v2" import * as Log from "@opencode-ai/core/util/log" import photonWasm from "@silvia-odwyer/photon-node/photon_rs_bg.wasm" with { type: "file" } @@ -52,7 +53,7 @@ export class SizeError extends Schema.TaggedErrorClass()("ImageSizeEr export type Error = ResizerUnavailableError | InvalidDataUrlError | DecodeError | SizeError export interface Interface { - readonly normalize: (input: MessageV2.FilePart) => Effect.Effect + readonly normalize: (input: SessionLegacy.FilePart) => Effect.Effect } export class Service extends Context.Service()("@opencode/Image") {} @@ -73,7 +74,7 @@ export const layer = Layer.effect( ), ) - const normalize = Effect.fn("Image.normalize")(function* (input: MessageV2.FilePart) { + const normalize = Effect.fn("Image.normalize")(function* (input: SessionLegacy.FilePart) { const image = (yield* config.get()).attachment?.image const info = { autoResize: image?.auto_resize ?? AUTO_RESIZE, diff --git a/packages/opencode/src/server/routes/instance/httpapi/groups/session.ts b/packages/opencode/src/server/routes/instance/httpapi/groups/session.ts index cd2f3be19..165b5a6ab 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/groups/session.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/groups/session.ts @@ -1,4 +1,5 @@ import { Permission } from "@/permission" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { PermissionID } from "@/permission/schema" import { ModelID, ProviderID } from "@/provider/schema" import { Session } from "@/session/session" @@ -175,7 +176,7 @@ export const SessionApi = HttpApi.make("session") HttpApiEndpoint.get("messages", SessionPaths.messages, { params: { sessionID: SessionID }, query: MessagesQuery, - success: described(Schema.Array(MessageV2.WithParts), "List of messages"), + success: described(Schema.Array(SessionLegacy.WithParts), "List of messages"), error: [HttpApiError.BadRequest, ApiNotFoundError], }).annotateMerge( OpenApi.annotations({ @@ -187,7 +188,7 @@ export const SessionApi = HttpApi.make("session") HttpApiEndpoint.get("message", SessionPaths.message, { params: { sessionID: SessionID, messageID: MessageID }, query: WorkspaceRoutingQuery, - success: described(MessageV2.WithParts, "Message"), + success: described(SessionLegacy.WithParts, "Message"), error: [HttpApiError.BadRequest, ApiNotFoundError], }).annotateMerge( OpenApi.annotations({ @@ -313,7 +314,7 @@ export const SessionApi = HttpApi.make("session") params: { sessionID: SessionID }, query: WorkspaceRoutingQuery, payload: PromptPayload, - success: described(MessageV2.WithParts, "Created message"), + success: described(SessionLegacy.WithParts, "Created message"), error: [HttpApiError.BadRequest, ApiNotFoundError], }).annotateMerge( OpenApi.annotations({ @@ -340,7 +341,7 @@ export const SessionApi = HttpApi.make("session") params: { sessionID: SessionID }, query: WorkspaceRoutingQuery, payload: CommandPayload, - success: described(MessageV2.WithParts, "Created message"), + success: described(SessionLegacy.WithParts, "Created message"), error: [HttpApiError.BadRequest, ApiNotFoundError], }).annotateMerge( OpenApi.annotations({ @@ -353,7 +354,7 @@ export const SessionApi = HttpApi.make("session") params: { sessionID: SessionID }, query: WorkspaceRoutingQuery, payload: ShellPayload, - success: described(MessageV2.WithParts, "Created message"), + success: described(SessionLegacy.WithParts, "Created message"), error: [HttpApiError.BadRequest, ApiNotFoundError, SessionBusyError], }).annotateMerge( OpenApi.annotations({ @@ -429,8 +430,8 @@ export const SessionApi = HttpApi.make("session") HttpApiEndpoint.patch("updatePart", SessionPaths.updatePart, { params: { sessionID: SessionID, messageID: MessageID, partID: PartID }, query: WorkspaceRoutingQuery, - payload: MessageV2.Part, - success: described(MessageV2.Part, "Successfully updated part"), + payload: SessionLegacy.Part, + success: described(SessionLegacy.Part, "Successfully updated part"), error: [HttpApiError.BadRequest, ApiNotFoundError], }).annotateMerge( OpenApi.annotations({ diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts index 4d4cce367..c89d8bedb 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts @@ -1,4 +1,5 @@ import { Agent } from "@/agent/agent" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { Bus } from "@/bus" import { Command } from "@/command" import { Permission } from "@/permission" @@ -389,10 +390,10 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", const updatePart = Effect.fn("SessionHttpApi.updatePart")(function* (ctx: { params: { sessionID: SessionID; messageID: MessageID; partID: PartID } - payload: typeof MessageV2.Part.Type + payload: typeof SessionLegacy.Part.Type }) { yield* requireSession(ctx.params.sessionID) - const payload = ctx.payload as MessageV2.Part + const payload = ctx.payload as SessionLegacy.Part if ( payload.id !== ctx.params.partID || payload.messageID !== ctx.params.messageID || diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index 4b7879d3c..c1abb6cd2 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -1,4 +1,5 @@ import { BusEvent } from "@/bus/bus-event" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { Bus } from "@/bus" import * as Session from "./session" import { SessionID, MessageID, PartID } from "./schema" @@ -92,9 +93,9 @@ type CompletedCompaction = { summary: string | undefined } -function summaryText(message: MessageV2.WithParts) { +function summaryText(message: SessionLegacy.WithParts) { const text = message.parts - .filter((part): part is MessageV2.TextPart => part.type === "text") + .filter((part): part is SessionLegacy.TextPart => part.type === "text") .map((part) => part.text.trim()) .filter(Boolean) .join("\n\n") @@ -102,7 +103,7 @@ function summaryText(message: MessageV2.WithParts) { return text || undefined } -function completedCompactions(messages: MessageV2.WithParts[]) { +function completedCompactions(messages: SessionLegacy.WithParts[]) { const users = new Map() for (let i = 0; i < messages.length; i++) { const msg = messages[i] @@ -140,7 +141,7 @@ function preserveRecentBudget(input: { cfg: Config.Info; model: Provider.Model } ) } -function turns(messages: MessageV2.WithParts[]) { +function turns(messages: SessionLegacy.WithParts[]) { const result: Turn[] = [] for (let i = 0; i < messages.length; i++) { const msg = messages[i] @@ -159,11 +160,11 @@ function turns(messages: MessageV2.WithParts[]) { } function splitTurn(input: { - messages: MessageV2.WithParts[] + messages: SessionLegacy.WithParts[] turn: Turn model: Provider.Model budget: number - estimate: (input: { messages: MessageV2.WithParts[]; model: Provider.Model }) => Effect.Effect + estimate: (input: { messages: SessionLegacy.WithParts[]; model: Provider.Model }) => Effect.Effect }) { return Effect.gen(function* () { if (input.budget <= 0) return undefined @@ -185,13 +186,13 @@ function splitTurn(input: { export interface Interface { readonly isOverflow: (input: { - tokens: MessageV2.Assistant["tokens"] + tokens: SessionLegacy.Assistant["tokens"] model: Provider.Model }) => Effect.Effect readonly prune: (input: { sessionID: SessionID }) => Effect.Effect readonly process: (input: { parentID: MessageID - messages: MessageV2.WithParts[] + messages: SessionLegacy.WithParts[] sessionID: SessionID auto: boolean overflow?: boolean @@ -223,7 +224,7 @@ export const layer = Layer.effect( const flags = yield* RuntimeFlags.Service const isOverflow = Effect.fn("SessionCompaction.isOverflow")(function* (input: { - tokens: MessageV2.Assistant["tokens"] + tokens: SessionLegacy.Assistant["tokens"] model: Provider.Model }) { return overflow({ @@ -235,7 +236,7 @@ export const layer = Layer.effect( }) const estimate = Effect.fn("SessionCompaction.estimate")(function* (input: { - messages: MessageV2.WithParts[] + messages: SessionLegacy.WithParts[] model: Provider.Model }) { const msgs = yield* MessageV2.toModelMessagesEffect(input.messages, input.model) @@ -243,7 +244,7 @@ export const layer = Layer.effect( }) const select = Effect.fn("SessionCompaction.select")(function* (input: { - messages: MessageV2.WithParts[] + messages: SessionLegacy.WithParts[] cfg: Config.Info model: Provider.Model }) { @@ -307,7 +308,7 @@ export const layer = Layer.effect( let total = 0 let pruned = 0 - const toPrune: MessageV2.ToolPart[] = [] + const toPrune: SessionLegacy.ToolPart[] = [] let turns = 0 loop: for (let msgIndex = msgs.length - 1; msgIndex >= 0; msgIndex--) { @@ -343,7 +344,7 @@ export const layer = Layer.effect( const processCompaction = Effect.fn("SessionCompaction.process")(function* (input: { parentID: MessageID - messages: MessageV2.WithParts[] + messages: SessionLegacy.WithParts[] sessionID: SessionID auto: boolean overflow?: boolean @@ -353,13 +354,13 @@ export const layer = Layer.effect( throw new Error(`Compaction parent must be a user message: ${input.parentID}`) } const userMessage = parent.info - const compactionPart = parent.parts.find((part): part is MessageV2.CompactionPart => part.type === "compaction") + const compactionPart = parent.parts.find((part): part is SessionLegacy.CompactionPart => part.type === "compaction") let messages = input.messages let replay: | { - info: MessageV2.User - parts: MessageV2.Part[] + info: SessionLegacy.User + parts: SessionLegacy.Part[] } | undefined if (input.overflow) { @@ -408,7 +409,7 @@ export const layer = Layer.effect( toolOutputMaxChars: TOOL_OUTPUT_MAX_CHARS, }) const ctx = yield* InstanceState.context - const msg: MessageV2.Assistant = { + const msg: SessionLegacy.Assistant = { id: MessageID.ascending(), role: "assistant", parentID: input.parentID, @@ -457,7 +458,7 @@ export const layer = Layer.effect( }) if (result === "compact") { - processor.message.error = new MessageV2.ContextOverflowError({ + processor.message.error = new SessionLegacy.ContextOverflowError({ message: replay ? "Conversation history too large to compact - exceeds model context limit" : "Session too large to compact - context exceeds model limit even after stripping media", diff --git a/packages/opencode/src/session/instruction.ts b/packages/opencode/src/session/instruction.ts index ad9a74445..855c58ba5 100644 --- a/packages/opencode/src/session/instruction.ts +++ b/packages/opencode/src/session/instruction.ts @@ -1,4 +1,5 @@ import path from "path" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { Effect, Layer, Context } from "effect" import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http" import { Config } from "@/config/config" @@ -17,7 +18,7 @@ const files = (disableClaudeCodePrompt: boolean) => [ "CONTEXT.md", // deprecated ] -function extract(messages: MessageV2.WithParts[]) { +function extract(messages: SessionLegacy.WithParts[]) { const paths = new Set() for (const msg of messages) { for (const part of msg.parts) { @@ -40,7 +41,7 @@ export interface Interface { readonly system: () => Effect.Effect readonly find: (dir: string) => Effect.Effect readonly resolve: ( - messages: MessageV2.WithParts[], + messages: SessionLegacy.WithParts[], filepath: string, messageID: MessageID, ) => Effect.Effect<{ filepath: string; content: string }[], AppFileSystem.Error> @@ -176,7 +177,7 @@ export const layer: Layer.Layer< }) const resolve = Effect.fn("Instruction.resolve")(function* ( - messages: MessageV2.WithParts[], + messages: SessionLegacy.WithParts[], filepath: string, messageID: MessageID, ) { @@ -231,7 +232,7 @@ export const defaultLayer = layer.pipe( Layer.provide(RuntimeFlags.defaultLayer), ) -export function loaded(messages: MessageV2.WithParts[]) { +export function loaded(messages: SessionLegacy.WithParts[]) { return extract(messages) } diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index ea2efc99d..da3301e23 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -1,4 +1,5 @@ import { Provider } from "@/provider/provider" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { serviceUse } from "@opencode-ai/core/effect/service-use" import * as Log from "@opencode-ai/core/util/log" import { Context, Effect, Layer } from "effect" @@ -31,7 +32,7 @@ const log = Log.create({ service: "llm" }) export const OUTPUT_TOKEN_MAX = ProviderTransform.OUTPUT_TOKEN_MAX export type StreamInput = { - user: MessageV2.User + user: SessionLegacy.User sessionID: string parentSessionID?: string model: Provider.Model diff --git a/packages/opencode/src/session/llm/request.ts b/packages/opencode/src/session/llm/request.ts index 347134240..60847dab3 100644 --- a/packages/opencode/src/session/llm/request.ts +++ b/packages/opencode/src/session/llm/request.ts @@ -1,4 +1,5 @@ import type { Auth } from "@/auth" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import type { RuntimeFlags } from "@/effect/runtime-flags" import { InstanceState } from "@/effect/instance-state" import { Permission } from "@/permission" @@ -16,7 +17,7 @@ import { mergeDeep } from "remeda" const USER_AGENT = `opencode/${InstallationVersion}` type PrepareInput = { - readonly user: MessageV2.User + readonly user: SessionLegacy.User readonly sessionID: string readonly parentSessionID?: string readonly model: Provider.Model diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 9107cdb65..db88ed45a 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -1,9 +1,26 @@ import { BusEvent } from "@/bus/bus-event" import { SessionID, MessageID, PartID } from "./schema" +import { + APIError, + AbortedError, + Assistant, + AuthError, + CompactionPart, + ContextOverflowError, + Info, + OutputLengthError, + Part, + StructuredOutputError, + SubtaskPart, + User, + WithParts, + type ModelID, + type ProviderID, + type ToolPart, +} from "@opencode-ai/core/session/legacy" + import { NamedError } from "@opencode-ai/core/util/error" import { APICallError, convertToModelMessages, LoadAPIKeyError, type ModelMessage, type UIMessage } from "ai" -import { LSP } from "@/lsp/lsp" -import { Snapshot } from "@/snapshot" import { SyncEvent } from "../sync" import { Database } from "@/storage/db" import { NotFoundError } from "@/storage/storage" @@ -20,13 +37,8 @@ import { errorMessage } from "@/util/error" import { isMedia } from "@/util/media" import type { SystemError } from "bun" import type { Provider } from "@/provider/provider" -import { ModelID, ProviderID } from "@/provider/schema" -import { Effect, Schema, Types } from "effect" -import { NonNegativeInt } from "@opencode-ai/core/schema" +import { Effect, Schema } from "effect" import * as EffectLogger from "@opencode-ai/core/effect/logger" -import { MessageError } from "./message-error" -import { AuthError, OutputLengthError } from "./message-error" -export { AuthError, OutputLengthError } from "./message-error" /** Error shape thrown by Bun's fetch() when gzip/br decompression fails mid-stream */ interface FetchDecompressionError extends Error { @@ -38,460 +50,12 @@ interface FetchDecompressionError extends Error { export const SYNTHETIC_ATTACHMENT_PROMPT = "Attached media from tool result:" export { isMedia } -export const AbortedError = NamedError.create("MessageAbortedError", { message: Schema.String }) -export const StructuredOutputError = NamedError.create("StructuredOutputError", { - message: Schema.String, - retries: NonNegativeInt, -}) -export const APIError = NamedError.create("APIError", { - message: Schema.String, - statusCode: Schema.optional(NonNegativeInt), - isRetryable: Schema.Boolean, - responseHeaders: Schema.optional(Schema.Record(Schema.String, Schema.String)), - responseBody: Schema.optional(Schema.String), - metadata: Schema.optional(Schema.Record(Schema.String, Schema.String)), -}) -export type APIError = Schema.Schema.Type -export const ContextOverflowError = NamedError.create("ContextOverflowError", { - message: Schema.String, - responseBody: Schema.optional(Schema.String), -}) - -export class OutputFormatText extends Schema.Class("OutputFormatText")({ - type: Schema.Literal("text"), -}) {} - -export class OutputFormatJsonSchema extends Schema.Class("OutputFormatJsonSchema")({ - type: Schema.Literal("json_schema"), - schema: Schema.Record(Schema.String, Schema.Any).annotate({ identifier: "JSONSchema" }), - retryCount: NonNegativeInt.pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed(2))), -}) {} - -export const Format = Schema.Union([OutputFormatText, OutputFormatJsonSchema]).annotate({ - discriminator: "type", - identifier: "OutputFormat", -}) -export type OutputFormat = Schema.Schema.Type - -const partBase = { - id: PartID, - sessionID: SessionID, - messageID: MessageID, -} - -export const SnapshotPart = Schema.Struct({ - ...partBase, - type: Schema.Literal("snapshot"), - snapshot: Schema.String, -}).annotate({ identifier: "SnapshotPart" }) -export type SnapshotPart = Types.DeepMutable> - -export const PatchPart = Schema.Struct({ - ...partBase, - type: Schema.Literal("patch"), - hash: Schema.String, - files: Schema.Array(Schema.String), -}).annotate({ identifier: "PatchPart" }) -export type PatchPart = Types.DeepMutable> - -export const TextPart = Schema.Struct({ - ...partBase, - type: Schema.Literal("text"), - text: Schema.String, - synthetic: Schema.optional(Schema.Boolean), - ignored: Schema.optional(Schema.Boolean), - time: Schema.optional( - Schema.Struct({ - start: NonNegativeInt, - end: Schema.optional(NonNegativeInt), - }), - ), - metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)), -}).annotate({ identifier: "TextPart" }) -export type TextPart = Types.DeepMutable> - -export const ReasoningPart = Schema.Struct({ - ...partBase, - type: Schema.Literal("reasoning"), - text: Schema.String, - metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)), - time: Schema.Struct({ - start: NonNegativeInt, - end: Schema.optional(NonNegativeInt), - }), -}).annotate({ identifier: "ReasoningPart" }) -export type ReasoningPart = Types.DeepMutable> - -const filePartSourceBase = { - text: Schema.Struct({ - value: Schema.String, - start: Schema.Finite, - end: Schema.Finite, - }).annotate({ identifier: "FilePartSourceText" }), -} - -export const FileSource = Schema.Struct({ - ...filePartSourceBase, - type: Schema.Literal("file"), - path: Schema.String, -}).annotate({ identifier: "FileSource" }) - -export const SymbolSource = Schema.Struct({ - ...filePartSourceBase, - type: Schema.Literal("symbol"), - path: Schema.String, - range: LSP.Range, - name: Schema.String, - kind: NonNegativeInt, -}).annotate({ identifier: "SymbolSource" }) - -export const ResourceSource = Schema.Struct({ - ...filePartSourceBase, - type: Schema.Literal("resource"), - clientName: Schema.String, - uri: Schema.String, -}).annotate({ identifier: "ResourceSource" }) - -export const FilePartSource = Schema.Union([FileSource, SymbolSource, ResourceSource]).annotate({ - discriminator: "type", - identifier: "FilePartSource", -}) - -export const FilePart = Schema.Struct({ - ...partBase, - type: Schema.Literal("file"), - mime: Schema.String, - filename: Schema.optional(Schema.String), - url: Schema.String, - source: Schema.optional(FilePartSource), -}).annotate({ identifier: "FilePart" }) -export type FilePart = Types.DeepMutable> - -export const AgentPart = Schema.Struct({ - ...partBase, - type: Schema.Literal("agent"), - name: Schema.String, - source: Schema.optional( - Schema.Struct({ - value: Schema.String, - start: NonNegativeInt, - end: NonNegativeInt, - }), - ), -}).annotate({ identifier: "AgentPart" }) -export type AgentPart = Types.DeepMutable> - -export const CompactionPart = Schema.Struct({ - ...partBase, - type: Schema.Literal("compaction"), - auto: Schema.Boolean, - overflow: Schema.optional(Schema.Boolean), - tail_start_id: Schema.optional(MessageID), -}).annotate({ identifier: "CompactionPart" }) -export type CompactionPart = Types.DeepMutable> - -export const SubtaskPart = Schema.Struct({ - ...partBase, - type: Schema.Literal("subtask"), - prompt: Schema.String, - description: Schema.String, - agent: Schema.String, - model: Schema.optional( - Schema.Struct({ - providerID: ProviderID, - modelID: ModelID, - }), - ), - command: Schema.optional(Schema.String), -}).annotate({ identifier: "SubtaskPart" }) -export type SubtaskPart = Types.DeepMutable> - -export const RetryPart = Schema.Struct({ - ...partBase, - type: Schema.Literal("retry"), - attempt: NonNegativeInt, - error: APIError.EffectSchema, - time: Schema.Struct({ - created: NonNegativeInt, - }), -}).annotate({ identifier: "RetryPart" }) -export type RetryPart = Omit>, "error"> & { - error: APIError -} - -export const StepStartPart = Schema.Struct({ - ...partBase, - type: Schema.Literal("step-start"), - snapshot: Schema.optional(Schema.String), -}).annotate({ identifier: "StepStartPart" }) -export type StepStartPart = Types.DeepMutable> - -export const StepFinishPart = Schema.Struct({ - ...partBase, - type: Schema.Literal("step-finish"), - reason: Schema.String, - snapshot: Schema.optional(Schema.String), - cost: Schema.Finite, - tokens: Schema.Struct({ - total: Schema.optional(Schema.Finite), - input: Schema.Finite, - output: Schema.Finite, - reasoning: Schema.Finite, - cache: Schema.Struct({ - read: Schema.Finite, - write: Schema.Finite, - }), - }), -}).annotate({ identifier: "StepFinishPart" }) -export type StepFinishPart = Types.DeepMutable> - -export const ToolStatePending = Schema.Struct({ - status: Schema.Literal("pending"), - input: Schema.Record(Schema.String, Schema.Any), - raw: Schema.String, -}).annotate({ identifier: "ToolStatePending" }) -export type ToolStatePending = Types.DeepMutable> - -export const ToolStateRunning = Schema.Struct({ - status: Schema.Literal("running"), - input: Schema.Record(Schema.String, Schema.Any), - title: Schema.optional(Schema.String), - metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)), - time: Schema.Struct({ - start: NonNegativeInt, - }), -}).annotate({ identifier: "ToolStateRunning" }) -export type ToolStateRunning = Types.DeepMutable> - -export const ToolStateCompleted = Schema.Struct({ - status: Schema.Literal("completed"), - input: Schema.Record(Schema.String, Schema.Any), - output: Schema.String, - title: Schema.String, - metadata: Schema.Record(Schema.String, Schema.Any), - time: Schema.Struct({ - start: NonNegativeInt, - end: NonNegativeInt, - compacted: Schema.optional(NonNegativeInt), - }), - attachments: Schema.optional(Schema.Array(FilePart)), -}).annotate({ identifier: "ToolStateCompleted" }) -export type ToolStateCompleted = Types.DeepMutable> - function truncateToolOutput(text: string, maxChars?: number) { if (!maxChars || text.length <= maxChars) return text const omitted = text.length - maxChars return `${text.slice(0, maxChars)}\n[Tool output truncated for compaction: omitted ${omitted} chars]` } -export const ToolStateError = Schema.Struct({ - status: Schema.Literal("error"), - input: Schema.Record(Schema.String, Schema.Any), - error: Schema.String, - metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)), - time: Schema.Struct({ - start: NonNegativeInt, - end: NonNegativeInt, - }), -}).annotate({ identifier: "ToolStateError" }) -export type ToolStateError = Types.DeepMutable> - -export const ToolState = Schema.Union([ - ToolStatePending, - ToolStateRunning, - ToolStateCompleted, - ToolStateError, -]).annotate({ - discriminator: "status", - identifier: "ToolState", -}) -export type ToolState = ToolStatePending | ToolStateRunning | ToolStateCompleted | ToolStateError - -export const ToolPart = Schema.Struct({ - ...partBase, - type: Schema.Literal("tool"), - callID: Schema.String, - tool: Schema.String, - state: ToolState, - metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)), -}).annotate({ identifier: "ToolPart" }) -export type ToolPart = Omit>, "state"> & { - state: ToolState -} - -const messageBase = { - id: MessageID, - sessionID: SessionID, -} - -export const User = Schema.Struct({ - ...messageBase, - role: Schema.Literal("user"), - time: Schema.Struct({ - created: NonNegativeInt, - }), - format: Schema.optional(Format), - summary: Schema.optional( - Schema.Struct({ - title: Schema.optional(Schema.String), - body: Schema.optional(Schema.String), - diffs: Schema.Array(Snapshot.FileDiff), - }), - ), - agent: Schema.String, - model: Schema.Struct({ - providerID: ProviderID, - modelID: ModelID, - variant: Schema.optional(Schema.String), - }), - system: Schema.optional(Schema.String), - tools: Schema.optional(Schema.Record(Schema.String, Schema.Boolean)), -}).annotate({ identifier: "UserMessage" }) -export type User = Types.DeepMutable> - -export const Part = Schema.Union([ - TextPart, - SubtaskPart, - ReasoningPart, - FilePart, - ToolPart, - StepStartPart, - StepFinishPart, - SnapshotPart, - PatchPart, - AgentPart, - RetryPart, - CompactionPart, -]).annotate({ discriminator: "type", identifier: "Part" }) -export type Part = - | TextPart - | SubtaskPart - | ReasoningPart - | FilePart - | ToolPart - | StepStartPart - | StepFinishPart - | SnapshotPart - | PatchPart - | AgentPart - | RetryPart - | CompactionPart - -const AssistantErrorSchema = Schema.Union([ - ...MessageError.Shared, - AbortedError.EffectSchema, - StructuredOutputError.EffectSchema, - ContextOverflowError.EffectSchema, - APIError.EffectSchema, -]).annotate({ discriminator: "name" }) -type AssistantError = Schema.Schema.Type - -// ── Prompt input schemas ───────────────────────────────────────────────────── -// -// Consumers of `SessionPrompt.PromptInput.parts` send part drafts without the -// ambient IDs (`messageID`, `sessionID`) that live on stored parts, and may -// omit `id` to let the server allocate one. These Schema-Struct variants -// carry that shape so prompt decoding can accept drafts without stored IDs. - -export const TextPartInput = Schema.Struct({ - id: Schema.optional(PartID), - type: Schema.Literal("text"), - text: Schema.String, - synthetic: Schema.optional(Schema.Boolean), - ignored: Schema.optional(Schema.Boolean), - time: Schema.optional( - Schema.Struct({ - start: NonNegativeInt, - end: Schema.optional(NonNegativeInt), - }), - ), - metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)), -}).annotate({ identifier: "TextPartInput" }) -export type TextPartInput = Types.DeepMutable> - -export const FilePartInput = Schema.Struct({ - id: Schema.optional(PartID), - type: Schema.Literal("file"), - mime: Schema.String, - filename: Schema.optional(Schema.String), - url: Schema.String, - source: Schema.optional(FilePartSource), -}).annotate({ identifier: "FilePartInput" }) -export type FilePartInput = Types.DeepMutable> - -export const AgentPartInput = Schema.Struct({ - id: Schema.optional(PartID), - type: Schema.Literal("agent"), - name: Schema.String, - source: Schema.optional( - Schema.Struct({ - value: Schema.String, - start: NonNegativeInt, - end: NonNegativeInt, - }), - ), -}).annotate({ identifier: "AgentPartInput" }) -export type AgentPartInput = Types.DeepMutable> - -export const SubtaskPartInput = Schema.Struct({ - id: Schema.optional(PartID), - type: Schema.Literal("subtask"), - prompt: Schema.String, - description: Schema.String, - agent: Schema.String, - model: Schema.optional( - Schema.Struct({ - providerID: ProviderID, - modelID: ModelID, - }), - ), - command: Schema.optional(Schema.String), -}).annotate({ identifier: "SubtaskPartInput" }) -export type SubtaskPartInput = Types.DeepMutable> - -export const Assistant = Schema.Struct({ - ...messageBase, - role: Schema.Literal("assistant"), - time: Schema.Struct({ - created: NonNegativeInt, - completed: Schema.optional(NonNegativeInt), - }), - error: Schema.optional(AssistantErrorSchema), - parentID: MessageID, - modelID: ModelID, - providerID: ProviderID, - /** - * @deprecated - */ - mode: Schema.String, - agent: Schema.String, - path: Schema.Struct({ - cwd: Schema.String, - root: Schema.String, - }), - summary: Schema.optional(Schema.Boolean), - cost: Schema.Finite, - tokens: Schema.Struct({ - total: Schema.optional(Schema.Finite), - input: Schema.Finite, - output: Schema.Finite, - reasoning: Schema.Finite, - cache: Schema.Struct({ - read: Schema.Finite, - write: Schema.Finite, - }), - }), - structured: Schema.optional(Schema.Any), - variant: Schema.optional(Schema.String), - finish: Schema.optional(Schema.String), -}).annotate({ identifier: "AssistantMessage" }) -export type Assistant = Omit>, "error"> & { - error?: AssistantError -} - -export const Info = Schema.Union([User, Assistant]).annotate({ discriminator: "role", identifier: "Message" }) -export type Info = User | Assistant - const UpdatedEventSchema = Schema.Struct({ sessionID: SessionID, info: Info, @@ -505,7 +69,7 @@ const RemovedEventSchema = Schema.Struct({ const PartUpdatedEventSchema = Schema.Struct({ sessionID: SessionID, part: Part, - time: NonNegativeInt, + time: Schema.Number, }) const PartRemovedEventSchema = Schema.Struct({ @@ -551,15 +115,6 @@ export const Event = { }), } -export const WithParts = Schema.Struct({ - info: Info, - parts: Schema.Array(Part), -}) -export type WithParts = { - info: Info - parts: Part[] -} - const Cursor = Schema.Struct({ id: MessageID, time: Schema.Finite.check(Schema.isGreaterThanOrEqualTo(0)), @@ -579,14 +134,14 @@ export const cursor = { const info = (row: typeof MessageTable.$inferSelect) => ({ - ...(row.data as object), + ...row.data, id: row.id, sessionID: row.session_id, }) as Info const part = (row: typeof PartTable.$inferSelect) => ({ - ...(row.data as object), + ...row.data, id: row.id, sessionID: row.session_id, messageID: row.message_id, @@ -988,7 +543,7 @@ export function parts(message_id: MessageID) { return rows.map( (row) => ({ - ...(row.data as object), + ...row.data, id: row.id, sessionID: row.session_id, messageID: row.message_id, diff --git a/packages/opencode/src/session/overflow.ts b/packages/opencode/src/session/overflow.ts index d01fe5c62..343c8408e 100644 --- a/packages/opencode/src/session/overflow.ts +++ b/packages/opencode/src/session/overflow.ts @@ -1,4 +1,5 @@ import type { Config } from "@/config/config" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import type { Provider } from "@/provider/provider" import { ProviderTransform } from "@/provider/transform" import type { MessageV2 } from "./message-v2" @@ -19,7 +20,7 @@ export function usable(input: { cfg: Config.Info; model: Provider.Model; outputT export function isOverflow(input: { cfg: Config.Info - tokens: MessageV2.Assistant["tokens"] + tokens: SessionLegacy.Assistant["tokens"] model: Provider.Model outputTokenMax?: number }) { diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts index b4e6ad423..0b323488e 100644 --- a/packages/opencode/src/session/processor.ts +++ b/packages/opencode/src/session/processor.ts @@ -1,4 +1,5 @@ import { Image } from "@/image/image" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { Cause, Deferred, Effect, Exit, Layer, Context, Scope, Schema } from "effect" import * as Stream from "effect/Stream" import { Agent } from "@/agent/agent" @@ -35,25 +36,25 @@ const log = Log.create({ service: "session.processor" }) export type Result = "compact" | "stop" | "continue" export interface Handle { - readonly message: MessageV2.Assistant + readonly message: SessionLegacy.Assistant readonly updateToolCall: ( toolCallID: string, - update: (part: MessageV2.ToolPart) => MessageV2.ToolPart, - ) => Effect.Effect + update: (part: SessionLegacy.ToolPart) => SessionLegacy.ToolPart, + ) => Effect.Effect readonly completeToolCall: ( toolCallID: string, output: { title: string metadata: Record output: string - attachments?: MessageV2.FilePart[] + attachments?: SessionLegacy.FilePart[] }, ) => Effect.Effect readonly process: (streamInput: LLM.StreamInput) => Effect.Effect } type Input = { - assistantMessage: MessageV2.Assistant + assistantMessage: SessionLegacy.Assistant sessionID: SessionID model: Provider.Model } @@ -63,9 +64,9 @@ export interface Interface { } type ToolCall = { - partID: MessageV2.ToolPart["id"] - messageID: MessageV2.ToolPart["messageID"] - sessionID: MessageV2.ToolPart["sessionID"] + partID: SessionLegacy.ToolPart["id"] + messageID: SessionLegacy.ToolPart["messageID"] + sessionID: SessionLegacy.ToolPart["sessionID"] done: Deferred.Deferred inputEnded: boolean } @@ -76,8 +77,8 @@ interface ProcessorContext extends Input { snapshot: string | undefined blocked: boolean needsCompaction: boolean - currentText: MessageV2.TextPart | undefined - reasoningMap: Record + currentText: SessionLegacy.TextPart | undefined + reasoningMap: Record } type StreamEvent = LLMEvent @@ -151,7 +152,7 @@ export const layer = Layer.effect( const updateToolCall = Effect.fn("SessionProcessor.updateToolCall")(function* ( toolCallID: string, - update: (part: MessageV2.ToolPart) => MessageV2.ToolPart, + update: (part: SessionLegacy.ToolPart) => SessionLegacy.ToolPart, ) { const match = yield* readToolCall(toolCallID) if (!match) return undefined @@ -171,7 +172,7 @@ export const layer = Layer.effect( title: string metadata: Record output: string - attachments?: MessageV2.FilePart[] + attachments?: SessionLegacy.FilePart[] }, ) { const match = yield* readToolCall(toolCallID) @@ -266,7 +267,7 @@ export const layer = Layer.effect( callID: input.id, state: { status: "pending", input: {}, raw: "" }, metadata: input.providerExecuted ? { providerExecuted: true } : undefined, - } satisfies MessageV2.ToolPart) + } satisfies SessionLegacy.ToolPart) ctx.toolcalls[input.id] = { done: yield* Deferred.make(), partID: part.id, @@ -277,11 +278,11 @@ export const layer = Layer.effect( return { call: ctx.toolcalls[input.id], part } }) - const isFilePart = (value: unknown): value is MessageV2.FilePart => Schema.is(MessageV2.FilePart)(value) + const isFilePart = (value: unknown): value is SessionLegacy.FilePart => Schema.is(SessionLegacy.FilePart)(value) const toolResultOutput = ( value: Extract, - ): { title: string; metadata: Record; output: string; attachments?: MessageV2.FilePart[] } => { + ): { title: string; metadata: Record; output: string; attachments?: SessionLegacy.FilePart[] } => { if (isRecord(value.result.value) && typeof value.result.value.output === "string") { return { title: typeof value.result.value.title === "string" ? value.result.value.title : value.name, @@ -461,7 +462,7 @@ export const layer = Layer.effect( ), Effect.exit, ) - : Effect.succeed(Exit.succeed(attachment)), + : Effect.succeed(Exit.succeed(attachment)), ) const omitted = normalized.filter(Exit.isFailure).length const attachments = normalized.filter(Exit.isSuccess).map((item) => item.value) @@ -484,7 +485,7 @@ export const layer = Layer.effect( type: "text", text: output.output, }, - ...(output.attachments?.map((item: MessageV2.FilePart) => ({ + ...(output.attachments?.map((item: SessionLegacy.FilePart) => ({ type: "file" as const, uri: item.url, mime: item.mime, @@ -751,7 +752,7 @@ export const layer = Layer.effect( const halt = Effect.fn("SessionProcessor.halt")(function* (e: unknown) { slog.error("process", { error: errorMessage(e), stack: e instanceof Error ? e.stack : undefined }) const error = parse(e) - if (MessageV2.ContextOverflowError.isInstance(error)) { + if (SessionLegacy.ContextOverflowError.isInstance(error)) { ctx.needsCompaction = true yield* bus.publish(Session.Event.Error, { sessionID: ctx.sessionID, error }) return diff --git a/packages/opencode/src/session/projectors.ts b/packages/opencode/src/session/projectors.ts index 77c1c1923..bb01b5e21 100644 --- a/packages/opencode/src/session/projectors.ts +++ b/packages/opencode/src/session/projectors.ts @@ -1,4 +1,5 @@ import { NotFoundError } from "@/storage/storage" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { eq } from "drizzle-orm" import { and } from "drizzle-orm" import { sql } from "drizzle-orm" @@ -21,9 +22,9 @@ function foreign(err: unknown) { export type DeepPartial = T extends object ? { [K in keyof T]?: DeepPartial | null } : T -type Usage = Pick +type Usage = Pick -function usage(part: MessageV2.Part | unknown): Usage | undefined { +function usage(part: SessionLegacy.Part | unknown): Usage | undefined { if (typeof part !== "object" || part === null) return undefined const value = part as Record if (value.type !== "step-finish") return undefined @@ -134,9 +135,9 @@ export default [ id, session_id: sessionID, time_created, - data: rest as never, + data: rest, }) - .onConflictDoUpdate({ target: MessageTable.id, set: { data: rest as never } }) + .onConflictDoUpdate({ target: MessageTable.id, set: { data: rest } }) .run() } catch (err) { if (!foreign(err)) throw err diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 9926da137..43ee7bb3f 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -1,4 +1,5 @@ import path from "path" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import os from "os" import { SessionID, MessageID, PartID } from "./schema" import { MessageV2 } from "./message-v2" @@ -65,8 +66,8 @@ import { LLMEvent } from "@opencode-ai/llm" // @ts-ignore globalThis.AI_SDK_LOG_WARNINGS = false -const decodeMessageInfo = Schema.decodeUnknownExit(MessageV2.Info) -const decodeMessagePart = Schema.decodeUnknownExit(MessageV2.Part) +const decodeMessageInfo = Schema.decodeUnknownExit(SessionLegacy.Info) +const decodeMessagePart = Schema.decodeUnknownExit(SessionLegacy.Part) const STRUCTURED_OUTPUT_DESCRIPTION = `Use this tool to return your final response in the requested structured format. @@ -83,10 +84,10 @@ const elog = EffectLogger.create({ service: "session.prompt" }) export interface Interface { readonly cancel: (sessionID: SessionID) => Effect.Effect - readonly prompt: (input: PromptInput) => Effect.Effect - readonly loop: (input: LoopInput) => Effect.Effect - readonly shell: (input: ShellInput) => Effect.Effect - readonly command: (input: CommandInput) => Effect.Effect + readonly prompt: (input: PromptInput) => Effect.Effect + readonly loop: (input: LoopInput) => Effect.Effect + readonly shell: (input: ShellInput) => Effect.Effect + readonly command: (input: CommandInput) => Effect.Effect readonly resolvePromptParts: (template: string) => Effect.Effect } @@ -235,14 +236,14 @@ export const layer = Layer.effect( const title = Effect.fn("SessionPrompt.ensureTitle")(function* (input: { session: Session.Info - history: MessageV2.WithParts[] + history: SessionLegacy.WithParts[] providerID: ProviderID modelID: ModelID }) { if (input.session.parentID) return if (!Session.isDefaultTitle(input.session.title)) return - const real = (m: MessageV2.WithParts) => + const real = (m: SessionLegacy.WithParts) => m.info.role === "user" && !m.parts.every((p) => "synthetic" in p && p.synthetic) const idx = input.history.findIndex(real) if (idx === -1) return @@ -253,7 +254,7 @@ export const layer = Layer.effect( if (!firstUser || firstUser.info.role !== "user") return const firstInfo = firstUser.info - const subtasks = firstUser.parts.filter((p): p is MessageV2.SubtaskPart => p.type === "subtask") + const subtasks = firstUser.parts.filter((p): p is SessionLegacy.SubtaskPart => p.type === "subtask") const onlySubtasks = subtasks.length > 0 && firstUser.parts.every((p) => p.type === "subtask") const ag = yield* agents.get("title") @@ -296,19 +297,19 @@ export const layer = Layer.effect( }) const handleSubtask = Effect.fn("SessionPrompt.handleSubtask")(function* (input: { - task: MessageV2.SubtaskPart + task: SessionLegacy.SubtaskPart model: Provider.Model - lastUser: MessageV2.User + lastUser: SessionLegacy.User sessionID: SessionID session: Session.Info - msgs: MessageV2.WithParts[] + msgs: SessionLegacy.WithParts[] }) { const { task, model, lastUser, sessionID, session, msgs } = input const ctx = yield* InstanceState.context const promptOps = yield* ops() const { task: taskTool } = yield* registry.named() const taskModel = task.model ? yield* getModel(task.model.providerID, task.model.modelID, sessionID) : model - const assistantMessage: MessageV2.Assistant = yield* sessions.updateMessage({ + const assistantMessage: SessionLegacy.Assistant = yield* sessions.updateMessage({ id: MessageID.ascending(), role: "assistant", parentID: lastUser.id, @@ -323,7 +324,7 @@ export const layer = Layer.effect( providerID: taskModel.providerID, time: { created: Date.now() }, }) - let part: MessageV2.ToolPart = yield* sessions.updatePart({ + let part: SessionLegacy.ToolPart = yield* sessions.updatePart({ id: PartID.ascending(), messageID: assistantMessage.id, sessionID: assistantMessage.sessionID, @@ -379,7 +380,7 @@ export const layer = Layer.effect( ...part, type: "tool", state: { ...part.state, ...val }, - } satisfies MessageV2.ToolPart) + } satisfies SessionLegacy.ToolPart) }), ask: (req: any) => permission @@ -413,7 +414,7 @@ export const layer = Layer.effect( metadata: part.state.metadata, input: part.state.input, }, - } satisfies MessageV2.ToolPart) + } satisfies SessionLegacy.ToolPart) } }), ), @@ -448,7 +449,7 @@ export const layer = Layer.effect( attachments, time: { ...part.state.time, end: Date.now() }, }, - } satisfies MessageV2.ToolPart) + } satisfies SessionLegacy.ToolPart) } if (!result) { @@ -464,12 +465,12 @@ export const layer = Layer.effect( metadata: part.state.status === "pending" ? undefined : part.state.metadata, input: part.state.input, }, - } satisfies MessageV2.ToolPart) + } satisfies SessionLegacy.ToolPart) } if (!task.command) return - const summaryUserMsg: MessageV2.User = { + const summaryUserMsg: SessionLegacy.User = { id: MessageID.ascending(), sessionID, role: "user", @@ -485,7 +486,7 @@ export const layer = Layer.effect( type: "text", text: "Summarize the task tool output above and continue with your task.", synthetic: true, - } satisfies MessageV2.TextPart) + } satisfies SessionLegacy.TextPart) }) const shellImpl = Effect.fn("SessionPrompt.shellImpl")(function* (input: ShellInput, ready?: Latch.Latch) { @@ -507,7 +508,7 @@ export const layer = Layer.effect( throw error } const model = input.model ?? agent.model ?? (yield* currentModel(input.sessionID)) - const userMsg: MessageV2.User = { + const userMsg: SessionLegacy.User = { id: input.messageID ?? MessageID.ascending(), sessionID: input.sessionID, time: { created: Date.now() }, @@ -516,7 +517,7 @@ export const layer = Layer.effect( model: { providerID: model.providerID, modelID: model.modelID }, } yield* sessions.updateMessage(userMsg) - const userPart: MessageV2.Part = { + const userPart: SessionLegacy.Part = { type: "text", id: PartID.ascending(), messageID: userMsg.id, @@ -526,7 +527,7 @@ export const layer = Layer.effect( } yield* sessions.updatePart(userPart) - const msg: MessageV2.Assistant = { + const msg: SessionLegacy.Assistant = { id: MessageID.ascending(), sessionID: input.sessionID, parentID: userMsg.id, @@ -542,7 +543,7 @@ export const layer = Layer.effect( } yield* sessions.updateMessage(msg) const started = Date.now() - const part: MessageV2.ToolPart = { + const part: SessionLegacy.ToolPart = { type: "tool", id: PartID.ascending(), messageID: msg.id, @@ -713,7 +714,7 @@ export const layer = Layer.effect( : undefined const variant = input.variant ?? (ag.variant && full?.variants?.[ag.variant] ? ag.variant : undefined) - const info: MessageV2.User = { + const info: SessionLegacy.User = { id: input.messageID ?? MessageID.ascending(), role: "user", sessionID: input.sessionID, @@ -754,8 +755,8 @@ export const layer = Layer.effect( yield* Effect.addFinalizer(() => instruction.clear(info.id)) - type Draft = T extends MessageV2.Part ? Omit & { id?: string } : never - const assign = (part: Draft): MessageV2.Part => ({ + type Draft = T extends SessionLegacy.Part ? Omit & { id?: string } : never + const assign = (part: Draft): SessionLegacy.Part => ({ ...part, id: part.id ? PartID.make(part.id) : PartID.ascending(), }) @@ -784,14 +785,14 @@ export const layer = Layer.effect( }) }) - const resolvePart: (part: PromptInput["parts"][number]) => Effect.Effect[]> = Effect.fn( + const resolvePart: (part: PromptInput["parts"][number]) => Effect.Effect[]> = Effect.fn( "SessionPrompt.resolveUserPart", )(function* (part) { if (part.type === "file") { if (part.source?.type === "resource") { const { clientName, uri } = part.source log.info("mcp resource", { clientName, uri, mime: part.mime }) - const pieces: Draft[] = [ + const pieces: Draft[] = [ { messageID: info.id, sessionID: input.sessionID, @@ -911,7 +912,7 @@ export const layer = Layer.effect( if (end) limit = end - (offset - 1) } const args = { filePath: filepath, offset, limit } - const pieces: Draft[] = [ + const pieces: Draft[] = [ ...(referenceContext ? [{ ...referenceContext, messageID: info.id, sessionID: input.sessionID }] : []), @@ -1207,7 +1208,7 @@ export const layer = Layer.effect( return { info, parts } }, Effect.scoped) - const prompt: (input: PromptInput) => Effect.Effect = Effect.fn( + const prompt: (input: PromptInput) => Effect.Effect = Effect.fn( "SessionPrompt.prompt", )(function* (input: PromptInput) { const session = yield* sessions.get(input.sessionID).pipe(Effect.orDie) @@ -1236,7 +1237,7 @@ export const layer = Layer.effect( throw new Error("Impossible") }) - const runLoop: (sessionID: SessionID) => Effect.Effect = Effect.fn("SessionPrompt.run")( + const runLoop: (sessionID: SessionID) => Effect.Effect = Effect.fn("SessionPrompt.run")( function* (sessionID: SessionID) { const ctx = yield* InstanceState.context const slog = elog.with({ sessionID }) @@ -1328,7 +1329,7 @@ export const layer = Layer.effect( Effect.provideService(Session.Service, sessions), ) - const msg: MessageV2.Assistant = { + const msg: SessionLegacy.Assistant = { id: MessageID.ascending(), parentID: lastUser.id, role: "assistant", @@ -1448,7 +1449,7 @@ export const layer = Layer.effect( const finished = handle.message.finish && !["tool-calls", "unknown"].includes(handle.message.finish) if (finished && !handle.message.error) { if (format.type === "json_schema") { - handle.message.error = new MessageV2.StructuredOutputError({ + handle.message.error = new SessionLegacy.StructuredOutputError({ message: "Model did not produce structured output", retries: 0, }).toObject() @@ -1481,13 +1482,13 @@ export const layer = Layer.effect( }, ) - const loop: (input: LoopInput) => Effect.Effect = Effect.fn("SessionPrompt.loop")(function* ( + const loop: (input: LoopInput) => Effect.Effect = Effect.fn("SessionPrompt.loop")(function* ( input: LoopInput, ) { return yield* state.ensureRunning(input.sessionID, lastAssistant(input.sessionID), runLoop(input.sessionID)) }) - const shell: (input: ShellInput) => Effect.Effect = Effect.fn( + const shell: (input: ShellInput) => Effect.Effect = Effect.fn( "SessionPrompt.shell", )(function* (input: ShellInput) { const ready = yield* Latch.make() @@ -1672,15 +1673,15 @@ export const PromptInput = Schema.Struct({ description: "@deprecated tools and permissions have been merged, you can set permissions on the session itself now", }), - format: Schema.optional(MessageV2.Format), + format: Schema.optional(SessionLegacy.Format), system: Schema.optional(Schema.String), variant: Schema.optional(Schema.String), parts: Schema.Array( Schema.Union([ - MessageV2.TextPartInput, - MessageV2.FilePartInput, - MessageV2.AgentPartInput, - MessageV2.SubtaskPartInput, + SessionLegacy.TextPartInput, + SessionLegacy.FilePartInput, + SessionLegacy.AgentPartInput, + SessionLegacy.SubtaskPartInput, ]).annotate({ discriminator: "type" }), ), }) @@ -1719,7 +1720,7 @@ export const CommandInput = Schema.Struct({ mime: Schema.String, filename: Schema.optional(Schema.String), url: Schema.String, - source: Schema.optional(MessageV2.FilePartSource), + source: Schema.optional(SessionLegacy.FilePartSource), }), ]).annotate({ discriminator: "type" }), ), diff --git a/packages/opencode/src/session/prompt/reference.ts b/packages/opencode/src/session/prompt/reference.ts index ae1a46579..de20b7f45 100644 --- a/packages/opencode/src/session/prompt/reference.ts +++ b/packages/opencode/src/session/prompt/reference.ts @@ -1,4 +1,5 @@ import { Option, Schema } from "effect" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { MessageV2 } from "../message-v2" import { Reference } from "@/reference/reference" @@ -33,7 +34,7 @@ export function referenceTextPart(input: { target?: string targetPath?: string problem?: string -}): MessageV2.TextPartInput { +}): SessionLegacy.TextPartInput { const metadata: ReferencePromptMetadata = { name: input.reference.name, kind: input.reference.kind, diff --git a/packages/opencode/src/session/reminders.ts b/packages/opencode/src/session/reminders.ts index a11bd5e67..206304b39 100644 --- a/packages/opencode/src/session/reminders.ts +++ b/packages/opencode/src/session/reminders.ts @@ -1,4 +1,5 @@ import path from "path" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { Effect } from "effect" import { Agent } from "@/agent/agent" import { AppFileSystem } from "@opencode-ai/core/filesystem" @@ -12,7 +13,7 @@ import BUILD_SWITCH from "./prompt/build-switch.txt" import PLAN_MODE from "./prompt/plan-mode.txt" export const apply = Effect.fn("SessionReminders.apply")(function* (input: { - messages: MessageV2.WithParts[] + messages: SessionLegacy.WithParts[] agent: Agent.Info session: Session.Info }) { diff --git a/packages/opencode/src/session/retry.ts b/packages/opencode/src/session/retry.ts index 463bc27a9..bcfb54c47 100644 --- a/packages/opencode/src/session/retry.ts +++ b/packages/opencode/src/session/retry.ts @@ -1,4 +1,5 @@ import type { NamedError } from "@opencode-ai/core/util/error" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { Cause, Clock, Duration, Effect, Schedule } from "effect" import { MessageV2 } from "./message-v2" import { iife } from "@/util/iife" @@ -31,7 +32,7 @@ function cap(ms: number) { return Math.min(ms, RETRY_MAX_DELAY) } -export function delay(attempt: number, error?: MessageV2.APIError) { +export function delay(attempt: number, error?: SessionLegacy.APIError) { if (error) { const headers = error.data.responseHeaders if (headers) { @@ -66,8 +67,8 @@ export function delay(attempt: number, error?: MessageV2.APIError) { export function retryable(error: Err, provider: string) { // context overflow errors should not be retried - if (MessageV2.ContextOverflowError.isInstance(error)) return undefined - if (MessageV2.APIError.isInstance(error)) { + if (SessionLegacy.ContextOverflowError.isInstance(error)) return undefined + if (SessionLegacy.APIError.isInstance(error)) { const status = error.data.statusCode // 5xx errors are transient server failures and should always be retried, // even when the provider SDK doesn't explicitly mark them as retryable. @@ -183,7 +184,7 @@ export function policy(opts: { const retry = retryable(error, opts.provider) if (!retry) return Cause.done(meta.attempt) return Effect.gen(function* () { - const wait = delay(meta.attempt, MessageV2.APIError.isInstance(error) ? error : undefined) + const wait = delay(meta.attempt, SessionLegacy.APIError.isInstance(error) ? error : undefined) const now = yield* Clock.currentTimeMillis yield* opts.set({ attempt: meta.attempt, diff --git a/packages/opencode/src/session/revert.ts b/packages/opencode/src/session/revert.ts index 950d533a3..457438171 100644 --- a/packages/opencode/src/session/revert.ts +++ b/packages/opencode/src/session/revert.ts @@ -1,4 +1,5 @@ import { Effect, Layer, Context, Schema } from "effect" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { Bus } from "../bus" import { Snapshot } from "../snapshot" import { Storage } from "@/storage/storage" @@ -41,7 +42,7 @@ export const layer = Layer.effect( const revert = Effect.fn("SessionRevert.revert")(function* (input: RevertInput) { yield* state.assertNotBusy(input.sessionID) const all = yield* sessions.messages({ sessionID: input.sessionID }).pipe(Effect.orDie) - let lastUser: MessageV2.User | undefined + let lastUser: SessionLegacy.User | undefined const session = yield* sessions.get(input.sessionID).pipe(Effect.orDie) let rev: Session.Info["revert"] @@ -105,8 +106,8 @@ export const layer = Layer.effect( const sessionID = session.id const msgs = yield* sessions.messages({ sessionID }).pipe(Effect.orDie) const messageID = session.revert.messageID - const remove = [] as MessageV2.WithParts[] - let target: MessageV2.WithParts | undefined + const remove = [] as SessionLegacy.WithParts[] + let target: SessionLegacy.WithParts | undefined for (const msg of msgs) { if (msg.info.id < messageID) continue if (msg.info.id > messageID) { diff --git a/packages/opencode/src/session/run-state.ts b/packages/opencode/src/session/run-state.ts index 8f0051dfb..1b92dce68 100644 --- a/packages/opencode/src/session/run-state.ts +++ b/packages/opencode/src/session/run-state.ts @@ -1,4 +1,5 @@ import { InstanceState } from "@/effect/instance-state" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { Runner } from "@/effect/runner" import { BackgroundJob } from "@/background/job" import { Effect, Latch, Layer, Scope, Context } from "effect" @@ -12,15 +13,15 @@ export interface Interface { readonly cancel: (sessionID: SessionID) => Effect.Effect readonly ensureRunning: ( sessionID: SessionID, - onInterrupt: Effect.Effect, - work: Effect.Effect, - ) => Effect.Effect + onInterrupt: Effect.Effect, + work: Effect.Effect, + ) => Effect.Effect readonly startShell: ( sessionID: SessionID, - onInterrupt: Effect.Effect, - work: Effect.Effect, + onInterrupt: Effect.Effect, + work: Effect.Effect, ready?: Latch.Latch, - ) => Effect.Effect + ) => Effect.Effect } export class Service extends Context.Service()("@opencode/SessionRunState") {} @@ -34,7 +35,7 @@ export const layer = Layer.effect( const state = yield* InstanceState.make( Effect.fn("SessionRunState.state")(function* () { const scope = yield* Scope.Scope - const runners = new Map>() + const runners = new Map>() yield* Effect.addFinalizer( Effect.fnUntraced(function* () { yield* Effect.forEach(runners.values(), (runner) => runner.cancel, { @@ -50,12 +51,12 @@ export const layer = Layer.effect( const runner = Effect.fn("SessionRunState.runner")(function* ( sessionID: SessionID, - onInterrupt: Effect.Effect, + onInterrupt: Effect.Effect, ) { const data = yield* InstanceState.get(state) const existing = data.runners.get(sessionID) if (existing) return existing - const next = Runner.make(data.scope, { + const next = Runner.make(data.scope, { onIdle: Effect.gen(function* () { data.runners.delete(sessionID) yield* status.set(sessionID, { type: "idle" }) @@ -86,16 +87,16 @@ export const layer = Layer.effect( const ensureRunning = Effect.fn("SessionRunState.ensureRunning")(function* ( sessionID: SessionID, - onInterrupt: Effect.Effect, - work: Effect.Effect, + onInterrupt: Effect.Effect, + work: Effect.Effect, ) { return yield* (yield* runner(sessionID, onInterrupt)).ensureRunning(work) }) const startShell = Effect.fn("SessionRunState.startShell")(function* ( sessionID: SessionID, - onInterrupt: Effect.Effect, - work: Effect.Effect, + onInterrupt: Effect.Effect, + work: Effect.Effect, ready?: Latch.Latch, ) { return yield* (yield* runner(sessionID, onInterrupt)) diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index 54b0e98e5..ecffd01ce 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -1,4 +1,5 @@ import { Slug } from "@opencode-ai/core/util/slug" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { serviceUse } from "@opencode-ai/core/effect/service-use" import path from "path" import { BackgroundJob } from "@/background/job" @@ -361,9 +362,9 @@ export const Event = { "session.error", Schema.Struct({ sessionID: Schema.optional(SessionID), - // Reuses MessageV2.Assistant.fields.error (already Schema.optional) so + // Reuses SessionLegacy.Assistant.fields.error (already Schema.optional) so // the derived zod keeps the same discriminated-union shape on the bus. - error: MessageV2.Assistant.fields.error, + error: SessionLegacy.Assistant.fields.error, }), ), } @@ -472,18 +473,18 @@ export interface Interface { readonly clearRevert: (sessionID: SessionID) => Effect.Effect readonly setSummary: (input: { sessionID: SessionID; summary: Info["summary"] }) => Effect.Effect readonly diff: (sessionID: SessionID) => Effect.Effect - readonly messages: (input: { sessionID: SessionID; limit?: number }) => Effect.Effect + readonly messages: (input: { sessionID: SessionID; limit?: number }) => Effect.Effect readonly children: (parentID: SessionID) => Effect.Effect readonly remove: (sessionID: SessionID) => Effect.Effect - readonly updateMessage: (msg: T) => Effect.Effect + readonly updateMessage: (msg: T) => Effect.Effect readonly removeMessage: (input: { sessionID: SessionID; messageID: MessageID }) => Effect.Effect readonly removePart: (input: { sessionID: SessionID; messageID: MessageID; partID: PartID }) => Effect.Effect readonly getPart: (input: { sessionID: SessionID messageID: MessageID partID: PartID - }) => Effect.Effect - readonly updatePart: (part: T) => Effect.Effect + }) => Effect.Effect + readonly updatePart: (part: T) => Effect.Effect readonly updatePartDelta: (input: { sessionID: SessionID messageID: MessageID @@ -494,8 +495,8 @@ export interface Interface { /** Finds the first message matching the predicate, searching newest-first. */ readonly findMessage: ( sessionID: SessionID, - predicate: (msg: MessageV2.WithParts) => boolean, - ) => Effect.Effect, NotFound> + predicate: (msg: SessionLegacy.WithParts) => boolean, + ) => Effect.Effect, NotFound> } export class Service extends Context.Service()("@opencode/Session") {} @@ -615,13 +616,13 @@ export const layer: Layer.Layer< } }) - const updateMessage = (msg: T): Effect.Effect => + const updateMessage = (msg: T): Effect.Effect => Effect.gen(function* () { yield* sync.run(MessageV2.Event.Updated, { sessionID: msg.sessionID, info: msg }) return msg }).pipe(Effect.withSpan("Session.updateMessage")) - const updatePart = (part: T): Effect.Effect => + const updatePart = (part: T): Effect.Effect => Effect.gen(function* () { yield* sync.run(MessageV2.Event.PartUpdated, { sessionID: part.sessionID, @@ -647,11 +648,11 @@ export const layer: Layer.Layer< ) if (!row) return return { - ...(row.data as object), + ...row.data, id: row.id, sessionID: row.session_id, messageID: row.message_id, - } as MessageV2.Part + } as SessionLegacy.Part }) const create = Effect.fn("Session.create")(function* (input?: { @@ -703,7 +704,7 @@ export const layer: Layer.Layer< }) for (const part of msg.parts) { - const p: MessageV2.Part = { + const p: SessionLegacy.Part = { ...part, id: PartID.ascending(), messageID: cloned.id, @@ -770,7 +771,7 @@ export const layer: Layer.Layer< } const size = 50 - const result = [] as MessageV2.WithParts[] + const result = [] as SessionLegacy.WithParts[] let before: string | undefined while (true) { const page = yield* MessageV2.page({ sessionID: input.sessionID, limit: size, before }) @@ -833,7 +834,7 @@ export const layer: Layer.Layer< if (!page.more || !page.cursor) break before = page.cursor } - return Option.none() + return Option.none() }) return Service.of({ diff --git a/packages/opencode/src/session/summary.ts b/packages/opencode/src/session/summary.ts index aa4b8719b..7f4d93112 100644 --- a/packages/opencode/src/session/summary.ts +++ b/packages/opencode/src/session/summary.ts @@ -1,4 +1,5 @@ import { Effect, Layer, Context, Schema } from "effect" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { Bus } from "@/bus" import { Snapshot } from "@/snapshot" import { Storage } from "@/storage/storage" @@ -65,7 +66,7 @@ function unquoteGitPath(input: string) { export interface Interface { readonly summarize: (input: { sessionID: SessionID; messageID: MessageID }) => Effect.Effect readonly diff: (input: { sessionID: SessionID; messageID?: MessageID }) => Effect.Effect - readonly computeDiff: (input: { messages: MessageV2.WithParts[] }) => Effect.Effect + readonly computeDiff: (input: { messages: SessionLegacy.WithParts[] }) => Effect.Effect } export class Service extends Context.Service()("@opencode/SessionSummary") {} @@ -78,7 +79,7 @@ export const layer = Layer.effect( const storage = yield* Storage.Service const bus = yield* Bus.Service - const computeDiff = Effect.fn("SessionSummary.computeDiff")(function* (input: { messages: MessageV2.WithParts[] }) { + const computeDiff = Effect.fn("SessionSummary.computeDiff")(function* (input: { messages: SessionLegacy.WithParts[] }) { let from: string | undefined let to: string | undefined for (const item of input.messages) { diff --git a/packages/opencode/src/session/tools.ts b/packages/opencode/src/session/tools.ts index f45df9d0f..8305f23c5 100644 --- a/packages/opencode/src/session/tools.ts +++ b/packages/opencode/src/session/tools.ts @@ -1,4 +1,5 @@ import { Agent } from "@/agent/agent" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { Provider } from "@/provider/provider" import { ProviderTransform } from "@/provider/transform" import { MCP } from "@/mcp" @@ -27,7 +28,7 @@ export const resolve = Effect.fn("SessionTools.resolve")(function* (input: { session: Session.Info processor: Pick bypassAgentCheck: boolean - messages: MessageV2.WithParts[] + messages: SessionLegacy.WithParts[] promptOps: TaskPromptOps }) { using _ = log.time("resolveTools") @@ -151,7 +152,7 @@ export const resolve = Effect.fn("SessionTools.resolve")(function* (input: { ) const textParts: string[] = [] - const attachments: Omit[] = [] + const attachments: Omit[] = [] for (const contentItem of result.content) { if (contentItem.type === "text") textParts.push(contentItem.text) else if (contentItem.type === "image") { diff --git a/packages/opencode/src/storage/db.ts b/packages/opencode/src/storage/db.ts index f7e1feaba..3978192c3 100644 --- a/packages/opencode/src/storage/db.ts +++ b/packages/opencode/src/storage/db.ts @@ -5,7 +5,7 @@ import * as Log from "@opencode-ai/core/util/log" import { NamedError } from "@opencode-ai/core/util/error" import { EffectBridge } from "@/effect/bridge" import { init } from "#db" -import { Effect, Schema } from "effect" +import { Schema } from "effect" import { Database } from "@opencode-ai/core/database/database" export const NotFoundError = NamedError.create("NotFoundError", { @@ -30,7 +30,7 @@ export const Client = Object.assign( const dbPath = getPath() log.info("opening database", { path: dbPath }) - Database.init({ path: dbPath }) + Database.init() const db = init(dbPath) diff --git a/packages/opencode/src/tool/plan.ts b/packages/opencode/src/tool/plan.ts index af206f66a..01ca4a69c 100644 --- a/packages/opencode/src/tool/plan.ts +++ b/packages/opencode/src/tool/plan.ts @@ -1,4 +1,5 @@ import path from "path" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { Effect, Schema } from "effect" import * as Tool from "./tool" import { Question } from "../question" @@ -49,7 +50,7 @@ export const PlanExitTool = Tool.define( const model = lastUser?.info.role === "user" && lastUser.info.model ? lastUser.info.model : yield* provider.defaultModel() - const msg: MessageV2.User = { + const msg: SessionLegacy.User = { id: MessageID.ascending(), sessionID: ctx.sessionID, role: "user", @@ -65,7 +66,7 @@ export const PlanExitTool = Tool.define( type: "text", text: `The plan at ${plan} has been approved, you can now edit files. Execute the plan`, synthetic: true, - } satisfies MessageV2.TextPart) + } satisfies SessionLegacy.TextPart) return { title: "Switching to build agent", diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index fece68800..889e78c97 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -1,4 +1,5 @@ import * as Tool from "./tool" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import DESCRIPTION from "./task.txt" import { ToolJsonSchema } from "./json-schema" import { BackgroundJob } from "@/background/job" @@ -19,8 +20,8 @@ import { RuntimeFlags } from "@/effect/runtime-flags" export interface TaskPromptOps { cancel(sessionID: SessionID): Effect.Effect resolvePromptParts(template: string): Effect.Effect - prompt(input: SessionPrompt.PromptInput): Effect.Effect - loop(input: SessionPrompt.LoopInput): Effect.Effect + prompt(input: SessionPrompt.PromptInput): Effect.Effect + loop(input: SessionPrompt.LoopInput): Effect.Effect } const id = "task" diff --git a/packages/opencode/src/tool/task_status.ts b/packages/opencode/src/tool/task_status.ts index b458b4fc4..8a6e3b9b6 100644 --- a/packages/opencode/src/tool/task_status.ts +++ b/packages/opencode/src/tool/task_status.ts @@ -1,4 +1,5 @@ import * as Tool from "./tool" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import DESCRIPTION from "./task_status.txt" import { BackgroundJob } from "@/background/job" import { Session } from "@/session/session" @@ -30,14 +31,14 @@ function format(input: { taskID: SessionID; state: State; text: string }) { return [`task_id: ${input.taskID}`, `state: ${input.state}`, "", `<${tag}>`, input.text, ``].join("\n") } -function errorText(error: NonNullable) { +function errorText(error: NonNullable) { const data = Reflect.get(error, "data") const message = data && typeof data === "object" ? Reflect.get(data, "message") : undefined if (typeof message === "string" && message) return message return error.name } -function inspectMessage(message: MessageV2.WithParts): InspectResult | undefined { +function inspectMessage(message: SessionLegacy.WithParts): InspectResult | undefined { if (message.info.role !== "assistant") return const text = message.parts.findLast((part) => part.type === "text")?.text ?? "" if (message.info.error) return { state: "error", text: text || errorText(message.info.error) } diff --git a/packages/opencode/src/tool/tool.ts b/packages/opencode/src/tool/tool.ts index f072773fa..4edbec94c 100644 --- a/packages/opencode/src/tool/tool.ts +++ b/packages/opencode/src/tool/tool.ts @@ -1,4 +1,5 @@ import { Effect, Schema } from "effect" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import type { JSONSchema7 } from "@ai-sdk/provider" import type { MessageV2 } from "../session/message-v2" import type { Permission } from "../permission" @@ -38,7 +39,7 @@ export type Context = { abort: AbortSignal callID?: string extra?: { [key: string]: unknown } - messages: MessageV2.WithParts[] + messages: SessionLegacy.WithParts[] metadata(input: { title?: string; metadata?: M }): Effect.Effect ask(input: Omit): Effect.Effect } @@ -47,7 +48,7 @@ export interface ExecuteResult { title: string metadata: M output: string - attachments?: Omit[] + attachments?: Omit[] } export interface Def< diff --git a/packages/opencode/test/cli/github-action.test.ts b/packages/opencode/test/cli/github-action.test.ts index 263f3a45f..35f8e44d7 100644 --- a/packages/opencode/test/cli/github-action.test.ts +++ b/packages/opencode/test/cli/github-action.test.ts @@ -1,10 +1,11 @@ import { test, expect, describe } from "bun:test" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" 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): MessageV2.Part { +function createTextPart(text: string): SessionLegacy.Part { return { id: PartID.ascending(), sessionID: SessionID.make("ses_test"), @@ -14,7 +15,7 @@ function createTextPart(text: string): MessageV2.Part { } } -function createReasoningPart(text: string): MessageV2.Part { +function createReasoningPart(text: string): SessionLegacy.Part { return { id: PartID.ascending(), sessionID: SessionID.make("ses_test"), @@ -25,7 +26,7 @@ function createReasoningPart(text: string): MessageV2.Part { } } -function createToolPart(tool: string, title: string, status: "completed" | "running" = "completed"): MessageV2.Part { +function createToolPart(tool: string, title: string, status: "completed" | "running" = "completed"): SessionLegacy.Part { if (status === "completed") { return { id: PartID.ascending(), @@ -59,7 +60,7 @@ function createToolPart(tool: string, title: string, status: "completed" | "runn } } -function createStepStartPart(): MessageV2.Part { +function createStepStartPart(): SessionLegacy.Part { return { id: PartID.ascending(), sessionID: SessionID.make("ses_test"), @@ -68,7 +69,7 @@ function createStepStartPart(): MessageV2.Part { } } -function createStepFinishPart(): MessageV2.Part { +function createStepFinishPart(): SessionLegacy.Part { return { id: PartID.ascending(), sessionID: SessionID.make("ses_test"), diff --git a/packages/opencode/test/server/httpapi-exercise/runner.ts b/packages/opencode/test/server/httpapi-exercise/runner.ts index b14647680..16eb9a88f 100644 --- a/packages/opencode/test/server/httpapi-exercise/runner.ts +++ b/packages/opencode/test/server/httpapi-exercise/runner.ts @@ -1,4 +1,5 @@ import { Flag } from "@opencode-ai/core/flag/flag" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { Cause, Duration, Effect } from "effect" import { TestLLMServer } from "../../lib/llm-server" import type { Config } from "../../../src/config/config" @@ -140,7 +141,7 @@ function withContext( }), message: (sessionID, input) => Effect.gen(function* () { - const info: MessageV2.User = { + const info: SessionLegacy.User = { id: MessageID.ascending(), sessionID, role: "user", @@ -151,7 +152,7 @@ function withContext( modelID: ModelID.make("test"), }, } - const part: MessageV2.TextPart = { + const part: SessionLegacy.TextPart = { id: PartID.ascending(), sessionID, messageID: info.id, diff --git a/packages/opencode/test/server/httpapi-exercise/types.ts b/packages/opencode/test/server/httpapi-exercise/types.ts index e1fe93ba7..49830686f 100644 --- a/packages/opencode/test/server/httpapi-exercise/types.ts +++ b/packages/opencode/test/server/httpapi-exercise/types.ts @@ -1,4 +1,5 @@ import type { Duration, Effect } from "effect" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import type { Config } from "../../../src/config/config" import type { Project } from "../../../src/project/project" import type { Worktree } from "../../../src/worktree" @@ -57,7 +58,7 @@ export type ScenarioContext = { sessionGet: (sessionID: SessionID) => Effect.Effect project: () => Effect.Effect message: (sessionID: SessionID, input?: { text?: string }) => Effect.Effect - messages: (sessionID: SessionID) => Effect.Effect + messages: (sessionID: SessionID) => Effect.Effect todos: (sessionID: SessionID, todos: TodoInfo[]) => Effect.Effect worktree: (input?: { name?: string }) => Effect.Effect worktreeRemove: (directory: string) => Effect.Effect @@ -118,4 +119,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: MessageV2.User; part: MessageV2.TextPart } +export type MessageSeed = { info: SessionLegacy.User; part: SessionLegacy.TextPart } diff --git a/packages/opencode/test/server/httpapi-sdk.test.ts b/packages/opencode/test/server/httpapi-sdk.test.ts index 6e99fa7b1..22f3d3038 100644 --- a/packages/opencode/test/server/httpapi-sdk.test.ts +++ b/packages/opencode/test/server/httpapi-sdk.test.ts @@ -1,4 +1,5 @@ import { afterEach, describe, expect } from "bun:test" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { ConfigProvider, Deferred, Effect, Layer } from "effect" import type * as Scope from "effect/Scope" import { HttpRouter } from "effect/unstable/http" @@ -312,7 +313,7 @@ function seedMessage(directory: string, sessionID: string) { agent: "test", model: { providerID: ProviderID.make("test"), modelID: ModelID.make("test") }, tools: {}, - } satisfies MessageV2.User) + } satisfies SessionLegacy.User) const part = yield* svc.updatePart({ id: PartID.ascending(), sessionID: id, diff --git a/packages/opencode/test/server/httpapi-session.test.ts b/packages/opencode/test/server/httpapi-session.test.ts index 0f7994ab3..af87e6a42 100644 --- a/packages/opencode/test/server/httpapi-session.test.ts +++ b/packages/opencode/test/server/httpapi-session.test.ts @@ -1,4 +1,5 @@ import { afterEach, describe, expect } from "bun:test" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { mkdir } from "node:fs/promises" import path from "node:path" import { Cause, Effect, Exit, Layer } from "effect" @@ -335,7 +336,7 @@ describe("session HttpApi", () => { const messages = yield* request(`${pathFor(SessionPaths.messages, { sessionID: parent.id })}?limit=1`, { headers, }) - const messagePage = yield* json(messages) + const messagePage = yield* json(messages) const nextCursor = messages.headers.get("x-next-cursor") expect(nextCursor).toBeTruthy() expect(messagePage[0]?.parts[0]).toMatchObject({ type: "text" }) @@ -352,7 +353,7 @@ describe("session HttpApi", () => { ).toBe(400) expect( - yield* requestJson( + yield* requestJson( pathFor(SessionPaths.message, { sessionID: parent.id, messageID: message.info.id }), { headers }, ), @@ -737,7 +738,7 @@ describe("session HttpApi", () => { const first = yield* createTextMessage(session.id, "first") const second = yield* createTextMessage(session.id, "second") - const updated = yield* requestJson( + const updated = yield* requestJson( pathFor(SessionPaths.updatePart, { sessionID: session.id, messageID: first.info.id, diff --git a/packages/opencode/test/server/session-messages.test.ts b/packages/opencode/test/server/session-messages.test.ts index 6cd17d255..fbe205f64 100644 --- a/packages/opencode/test/server/session-messages.test.ts +++ b/packages/opencode/test/server/session-messages.test.ts @@ -1,4 +1,5 @@ import { afterEach, describe, expect } from "bun:test" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { Effect } from "effect" import { Server } from "../../src/server/server" import { Session as SessionNs } from "@/session/session" @@ -62,14 +63,14 @@ const fill = Effect.fn("SessionMessagesTest.fill")(function* ( agent: "test", model, tools: {}, - } satisfies MessageV2.User) + } satisfies SessionLegacy.User) yield* session.updatePart({ id: PartID.ascending(), sessionID, messageID: id, type: "text", text: `m${i}`, - } satisfies MessageV2.TextPart) + } satisfies SessionLegacy.TextPart) return id }), ) @@ -93,7 +94,7 @@ describe("session messages endpoint", () => { const a = yield* request(`/session/${session.id}/message?limit=2`) expect(a.status).toBe(200) - const aBody = yield* json(a) + const aBody = yield* json(a) expect(aBody.map((item) => item.info.id)).toEqual(ids.slice(-2)) const cursor = a.headers.get("x-next-cursor") expect(cursor).toBeTruthy() @@ -101,7 +102,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(b) + const bBody = yield* json(b) expect(bBody.map((item) => item.info.id)).toEqual(ids.slice(-4, -2)) }), ), @@ -117,7 +118,7 @@ describe("session messages endpoint", () => { const res = yield* request(`/session/${session.id}/message`) expect(res.status).toBe(200) - const body = yield* json(res) + const body = yield* json(res) expect(body.map((item) => item.info.id)).toEqual(ids) }), ), @@ -149,7 +150,7 @@ describe("session messages endpoint", () => { const res = yield* request(`/session/${session.id}/message?limit=510`) expect(res.status).toBe(200) - const body = yield* json(res) + const body = yield* json(res) expect(body).toHaveLength(510) }), ), diff --git a/packages/opencode/test/session/compaction.test.ts b/packages/opencode/test/session/compaction.test.ts index fd202a5c5..a631cb66e 100644 --- a/packages/opencode/test/session/compaction.test.ts +++ b/packages/opencode/test/session/compaction.test.ts @@ -1,4 +1,5 @@ import { afterEach, describe, expect, mock, test } from "bun:test" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { APICallError } from "ai" import { Cause, Deferred, Effect, Exit, Fiber, Layer, Schema } from "effect" import * as Stream from "effect/Stream" @@ -296,7 +297,7 @@ function readCompactionPart(sessionID: SessionID) { .messages({ sessionID }) .pipe( Effect.map((messages) => - messages.at(-2)?.parts.find((item): item is MessageV2.CompactionPart => item.type === "compaction"), + messages.at(-2)?.parts.find((item): item is SessionLegacy.CompactionPart => item.type === "compaction"), ), ) } @@ -623,7 +624,7 @@ describe("session.compaction.prune", () => { type: "text", text: "first", }) - const b: MessageV2.Assistant = { + const b: SessionLegacy.Assistant = { id: MessageID.ascending(), role: "assistant", sessionID: info.id, @@ -719,7 +720,7 @@ describe("session.compaction.prune", () => { type: "text", text: "first", }) - const b: MessageV2.Assistant = { + const b: SessionLegacy.Assistant = { id: MessageID.ascending(), role: "assistant", sessionID: info.id, diff --git a/packages/opencode/test/session/instruction.test.ts b/packages/opencode/test/session/instruction.test.ts index 0f9c340dd..a47af8b1a 100644 --- a/packages/opencode/test/session/instruction.test.ts +++ b/packages/opencode/test/session/instruction.test.ts @@ -1,4 +1,5 @@ import { describe, expect, test } from "bun:test" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import path from "path" import { Effect, FileSystem, Layer } from "effect" import { FetchHttpClient } from "effect/unstable/http" @@ -61,7 +62,7 @@ const tmpWithFiles = (files: Record) => return dir }) -function loaded(filepath: string): MessageV2.WithParts[] { +function loaded(filepath: string): SessionLegacy.WithParts[] { const sessionID = SessionID.make("session-loaded-1") const messageID = MessageID.make("msg_message-loaded-1") diff --git a/packages/opencode/test/session/llm-native-recorded.test.ts b/packages/opencode/test/session/llm-native-recorded.test.ts index 19d8f6f42..97906c0d2 100644 --- a/packages/opencode/test/session/llm-native-recorded.test.ts +++ b/packages/opencode/test/session/llm-native-recorded.test.ts @@ -1,4 +1,5 @@ import { NodeFileSystem } from "@effect/platform-node" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { ModelsDev } from "@opencode-ai/core/models-dev" import { HttpRecorder, Redactor } from "@opencode-ai/http-recorder" @@ -392,7 +393,7 @@ const driveToolLoop = (scenario: RecordedScenario) => time: { created: 0 }, agent: agent.name, model: { providerID: scenario.providerID, modelID }, - } satisfies MessageV2.User, + } satisfies SessionLegacy.User, sessionID, model: resolved, agent, diff --git a/packages/opencode/test/session/llm.test.ts b/packages/opencode/test/session/llm.test.ts index cd381ecd0..941129610 100644 --- a/packages/opencode/test/session/llm.test.ts +++ b/packages/opencode/test/session/llm.test.ts @@ -1,4 +1,5 @@ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import path from "path" import { tool, type ModelMessage } from "ai" import { Cause, Effect, Exit, Fiber, Layer, Stream } from "effect" @@ -732,7 +733,7 @@ describe("session.llm.stream", () => { time: { created: Date.now() }, agent: agent.name, model: { providerID: ProviderID.make(vivgridFixture.providerID), modelID: resolved.id, variant: "high" }, - } satisfies MessageV2.User + } satisfies SessionLegacy.User yield* drain({ user, @@ -803,7 +804,7 @@ describe("session.llm.stream", () => { time: { created: Date.now() }, agent: agent.name, model: { providerID: ProviderID.make(alibabaQwenFixture.providerID), modelID: resolved.id }, - } satisfies MessageV2.User + } satisfies SessionLegacy.User const fiber = yield* drain({ user, @@ -873,7 +874,7 @@ describe("session.llm.stream", () => { agent: agent.name, model: { providerID: ProviderID.make(alibabaQwenFixture.providerID), modelID: resolved.id }, tools: { question: true }, - } satisfies MessageV2.User + } satisfies SessionLegacy.User yield* drain({ user, @@ -975,7 +976,7 @@ describe("session.llm.stream", () => { time: { created: Date.now() }, agent: agent.name, model: { providerID: ProviderID.make("openai"), modelID: resolved.id, variant: "high" }, - } satisfies MessageV2.User + } satisfies SessionLegacy.User yield* drain({ user, @@ -1089,7 +1090,7 @@ describe("session.llm.stream", () => { time: { created: Date.now() }, agent: agent.name, model: { providerID: ProviderID.make("openai"), modelID: resolved.id, variant: "high" }, - } satisfies MessageV2.User, + } satisfies SessionLegacy.User, sessionID, model: resolved, agent, @@ -1151,7 +1152,7 @@ describe("session.llm.stream", () => { time: { created: Date.now() }, agent: agent.name, model: { providerID: ProviderID.make("openai"), modelID: resolved.id, variant: "high" }, - } satisfies MessageV2.User, + } satisfies SessionLegacy.User, sessionID, model: resolved, agent, @@ -1234,7 +1235,7 @@ describe("session.llm.stream", () => { time: { created: Date.now() }, agent: agent.name, model: { providerID: ProviderID.make("openai"), modelID: resolved.id }, - } satisfies MessageV2.User, + } satisfies SessionLegacy.User, sessionID, model: resolved, agent, @@ -1322,7 +1323,7 @@ describe("session.llm.stream", () => { time: { created: Date.now() }, agent: agent.name, model: { providerID: ProviderID.make("openai"), modelID: resolved.id }, - } satisfies MessageV2.User, + } satisfies SessionLegacy.User, sessionID, model: resolved, agent, @@ -1447,7 +1448,7 @@ describe("session.llm.stream", () => { time: { created: Date.now() }, agent: agent.name, model: { providerID: ProviderID.make("openai"), modelID: resolved.id }, - } satisfies MessageV2.User + } satisfies SessionLegacy.User yield* drain({ user, @@ -1539,7 +1540,7 @@ describe("session.llm.stream", () => { time: { created: Date.now() }, agent: agent.name, model: { providerID: ProviderID.make("minimax"), modelID: ModelID.make("MiniMax-M2.5") }, - } satisfies MessageV2.User + } satisfies SessionLegacy.User yield* drain({ user, @@ -1630,7 +1631,7 @@ describe("session.llm.stream", () => { time: { created: Date.now() }, agent: agent.name, model: { providerID: ProviderID.make("anthropic"), modelID: resolved.id, variant: "max" }, - } satisfies MessageV2.User + } satisfies SessionLegacy.User const input = [ { @@ -1832,7 +1833,7 @@ describe("session.llm.stream", () => { time: { created: Date.now() }, agent: agent.name, model: { providerID: ProviderID.make(geminiFixture.providerID), modelID: resolved.id }, - } satisfies MessageV2.User + } satisfies SessionLegacy.User yield* drain({ user, diff --git a/packages/opencode/test/session/message-v2.test.ts b/packages/opencode/test/session/message-v2.test.ts index 82bed0e9c..dfbff389c 100644 --- a/packages/opencode/test/session/message-v2.test.ts +++ b/packages/opencode/test/session/message-v2.test.ts @@ -1,4 +1,5 @@ import { describe, expect, test } from "bun:test" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { APICallError } from "ai" import { MessageV2 } from "../../src/session/message-v2" import { ProviderTransform } from "@/provider/transform" @@ -58,7 +59,7 @@ const model: Provider.Model = { release_date: "2026-01-01", } -function userInfo(id: string): MessageV2.User { +function userInfo(id: string): SessionLegacy.User { return { id, sessionID, @@ -68,15 +69,15 @@ function userInfo(id: string): MessageV2.User { model: { providerID, modelID: ModelID.make("test") }, tools: {}, mode: "", - } as unknown as MessageV2.User + } as unknown as SessionLegacy.User } function assistantInfo( id: string, parentID: string, - error?: MessageV2.Assistant["error"], + error?: SessionLegacy.Assistant["error"], meta?: { providerID: string; modelID: string }, -): MessageV2.Assistant { +): SessionLegacy.Assistant { const infoModel = meta ?? { providerID: model.providerID, modelID: model.api.id } return { id, @@ -97,7 +98,7 @@ function assistantInfo( reasoning: 0, cache: { read: 0, write: 0 }, }, - } as unknown as MessageV2.Assistant + } as unknown as SessionLegacy.Assistant } function basePart(messageID: string, id: string) { @@ -110,7 +111,7 @@ function basePart(messageID: string, id: string) { describe("session.message-v2.toModelMessage", () => { test("filters out messages with no parts", async () => { - const input: MessageV2.WithParts[] = [ + const input: SessionLegacy.WithParts[] = [ { info: userInfo("m-empty"), parts: [], @@ -123,7 +124,7 @@ describe("session.message-v2.toModelMessage", () => { type: "text", text: "hello", }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], }, ] @@ -138,7 +139,7 @@ describe("session.message-v2.toModelMessage", () => { test("filters out messages with only ignored parts", async () => { const messageID = "m-user" - const input: MessageV2.WithParts[] = [ + const input: SessionLegacy.WithParts[] = [ { info: userInfo(messageID), parts: [ @@ -148,7 +149,7 @@ describe("session.message-v2.toModelMessage", () => { text: "ignored", ignored: true, }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], }, ] @@ -158,7 +159,7 @@ describe("session.message-v2.toModelMessage", () => { test("filters out user messages with only empty text parts", async () => { const messageID = "m-user" - const input: MessageV2.WithParts[] = [ + const input: SessionLegacy.WithParts[] = [ { info: userInfo(messageID), parts: [ @@ -167,7 +168,7 @@ describe("session.message-v2.toModelMessage", () => { type: "text", text: "", }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], }, ] @@ -177,7 +178,7 @@ describe("session.message-v2.toModelMessage", () => { test("filters empty user text parts while keeping non-empty parts", async () => { const messageID = "m-user" - const input: MessageV2.WithParts[] = [ + const input: SessionLegacy.WithParts[] = [ { info: userInfo(messageID), parts: [ @@ -191,7 +192,7 @@ describe("session.message-v2.toModelMessage", () => { type: "text", text: "hello", }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], }, ] @@ -206,7 +207,7 @@ describe("session.message-v2.toModelMessage", () => { test("includes synthetic text parts", async () => { const messageID = "m-user" - const input: MessageV2.WithParts[] = [ + const input: SessionLegacy.WithParts[] = [ { info: userInfo(messageID), parts: [ @@ -216,7 +217,7 @@ describe("session.message-v2.toModelMessage", () => { text: "hello", synthetic: true, }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], }, { info: assistantInfo("m-assistant", messageID), @@ -227,7 +228,7 @@ describe("session.message-v2.toModelMessage", () => { text: "assistant", synthetic: true, }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], }, ] @@ -246,7 +247,7 @@ describe("session.message-v2.toModelMessage", () => { test("converts user text/file parts and injects compaction/subtask prompts", async () => { const messageID = "m-user" - const input: MessageV2.WithParts[] = [ + const input: SessionLegacy.WithParts[] = [ { info: userInfo(messageID), parts: [ @@ -294,7 +295,7 @@ describe("session.message-v2.toModelMessage", () => { description: "desc", agent: "agent", }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], }, ] @@ -320,7 +321,7 @@ describe("session.message-v2.toModelMessage", () => { const userID = "m-user" const assistantID = "m-assistant" - const input: MessageV2.WithParts[] = [ + const input: SessionLegacy.WithParts[] = [ { info: userInfo(userID), parts: [ @@ -329,7 +330,7 @@ describe("session.message-v2.toModelMessage", () => { type: "text", text: "run tool", }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], }, { info: assistantInfo(assistantID, userID), @@ -364,7 +365,7 @@ describe("session.message-v2.toModelMessage", () => { }, metadata: { openai: { tool: "meta" } }, }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], }, ] @@ -433,7 +434,7 @@ describe("session.message-v2.toModelMessage", () => { ) const userID = "m-user-anthropic" const assistantID = "m-assistant-anthropic" - const input: MessageV2.WithParts[] = [ + const input: SessionLegacy.WithParts[] = [ { info: userInfo(userID), parts: [ @@ -442,7 +443,7 @@ describe("session.message-v2.toModelMessage", () => { type: "text", text: "run tool", }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], }, { info: assistantInfo(assistantID, userID), @@ -470,7 +471,7 @@ describe("session.message-v2.toModelMessage", () => { ], }, }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], }, ] @@ -514,7 +515,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: MessageV2.WithParts[] = [ + const input: SessionLegacy.WithParts[] = [ { info: userInfo(userID), parts: [ @@ -523,7 +524,7 @@ describe("session.message-v2.toModelMessage", () => { type: "text", text: "run tool", }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], }, { info: assistantInfo(assistantID, userID), @@ -551,7 +552,7 @@ describe("session.message-v2.toModelMessage", () => { ], }, }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], }, ] @@ -602,7 +603,7 @@ describe("session.message-v2.toModelMessage", () => { const userID = "m-user" const assistantID = "m-assistant" - const input: MessageV2.WithParts[] = [ + const input: SessionLegacy.WithParts[] = [ { info: userInfo(userID), parts: [ @@ -611,7 +612,7 @@ describe("session.message-v2.toModelMessage", () => { type: "text", text: "run tool", }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], }, { info: assistantInfo(assistantID, userID, undefined, { providerID: "other", modelID: "other" }), @@ -644,7 +645,7 @@ describe("session.message-v2.toModelMessage", () => { }, metadata: { openai: { tool: "meta" } }, }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], }, ] @@ -685,7 +686,7 @@ describe("session.message-v2.toModelMessage", () => { const userID = "m-user" const assistantID = "m-assistant" - const input: MessageV2.WithParts[] = [ + const input: SessionLegacy.WithParts[] = [ { info: userInfo(userID), parts: [ @@ -694,7 +695,7 @@ describe("session.message-v2.toModelMessage", () => { type: "text", text: "run tool", }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], }, { info: assistantInfo(assistantID, userID), @@ -713,7 +714,7 @@ describe("session.message-v2.toModelMessage", () => { time: { start: 0, end: 1, compacted: 1 }, }, }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], }, ] @@ -752,7 +753,7 @@ describe("session.message-v2.toModelMessage", () => { const userID = "m-user" const assistantID = "m-assistant" - const input: MessageV2.WithParts[] = [ + const input: SessionLegacy.WithParts[] = [ { info: userInfo(userID), parts: [ @@ -761,7 +762,7 @@ describe("session.message-v2.toModelMessage", () => { type: "text", text: "run tool", }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], }, { info: assistantInfo(assistantID, userID), @@ -780,7 +781,7 @@ describe("session.message-v2.toModelMessage", () => { time: { start: 0, end: 1 }, }, }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], }, ] @@ -822,7 +823,7 @@ describe("session.message-v2.toModelMessage", () => { const userID = "m-user" const assistantID = "m-assistant" - const input: MessageV2.WithParts[] = [ + const input: SessionLegacy.WithParts[] = [ { info: userInfo(userID), parts: [ @@ -831,7 +832,7 @@ describe("session.message-v2.toModelMessage", () => { type: "text", text: "run tool", }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], }, { info: assistantInfo(assistantID, userID), @@ -850,7 +851,7 @@ describe("session.message-v2.toModelMessage", () => { }, metadata: { openai: { tool: "meta" } }, }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], }, ] @@ -900,7 +901,7 @@ describe("session.message-v2.toModelMessage", () => { "", ].join("\n") - const input: MessageV2.WithParts[] = [ + const input: SessionLegacy.WithParts[] = [ { info: userInfo(userID), parts: [ @@ -909,7 +910,7 @@ describe("session.message-v2.toModelMessage", () => { type: "text", text: "run tool", }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], }, { info: assistantInfo(assistantID, userID), @@ -927,7 +928,7 @@ describe("session.message-v2.toModelMessage", () => { time: { start: 0, end: 1 }, }, }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], }, ] @@ -965,12 +966,12 @@ describe("session.message-v2.toModelMessage", () => { test("filters assistant messages with non-abort errors", async () => { const assistantID = "m-assistant" - const input: MessageV2.WithParts[] = [ + const input: SessionLegacy.WithParts[] = [ { info: assistantInfo( assistantID, "m-parent", - new MessageV2.APIError({ message: "boom", isRetryable: true }).toObject() as MessageV2.APIError, + new SessionLegacy.APIError({ message: "boom", isRetryable: true }).toObject() as SessionLegacy.APIError, ), parts: [ { @@ -978,7 +979,7 @@ describe("session.message-v2.toModelMessage", () => { type: "text", text: "should not render", }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], }, ] @@ -989,9 +990,9 @@ describe("session.message-v2.toModelMessage", () => { const assistantID1 = "m-assistant-1" const assistantID2 = "m-assistant-2" - const aborted = new MessageV2.AbortedError({ message: "aborted" }).toObject() as MessageV2.Assistant["error"] + const aborted = new SessionLegacy.AbortedError({ message: "aborted" }).toObject() as SessionLegacy.Assistant["error"] - const input: MessageV2.WithParts[] = [ + const input: SessionLegacy.WithParts[] = [ { info: assistantInfo(assistantID1, "m-parent", aborted), parts: [ @@ -1006,7 +1007,7 @@ describe("session.message-v2.toModelMessage", () => { type: "text", text: "partial answer", }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], }, { info: assistantInfo(assistantID2, "m-parent", aborted), @@ -1021,7 +1022,7 @@ describe("session.message-v2.toModelMessage", () => { text: "thinking", time: { start: 0 }, }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], }, ] @@ -1061,7 +1062,7 @@ describe("session.message-v2.toModelMessage", () => { index: 0, }, ] - const input: MessageV2.WithParts[] = [ + const input: SessionLegacy.WithParts[] = [ { info: assistantInfo(assistantID, "m-parent", undefined, { providerID: openrouterModel.providerID, @@ -1084,7 +1085,7 @@ describe("session.message-v2.toModelMessage", () => { type: "text", text: "answer", }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], }, ] @@ -1112,7 +1113,7 @@ describe("session.message-v2.toModelMessage", () => { test("splits assistant messages on step-start boundaries", async () => { const assistantID = "m-assistant" - const input: MessageV2.WithParts[] = [ + const input: SessionLegacy.WithParts[] = [ { info: assistantInfo(assistantID, "m-parent"), parts: [ @@ -1130,7 +1131,7 @@ describe("session.message-v2.toModelMessage", () => { type: "text", text: "second", }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], }, ] @@ -1149,7 +1150,7 @@ describe("session.message-v2.toModelMessage", () => { test("drops messages that only contain step-start parts", async () => { const assistantID = "m-assistant" - const input: MessageV2.WithParts[] = [ + const input: SessionLegacy.WithParts[] = [ { info: assistantInfo(assistantID, "m-parent"), parts: [ @@ -1157,7 +1158,7 @@ describe("session.message-v2.toModelMessage", () => { ...basePart(assistantID, "p1"), type: "step-start", }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], }, ] @@ -1168,7 +1169,7 @@ describe("session.message-v2.toModelMessage", () => { const userID = "m-user" const assistantID = "m-assistant" - const input: MessageV2.WithParts[] = [ + const input: SessionLegacy.WithParts[] = [ { info: userInfo(userID), parts: [ @@ -1177,7 +1178,7 @@ describe("session.message-v2.toModelMessage", () => { type: "text", text: "run tool", }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], }, { info: assistantInfo(assistantID, userID), @@ -1204,7 +1205,7 @@ describe("session.message-v2.toModelMessage", () => { time: { start: 0 }, }, }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], }, ] @@ -1257,7 +1258,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: MessageV2.WithParts[] = [ + const input: SessionLegacy.WithParts[] = [ { info: assistantInfo(assistantID, "m-parent"), parts: [ @@ -1277,7 +1278,7 @@ describe("session.message-v2.toModelMessage", () => { metadata: { anthropic: { signature: "sig2" } }, }, { ...basePart(assistantID, "p6"), type: "text", text: "the answer" }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], }, ] @@ -1293,7 +1294,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: MessageV2.WithParts[] = [ + const input: SessionLegacy.WithParts[] = [ { info: assistantInfo(assistantID, "m-parent"), parts: [ @@ -1305,7 +1306,7 @@ describe("session.message-v2.toModelMessage", () => { }, { ...basePart(assistantID, "p2"), type: "text", text: "" }, { ...basePart(assistantID, "p3"), type: "text", text: "answer" }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], }, ] @@ -1320,14 +1321,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: MessageV2.WithParts[] = [ + const input: SessionLegacy.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 MessageV2.Part[], + ] as SessionLegacy.Part[], }, ] @@ -1340,13 +1341,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: MessageV2.WithParts[] = [ + const input: SessionLegacy.WithParts[] = [ { info: assistantInfo(assistantID, "m-parent"), parts: [ { ...basePart(assistantID, "p1"), type: "text", text: "" }, { ...basePart(assistantID, "p2"), type: "text", text: "hello" }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], }, ] @@ -1458,7 +1459,7 @@ describe("session.message-v2.fromError", () => { isRetryable: false, }) const result = MessageV2.fromError(error, { providerID }) - expect(MessageV2.ContextOverflowError.isInstance(result)).toBe(true) + expect(SessionLegacy.ContextOverflowError.isInstance(result)).toBe(true) }) }) @@ -1479,7 +1480,7 @@ describe("session.message-v2.fromError", () => { isRetryable: false, }) const result = MessageV2.fromError(error, { providerID }) - expect(MessageV2.ContextOverflowError.isInstance(result)).toBe(true) + expect(SessionLegacy.ContextOverflowError.isInstance(result)).toBe(true) }) test("does not classify 429 no body as context overflow", () => { @@ -1494,8 +1495,8 @@ describe("session.message-v2.fromError", () => { }), { providerID }, ) - expect(MessageV2.ContextOverflowError.isInstance(result)).toBe(false) - expect(MessageV2.APIError.isInstance(result)).toBe(true) + expect(SessionLegacy.ContextOverflowError.isInstance(result)).toBe(false) + expect(SessionLegacy.APIError.isInstance(result)).toBe(true) }) test("serializes unknown inputs", () => { @@ -1530,9 +1531,9 @@ describe("session.message-v2.fromError", () => { const result = MessageV2.fromError(zlibError, { providerID }) - expect(MessageV2.APIError.isInstance(result)).toBe(true) - expect((result as MessageV2.APIError).data.isRetryable).toBe(true) - expect((result as MessageV2.APIError).data.message).toInclude("decompression") + 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") }) test("classifies ZlibError as AbortedError when abort context is provided", () => { @@ -1556,21 +1557,21 @@ describe("session.message-v2.latest", () => { const CONTINUE_USER = MessageID.make("msg_005") const NEW_COMPACTION_USER = MessageID.make("msg_006") - const tailUser: MessageV2.WithParts = { + const tailUser: SessionLegacy.WithParts = { info: userInfo(TAIL_USER), - parts: [{ ...basePart(TAIL_USER, "p1"), type: "text", text: "original prompt" }] as MessageV2.Part[], + parts: [{ ...basePart(TAIL_USER, "p1"), type: "text", text: "original prompt" }] as SessionLegacy.Part[], } - const overflowAssistant: MessageV2.WithParts = { + const overflowAssistant: SessionLegacy.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 MessageV2.Assistant, + } as SessionLegacy.Assistant, parts: [], } - const compactionUser: MessageV2.WithParts = { + const compactionUser: SessionLegacy.WithParts = { info: userInfo(COMPACTION_USER), parts: [ { @@ -1579,20 +1580,20 @@ describe("session.message-v2.latest", () => { auto: true, tail_start_id: TAIL_USER, }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], } - const summaryAssistant: MessageV2.WithParts = { + const summaryAssistant: SessionLegacy.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 MessageV2.Assistant, + } as SessionLegacy.Assistant, parts: [], } - const continueUser: MessageV2.WithParts = { + const continueUser: SessionLegacy.WithParts = { info: userInfo(CONTINUE_USER), parts: [ { @@ -1602,7 +1603,7 @@ describe("session.message-v2.latest", () => { synthetic: true, metadata: { compaction_continue: true }, }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], } // Regression for double auto-compaction. The reorder in filterCompacted @@ -1628,7 +1629,7 @@ describe("session.message-v2.latest", () => { }) test("a fresh compaction-user newer than the latest summary surfaces in tasks", () => { - const newCompactionUser: MessageV2.WithParts = { + const newCompactionUser: SessionLegacy.WithParts = { info: userInfo(NEW_COMPACTION_USER), parts: [ { @@ -1636,7 +1637,7 @@ describe("session.message-v2.latest", () => { type: "compaction", auto: true, }, - ] as MessageV2.Part[], + ] as SessionLegacy.Part[], } const state = MessageV2.latest([ diff --git a/packages/opencode/test/session/messages-pagination.test.ts b/packages/opencode/test/session/messages-pagination.test.ts index e558d07b5..11c169e62 100644 --- a/packages/opencode/test/session/messages-pagination.test.ts +++ b/packages/opencode/test/session/messages-pagination.test.ts @@ -1,4 +1,5 @@ import { describe, expect, test } from "bun:test" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { Effect, Option } from "effect" import { Session as SessionNs } from "@/session/session" import { MessageV2 } from "../../src/session/message-v2" @@ -45,7 +46,7 @@ const fill = Effect.fn("Test.fill")(function* ( model: { providerID: "test", modelID: "test" }, tools: {}, mode: "", - } as unknown as MessageV2.Info) + } as unknown as SessionLegacy.Info) yield* session.updatePart({ id: PartID.ascending(), sessionID, @@ -69,7 +70,7 @@ const addUser = Effect.fn("Test.addUser")(function* (sessionID: SessionID, text? model: { providerID: "test", modelID: "test" }, tools: {}, mode: "", - } as unknown as MessageV2.Info) + } as unknown as SessionLegacy.Info) if (text) { yield* session.updatePart({ id: PartID.ascending(), @@ -85,7 +86,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?: MessageV2.Assistant["error"] }, + opts?: { summary?: boolean; finish?: string; error?: SessionLegacy.Assistant["error"] }, ) { const session = yield* SessionNs.Service const id = MessageID.ascending() @@ -105,7 +106,7 @@ const addAssistant = Effect.fn("Test.addAssistant")(function* ( summary: opts?.summary, finish: opts?.finish, error: opts?.error, - } as unknown as MessageV2.Info) + } as unknown as SessionLegacy.Info) return id }) @@ -389,7 +390,7 @@ describe("MessageV2.parts", () => { const result = MessageV2.parts(id) expect(result).toHaveLength(1) expect(result[0].type).toBe("text") - expect((result[0] as MessageV2.TextPart).text).toBe("m0") + expect((result[0] as SessionLegacy.TextPart).text).toBe("m0") }), ), ) @@ -427,9 +428,9 @@ describe("MessageV2.parts", () => { const result = MessageV2.parts(id) expect(result).toHaveLength(3) - expect((result[0] as MessageV2.TextPart).text).toBe("m0") - expect((result[1] as MessageV2.TextPart).text).toBe("second") - expect((result[2] as MessageV2.TextPart).text).toBe("third") + 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") }), ), ) @@ -466,7 +467,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 MessageV2.TextPart).text).toBe("m0") + expect((result.parts[0] as SessionLegacy.TextPart).text).toBe("m0") }), ), ) @@ -536,7 +537,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 MessageV2.TextPart).text).toBe("response") + expect((result.parts[0] as SessionLegacy.TextPart).text).toBe("response") }), ), ) @@ -672,10 +673,10 @@ describe("MessageV2.filterCompacted", () => { const u1 = yield* addUser(sessionID, "hello") yield* addCompactionPart(sessionID, u1) - const error = new MessageV2.APIError({ + const error = new SessionLegacy.APIError({ message: "boom", isRetryable: true, - }).toObject() as MessageV2.Assistant["error"] + }).toObject() as SessionLegacy.Assistant["error"] yield* addAssistant(sessionID, u1, { summary: true, finish: "end_turn", error }) yield* addUser(sessionID, "retry") @@ -951,7 +952,7 @@ describe("MessageV2.filterCompacted", () => { test("works with array input", () => { // filterCompacted accepts any Iterable, not just generators const id = MessageID.ascending() - const items: MessageV2.WithParts[] = [ + const items: SessionLegacy.WithParts[] = [ { info: { id, @@ -960,8 +961,8 @@ describe("MessageV2.filterCompacted", () => { time: { created: 1 }, agent: "test", model: { providerID: "test", modelID: "test" }, - } as unknown as MessageV2.Info, - parts: [{ type: "text", text: "hello" }] as unknown as MessageV2.Part[], + } as unknown as SessionLegacy.Info, + parts: [{ type: "text", text: "hello" }] as unknown as SessionLegacy.Part[], }, ] const result = MessageV2.filterCompacted(items) @@ -1027,7 +1028,7 @@ describe("MessageV2 consistency", () => { const streamed = Array.from(MessageV2.stream(sessionID)) - const paged = [] as MessageV2.WithParts[] + const paged = [] as SessionLegacy.WithParts[] let cursor: string | undefined while (true) { const result = yield* MessageV2.page({ sessionID, limit: 3, before: cursor }) diff --git a/packages/opencode/test/session/processor-effect.test.ts b/packages/opencode/test/session/processor-effect.test.ts index ede122297..ee3c7975e 100644 --- a/packages/opencode/test/session/processor-effect.test.ts +++ b/packages/opencode/test/session/processor-effect.test.ts @@ -1,4 +1,5 @@ import { NodeFileSystem } from "@effect/platform-node" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { expect } from "bun:test" import { tool } from "ai" import { Cause, Effect, Exit, Fiber, Layer } from "effect" @@ -145,7 +146,7 @@ const assistant = Effect.fn("TestSession.assistant")(function* ( root: string, ) { const session = yield* Session.Service - const msg: MessageV2.Assistant = { + const msg: SessionLegacy.Assistant = { id: MessageID.ascending(), role: "assistant", sessionID, @@ -234,7 +235,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 MessageV2.User, + } satisfies SessionLegacy.User, sessionID: chat.id, model: mdl, agent: agent(), @@ -306,7 +307,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 MessageV2.User, + } satisfies SessionLegacy.User, sessionID: chat.id, model: mdl, agent: agent(), @@ -317,14 +318,14 @@ it.live("session.processor effect tests preserve text start time", () => .pipe(Effect.forkChild) yield* waitFor( - Effect.sync(() => MessageV2.parts(msg.id).find((part): part is MessageV2.TextPart => part.type === "text")), + Effect.sync(() => MessageV2.parts(msg.id).find((part): part is SessionLegacy.TextPart => part.type === "text")), "timed out waiting for text part", ) yield* Effect.sleep("20 millis") gate.resolve() const exit = yield* Fiber.await(run) - const text = MessageV2.parts(msg.id).find((part): part is MessageV2.TextPart => part.type === "text") + const text = MessageV2.parts(msg.id).find((part): part is SessionLegacy.TextPart => part.type === "text") expect(Exit.isSuccess(exit)).toBe(true) expect(text?.text).toBe("hello") @@ -364,7 +365,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 MessageV2.User, + } satisfies SessionLegacy.User, sessionID: chat.id, model: mdl, agent: agent(), @@ -409,7 +410,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 MessageV2.User, + } satisfies SessionLegacy.User, sessionID: chat.id, model: mdl, agent: agent(), @@ -419,8 +420,8 @@ it.live("session.processor effect tests capture reasoning from http mock", () => }) const parts = MessageV2.parts(msg.id) - const reasoning = parts.find((part): part is MessageV2.ReasoningPart => part.type === "reasoning") - const text = parts.find((part): part is MessageV2.TextPart => part.type === "text") + const reasoning = parts.find((part): part is SessionLegacy.ReasoningPart => part.type === "reasoning") + const text = parts.find((part): part is SessionLegacy.TextPart => part.type === "text") expect(value).toBe("continue") expect(yield* llm.calls).toBe(1) @@ -457,7 +458,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 MessageV2.User, + } satisfies SessionLegacy.User, sessionID: chat.id, model: mdl, agent: agent(), @@ -467,7 +468,7 @@ it.live("session.processor effect tests reset reasoning state across retries", ( }) const parts = MessageV2.parts(msg.id) - const reasoning = parts.filter((part): part is MessageV2.ReasoningPart => part.type === "reasoning") + const reasoning = parts.filter((part): part is SessionLegacy.ReasoningPart => part.type === "reasoning") expect(value).toBe("continue") expect(yield* llm.calls).toBe(2) @@ -504,7 +505,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 MessageV2.User, + } satisfies SessionLegacy.User, sessionID: chat.id, model: mdl, agent: agent(), @@ -548,7 +549,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 MessageV2.User, + } satisfies SessionLegacy.User, sessionID: chat.id, model: mdl, agent: agent(), @@ -601,7 +602,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 MessageV2.User, + } satisfies SessionLegacy.User, sessionID: chat.id, model: mdl, agent: agent(), @@ -646,7 +647,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 MessageV2.User, + } satisfies SessionLegacy.User, sessionID: chat.id, model: mdl, agent: agent(), @@ -689,7 +690,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 MessageV2.User, + } satisfies SessionLegacy.User, sessionID: chat.id, model: mdl, agent: agent(), @@ -709,7 +710,7 @@ it.live("session.processor effect tests complete AI SDK tool calls when native f }) const parts = MessageV2.parts(msg.id) - const call = parts.find((part): part is MessageV2.ToolPart => part.type === "tool") + const call = parts.find((part): part is SessionLegacy.ToolPart => part.type === "tool") expect(value).toBe("continue") expect(yield* llm.calls).toBe(1) @@ -755,7 +756,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 MessageV2.User, + } satisfies SessionLegacy.User, sessionID: chat.id, model: mdl, agent: agent(), @@ -767,14 +768,14 @@ it.live("session.processor effect tests mark pending tools as aborted on cleanup yield* llm.wait(1) yield* waitFor( - Effect.sync(() => MessageV2.parts(msg.id).find((part): part is MessageV2.ToolPart => part.type === "tool")), + Effect.sync(() => MessageV2.parts(msg.id).find((part): part is SessionLegacy.ToolPart => part.type === "tool")), "timed out waiting for tool part", ) yield* Fiber.interrupt(run) const exit = yield* Fiber.await(run) const parts = MessageV2.parts(msg.id) - const call = parts.find((part): part is MessageV2.ToolPart => part.type === "tool") + const call = parts.find((part): part is SessionLegacy.ToolPart => part.type === "tool") expect(Exit.isFailure(exit)).toBe(true) if (Exit.isFailure(exit)) { @@ -829,7 +830,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 MessageV2.User, + } satisfies SessionLegacy.User, sessionID: chat.id, model: mdl, agent: agent(), @@ -892,7 +893,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 MessageV2.User, + } satisfies SessionLegacy.User, sessionID: chat.id, model: mdl, agent: agent(), diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts index 17a9bc038..4ade7cbfa 100644 --- a/packages/opencode/test/session/prompt.test.ts +++ b/packages/opencode/test/session/prompt.test.ts @@ -1,4 +1,5 @@ import { NodeFileSystem } from "@effect/platform-node" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { FetchHttpClient } from "effect/unstable/http" import { expect } from "bun:test" import { Cause, Deferred, Duration, Effect, Exit, Fiber, Layer } from "effect" @@ -90,20 +91,20 @@ function withSh(fx: () => Effect.Effect) { ) } -function toolPart(parts: MessageV2.Part[]) { - return parts.find((part): part is MessageV2.ToolPart => part.type === "tool") +function toolPart(parts: SessionLegacy.Part[]) { + return parts.find((part): part is SessionLegacy.ToolPart => part.type === "tool") } -type CompletedToolPart = MessageV2.ToolPart & { state: MessageV2.ToolStateCompleted } -type ErrorToolPart = MessageV2.ToolPart & { state: MessageV2.ToolStateError } +type CompletedToolPart = SessionLegacy.ToolPart & { state: SessionLegacy.ToolStateCompleted } +type ErrorToolPart = SessionLegacy.ToolPart & { state: SessionLegacy.ToolStateError } -function completedTool(parts: MessageV2.Part[]) { +function completedTool(parts: SessionLegacy.Part[]) { const part = toolPart(parts) expect(part?.state.status).toBe("completed") return part?.state.status === "completed" ? (part as CompletedToolPart) : undefined } -function errorTool(parts: MessageV2.Part[]) { +function errorTool(parts: SessionLegacy.Part[]) { const part = toolPart(parts) expect(part?.state.status).toBe("error") return part?.state.status === "error" ? (part as ErrorToolPart) : undefined @@ -388,7 +389,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: MessageV2.Assistant = { + const assistant: SessionLegacy.Assistant = { id: MessageID.ascending(), role: "assistant", parentID: msg.id, @@ -748,7 +749,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 MessageV2.ToolPart => part.type === "tool") + const tool = taskMsg?.parts.find((part): part is SessionLegacy.ToolPart => part.type === "tool") if (tool?.state.status === "running" && tool.state.metadata?.sessionId) return tool }), "timed out waiting for running subtask metadata", @@ -791,7 +792,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 MessageV2.ToolPart => part.type === "tool" && part.tool === "task", + (part): part is SessionLegacy.ToolPart => part.type === "tool" && part.tool === "task", ) if (tool?.state.status === "running" && tool.state.metadata?.sessionId) return tool }), @@ -1910,11 +1911,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 MessageV2.TextPartInput => + (part): part is SessionLegacy.TextPartInput => part.type === "text" && part.synthetic === true && part.text.startsWith("Referenced configured reference "), ) - const files = parts.filter((part): part is MessageV2.FilePartInput => part.type === "file") - const agents = parts.filter((part): part is MessageV2.AgentPartInput => part.type === "agent") + 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 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") @@ -1967,7 +1968,7 @@ noLLMServer.instance( const stored = yield* MessageV2.get({ sessionID: session.id, messageID: message.info.id }) const synthetic = stored.parts.filter( - (part): part is MessageV2.TextPart => part.type === "text" && part.synthetic === true, + (part): part is SessionLegacy.TextPart => part.type === "text" && part.synthetic === true, ) const reference = synthetic.find((part) => part.text.startsWith("Referenced configured reference @docs.")) @@ -2022,7 +2023,7 @@ noLLMServer.instance( const stored = yield* MessageV2.get({ sessionID: session.id, messageID: message.info.id }) const synthetic = stored.parts.filter( - (part): part is MessageV2.TextPart => part.type === "text" && part.synthetic === true, + (part): part is SessionLegacy.TextPart => part.type === "text" && part.synthetic === true, ) const reference = synthetic.find((part) => part.text.startsWith("Referenced configured reference @docs/README.md."), diff --git a/packages/opencode/test/session/retry.test.ts b/packages/opencode/test/session/retry.test.ts index 22ff6cde8..98305b904 100644 --- a/packages/opencode/test/session/retry.test.ts +++ b/packages/opencode/test/session/retry.test.ts @@ -1,4 +1,5 @@ import { describe, expect, test } from "bun:test" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import type { NamedError } from "@opencode-ai/core/util/error" import { APICallError } from "ai" import { setTimeout as sleep } from "node:timers/promises" @@ -16,9 +17,9 @@ const providerID = ProviderID.make("test") const retryProvider = "test" const it = testEffect(Layer.mergeAll(SessionStatus.defaultLayer, CrossSpawnSpawner.defaultLayer)) -function apiError(headers?: Record): MessageV2.APIError { - return Schema.decodeUnknownSync(MessageV2.APIError.Schema)( - new MessageV2.APIError({ +function apiError(headers?: Record): SessionLegacy.APIError { + return Schema.decodeUnknownSync(SessionLegacy.APIError.Schema)( + new SessionLegacy.APIError({ message: "boom", isRetryable: true, responseHeaders: headers, @@ -94,7 +95,7 @@ describe("session.retry.delay", () => { const step = yield* Schedule.toStepWithMetadata( SessionRetry.policy({ provider: "test", - parse: Schema.decodeUnknownSync(MessageV2.APIError.Schema), + parse: Schema.decodeUnknownSync(SessionLegacy.APIError.Schema), set: (info) => status.set(sessionID, { type: "retry", @@ -164,7 +165,7 @@ describe("session.retry.retryable", () => { }) test("does not retry context overflow errors", () => { - const error = new MessageV2.ContextOverflowError({ + const error = new SessionLegacy.ContextOverflowError({ message: "Input exceeds context window of this model", responseBody: '{"error":{"code":"context_length_exceeded"}}', }).toObject() @@ -173,8 +174,8 @@ describe("session.retry.retryable", () => { }) test("retries 500 errors even when isRetryable is false", () => { - const error = Schema.decodeUnknownSync(MessageV2.APIError.Schema)( - new MessageV2.APIError({ + const error = Schema.decodeUnknownSync(SessionLegacy.APIError.Schema)( + new SessionLegacy.APIError({ message: "Internal server error", isRetryable: false, statusCode: 500, @@ -186,8 +187,8 @@ describe("session.retry.retryable", () => { }) test("retries 502 bad gateway errors", () => { - const error = Schema.decodeUnknownSync(MessageV2.APIError.Schema)( - new MessageV2.APIError({ + const error = Schema.decodeUnknownSync(SessionLegacy.APIError.Schema)( + new SessionLegacy.APIError({ message: "Bad gateway", isRetryable: false, statusCode: 502, @@ -198,8 +199,8 @@ describe("session.retry.retryable", () => { }) test("retries 503 service unavailable errors", () => { - const error = Schema.decodeUnknownSync(MessageV2.APIError.Schema)( - new MessageV2.APIError({ + const error = Schema.decodeUnknownSync(SessionLegacy.APIError.Schema)( + new SessionLegacy.APIError({ message: "Service unavailable", isRetryable: false, statusCode: 503, @@ -210,8 +211,8 @@ describe("session.retry.retryable", () => { }) test("does not retry 4xx errors when isRetryable is false", () => { - const error = Schema.decodeUnknownSync(MessageV2.APIError.Schema)( - new MessageV2.APIError({ + const error = Schema.decodeUnknownSync(SessionLegacy.APIError.Schema)( + new SessionLegacy.APIError({ message: "Bad request", isRetryable: false, statusCode: 400, @@ -222,8 +223,8 @@ describe("session.retry.retryable", () => { }) test("retries ZlibError decompression failures", () => { - const error = Schema.decodeUnknownSync(MessageV2.APIError.Schema)( - new MessageV2.APIError({ + const error = Schema.decodeUnknownSync(SessionLegacy.APIError.Schema)( + new SessionLegacy.APIError({ message: "Response decompression failed", isRetryable: true, metadata: { code: "ZlibError" }, @@ -236,8 +237,8 @@ describe("session.retry.retryable", () => { }) test("maps free limits to Go upsell action", () => { - const error = Schema.decodeUnknownSync(MessageV2.APIError.Schema)( - new MessageV2.APIError({ + const error = Schema.decodeUnknownSync(SessionLegacy.APIError.Schema)( + new SessionLegacy.APIError({ message: "Free usage exceeded", isRetryable: true, statusCode: 429, @@ -262,8 +263,8 @@ describe("session.retry.retryable", () => { }) test("maps Go subscription limits to workspace PAYG upsell", () => { - const error = Schema.decodeUnknownSync(MessageV2.APIError.Schema)( - new MessageV2.APIError({ + const error = Schema.decodeUnknownSync(SessionLegacy.APIError.Schema)( + new SessionLegacy.APIError({ message: "Subscription quota exceeded. You can continue using free models.", isRetryable: true, statusCode: 429, @@ -300,8 +301,8 @@ describe("session.retry.retryable", () => { }) test("maps Go subscription limits without limit metadata", () => { - const error = Schema.decodeUnknownSync(MessageV2.APIError.Schema)( - new MessageV2.APIError({ + const error = Schema.decodeUnknownSync(SessionLegacy.APIError.Schema)( + new SessionLegacy.APIError({ message: "Subscription quota exceeded. You can continue using free models.", isRetryable: true, statusCode: 429, @@ -355,8 +356,8 @@ describe("session.message-v2.fromError", () => { const result = MessageV2.fromError(error, { providerID }) - expect(MessageV2.APIError.isInstance(result)).toBe(true) - if (!MessageV2.APIError.isInstance(result)) throw new Error("expected APIError") + expect(SessionLegacy.APIError.isInstance(result)).toBe(true) + if (!SessionLegacy.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") @@ -366,8 +367,8 @@ describe("session.message-v2.fromError", () => { ) test("ECONNRESET socket error is retryable", () => { - const error = Schema.decodeUnknownSync(MessageV2.APIError.Schema)( - new MessageV2.APIError({ + const error = Schema.decodeUnknownSync(SessionLegacy.APIError.Schema)( + new SessionLegacy.APIError({ message: "Connection reset by server", isRetryable: true, metadata: { code: "ECONNRESET", message: "The socket connection was closed unexpectedly" }, @@ -390,7 +391,7 @@ describe("session.message-v2.fromError", () => { isRetryable: false, }) const result = MessageV2.fromError(error, { providerID: ProviderID.make("openai") }) - if (!MessageV2.APIError.isInstance(result)) throw new Error("expected APIError") + if (!SessionLegacy.APIError.isInstance(result)) throw new Error("expected APIError") expect(result.data.isRetryable).toBe(true) }) @@ -411,8 +412,8 @@ describe("session.message-v2.fromError", () => { { providerID: ProviderID.make("openai") }, ) - expect(MessageV2.APIError.isInstance(result)).toBe(true) - if (!MessageV2.APIError.isInstance(result)) throw new Error("expected APIError") + expect(SessionLegacy.APIError.isInstance(result)).toBe(true) + if (!SessionLegacy.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.", diff --git a/packages/opencode/test/session/revert-compact.test.ts b/packages/opencode/test/session/revert-compact.test.ts index c70c17d45..ab17cc6cd 100644 --- a/packages/opencode/test/session/revert-compact.test.ts +++ b/packages/opencode/test/session/revert-compact.test.ts @@ -1,4 +1,5 @@ import { describe, expect } from "bun:test" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import fs from "fs/promises" import path from "path" import { Effect, Layer } from "effect" @@ -130,7 +131,7 @@ describe("revert + compact workflow", () => { text: "Hello, please help me", }) - const assistantMsg1: MessageV2.Assistant = { + const assistantMsg1: SessionLegacy.Assistant = { id: MessageID.ascending(), role: "assistant", sessionID, @@ -187,7 +188,7 @@ describe("revert + compact workflow", () => { text: "What's the capital of France?", }) - const assistantMsg2: MessageV2.Assistant = { + const assistantMsg2: SessionLegacy.Assistant = { id: MessageID.ascending(), role: "assistant", sessionID, @@ -292,7 +293,7 @@ describe("revert + compact workflow", () => { text: "Hello", }) - const assistantMsg: MessageV2.Assistant = { + const assistantMsg: SessionLegacy.Assistant = { id: MessageID.ascending(), role: "assistant", sessionID, diff --git a/packages/opencode/test/session/session.test.ts b/packages/opencode/test/session/session.test.ts index 9a2b15578..773d78882 100644 --- a/packages/opencode/test/session/session.test.ts +++ b/packages/opencode/test/session/session.test.ts @@ -1,4 +1,5 @@ import { describe, expect } from "bun:test" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { Deferred, Effect, Exit, Layer } from "effect" import { Session as SessionNs } from "@/session/session" import { GlobalBus, type GlobalEvent } from "../../src/bus/global" @@ -121,14 +122,14 @@ describe("step-finish token propagation via Bus event", () => { model: { providerID: "test", modelID: "test" }, tools: {}, mode: "", - } as unknown as MessageV2.Info) + } as unknown as SessionLegacy.Info) - // Bus subscribers receive readonly Schema.Type payloads; `MessageV2.Part` + // Bus subscribers receive readonly Schema.Type payloads; `SessionLegacy.Part` // is the mutable domain type. Cast bridges the two — safe because the // test only reads the value afterwards. - const received = yield* Deferred.make() + const received = yield* Deferred.make() const unsub = subscribeGlobal(MessageV2.Event.PartUpdated.type, (event) => { - Deferred.doneUnsafe(received, Effect.succeed(event.properties.part as MessageV2.Part)) + Deferred.doneUnsafe(received, Effect.succeed(event.properties.part as SessionLegacy.Part)) }) yield* Effect.addFinalizer(() => Effect.sync(unsub)) @@ -154,7 +155,7 @@ describe("step-finish token propagation via Bus event", () => { const receivedPart = yield* awaitDeferred(received, "timed out waiting for message.part.updated") expect(receivedPart.type).toBe("step-finish") - const finish = receivedPart as MessageV2.StepFinishPart + const finish = receivedPart as SessionLegacy.StepFinishPart expect(finish.tokens.input).toBe(500) expect(finish.tokens.output).toBe(800) expect(finish.tokens.reasoning).toBe(200) diff --git a/packages/opencode/test/session/snapshot-tool-race.test.ts b/packages/opencode/test/session/snapshot-tool-race.test.ts index 89ed11613..73516d3e1 100644 --- a/packages/opencode/test/session/snapshot-tool-race.test.ts +++ b/packages/opencode/test/session/snapshot-tool-race.test.ts @@ -22,6 +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 * as Log from "@opencode-ai/core/util/log" import { provideTmpdirServer } from "../fixture/fixture" import { testEffect } from "../lib/effect" @@ -259,7 +260,7 @@ it.live("tool execution produces non-empty session diff (snapshot race)", () => const allMsgs = yield* MessageV2.filterCompactedEffect(session.id) const tool = allMsgs .flatMap((m) => m.parts) - .find((p): p is MessageV2.ToolPart => p.type === "tool" && p.tool === "bash") + .find((p): p is SessionLegacy.ToolPart => p.type === "tool" && p.tool === "bash") expect(tool?.state.status).toBe("completed") // Poll for diff — summarize() is fire-and-forget diff --git a/packages/opencode/test/session/structured-output-integration.test.ts b/packages/opencode/test/session/structured-output-integration.test.ts index 125c63c0f..f2d28864b 100644 --- a/packages/opencode/test/session/structured-output-integration.test.ts +++ b/packages/opencode/test/session/structured-output-integration.test.ts @@ -1,4 +1,5 @@ import { describe, expect, test } from "bun:test" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { Effect, Layer } from "effect" import { Session } from "@/session/session" import { SessionPrompt } from "../../src/session/prompt" @@ -218,7 +219,7 @@ describe("StructuredOutput Integration", () => { ) test("unit test: StructuredOutputError is properly structured", () => { - const error = new MessageV2.StructuredOutputError({ + const error = new SessionLegacy.StructuredOutputError({ message: "Failed to produce valid structured output after 3 attempts", retries: 3, }) diff --git a/packages/opencode/test/session/structured-output.test.ts b/packages/opencode/test/session/structured-output.test.ts index 806c57483..14bc876c8 100644 --- a/packages/opencode/test/session/structured-output.test.ts +++ b/packages/opencode/test/session/structured-output.test.ts @@ -1,12 +1,13 @@ import { describe, expect, test } from "bun:test" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" 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(MessageV2.Format) -const decodeUser = Schema.decodeUnknownExit(MessageV2.User) -const decodeAssistant = Schema.decodeUnknownExit(MessageV2.Assistant) +const decodeFormat = Schema.decodeUnknownExit(SessionLegacy.Format) +const decodeUser = Schema.decodeUnknownExit(SessionLegacy.User) +const decodeAssistant = Schema.decodeUnknownExit(SessionLegacy.Assistant) describe("structured-output.OutputFormat", () => { test("parses text format", () => { @@ -65,7 +66,7 @@ describe("structured-output.OutputFormat", () => { describe("structured-output.StructuredOutputError", () => { test("creates error with message and retries", () => { - const error = new MessageV2.StructuredOutputError({ + const error = new SessionLegacy.StructuredOutputError({ message: "Failed to validate", retries: 3, }) @@ -76,7 +77,7 @@ describe("structured-output.StructuredOutputError", () => { }) test("converts to object correctly", () => { - const error = new MessageV2.StructuredOutputError({ + const error = new SessionLegacy.StructuredOutputError({ message: "Test error", retries: 2, }) @@ -88,13 +89,13 @@ describe("structured-output.StructuredOutputError", () => { }) test("isInstance correctly identifies error", () => { - const error = new MessageV2.StructuredOutputError({ + const error = new SessionLegacy.StructuredOutputError({ message: "Test", retries: 1, }) - expect(MessageV2.StructuredOutputError.isInstance(error)).toBe(true) - expect(MessageV2.StructuredOutputError.isInstance({ name: "other" })).toBe(false) + expect(SessionLegacy.StructuredOutputError.isInstance(error)).toBe(true) + expect(SessionLegacy.StructuredOutputError.isInstance({ name: "other" })).toBe(false) }) }) diff --git a/packages/opencode/test/tool/task.test.ts b/packages/opencode/test/tool/task.test.ts index 2b7d00157..b1b7fe67c 100644 --- a/packages/opencode/test/tool/task.test.ts +++ b/packages/opencode/test/tool/task.test.ts @@ -1,4 +1,5 @@ import { afterEach, describe, expect } from "bun:test" +import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { Effect, Exit, Fiber, Layer } from "effect" import { Agent } from "../../src/agent/agent" import { BackgroundJob } from "@/background/job" @@ -65,7 +66,7 @@ const seed = Effect.fn("TaskToolTest.seed")(function* (title = "Pinned") { model: ref, time: { created: Date.now() }, }) - const assistant: MessageV2.Assistant = { + const assistant: SessionLegacy.Assistant = { id: MessageID.ascending(), role: "assistant", parentID: user.id, @@ -96,7 +97,7 @@ function stubOps(opts?: { onPrompt?: (input: SessionPrompt.PromptInput) => void; } } -function reply(input: SessionPrompt.PromptInput, text: string): MessageV2.WithParts { +function reply(input: SessionPrompt.PromptInput, text: string): SessionLegacy.WithParts { const id = MessageID.ascending() return { info: {