Compare commits

...

1 Commits

Author SHA1 Message Date
Kit Langton a83e989ffa feat: unwrap session namespaces to flat exports + barrel 2026-04-15 23:14:55 -04:00
54 changed files with 5025 additions and 5039 deletions
+2 -2
View File
@@ -42,9 +42,9 @@ import { ModelID, ProviderID } from "../provider/schema"
import { Agent as AgentModule } from "../agent/agent" import { Agent as AgentModule } from "../agent/agent"
import { AppRuntime } from "@/effect/app-runtime" import { AppRuntime } from "@/effect/app-runtime"
import { Installation } from "@/installation" import { Installation } from "@/installation"
import { MessageV2 } from "@/session/message-v2" import { MessageV2 } from "@/session"
import { Config } from "@/config" import { Config } from "@/config"
import { Todo } from "@/session/todo" import { Todo } from "@/session"
import { z } from "zod" import { z } from "zod"
import { LoadAPIKeyError } from "ai" import { LoadAPIKeyError } from "ai"
import type { AssistantMessage, Event, OpencodeClient, SessionMessageResponse, ToolPart } from "@opencode-ai/sdk/v2" import type { AssistantMessage, Event, OpencodeClient, SessionMessageResponse, ToolPart } from "@opencode-ai/sdk/v2"
+1 -1
View File
@@ -4,7 +4,7 @@ import { Effect } from "effect"
import { Agent } from "../../../agent/agent" import { Agent } from "../../../agent/agent"
import { Provider } from "../../../provider" import { Provider } from "../../../provider"
import { Session } from "../../../session" import { Session } from "../../../session"
import type { MessageV2 } from "../../../session/message-v2" import type { MessageV2 } from "../../../session"
import { MessageID, PartID } from "../../../session/schema" import { MessageID, PartID } from "../../../session/schema"
import { ToolRegistry } from "../../../tool/registry" import { ToolRegistry } from "../../../tool/registry"
import { Instance } from "../../../project/instance" import { Instance } from "../../../project/instance"
+1 -1
View File
@@ -1,6 +1,6 @@
import type { Argv } from "yargs" import type { Argv } from "yargs"
import { Session } from "../../session" import { Session } from "../../session"
import { MessageV2 } from "../../session/message-v2" import { MessageV2 } from "../../session"
import { SessionID } from "../../session/schema" import { SessionID } from "../../session/schema"
import { cmd } from "./cmd" import { cmd } from "./cmd"
import { bootstrap } from "../bootstrap" import { bootstrap } from "../bootstrap"
+2 -2
View File
@@ -27,8 +27,8 @@ import type { SessionID } from "../../session/schema"
import { MessageID, PartID } from "../../session/schema" import { MessageID, PartID } from "../../session/schema"
import { Provider } from "../../provider" import { Provider } from "../../provider"
import { Bus } from "../../bus" import { Bus } from "../../bus"
import { MessageV2 } from "../../session/message-v2" import { MessageV2 } from "../../session"
import { SessionPrompt } from "@/session/prompt" import { SessionPrompt } from "@/session"
import { AppRuntime } from "@/effect/app-runtime" import { AppRuntime } from "@/effect/app-runtime"
import { Git } from "@/git" import { Git } from "@/git"
import { setTimeout as sleep } from "node:timers/promises" import { setTimeout as sleep } from "node:timers/promises"
+1 -1
View File
@@ -1,7 +1,7 @@
import type { Argv } from "yargs" import type { Argv } from "yargs"
import type { Session as SDKSession, Message, Part } from "@opencode-ai/sdk/v2" import type { Session as SDKSession, Message, Part } from "@opencode-ai/sdk/v2"
import { Session } from "../../session" import { Session } from "../../session"
import { MessageV2 } from "../../session/message-v2" import { MessageV2 } from "../../session"
import { cmd } from "./cmd" import { cmd } from "./cmd"
import { bootstrap } from "../bootstrap" import { bootstrap } from "../bootstrap"
import { Database } from "../../storage/db" import { Database } from "../../storage/db"
@@ -85,7 +85,7 @@ import { useTuiConfig } from "../../context/tui-config"
import { getScrollAcceleration } from "../../util/scroll" import { getScrollAcceleration } from "../../util/scroll"
import { TuiPluginRuntime } from "../../plugin" import { TuiPluginRuntime } from "../../plugin"
import { DialogGoUpsell } from "../../component/dialog-go-upsell" import { DialogGoUpsell } from "../../component/dialog-go-upsell"
import { SessionRetry } from "@/session/retry" import { SessionRetry } from "@/session"
addDefaultParsers(parsers.parsers) addDefaultParsers(parsers.parsers)
+10 -10
View File
@@ -22,17 +22,17 @@ import { Skill } from "@/skill"
import { Discovery } from "@/skill/discovery" import { Discovery } from "@/skill/discovery"
import { Question } from "@/question" import { Question } from "@/question"
import { Permission } from "@/permission" import { Permission } from "@/permission"
import { Todo } from "@/session/todo" import { Todo } from "@/session"
import { Session } from "@/session" import { Session } from "@/session"
import { SessionStatus } from "@/session/status" import { SessionStatus } from "@/session"
import { SessionRunState } from "@/session/run-state" import { SessionRunState } from "@/session"
import { SessionProcessor } from "@/session/processor" import { SessionProcessor } from "@/session"
import { SessionCompaction } from "@/session/compaction" import { SessionCompaction } from "@/session"
import { SessionRevert } from "@/session/revert" import { SessionRevert } from "@/session"
import { SessionSummary } from "@/session/summary" import { SessionSummary } from "@/session"
import { SessionPrompt } from "@/session/prompt" import { SessionPrompt } from "@/session"
import { Instruction } from "@/session/instruction" import { Instruction } from "@/session"
import { LLM } from "@/session/llm" import { LLM } from "@/session"
import { LSP } from "@/lsp" import { LSP } from "@/lsp"
import { MCP } from "@/mcp" import { MCP } from "@/mcp"
import { McpAuth } from "@/mcp/auth" import { McpAuth } from "@/mcp/auth"
@@ -5,7 +5,7 @@ import { iife } from "@/util/iife"
import { Log } from "../../util/log" import { Log } from "../../util/log"
import { setTimeout as sleep } from "node:timers/promises" import { setTimeout as sleep } from "node:timers/promises"
import { CopilotModels } from "./models" import { CopilotModels } from "./models"
import { MessageV2 } from "@/session/message-v2" import { MessageV2 } from "@/session"
const log = Log.create({ service: "plugin.copilot" }) const log = Log.create({ service: "plugin.copilot" })
@@ -4,15 +4,15 @@ import { describeRoute, validator, resolver } from "hono-openapi"
import { SessionID, MessageID, PartID } from "@/session/schema" import { SessionID, MessageID, PartID } from "@/session/schema"
import z from "zod" import z from "zod"
import { Session } from "../../session" import { Session } from "../../session"
import { MessageV2 } from "../../session/message-v2" import { MessageV2 } from "../../session"
import { SessionPrompt } from "../../session/prompt" import { SessionPrompt } from "../../session"
import { SessionRunState } from "@/session/run-state" import { SessionRunState } from "@/session"
import { SessionCompaction } from "../../session/compaction" import { SessionCompaction } from "../../session"
import { SessionRevert } from "../../session/revert" import { SessionRevert } from "../../session"
import { SessionShare } from "@/share/session" import { SessionShare } from "@/share/session"
import { SessionStatus } from "@/session/status" import { SessionStatus } from "@/session"
import { SessionSummary } from "@/session/summary" import { SessionSummary } from "@/session"
import { Todo } from "../../session/todo" import { Todo } from "../../session"
import { Effect } from "effect" import { Effect } from "effect"
import { AppRuntime } from "../../effect/app-runtime" import { AppRuntime } from "../../effect/app-runtime"
import { Agent } from "../../agent/agent" import { Agent } from "../../agent/agent"
+16 -18
View File
@@ -3,11 +3,11 @@ import { Bus } from "@/bus"
import { Session } from "." import { Session } from "."
import { SessionID, MessageID, PartID } from "./schema" import { SessionID, MessageID, PartID } from "./schema"
import { Provider } from "../provider" import { Provider } from "../provider"
import { MessageV2 } from "./message-v2" import { MessageV2 } from "."
import z from "zod" import z from "zod"
import { Token } from "../util/token" import { Token } from "../util/token"
import { Log } from "../util/log" import { Log } from "../util/log"
import { SessionProcessor } from "./processor" import { SessionProcessor } from "."
import { Agent } from "@/agent/agent" import { Agent } from "@/agent/agent"
import { Plugin } from "@/plugin" import { Plugin } from "@/plugin"
import { Config } from "@/config" import { Config } from "@/config"
@@ -17,23 +17,22 @@ import { Effect, Layer, Context } from "effect"
import { InstanceState } from "@/effect" import { InstanceState } from "@/effect"
import { isOverflow as overflow } from "./overflow" import { isOverflow as overflow } from "./overflow"
export namespace SessionCompaction { const log = Log.create({ service: "session.compaction" })
const log = Log.create({ service: "session.compaction" })
export const Event = { export const Event = {
Compacted: BusEvent.define( Compacted: BusEvent.define(
"session.compacted", "session.compacted",
z.object({ z.object({
sessionID: SessionID.zod, sessionID: SessionID.zod,
}), }),
), ),
} }
export const PRUNE_MINIMUM = 20_000 export const PRUNE_MINIMUM = 20_000
export const PRUNE_PROTECT = 40_000 export const PRUNE_PROTECT = 40_000
const PRUNE_PROTECTED_TOOLS = ["skill"] const PRUNE_PROTECTED_TOOLS = ["skill"]
export interface Interface { export interface Interface {
readonly isOverflow: (input: { readonly isOverflow: (input: {
tokens: MessageV2.Assistant["tokens"] tokens: MessageV2.Assistant["tokens"]
model: Provider.Model model: Provider.Model
@@ -53,11 +52,11 @@ export namespace SessionCompaction {
auto: boolean auto: boolean
overflow?: boolean overflow?: boolean
}) => Effect.Effect<void> }) => Effect.Effect<void>
} }
export class Service extends Context.Service<Service, Interface>()("@opencode/SessionCompaction") {} export class Service extends Context.Service<Service, Interface>()("@opencode/SessionCompaction") {}
export const layer: Layer.Layer< export const layer: Layer.Layer<
Service, Service,
never, never,
| Bus.Service | Bus.Service
@@ -67,7 +66,7 @@ export namespace SessionCompaction {
| Plugin.Service | Plugin.Service
| SessionProcessor.Service | SessionProcessor.Service
| Provider.Service | Provider.Service
> = Layer.effect( > = Layer.effect(
Service, Service,
Effect.gen(function* () { Effect.gen(function* () {
const bus = yield* Bus.Service const bus = yield* Bus.Service
@@ -396,9 +395,9 @@ When constructing the summary, try to stick to this template:
create, create,
}) })
}), }),
) )
export const defaultLayer = Layer.suspend(() => export const defaultLayer = Layer.suspend(() =>
layer.pipe( layer.pipe(
Layer.provide(Provider.defaultLayer), Layer.provide(Provider.defaultLayer),
Layer.provide(Session.defaultLayer), Layer.provide(Session.defaultLayer),
@@ -408,5 +407,4 @@ When constructing the summary, try to stick to this template:
Layer.provide(Bus.layer), Layer.provide(Bus.layer),
Layer.provide(Config.defaultLayer), Layer.provide(Config.defaultLayer),
), ),
) )
}
+14
View File
@@ -1 +1,15 @@
export * as Session from "./session" export * as Session from "./session"
export * as SessionRunState from "./run-state"
export * as SystemPrompt from "./system"
export * as Message from "./message"
export * as SessionRetry from "./retry"
export * as SessionProcessor from "./processor"
export * as SessionRevert from "./revert"
export * as Instruction from "./instruction"
export * as SessionSummary from "./summary"
export * as Todo from "./todo"
export * as LLM from "./llm"
export * as SessionStatus from "./status"
export * as SessionCompaction from "./compaction"
export * as SessionPrompt from "./prompt"
export * as MessageV2 from "./message-v2"
+8 -10
View File
@@ -10,7 +10,7 @@ import { withTransientReadRetry } from "@/util/effect-http-client"
import { Global } from "../global" import { Global } from "../global"
import { Instance } from "../project/instance" import { Instance } from "../project/instance"
import { Log } from "../util/log" import { Log } from "../util/log"
import type { MessageV2 } from "./message-v2" import type { MessageV2 } from "."
import type { MessageID } from "./schema" import type { MessageID } from "./schema"
const log = Log.create({ service: "instruction" }) const log = Log.create({ service: "instruction" })
@@ -50,8 +50,7 @@ function extract(messages: MessageV2.WithParts[]) {
return paths return paths
} }
export namespace Instruction { export interface Interface {
export interface Interface {
readonly clear: (messageID: MessageID) => Effect.Effect<void> readonly clear: (messageID: MessageID) => Effect.Effect<void>
readonly systemPaths: () => Effect.Effect<Set<string>, AppFileSystem.Error> readonly systemPaths: () => Effect.Effect<Set<string>, AppFileSystem.Error>
readonly system: () => Effect.Effect<string[], AppFileSystem.Error> readonly system: () => Effect.Effect<string[], AppFileSystem.Error>
@@ -61,11 +60,11 @@ export namespace Instruction {
filepath: string, filepath: string,
messageID: MessageID, messageID: MessageID,
) => Effect.Effect<{ filepath: string; content: string }[], AppFileSystem.Error> ) => Effect.Effect<{ filepath: string; content: string }[], AppFileSystem.Error>
} }
export class Service extends Context.Service<Service, Interface>()("@opencode/Instruction") {} export class Service extends Context.Service<Service, Interface>()("@opencode/Instruction") {}
export const layer: Layer.Layer<Service, never, AppFileSystem.Service | Config.Service | HttpClient.HttpClient> = export const layer: Layer.Layer<Service, never, AppFileSystem.Service | Config.Service | HttpClient.HttpClient> =
Layer.effect( Layer.effect(
Service, Service,
Effect.gen(function* () { Effect.gen(function* () {
@@ -231,13 +230,12 @@ export namespace Instruction {
}), }),
) )
export const defaultLayer = layer.pipe( export const defaultLayer = layer.pipe(
Layer.provide(Config.defaultLayer), Layer.provide(Config.defaultLayer),
Layer.provide(AppFileSystem.defaultLayer), Layer.provide(AppFileSystem.defaultLayer),
Layer.provide(FetchHttpClient.layer), Layer.provide(FetchHttpClient.layer),
) )
export function loaded(messages: MessageV2.WithParts[]) { export function loaded(messages: MessageV2.WithParts[]) {
return extract(messages) return extract(messages)
}
} }
+24 -26
View File
@@ -9,9 +9,9 @@ import { ProviderTransform } from "@/provider/transform"
import { Config } from "@/config" import { Config } from "@/config"
import { Instance } from "@/project/instance" import { Instance } from "@/project/instance"
import type { Agent } from "@/agent/agent" import type { Agent } from "@/agent/agent"
import type { MessageV2 } from "./message-v2" import type { MessageV2 } from "."
import { Plugin } from "@/plugin" import { Plugin } from "@/plugin"
import { SystemPrompt } from "./system" import { SystemPrompt } from "."
import { Flag } from "@/flag/flag" import { Flag } from "@/flag/flag"
import { Permission } from "@/permission" import { Permission } from "@/permission"
import { PermissionID } from "@/permission/schema" import { PermissionID } from "@/permission/schema"
@@ -24,12 +24,11 @@ import { EffectBridge } from "@/effect"
import * as Option from "effect/Option" import * as Option from "effect/Option"
import * as OtelTracer from "@effect/opentelemetry/Tracer" import * as OtelTracer from "@effect/opentelemetry/Tracer"
export namespace LLM { const log = Log.create({ service: "llm" })
const log = Log.create({ service: "llm" }) export const OUTPUT_TOKEN_MAX = ProviderTransform.OUTPUT_TOKEN_MAX
export const OUTPUT_TOKEN_MAX = ProviderTransform.OUTPUT_TOKEN_MAX type Result = Awaited<ReturnType<typeof streamText>>
type Result = Awaited<ReturnType<typeof streamText>>
export type StreamInput = { export type StreamInput = {
user: MessageV2.User user: MessageV2.User
sessionID: string sessionID: string
parentSessionID?: string parentSessionID?: string
@@ -42,25 +41,25 @@ export namespace LLM {
tools: Record<string, Tool> tools: Record<string, Tool>
retries?: number retries?: number
toolChoice?: "auto" | "required" | "none" toolChoice?: "auto" | "required" | "none"
} }
export type StreamRequest = StreamInput & { export type StreamRequest = StreamInput & {
abort: AbortSignal abort: AbortSignal
} }
export type Event = Result["fullStream"] extends AsyncIterable<infer T> ? T : never export type Event = Result["fullStream"] extends AsyncIterable<infer T> ? T : never
export interface Interface { export interface Interface {
readonly stream: (input: StreamInput) => Stream.Stream<Event, unknown> readonly stream: (input: StreamInput) => Stream.Stream<Event, unknown>
} }
export class Service extends Context.Service<Service, Interface>()("@opencode/LLM") {} export class Service extends Context.Service<Service, Interface>()("@opencode/LLM") {}
const live: Layer.Layer< const live: Layer.Layer<
Service, Service,
never, never,
Auth.Service | Config.Service | Provider.Service | Plugin.Service | Permission.Service Auth.Service | Config.Service | Provider.Service | Plugin.Service | Permission.Service
> = Layer.effect( > = Layer.effect(
Service, Service,
Effect.gen(function* () { Effect.gen(function* () {
const auth = yield* Auth.Service const auth = yield* Auth.Service
@@ -418,30 +417,30 @@ export namespace LLM {
return Service.of({ stream }) return Service.of({ stream })
}), }),
) )
export const layer = live.pipe(Layer.provide(Permission.defaultLayer)) export const layer = live.pipe(Layer.provide(Permission.defaultLayer))
export const defaultLayer = Layer.suspend(() => export const defaultLayer = Layer.suspend(() =>
layer.pipe( layer.pipe(
Layer.provide(Auth.defaultLayer), Layer.provide(Auth.defaultLayer),
Layer.provide(Config.defaultLayer), Layer.provide(Config.defaultLayer),
Layer.provide(Provider.defaultLayer), Layer.provide(Provider.defaultLayer),
Layer.provide(Plugin.defaultLayer), Layer.provide(Plugin.defaultLayer),
), ),
) )
function resolveTools(input: Pick<StreamInput, "tools" | "agent" | "permission" | "user">) { function resolveTools(input: Pick<StreamInput, "tools" | "agent" | "permission" | "user">) {
const disabled = Permission.disabled( const disabled = Permission.disabled(
Object.keys(input.tools), Object.keys(input.tools),
Permission.merge(input.agent.permission, input.permission ?? []), Permission.merge(input.agent.permission, input.permission ?? []),
) )
return Record.filter(input.tools, (_, k) => input.user.tools?.[k] !== false && !disabled.has(k)) return Record.filter(input.tools, (_, k) => input.user.tools?.[k] !== false && !disabled.has(k))
} }
// Check if messages contain any tool-call content // Check if messages contain any tool-call content
// Used to determine if a dummy tool should be added for LiteLLM proxy compatibility // Used to determine if a dummy tool should be added for LiteLLM proxy compatibility
export function hasToolCalls(messages: ModelMessage[]): boolean { export function hasToolCalls(messages: ModelMessage[]): boolean {
for (const msg of messages) { for (const msg of messages) {
if (!Array.isArray(msg.content)) continue if (!Array.isArray(msg.content)) continue
for (const part of msg.content) { for (const part of msg.content) {
@@ -449,5 +448,4 @@ export namespace LLM {
} }
} }
return false return false
}
} }
+160 -162
View File
@@ -24,30 +24,29 @@ interface FetchDecompressionError extends Error {
path: string path: string
} }
export namespace MessageV2 { export const SYNTHETIC_ATTACHMENT_PROMPT = "Attached image(s) from tool result:"
export const SYNTHETIC_ATTACHMENT_PROMPT = "Attached image(s) from tool result:"
export function isMedia(mime: string) { export function isMedia(mime: string) {
return mime.startsWith("image/") || mime === "application/pdf" return mime.startsWith("image/") || mime === "application/pdf"
} }
export const OutputLengthError = NamedError.create("MessageOutputLengthError", z.object({})) export const OutputLengthError = NamedError.create("MessageOutputLengthError", z.object({}))
export const AbortedError = NamedError.create("MessageAbortedError", z.object({ message: z.string() })) export const AbortedError = NamedError.create("MessageAbortedError", z.object({ message: z.string() }))
export const StructuredOutputError = NamedError.create( export const StructuredOutputError = NamedError.create(
"StructuredOutputError", "StructuredOutputError",
z.object({ z.object({
message: z.string(), message: z.string(),
retries: z.number(), retries: z.number(),
}), }),
) )
export const AuthError = NamedError.create( export const AuthError = NamedError.create(
"ProviderAuthError", "ProviderAuthError",
z.object({ z.object({
providerID: z.string(), providerID: z.string(),
message: z.string(), message: z.string(),
}), }),
) )
export const APIError = NamedError.create( export const APIError = NamedError.create(
"APIError", "APIError",
z.object({ z.object({
message: z.string(), message: z.string(),
@@ -57,14 +56,14 @@ export namespace MessageV2 {
responseBody: z.string().optional(), responseBody: z.string().optional(),
metadata: z.record(z.string(), z.string()).optional(), metadata: z.record(z.string(), z.string()).optional(),
}), }),
) )
export type APIError = z.infer<typeof APIError.Schema> export type APIError = z.infer<typeof APIError.Schema>
export const ContextOverflowError = NamedError.create( export const ContextOverflowError = NamedError.create(
"ContextOverflowError", "ContextOverflowError",
z.object({ message: z.string(), responseBody: z.string().optional() }), z.object({ message: z.string(), responseBody: z.string().optional() }),
) )
export const OutputFormatText = z export const OutputFormatText = z
.object({ .object({
type: z.literal("text"), type: z.literal("text"),
}) })
@@ -72,7 +71,7 @@ export namespace MessageV2 {
ref: "OutputFormatText", ref: "OutputFormatText",
}) })
export const OutputFormatJsonSchema = z export const OutputFormatJsonSchema = z
.object({ .object({
type: z.literal("json_schema"), type: z.literal("json_schema"),
schema: z.record(z.string(), z.any()).meta({ ref: "JSONSchema" }), schema: z.record(z.string(), z.any()).meta({ ref: "JSONSchema" }),
@@ -82,35 +81,35 @@ export namespace MessageV2 {
ref: "OutputFormatJsonSchema", ref: "OutputFormatJsonSchema",
}) })
export const Format = z.discriminatedUnion("type", [OutputFormatText, OutputFormatJsonSchema]).meta({ export const Format = z.discriminatedUnion("type", [OutputFormatText, OutputFormatJsonSchema]).meta({
ref: "OutputFormat", ref: "OutputFormat",
}) })
export type OutputFormat = z.infer<typeof Format> export type OutputFormat = z.infer<typeof Format>
const PartBase = z.object({ const PartBase = z.object({
id: PartID.zod, id: PartID.zod,
sessionID: SessionID.zod, sessionID: SessionID.zod,
messageID: MessageID.zod, messageID: MessageID.zod,
}) })
export const SnapshotPart = PartBase.extend({ export const SnapshotPart = PartBase.extend({
type: z.literal("snapshot"), type: z.literal("snapshot"),
snapshot: z.string(), snapshot: z.string(),
}).meta({ }).meta({
ref: "SnapshotPart", ref: "SnapshotPart",
}) })
export type SnapshotPart = z.infer<typeof SnapshotPart> export type SnapshotPart = z.infer<typeof SnapshotPart>
export const PatchPart = PartBase.extend({ export const PatchPart = PartBase.extend({
type: z.literal("patch"), type: z.literal("patch"),
hash: z.string(), hash: z.string(),
files: z.string().array(), files: z.string().array(),
}).meta({ }).meta({
ref: "PatchPart", ref: "PatchPart",
}) })
export type PatchPart = z.infer<typeof PatchPart> export type PatchPart = z.infer<typeof PatchPart>
export const TextPart = PartBase.extend({ export const TextPart = PartBase.extend({
type: z.literal("text"), type: z.literal("text"),
text: z.string(), text: z.string(),
synthetic: z.boolean().optional(), synthetic: z.boolean().optional(),
@@ -122,12 +121,12 @@ export namespace MessageV2 {
}) })
.optional(), .optional(),
metadata: z.record(z.string(), z.any()).optional(), metadata: z.record(z.string(), z.any()).optional(),
}).meta({ }).meta({
ref: "TextPart", ref: "TextPart",
}) })
export type TextPart = z.infer<typeof TextPart> export type TextPart = z.infer<typeof TextPart>
export const ReasoningPart = PartBase.extend({ export const ReasoningPart = PartBase.extend({
type: z.literal("reasoning"), type: z.literal("reasoning"),
text: z.string(), text: z.string(),
metadata: z.record(z.string(), z.any()).optional(), metadata: z.record(z.string(), z.any()).optional(),
@@ -135,12 +134,12 @@ export namespace MessageV2 {
start: z.number(), start: z.number(),
end: z.number().optional(), end: z.number().optional(),
}), }),
}).meta({ }).meta({
ref: "ReasoningPart", ref: "ReasoningPart",
}) })
export type ReasoningPart = z.infer<typeof ReasoningPart> export type ReasoningPart = z.infer<typeof ReasoningPart>
const FilePartSourceBase = z.object({ const FilePartSourceBase = z.object({
text: z text: z
.object({ .object({
value: z.string(), value: z.string(),
@@ -150,49 +149,49 @@ export namespace MessageV2 {
.meta({ .meta({
ref: "FilePartSourceText", ref: "FilePartSourceText",
}), }),
}) })
export const FileSource = FilePartSourceBase.extend({ export const FileSource = FilePartSourceBase.extend({
type: z.literal("file"), type: z.literal("file"),
path: z.string(), path: z.string(),
}).meta({ }).meta({
ref: "FileSource", ref: "FileSource",
}) })
export const SymbolSource = FilePartSourceBase.extend({ export const SymbolSource = FilePartSourceBase.extend({
type: z.literal("symbol"), type: z.literal("symbol"),
path: z.string(), path: z.string(),
range: LSP.Range, range: LSP.Range,
name: z.string(), name: z.string(),
kind: z.number().int(), kind: z.number().int(),
}).meta({ }).meta({
ref: "SymbolSource", ref: "SymbolSource",
}) })
export const ResourceSource = FilePartSourceBase.extend({ export const ResourceSource = FilePartSourceBase.extend({
type: z.literal("resource"), type: z.literal("resource"),
clientName: z.string(), clientName: z.string(),
uri: z.string(), uri: z.string(),
}).meta({ }).meta({
ref: "ResourceSource", ref: "ResourceSource",
}) })
export const FilePartSource = z.discriminatedUnion("type", [FileSource, SymbolSource, ResourceSource]).meta({ export const FilePartSource = z.discriminatedUnion("type", [FileSource, SymbolSource, ResourceSource]).meta({
ref: "FilePartSource", ref: "FilePartSource",
}) })
export const FilePart = PartBase.extend({ export const FilePart = PartBase.extend({
type: z.literal("file"), type: z.literal("file"),
mime: z.string(), mime: z.string(),
filename: z.string().optional(), filename: z.string().optional(),
url: z.string(), url: z.string(),
source: FilePartSource.optional(), source: FilePartSource.optional(),
}).meta({ }).meta({
ref: "FilePart", ref: "FilePart",
}) })
export type FilePart = z.infer<typeof FilePart> export type FilePart = z.infer<typeof FilePart>
export const AgentPart = PartBase.extend({ export const AgentPart = PartBase.extend({
type: z.literal("agent"), type: z.literal("agent"),
name: z.string(), name: z.string(),
source: z source: z
@@ -202,21 +201,21 @@ export namespace MessageV2 {
end: z.number().int(), end: z.number().int(),
}) })
.optional(), .optional(),
}).meta({ }).meta({
ref: "AgentPart", ref: "AgentPart",
}) })
export type AgentPart = z.infer<typeof AgentPart> export type AgentPart = z.infer<typeof AgentPart>
export const CompactionPart = PartBase.extend({ export const CompactionPart = PartBase.extend({
type: z.literal("compaction"), type: z.literal("compaction"),
auto: z.boolean(), auto: z.boolean(),
overflow: z.boolean().optional(), overflow: z.boolean().optional(),
}).meta({ }).meta({
ref: "CompactionPart", ref: "CompactionPart",
}) })
export type CompactionPart = z.infer<typeof CompactionPart> export type CompactionPart = z.infer<typeof CompactionPart>
export const SubtaskPart = PartBase.extend({ export const SubtaskPart = PartBase.extend({
type: z.literal("subtask"), type: z.literal("subtask"),
prompt: z.string(), prompt: z.string(),
description: z.string(), description: z.string(),
@@ -228,32 +227,32 @@ export namespace MessageV2 {
}) })
.optional(), .optional(),
command: z.string().optional(), command: z.string().optional(),
}).meta({ }).meta({
ref: "SubtaskPart", ref: "SubtaskPart",
}) })
export type SubtaskPart = z.infer<typeof SubtaskPart> export type SubtaskPart = z.infer<typeof SubtaskPart>
export const RetryPart = PartBase.extend({ export const RetryPart = PartBase.extend({
type: z.literal("retry"), type: z.literal("retry"),
attempt: z.number(), attempt: z.number(),
error: APIError.Schema, error: APIError.Schema,
time: z.object({ time: z.object({
created: z.number(), created: z.number(),
}), }),
}).meta({ }).meta({
ref: "RetryPart", ref: "RetryPart",
}) })
export type RetryPart = z.infer<typeof RetryPart> export type RetryPart = z.infer<typeof RetryPart>
export const StepStartPart = PartBase.extend({ export const StepStartPart = PartBase.extend({
type: z.literal("step-start"), type: z.literal("step-start"),
snapshot: z.string().optional(), snapshot: z.string().optional(),
}).meta({ }).meta({
ref: "StepStartPart", ref: "StepStartPart",
}) })
export type StepStartPart = z.infer<typeof StepStartPart> export type StepStartPart = z.infer<typeof StepStartPart>
export const StepFinishPart = PartBase.extend({ export const StepFinishPart = PartBase.extend({
type: z.literal("step-finish"), type: z.literal("step-finish"),
reason: z.string(), reason: z.string(),
snapshot: z.string().optional(), snapshot: z.string().optional(),
@@ -268,12 +267,12 @@ export namespace MessageV2 {
write: z.number(), write: z.number(),
}), }),
}), }),
}).meta({ }).meta({
ref: "StepFinishPart", ref: "StepFinishPart",
}) })
export type StepFinishPart = z.infer<typeof StepFinishPart> export type StepFinishPart = z.infer<typeof StepFinishPart>
export const ToolStatePending = z export const ToolStatePending = z
.object({ .object({
status: z.literal("pending"), status: z.literal("pending"),
input: z.record(z.string(), z.any()), input: z.record(z.string(), z.any()),
@@ -283,9 +282,9 @@ export namespace MessageV2 {
ref: "ToolStatePending", ref: "ToolStatePending",
}) })
export type ToolStatePending = z.infer<typeof ToolStatePending> export type ToolStatePending = z.infer<typeof ToolStatePending>
export const ToolStateRunning = z export const ToolStateRunning = z
.object({ .object({
status: z.literal("running"), status: z.literal("running"),
input: z.record(z.string(), z.any()), input: z.record(z.string(), z.any()),
@@ -298,9 +297,9 @@ export namespace MessageV2 {
.meta({ .meta({
ref: "ToolStateRunning", ref: "ToolStateRunning",
}) })
export type ToolStateRunning = z.infer<typeof ToolStateRunning> export type ToolStateRunning = z.infer<typeof ToolStateRunning>
export const ToolStateCompleted = z export const ToolStateCompleted = z
.object({ .object({
status: z.literal("completed"), status: z.literal("completed"),
input: z.record(z.string(), z.any()), input: z.record(z.string(), z.any()),
@@ -317,9 +316,9 @@ export namespace MessageV2 {
.meta({ .meta({
ref: "ToolStateCompleted", ref: "ToolStateCompleted",
}) })
export type ToolStateCompleted = z.infer<typeof ToolStateCompleted> export type ToolStateCompleted = z.infer<typeof ToolStateCompleted>
export const ToolStateError = z export const ToolStateError = z
.object({ .object({
status: z.literal("error"), status: z.literal("error"),
input: z.record(z.string(), z.any()), input: z.record(z.string(), z.any()),
@@ -333,31 +332,31 @@ export namespace MessageV2 {
.meta({ .meta({
ref: "ToolStateError", ref: "ToolStateError",
}) })
export type ToolStateError = z.infer<typeof ToolStateError> export type ToolStateError = z.infer<typeof ToolStateError>
export const ToolState = z export const ToolState = z
.discriminatedUnion("status", [ToolStatePending, ToolStateRunning, ToolStateCompleted, ToolStateError]) .discriminatedUnion("status", [ToolStatePending, ToolStateRunning, ToolStateCompleted, ToolStateError])
.meta({ .meta({
ref: "ToolState", ref: "ToolState",
}) })
export const ToolPart = PartBase.extend({ export const ToolPart = PartBase.extend({
type: z.literal("tool"), type: z.literal("tool"),
callID: z.string(), callID: z.string(),
tool: z.string(), tool: z.string(),
state: ToolState, state: ToolState,
metadata: z.record(z.string(), z.any()).optional(), metadata: z.record(z.string(), z.any()).optional(),
}).meta({ }).meta({
ref: "ToolPart", ref: "ToolPart",
}) })
export type ToolPart = z.infer<typeof ToolPart> export type ToolPart = z.infer<typeof ToolPart>
const Base = z.object({ const Base = z.object({
id: MessageID.zod, id: MessageID.zod,
sessionID: SessionID.zod, sessionID: SessionID.zod,
}) })
export const User = Base.extend({ export const User = Base.extend({
role: z.literal("user"), role: z.literal("user"),
time: z.object({ time: z.object({
created: z.number(), created: z.number(),
@@ -378,12 +377,12 @@ export namespace MessageV2 {
}), }),
system: z.string().optional(), system: z.string().optional(),
tools: z.record(z.string(), z.boolean()).optional(), tools: z.record(z.string(), z.boolean()).optional(),
}).meta({ }).meta({
ref: "UserMessage", ref: "UserMessage",
}) })
export type User = z.infer<typeof User> export type User = z.infer<typeof User>
export const Part = z export const Part = z
.discriminatedUnion("type", [ .discriminatedUnion("type", [
TextPart, TextPart,
SubtaskPart, SubtaskPart,
@@ -401,9 +400,9 @@ export namespace MessageV2 {
.meta({ .meta({
ref: "Part", ref: "Part",
}) })
export type Part = z.infer<typeof Part> export type Part = z.infer<typeof Part>
export const Assistant = Base.extend({ export const Assistant = Base.extend({
role: z.literal("assistant"), role: z.literal("assistant"),
time: z.object({ time: z.object({
created: z.number(), created: z.number(),
@@ -447,17 +446,17 @@ export namespace MessageV2 {
structured: z.any().optional(), structured: z.any().optional(),
variant: z.string().optional(), variant: z.string().optional(),
finish: z.string().optional(), finish: z.string().optional(),
}).meta({ }).meta({
ref: "AssistantMessage", ref: "AssistantMessage",
}) })
export type Assistant = z.infer<typeof Assistant> export type Assistant = z.infer<typeof Assistant>
export const Info = z.discriminatedUnion("role", [User, Assistant]).meta({ export const Info = z.discriminatedUnion("role", [User, Assistant]).meta({
ref: "Message", ref: "Message",
}) })
export type Info = z.infer<typeof Info> export type Info = z.infer<typeof Info>
export const Event = { export const Event = {
Updated: SyncEvent.define({ Updated: SyncEvent.define({
type: "message.updated", type: "message.updated",
version: 1, version: 1,
@@ -506,53 +505,53 @@ export namespace MessageV2 {
partID: PartID.zod, partID: PartID.zod,
}), }),
}), }),
} }
export const WithParts = z.object({ export const WithParts = z.object({
info: Info, info: Info,
parts: z.array(Part), parts: z.array(Part),
}) })
export type WithParts = z.infer<typeof WithParts> export type WithParts = z.infer<typeof WithParts>
const Cursor = z.object({ const Cursor = z.object({
id: MessageID.zod, id: MessageID.zod,
time: z.number(), time: z.number(),
}) })
type Cursor = z.infer<typeof Cursor> type Cursor = z.infer<typeof Cursor>
export const cursor = { export const cursor = {
encode(input: Cursor) { encode(input: Cursor) {
return Buffer.from(JSON.stringify(input)).toString("base64url") return Buffer.from(JSON.stringify(input)).toString("base64url")
}, },
decode(input: string) { decode(input: string) {
return Cursor.parse(JSON.parse(Buffer.from(input, "base64url").toString("utf8"))) return Cursor.parse(JSON.parse(Buffer.from(input, "base64url").toString("utf8")))
}, },
} }
const info = (row: typeof MessageTable.$inferSelect) => const info = (row: typeof MessageTable.$inferSelect) =>
({ ({
...row.data, ...row.data,
id: row.id, id: row.id,
sessionID: row.session_id, sessionID: row.session_id,
}) as MessageV2.Info }) as Info
const part = (row: typeof PartTable.$inferSelect) => const part = (row: typeof PartTable.$inferSelect) =>
({ ({
...row.data, ...row.data,
id: row.id, id: row.id,
sessionID: row.session_id, sessionID: row.session_id,
messageID: row.message_id, messageID: row.message_id,
}) as MessageV2.Part }) as Part
const older = (row: Cursor) => const older = (row: Cursor) =>
or( or(
lt(MessageTable.time_created, row.time), lt(MessageTable.time_created, row.time),
and(eq(MessageTable.time_created, row.time), lt(MessageTable.id, row.id)), and(eq(MessageTable.time_created, row.time), lt(MessageTable.id, row.id)),
) )
function hydrate(rows: (typeof MessageTable.$inferSelect)[]) { function hydrate(rows: (typeof MessageTable.$inferSelect)[]) {
const ids = rows.map((row) => row.id) const ids = rows.map((row) => row.id)
const partByMessage = new Map<string, MessageV2.Part[]>() const partByMessage = new Map<string, Part[]>()
if (ids.length > 0) { if (ids.length > 0) {
const partRows = Database.use((db) => const partRows = Database.use((db) =>
db db
@@ -574,19 +573,19 @@ export namespace MessageV2 {
info: info(row), info: info(row),
parts: partByMessage.get(row.id) ?? [], parts: partByMessage.get(row.id) ?? [],
})) }))
} }
function providerMeta(metadata: Record<string, any> | undefined) { function providerMeta(metadata: Record<string, any> | undefined) {
if (!metadata) return undefined if (!metadata) return undefined
const { providerExecuted: _, ...rest } = metadata const { providerExecuted: _, ...rest } = metadata
return Object.keys(rest).length > 0 ? rest : undefined return Object.keys(rest).length > 0 ? rest : undefined
} }
export const toModelMessagesEffect = Effect.fnUntraced(function* ( export const toModelMessagesEffect = Effect.fnUntraced(function* (
input: WithParts[], input: WithParts[],
model: Provider.Model, model: Provider.Model,
options?: { stripMedia?: boolean }, options?: { stripMedia?: boolean },
) { ) {
const result: UIMessage[] = [] const result: UIMessage[] = []
const toolNames = new Set<string>() const toolNames = new Set<string>()
// Track media from tool results that need to be injected as user messages // Track media from tool results that need to be injected as user messages
@@ -699,7 +698,7 @@ export namespace MessageV2 {
if ( if (
msg.info.error && msg.info.error &&
!( !(
MessageV2.AbortedError.isInstance(msg.info.error) && AbortedError.isInstance(msg.info.error) &&
msg.parts.some((part) => part.type !== "step-start" && part.type !== "reasoning") msg.parts.some((part) => part.type !== "step-start" && part.type !== "reasoning")
) )
) { ) {
@@ -835,17 +834,17 @@ export namespace MessageV2 {
}, },
), ),
) )
}) })
export function toModelMessages( export function toModelMessages(
input: WithParts[], input: WithParts[],
model: Provider.Model, model: Provider.Model,
options?: { stripMedia?: boolean }, options?: { stripMedia?: boolean },
): Promise<ModelMessage[]> { ): Promise<ModelMessage[]> {
return Effect.runPromise(toModelMessagesEffect(input, model, options).pipe(Effect.provide(EffectLogger.layer))) return Effect.runPromise(toModelMessagesEffect(input, model, options).pipe(Effect.provide(EffectLogger.layer)))
} }
export function page(input: { sessionID: SessionID; limit: number; before?: string }) { export function page(input: { sessionID: SessionID; limit: number; before?: string }) {
const before = input.before ? cursor.decode(input.before) : undefined const before = input.before ? cursor.decode(input.before) : undefined
const where = before const where = before
? and(eq(MessageTable.session_id, input.sessionID), older(before)) ? and(eq(MessageTable.session_id, input.sessionID), older(before))
@@ -865,7 +864,7 @@ export namespace MessageV2 {
) )
if (!row) throw new NotFoundError({ message: `Session not found: ${input.sessionID}` }) if (!row) throw new NotFoundError({ message: `Session not found: ${input.sessionID}` })
return { return {
items: [] as MessageV2.WithParts[], items: [] as WithParts[],
more: false, more: false,
} }
} }
@@ -880,9 +879,9 @@ export namespace MessageV2 {
more, more,
cursor: more && tail ? cursor.encode({ id: tail.id, time: tail.time_created }) : undefined, cursor: more && tail ? cursor.encode({ id: tail.id, time: tail.time_created }) : undefined,
} }
} }
export function* stream(sessionID: SessionID) { export function* stream(sessionID: SessionID) {
const size = 50 const size = 50
let before: string | undefined let before: string | undefined
while (true) { while (true) {
@@ -894,9 +893,9 @@ export namespace MessageV2 {
if (!next.more || !next.cursor) break if (!next.more || !next.cursor) break
before = next.cursor before = next.cursor
} }
} }
export function parts(message_id: MessageID) { export function parts(message_id: MessageID) {
const rows = Database.use((db) => const rows = Database.use((db) =>
db.select().from(PartTable).where(eq(PartTable.message_id, message_id)).orderBy(PartTable.id).all(), db.select().from(PartTable).where(eq(PartTable.message_id, message_id)).orderBy(PartTable.id).all(),
) )
@@ -907,11 +906,11 @@ export namespace MessageV2 {
id: row.id, id: row.id,
sessionID: row.session_id, sessionID: row.session_id,
messageID: row.message_id, messageID: row.message_id,
}) as MessageV2.Part, }) as Part,
) )
} }
export function get(input: { sessionID: SessionID; messageID: MessageID }): WithParts { export function get(input: { sessionID: SessionID; messageID: MessageID }): WithParts {
const row = Database.use((db) => const row = Database.use((db) =>
db db
.select() .select()
@@ -924,10 +923,10 @@ export namespace MessageV2 {
info: info(row), info: info(row),
parts: parts(input.messageID), parts: parts(input.messageID),
} }
} }
export function filterCompacted(msgs: Iterable<MessageV2.WithParts>) { export function filterCompacted(msgs: Iterable<WithParts>) {
const result = [] as MessageV2.WithParts[] const result = [] as WithParts[]
const completed = new Set<string>() const completed = new Set<string>()
for (const msg of msgs) { for (const msg of msgs) {
result.push(msg) result.push(msg)
@@ -942,28 +941,28 @@ export namespace MessageV2 {
} }
result.reverse() result.reverse()
return result return result
} }
export const filterCompactedEffect = Effect.fnUntraced(function* (sessionID: SessionID) { export const filterCompactedEffect = Effect.fnUntraced(function* (sessionID: SessionID) {
return filterCompacted(stream(sessionID)) return filterCompacted(stream(sessionID))
}) })
export function fromError( export function fromError(
e: unknown, e: unknown,
ctx: { providerID: ProviderID; aborted?: boolean }, ctx: { providerID: ProviderID; aborted?: boolean },
): NonNullable<Assistant["error"]> { ): NonNullable<Assistant["error"]> {
switch (true) { switch (true) {
case e instanceof DOMException && e.name === "AbortError": case e instanceof DOMException && e.name === "AbortError":
return new MessageV2.AbortedError( return new AbortedError(
{ message: e.message }, { message: e.message },
{ {
cause: e, cause: e,
}, },
).toObject() ).toObject()
case MessageV2.OutputLengthError.isInstance(e): case OutputLengthError.isInstance(e):
return e return e
case LoadAPIKeyError.isInstance(e): case LoadAPIKeyError.isInstance(e):
return new MessageV2.AuthError( return new AuthError(
{ {
providerID: ctx.providerID, providerID: ctx.providerID,
message: e.message, message: e.message,
@@ -971,7 +970,7 @@ export namespace MessageV2 {
{ cause: e }, { cause: e },
).toObject() ).toObject()
case (e as SystemError)?.code === "ECONNRESET": case (e as SystemError)?.code === "ECONNRESET":
return new MessageV2.APIError( return new APIError(
{ {
message: "Connection reset by server", message: "Connection reset by server",
isRetryable: true, isRetryable: true,
@@ -985,9 +984,9 @@ export namespace MessageV2 {
).toObject() ).toObject()
case e instanceof Error && (e as FetchDecompressionError).code === "ZlibError": case e instanceof Error && (e as FetchDecompressionError).code === "ZlibError":
if (ctx.aborted) { if (ctx.aborted) {
return new MessageV2.AbortedError({ message: e.message }, { cause: e }).toObject() return new AbortedError({ message: e.message }, { cause: e }).toObject()
} }
return new MessageV2.APIError( return new APIError(
{ {
message: "Response decompression failed", message: "Response decompression failed",
isRetryable: true, isRetryable: true,
@@ -1004,7 +1003,7 @@ export namespace MessageV2 {
error: e, error: e,
}) })
if (parsed.type === "context_overflow") { if (parsed.type === "context_overflow") {
return new MessageV2.ContextOverflowError( return new ContextOverflowError(
{ {
message: parsed.message, message: parsed.message,
responseBody: parsed.responseBody, responseBody: parsed.responseBody,
@@ -1013,7 +1012,7 @@ export namespace MessageV2 {
).toObject() ).toObject()
} }
return new MessageV2.APIError( return new APIError(
{ {
message: parsed.message, message: parsed.message,
statusCode: parsed.statusCode, statusCode: parsed.statusCode,
@@ -1031,7 +1030,7 @@ export namespace MessageV2 {
const parsed = ProviderError.parseStreamError(e) const parsed = ProviderError.parseStreamError(e)
if (parsed) { if (parsed) {
if (parsed.type === "context_overflow") { if (parsed.type === "context_overflow") {
return new MessageV2.ContextOverflowError( return new ContextOverflowError(
{ {
message: parsed.message, message: parsed.message,
responseBody: parsed.responseBody, responseBody: parsed.responseBody,
@@ -1039,7 +1038,7 @@ export namespace MessageV2 {
{ cause: e }, { cause: e },
).toObject() ).toObject()
} }
return new MessageV2.APIError( return new APIError(
{ {
message: parsed.message, message: parsed.message,
isRetryable: parsed.isRetryable, isRetryable: parsed.isRetryable,
@@ -1053,5 +1052,4 @@ export namespace MessageV2 {
} catch {} } catch {}
return new NamedError.Unknown({ message: JSON.stringify(e) }, { cause: e }).toObject() return new NamedError.Unknown({ message: JSON.stringify(e) }, { cause: e }).toObject()
} }
}
} }
+28 -30
View File
@@ -3,17 +3,16 @@ import { SessionID } from "./schema"
import { ModelID, ProviderID } from "../provider/schema" import { ModelID, ProviderID } from "../provider/schema"
import { NamedError } from "@opencode-ai/shared/util/error" import { NamedError } from "@opencode-ai/shared/util/error"
export namespace Message { export const OutputLengthError = NamedError.create("MessageOutputLengthError", z.object({}))
export const OutputLengthError = NamedError.create("MessageOutputLengthError", z.object({})) export const AuthError = NamedError.create(
export const AuthError = NamedError.create(
"ProviderAuthError", "ProviderAuthError",
z.object({ z.object({
providerID: z.string(), providerID: z.string(),
message: z.string(), message: z.string(),
}), }),
) )
export const ToolCall = z export const ToolCall = z
.object({ .object({
state: z.literal("call"), state: z.literal("call"),
step: z.number().optional(), step: z.number().optional(),
@@ -24,9 +23,9 @@ export namespace Message {
.meta({ .meta({
ref: "ToolCall", ref: "ToolCall",
}) })
export type ToolCall = z.infer<typeof ToolCall> export type ToolCall = z.infer<typeof ToolCall>
export const ToolPartialCall = z export const ToolPartialCall = z
.object({ .object({
state: z.literal("partial-call"), state: z.literal("partial-call"),
step: z.number().optional(), step: z.number().optional(),
@@ -37,9 +36,9 @@ export namespace Message {
.meta({ .meta({
ref: "ToolPartialCall", ref: "ToolPartialCall",
}) })
export type ToolPartialCall = z.infer<typeof ToolPartialCall> export type ToolPartialCall = z.infer<typeof ToolPartialCall>
export const ToolResult = z export const ToolResult = z
.object({ .object({
state: z.literal("result"), state: z.literal("result"),
step: z.number().optional(), step: z.number().optional(),
@@ -51,14 +50,14 @@ export namespace Message {
.meta({ .meta({
ref: "ToolResult", ref: "ToolResult",
}) })
export type ToolResult = z.infer<typeof ToolResult> export type ToolResult = z.infer<typeof ToolResult>
export const ToolInvocation = z.discriminatedUnion("state", [ToolCall, ToolPartialCall, ToolResult]).meta({ export const ToolInvocation = z.discriminatedUnion("state", [ToolCall, ToolPartialCall, ToolResult]).meta({
ref: "ToolInvocation", ref: "ToolInvocation",
}) })
export type ToolInvocation = z.infer<typeof ToolInvocation> export type ToolInvocation = z.infer<typeof ToolInvocation>
export const TextPart = z export const TextPart = z
.object({ .object({
type: z.literal("text"), type: z.literal("text"),
text: z.string(), text: z.string(),
@@ -66,9 +65,9 @@ export namespace Message {
.meta({ .meta({
ref: "TextPart", ref: "TextPart",
}) })
export type TextPart = z.infer<typeof TextPart> export type TextPart = z.infer<typeof TextPart>
export const ReasoningPart = z export const ReasoningPart = z
.object({ .object({
type: z.literal("reasoning"), type: z.literal("reasoning"),
text: z.string(), text: z.string(),
@@ -77,9 +76,9 @@ export namespace Message {
.meta({ .meta({
ref: "ReasoningPart", ref: "ReasoningPart",
}) })
export type ReasoningPart = z.infer<typeof ReasoningPart> export type ReasoningPart = z.infer<typeof ReasoningPart>
export const ToolInvocationPart = z export const ToolInvocationPart = z
.object({ .object({
type: z.literal("tool-invocation"), type: z.literal("tool-invocation"),
toolInvocation: ToolInvocation, toolInvocation: ToolInvocation,
@@ -87,9 +86,9 @@ export namespace Message {
.meta({ .meta({
ref: "ToolInvocationPart", ref: "ToolInvocationPart",
}) })
export type ToolInvocationPart = z.infer<typeof ToolInvocationPart> export type ToolInvocationPart = z.infer<typeof ToolInvocationPart>
export const SourceUrlPart = z export const SourceUrlPart = z
.object({ .object({
type: z.literal("source-url"), type: z.literal("source-url"),
sourceId: z.string(), sourceId: z.string(),
@@ -100,9 +99,9 @@ export namespace Message {
.meta({ .meta({
ref: "SourceUrlPart", ref: "SourceUrlPart",
}) })
export type SourceUrlPart = z.infer<typeof SourceUrlPart> export type SourceUrlPart = z.infer<typeof SourceUrlPart>
export const FilePart = z export const FilePart = z
.object({ .object({
type: z.literal("file"), type: z.literal("file"),
mediaType: z.string(), mediaType: z.string(),
@@ -112,25 +111,25 @@ export namespace Message {
.meta({ .meta({
ref: "FilePart", ref: "FilePart",
}) })
export type FilePart = z.infer<typeof FilePart> export type FilePart = z.infer<typeof FilePart>
export const StepStartPart = z export const StepStartPart = z
.object({ .object({
type: z.literal("step-start"), type: z.literal("step-start"),
}) })
.meta({ .meta({
ref: "StepStartPart", ref: "StepStartPart",
}) })
export type StepStartPart = z.infer<typeof StepStartPart> export type StepStartPart = z.infer<typeof StepStartPart>
export const MessagePart = z export const MessagePart = z
.discriminatedUnion("type", [TextPart, ReasoningPart, ToolInvocationPart, SourceUrlPart, FilePart, StepStartPart]) .discriminatedUnion("type", [TextPart, ReasoningPart, ToolInvocationPart, SourceUrlPart, FilePart, StepStartPart])
.meta({ .meta({
ref: "MessagePart", ref: "MessagePart",
}) })
export type MessagePart = z.infer<typeof MessagePart> export type MessagePart = z.infer<typeof MessagePart>
export const Info = z export const Info = z
.object({ .object({
id: z.string(), id: z.string(),
role: z.enum(["user", "assistant"]), role: z.enum(["user", "assistant"]),
@@ -187,5 +186,4 @@ export namespace Message {
.meta({ .meta({
ref: "Message", ref: "Message",
}) })
export type Info = z.infer<typeof Info> export type Info = z.infer<typeof Info>
}
+1 -1
View File
@@ -1,7 +1,7 @@
import type { Config } from "@/config" import type { Config } from "@/config"
import type { Provider } from "@/provider" import type { Provider } from "@/provider"
import { ProviderTransform } from "@/provider/transform" import { ProviderTransform } from "@/provider/transform"
import type { MessageV2 } from "./message-v2" import type { MessageV2 } from "."
const COMPACTION_BUFFER = 20_000 const COMPACTION_BUFFER = 20_000
+26 -28
View File
@@ -7,29 +7,28 @@ import { Permission } from "@/permission"
import { Plugin } from "@/plugin" import { Plugin } from "@/plugin"
import { Snapshot } from "@/snapshot" import { Snapshot } from "@/snapshot"
import { Session } from "." import { Session } from "."
import { LLM } from "./llm" import { LLM } from "."
import { MessageV2 } from "./message-v2" import { MessageV2 } from "."
import { isOverflow } from "./overflow" import { isOverflow } from "./overflow"
import { PartID } from "./schema" import { PartID } from "./schema"
import type { SessionID } from "./schema" import type { SessionID } from "./schema"
import { SessionRetry } from "./retry" import { SessionRetry } from "."
import { SessionStatus } from "./status" import { SessionStatus } from "."
import { SessionSummary } from "./summary" import { SessionSummary } from "."
import type { Provider } from "@/provider" import type { Provider } from "@/provider"
import { Question } from "@/question" import { Question } from "@/question"
import { errorMessage } from "@/util/error" import { errorMessage } from "@/util/error"
import { Log } from "@/util/log" import { Log } from "@/util/log"
import { isRecord } from "@/util/record" import { isRecord } from "@/util/record"
export namespace SessionProcessor { const DOOM_LOOP_THRESHOLD = 3
const DOOM_LOOP_THRESHOLD = 3 const log = Log.create({ service: "session.processor" })
const log = Log.create({ service: "session.processor" })
export type Result = "compact" | "stop" | "continue" export type Result = "compact" | "stop" | "continue"
export type Event = LLM.Event export type Event = LLM.Event
export interface Handle { export interface Handle {
readonly message: MessageV2.Assistant readonly message: MessageV2.Assistant
readonly updateToolCall: ( readonly updateToolCall: (
toolCallID: string, toolCallID: string,
@@ -45,26 +44,26 @@ export namespace SessionProcessor {
}, },
) => Effect.Effect<void> ) => Effect.Effect<void>
readonly process: (streamInput: LLM.StreamInput) => Effect.Effect<Result> readonly process: (streamInput: LLM.StreamInput) => Effect.Effect<Result>
} }
type Input = { type Input = {
assistantMessage: MessageV2.Assistant assistantMessage: MessageV2.Assistant
sessionID: SessionID sessionID: SessionID
model: Provider.Model model: Provider.Model
} }
export interface Interface { export interface Interface {
readonly create: (input: Input) => Effect.Effect<Handle> readonly create: (input: Input) => Effect.Effect<Handle>
} }
type ToolCall = { type ToolCall = {
partID: MessageV2.ToolPart["id"] partID: MessageV2.ToolPart["id"]
messageID: MessageV2.ToolPart["messageID"] messageID: MessageV2.ToolPart["messageID"]
sessionID: MessageV2.ToolPart["sessionID"] sessionID: MessageV2.ToolPart["sessionID"]
done: Deferred.Deferred<void> done: Deferred.Deferred<void>
} }
interface ProcessorContext extends Input { interface ProcessorContext extends Input {
toolcalls: Record<string, ToolCall> toolcalls: Record<string, ToolCall>
shouldBreak: boolean shouldBreak: boolean
snapshot: string | undefined snapshot: string | undefined
@@ -72,13 +71,13 @@ export namespace SessionProcessor {
needsCompaction: boolean needsCompaction: boolean
currentText: MessageV2.TextPart | undefined currentText: MessageV2.TextPart | undefined
reasoningMap: Record<string, MessageV2.ReasoningPart> reasoningMap: Record<string, MessageV2.ReasoningPart>
} }
type StreamEvent = Event type StreamEvent = Event
export class Service extends Context.Service<Service, Interface>()("@opencode/SessionProcessor") {} export class Service extends Context.Service<Service, Interface>()("@opencode/SessionProcessor") {}
export const layer: Layer.Layer< export const layer: Layer.Layer<
Service, Service,
never, never,
| Session.Service | Session.Service
@@ -91,7 +90,7 @@ export namespace SessionProcessor {
| Plugin.Service | Plugin.Service
| SessionSummary.Service | SessionSummary.Service
| SessionStatus.Service | SessionStatus.Service
> = Layer.effect( > = Layer.effect(
Service, Service,
Effect.gen(function* () { Effect.gen(function* () {
const session = yield* Session.Service const session = yield* Session.Service
@@ -600,9 +599,9 @@ export namespace SessionProcessor {
return Service.of({ create }) return Service.of({ create })
}), }),
) )
export const defaultLayer = Layer.suspend(() => export const defaultLayer = Layer.suspend(() =>
layer.pipe( layer.pipe(
Layer.provide(Session.defaultLayer), Layer.provide(Session.defaultLayer),
Layer.provide(Snapshot.defaultLayer), Layer.provide(Snapshot.defaultLayer),
@@ -615,5 +614,4 @@ export namespace SessionProcessor {
Layer.provide(Bus.layer), Layer.provide(Bus.layer),
Layer.provide(Config.defaultLayer), Layer.provide(Config.defaultLayer),
), ),
) )
}
+1 -1
View File
@@ -1,7 +1,7 @@
import { NotFoundError, eq, and } from "../storage/db" import { NotFoundError, eq, and } from "../storage/db"
import { SyncEvent } from "@/sync" import { SyncEvent } from "@/sync"
import { Session } from "." import { Session } from "."
import { MessageV2 } from "./message-v2" import { MessageV2 } from "."
import { SessionTable, MessageTable, PartTable } from "./session.sql" import { SessionTable, MessageTable, PartTable } from "./session.sql"
import { Log } from "../util/log" import { Log } from "../util/log"
+38 -40
View File
@@ -2,19 +2,19 @@ import path from "path"
import os from "os" import os from "os"
import z from "zod" import z from "zod"
import { SessionID, MessageID, PartID } from "./schema" import { SessionID, MessageID, PartID } from "./schema"
import { MessageV2 } from "./message-v2" import { MessageV2 } from "."
import { Log } from "../util/log" import { Log } from "../util/log"
import { SessionRevert } from "./revert" import { SessionRevert } from "."
import { Session } from "." import { Session } from "."
import { Agent } from "../agent/agent" import { Agent } from "../agent/agent"
import { Provider } from "../provider" import { Provider } from "../provider"
import { ModelID, ProviderID } from "../provider/schema" import { ModelID, ProviderID } from "../provider/schema"
import { type Tool as AITool, tool, jsonSchema, type ToolExecutionOptions, asSchema } from "ai" import { type Tool as AITool, tool, jsonSchema, type ToolExecutionOptions, asSchema } from "ai"
import { SessionCompaction } from "./compaction" import { SessionCompaction } from "."
import { Bus } from "../bus" import { Bus } from "../bus"
import { ProviderTransform } from "../provider/transform" import { ProviderTransform } from "../provider/transform"
import { SystemPrompt } from "./system" import { SystemPrompt } from "."
import { Instruction } from "./instruction" import { Instruction } from "."
import { Plugin } from "../plugin" import { Plugin } from "../plugin"
import PROMPT_PLAN from "../session/prompt/plan.txt" import PROMPT_PLAN from "../session/prompt/plan.txt"
import BUILD_SWITCH from "../session/prompt/build-switch.txt" import BUILD_SWITCH from "../session/prompt/build-switch.txt"
@@ -31,13 +31,13 @@ import * as Stream from "effect/Stream"
import { Command } from "../command" import { Command } from "../command"
import { pathToFileURL, fileURLToPath } from "url" import { pathToFileURL, fileURLToPath } from "url"
import { ConfigMarkdown } from "../config/markdown" import { ConfigMarkdown } from "../config/markdown"
import { SessionSummary } from "./summary" import { SessionSummary } from "."
import { NamedError } from "@opencode-ai/shared/util/error" import { NamedError } from "@opencode-ai/shared/util/error"
import { SessionProcessor } from "./processor" import { SessionProcessor } from "."
import { Tool } from "@/tool/tool" import { Tool } from "@/tool/tool"
import { Permission } from "@/permission" import { Permission } from "@/permission"
import { SessionStatus } from "./status" import { SessionStatus } from "."
import { LLM } from "./llm" import { LLM } from "."
import { Shell } from "@/shell/shell" import { Shell } from "@/shell/shell"
import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { AppFileSystem } from "@opencode-ai/shared/filesystem"
import { Truncate } from "@/tool/truncate" import { Truncate } from "@/tool/truncate"
@@ -47,7 +47,7 @@ import { Cause, Effect, Exit, Layer, Option, Scope, Context } from "effect"
import { EffectLogger } from "@/effect/logger" import { EffectLogger } from "@/effect/logger"
import { InstanceState } from "@/effect" import { InstanceState } from "@/effect"
import { TaskTool, type TaskPromptOps } from "@/tool/task" import { TaskTool, type TaskPromptOps } from "@/tool/task"
import { SessionRunState } from "./run-state" import { SessionRunState } from "."
import { EffectBridge } from "@/effect" import { EffectBridge } from "@/effect"
// @ts-ignore // @ts-ignore
@@ -63,22 +63,21 @@ IMPORTANT:
const STRUCTURED_OUTPUT_SYSTEM_PROMPT = `IMPORTANT: The user has requested structured output. You MUST use the StructuredOutput tool to provide your final response. Do NOT respond with plain text - you MUST call the StructuredOutput tool with your answer formatted according to the schema.` const STRUCTURED_OUTPUT_SYSTEM_PROMPT = `IMPORTANT: The user has requested structured output. You MUST use the StructuredOutput tool to provide your final response. Do NOT respond with plain text - you MUST call the StructuredOutput tool with your answer formatted according to the schema.`
export namespace SessionPrompt { const log = Log.create({ service: "session.prompt" })
const log = Log.create({ service: "session.prompt" }) const elog = EffectLogger.create({ service: "session.prompt" })
const elog = EffectLogger.create({ service: "session.prompt" })
export interface Interface { export interface Interface {
readonly cancel: (sessionID: SessionID) => Effect.Effect<void> readonly cancel: (sessionID: SessionID) => Effect.Effect<void>
readonly prompt: (input: PromptInput) => Effect.Effect<MessageV2.WithParts> readonly prompt: (input: PromptInput) => Effect.Effect<MessageV2.WithParts>
readonly loop: (input: z.infer<typeof LoopInput>) => Effect.Effect<MessageV2.WithParts> readonly loop: (input: z.infer<typeof LoopInput>) => Effect.Effect<MessageV2.WithParts>
readonly shell: (input: ShellInput) => Effect.Effect<MessageV2.WithParts> readonly shell: (input: ShellInput) => Effect.Effect<MessageV2.WithParts>
readonly command: (input: CommandInput) => Effect.Effect<MessageV2.WithParts> readonly command: (input: CommandInput) => Effect.Effect<MessageV2.WithParts>
readonly resolvePromptParts: (template: string) => Effect.Effect<PromptInput["parts"]> readonly resolvePromptParts: (template: string) => Effect.Effect<PromptInput["parts"]>
} }
export class Service extends Context.Service<Service, Interface>()("@opencode/SessionPrompt") {} export class Service extends Context.Service<Service, Interface>()("@opencode/SessionPrompt") {}
export const layer = Layer.effect( export const layer = Layer.effect(
Service, Service,
Effect.gen(function* () { Effect.gen(function* () {
const bus = yield* Bus.Service const bus = yield* Bus.Service
@@ -1677,9 +1676,9 @@ NOTE: At any point in time through this workflow you should feel free to ask the
resolvePromptParts, resolvePromptParts,
}) })
}), }),
) )
export const defaultLayer = Layer.suspend(() => export const defaultLayer = Layer.suspend(() =>
layer.pipe( layer.pipe(
Layer.provide(SessionRunState.defaultLayer), Layer.provide(SessionRunState.defaultLayer),
Layer.provide(SessionStatus.defaultLayer), Layer.provide(SessionStatus.defaultLayer),
@@ -1709,8 +1708,8 @@ NOTE: At any point in time through this workflow you should feel free to ask the
), ),
), ),
), ),
) )
export const PromptInput = z.object({ export const PromptInput = z.object({
sessionID: SessionID.zod, sessionID: SessionID.zod,
messageID: MessageID.zod.optional(), messageID: MessageID.zod.optional(),
model: z model: z
@@ -1774,14 +1773,14 @@ NOTE: At any point in time through this workflow you should feel free to ask the
}), }),
]), ]),
), ),
}) })
export type PromptInput = z.infer<typeof PromptInput> export type PromptInput = z.infer<typeof PromptInput>
export const LoopInput = z.object({ export const LoopInput = z.object({
sessionID: SessionID.zod, sessionID: SessionID.zod,
}) })
export const ShellInput = z.object({ export const ShellInput = z.object({
sessionID: SessionID.zod, sessionID: SessionID.zod,
messageID: MessageID.zod.optional(), messageID: MessageID.zod.optional(),
agent: z.string(), agent: z.string(),
@@ -1792,10 +1791,10 @@ NOTE: At any point in time through this workflow you should feel free to ask the
}) })
.optional(), .optional(),
command: z.string(), command: z.string(),
}) })
export type ShellInput = z.infer<typeof ShellInput> export type ShellInput = z.infer<typeof ShellInput>
export const CommandInput = z.object({ export const CommandInput = z.object({
messageID: MessageID.zod.optional(), messageID: MessageID.zod.optional(),
sessionID: SessionID.zod, sessionID: SessionID.zod,
agent: z.string().optional(), agent: z.string().optional(),
@@ -1815,14 +1814,14 @@ NOTE: At any point in time through this workflow you should feel free to ask the
]), ]),
) )
.optional(), .optional(),
}) })
export type CommandInput = z.infer<typeof CommandInput> export type CommandInput = z.infer<typeof CommandInput>
/** @internal Exported for testing */ /** @internal Exported for testing */
export function createStructuredOutputTool(input: { export function createStructuredOutputTool(input: {
schema: Record<string, any> schema: Record<string, any>
onSuccess: (output: unknown) => void onSuccess: (output: unknown) => void
}): AITool { }): AITool {
// Remove $schema property if present (not needed for tool input) // Remove $schema property if present (not needed for tool input)
const { $schema: _, ...toolSchema } = input.schema const { $schema: _, ...toolSchema } = input.schema
@@ -1846,10 +1845,9 @@ NOTE: At any point in time through this workflow you should feel free to ask the
} }
}, },
}) })
}
const bashRegex = /!`([^`]+)`/g
// Match [Image N] as single token, quoted strings, or non-space sequences
const argsRegex = /(?:\[Image\s+\d+\]|"[^"]*"|'[^']*'|[^\s"']+)/gi
const placeholderRegex = /\$(\d+)/g
const quoteTrimRegex = /^["']|["']$/g
} }
const bashRegex = /!`([^`]+)`/g
// Match [Image N] as single token, quoted strings, or non-space sequences
const argsRegex = /(?:\[Image\s+\d+\]|"[^"]*"|'[^']*'|[^\s"']+)/gi
const placeholderRegex = /\$(\d+)/g
const quoteTrimRegex = /^["']|["']$/g
+17 -19
View File
@@ -1,25 +1,24 @@
import type { NamedError } from "@opencode-ai/shared/util/error" import type { NamedError } from "@opencode-ai/shared/util/error"
import { Cause, Clock, Duration, Effect, Schedule } from "effect" import { Cause, Clock, Duration, Effect, Schedule } from "effect"
import { MessageV2 } from "./message-v2" import { MessageV2 } from "."
import { iife } from "@/util/iife" import { iife } from "@/util/iife"
export namespace SessionRetry { export type Err = ReturnType<NamedError["toObject"]>
export type Err = ReturnType<NamedError["toObject"]>
// This exported message is shared with the TUI upsell detector. Matching on a // This exported message is shared with the TUI upsell detector. Matching on a
// literal error string kind of sucks, but it is the simplest for now. // literal error string kind of sucks, but it is the simplest for now.
export const GO_UPSELL_MESSAGE = "Free usage exceeded, subscribe to Go https://opencode.ai/go" export const GO_UPSELL_MESSAGE = "Free usage exceeded, subscribe to Go https://opencode.ai/go"
export const RETRY_INITIAL_DELAY = 2000 export const RETRY_INITIAL_DELAY = 2000
export const RETRY_BACKOFF_FACTOR = 2 export const RETRY_BACKOFF_FACTOR = 2
export const RETRY_MAX_DELAY_NO_HEADERS = 30_000 // 30 seconds export const RETRY_MAX_DELAY_NO_HEADERS = 30_000 // 30 seconds
export const RETRY_MAX_DELAY = 2_147_483_647 // max 32-bit signed integer for setTimeout export const RETRY_MAX_DELAY = 2_147_483_647 // max 32-bit signed integer for setTimeout
function cap(ms: number) { function cap(ms: number) {
return Math.min(ms, RETRY_MAX_DELAY) return Math.min(ms, RETRY_MAX_DELAY)
} }
export function delay(attempt: number, error?: MessageV2.APIError) { export function delay(attempt: number, error?: MessageV2.APIError) {
if (error) { if (error) {
const headers = error.data.responseHeaders const headers = error.data.responseHeaders
if (headers) { if (headers) {
@@ -50,9 +49,9 @@ export namespace SessionRetry {
} }
return cap(Math.min(RETRY_INITIAL_DELAY * Math.pow(RETRY_BACKOFF_FACTOR, attempt - 1), RETRY_MAX_DELAY_NO_HEADERS)) return cap(Math.min(RETRY_INITIAL_DELAY * Math.pow(RETRY_BACKOFF_FACTOR, attempt - 1), RETRY_MAX_DELAY_NO_HEADERS))
} }
export function retryable(error: Err) { export function retryable(error: Err) {
// context overflow errors should not be retried // context overflow errors should not be retried
if (MessageV2.ContextOverflowError.isInstance(error)) return undefined if (MessageV2.ContextOverflowError.isInstance(error)) return undefined
if (MessageV2.APIError.isInstance(error)) { if (MessageV2.APIError.isInstance(error)) {
@@ -102,12 +101,12 @@ export namespace SessionRetry {
return "Rate Limited" return "Rate Limited"
} }
return undefined return undefined
} }
export function policy(opts: { export function policy(opts: {
parse: (error: unknown) => Err parse: (error: unknown) => Err
set: (input: { attempt: number; message: string; next: number }) => Effect.Effect<void> set: (input: { attempt: number; message: string; next: number }) => Effect.Effect<void>
}) { }) {
return Schedule.fromStepWithMetadata( return Schedule.fromStepWithMetadata(
Effect.succeed((meta: Schedule.InputMetadata<unknown>) => { Effect.succeed((meta: Schedule.InputMetadata<unknown>) => {
const error = opts.parse(meta.input) const error = opts.parse(meta.input)
@@ -121,5 +120,4 @@ export namespace SessionRetry {
}) })
}), }),
) )
}
} }
+14 -16
View File
@@ -6,30 +6,29 @@ import { Storage } from "@/storage/storage"
import { SyncEvent } from "../sync" import { SyncEvent } from "../sync"
import { Log } from "../util/log" import { Log } from "../util/log"
import { Session } from "." import { Session } from "."
import { MessageV2 } from "./message-v2" import { MessageV2 } from "."
import { SessionID, MessageID, PartID } from "./schema" import { SessionID, MessageID, PartID } from "./schema"
import { SessionRunState } from "./run-state" import { SessionRunState } from "."
import { SessionSummary } from "./summary" import { SessionSummary } from "."
export namespace SessionRevert { const log = Log.create({ service: "session.revert" })
const log = Log.create({ service: "session.revert" })
export const RevertInput = z.object({ export const RevertInput = z.object({
sessionID: SessionID.zod, sessionID: SessionID.zod,
messageID: MessageID.zod, messageID: MessageID.zod,
partID: PartID.zod.optional(), partID: PartID.zod.optional(),
}) })
export type RevertInput = z.infer<typeof RevertInput> export type RevertInput = z.infer<typeof RevertInput>
export interface Interface { export interface Interface {
readonly revert: (input: RevertInput) => Effect.Effect<Session.Info> readonly revert: (input: RevertInput) => Effect.Effect<Session.Info>
readonly unrevert: (input: { sessionID: SessionID }) => Effect.Effect<Session.Info> readonly unrevert: (input: { sessionID: SessionID }) => Effect.Effect<Session.Info>
readonly cleanup: (session: Session.Info) => Effect.Effect<void> readonly cleanup: (session: Session.Info) => Effect.Effect<void>
} }
export class Service extends Context.Service<Service, Interface>()("@opencode/SessionRevert") {} export class Service extends Context.Service<Service, Interface>()("@opencode/SessionRevert") {}
export const layer = Layer.effect( export const layer = Layer.effect(
Service, Service,
Effect.gen(function* () { Effect.gen(function* () {
const sessions = yield* Session.Service const sessions = yield* Session.Service
@@ -146,9 +145,9 @@ export namespace SessionRevert {
return Service.of({ revert, unrevert, cleanup }) return Service.of({ revert, unrevert, cleanup })
}), }),
) )
export const defaultLayer = Layer.suspend(() => export const defaultLayer = Layer.suspend(() =>
layer.pipe( layer.pipe(
Layer.provide(SessionRunState.defaultLayer), Layer.provide(SessionRunState.defaultLayer),
Layer.provide(Session.defaultLayer), Layer.provide(Session.defaultLayer),
@@ -157,5 +156,4 @@ export namespace SessionRevert {
Layer.provide(Bus.layer), Layer.provide(Bus.layer),
Layer.provide(SessionSummary.defaultLayer), Layer.provide(SessionSummary.defaultLayer),
), ),
) )
}
+8 -10
View File
@@ -2,12 +2,11 @@ import { InstanceState } from "@/effect"
import { Runner } from "@/effect/runner" import { Runner } from "@/effect/runner"
import { Effect, Layer, Scope, Context } from "effect" import { Effect, Layer, Scope, Context } from "effect"
import { Session } from "." import { Session } from "."
import { MessageV2 } from "./message-v2" import { MessageV2 } from "."
import { SessionID } from "./schema" import { SessionID } from "./schema"
import { SessionStatus } from "./status" import { SessionStatus } from "."
export namespace SessionRunState { export interface Interface {
export interface Interface {
readonly assertNotBusy: (sessionID: SessionID) => Effect.Effect<void> readonly assertNotBusy: (sessionID: SessionID) => Effect.Effect<void>
readonly cancel: (sessionID: SessionID) => Effect.Effect<void> readonly cancel: (sessionID: SessionID) => Effect.Effect<void>
readonly ensureRunning: ( readonly ensureRunning: (
@@ -20,11 +19,11 @@ export namespace SessionRunState {
onInterrupt: Effect.Effect<MessageV2.WithParts>, onInterrupt: Effect.Effect<MessageV2.WithParts>,
work: Effect.Effect<MessageV2.WithParts>, work: Effect.Effect<MessageV2.WithParts>,
) => Effect.Effect<MessageV2.WithParts> ) => Effect.Effect<MessageV2.WithParts>
} }
export class Service extends Context.Service<Service, Interface>()("@opencode/SessionRunState") {} export class Service extends Context.Service<Service, Interface>()("@opencode/SessionRunState") {}
export const layer = Layer.effect( export const layer = Layer.effect(
Service, Service,
Effect.gen(function* () { Effect.gen(function* () {
const status = yield* SessionStatus.Service const status = yield* SessionStatus.Service
@@ -102,7 +101,6 @@ export namespace SessionRunState {
return Service.of({ assertNotBusy, cancel, ensureRunning, startShell }) return Service.of({ assertNotBusy, cancel, ensureRunning, startShell })
}), }),
) )
export const defaultLayer = layer.pipe(Layer.provide(SessionStatus.defaultLayer)) export const defaultLayer = layer.pipe(Layer.provide(SessionStatus.defaultLayer))
}
+1 -1
View File
@@ -1,6 +1,6 @@
import { sqliteTable, text, integer, index, primaryKey } from "drizzle-orm/sqlite-core" import { sqliteTable, text, integer, index, primaryKey } from "drizzle-orm/sqlite-core"
import { ProjectTable } from "../project/project.sql" import { ProjectTable } from "../project/project.sql"
import type { MessageV2 } from "./message-v2" import type { MessageV2 } from "."
import type { SessionEntry } from "../v2/session-entry" import type { SessionEntry } from "../v2/session-entry"
import type { Snapshot } from "../snapshot" import type { Snapshot } from "../snapshot"
import type { Permission } from "../permission" import type { Permission } from "../permission"
+1 -1
View File
@@ -16,7 +16,7 @@ import { ProjectTable } from "../project/project.sql"
import { Storage } from "@/storage/storage" import { Storage } from "@/storage/storage"
import { Log } from "../util/log" import { Log } from "../util/log"
import { updateSchema } from "../util/update-schema" import { updateSchema } from "../util/update-schema"
import { MessageV2 } from "./message-v2" import { MessageV2 } from "."
import { Instance } from "../project/instance" import { Instance } from "../project/instance"
import { InstanceState } from "@/effect" import { InstanceState } from "@/effect"
import { Snapshot } from "@/snapshot" import { Snapshot } from "@/snapshot"
+10 -12
View File
@@ -5,8 +5,7 @@ import { SessionID } from "./schema"
import { Effect, Layer, Context } from "effect" import { Effect, Layer, Context } from "effect"
import z from "zod" import z from "zod"
export namespace SessionStatus { export const Info = z
export const Info = z
.union([ .union([
z.object({ z.object({
type: z.literal("idle"), type: z.literal("idle"),
@@ -24,9 +23,9 @@ export namespace SessionStatus {
.meta({ .meta({
ref: "SessionStatus", ref: "SessionStatus",
}) })
export type Info = z.infer<typeof Info> export type Info = z.infer<typeof Info>
export const Event = { export const Event = {
Status: BusEvent.define( Status: BusEvent.define(
"session.status", "session.status",
z.object({ z.object({
@@ -41,17 +40,17 @@ export namespace SessionStatus {
sessionID: SessionID.zod, sessionID: SessionID.zod,
}), }),
), ),
} }
export interface Interface { export interface Interface {
readonly get: (sessionID: SessionID) => Effect.Effect<Info> readonly get: (sessionID: SessionID) => Effect.Effect<Info>
readonly list: () => Effect.Effect<Map<SessionID, Info>> readonly list: () => Effect.Effect<Map<SessionID, Info>>
readonly set: (sessionID: SessionID, status: Info) => Effect.Effect<void> readonly set: (sessionID: SessionID, status: Info) => Effect.Effect<void>
} }
export class Service extends Context.Service<Service, Interface>()("@opencode/SessionStatus") {} export class Service extends Context.Service<Service, Interface>()("@opencode/SessionStatus") {}
export const layer = Layer.effect( export const layer = Layer.effect(
Service, Service,
Effect.gen(function* () { Effect.gen(function* () {
const bus = yield* Bus.Service const bus = yield* Bus.Service
@@ -82,7 +81,6 @@ export namespace SessionStatus {
return Service.of({ get, list, set }) return Service.of({ get, list, set })
}), }),
) )
export const defaultLayer = layer.pipe(Layer.provide(Bus.layer)) export const defaultLayer = layer.pipe(Layer.provide(Bus.layer))
}
+12 -14
View File
@@ -4,11 +4,10 @@ import { Bus } from "@/bus"
import { Snapshot } from "@/snapshot" import { Snapshot } from "@/snapshot"
import { Storage } from "@/storage/storage" import { Storage } from "@/storage/storage"
import { Session } from "." import { Session } from "."
import { MessageV2 } from "./message-v2" import { MessageV2 } from "."
import { SessionID, MessageID } from "./schema" import { SessionID, MessageID } from "./schema"
export namespace SessionSummary { function unquoteGitPath(input: string) {
function unquoteGitPath(input: string) {
if (!input.startsWith('"')) return input if (!input.startsWith('"')) return input
if (!input.endsWith('"')) return input if (!input.endsWith('"')) return input
const body = input.slice(1, -1) const body = input.slice(1, -1)
@@ -62,17 +61,17 @@ export namespace SessionSummary {
} }
return Buffer.from(bytes).toString() return Buffer.from(bytes).toString()
} }
export interface Interface { export interface Interface {
readonly summarize: (input: { sessionID: SessionID; messageID: MessageID }) => Effect.Effect<void> readonly summarize: (input: { sessionID: SessionID; messageID: MessageID }) => Effect.Effect<void>
readonly diff: (input: { sessionID: SessionID; messageID?: MessageID }) => Effect.Effect<Snapshot.FileDiff[]> readonly diff: (input: { sessionID: SessionID; messageID?: MessageID }) => Effect.Effect<Snapshot.FileDiff[]>
readonly computeDiff: (input: { messages: MessageV2.WithParts[] }) => Effect.Effect<Snapshot.FileDiff[]> readonly computeDiff: (input: { messages: MessageV2.WithParts[] }) => Effect.Effect<Snapshot.FileDiff[]>
} }
export class Service extends Context.Service<Service, Interface>()("@opencode/SessionSummary") {} export class Service extends Context.Service<Service, Interface>()("@opencode/SessionSummary") {}
export const layer = Layer.effect( export const layer = Layer.effect(
Service, Service,
Effect.gen(function* () { Effect.gen(function* () {
const sessions = yield* Session.Service const sessions = yield* Session.Service
@@ -147,19 +146,18 @@ export namespace SessionSummary {
return Service.of({ summarize, diff, computeDiff }) return Service.of({ summarize, diff, computeDiff })
}), }),
) )
export const defaultLayer = Layer.suspend(() => export const defaultLayer = Layer.suspend(() =>
layer.pipe( layer.pipe(
Layer.provide(Session.defaultLayer), Layer.provide(Session.defaultLayer),
Layer.provide(Snapshot.defaultLayer), Layer.provide(Snapshot.defaultLayer),
Layer.provide(Storage.defaultLayer), Layer.provide(Storage.defaultLayer),
Layer.provide(Bus.layer), Layer.provide(Bus.layer),
), ),
) )
export const DiffInput = z.object({ export const DiffInput = z.object({
sessionID: SessionID.zod, sessionID: SessionID.zod,
messageID: MessageID.zod.optional(), messageID: MessageID.zod.optional(),
}) })
}
+8 -10
View File
@@ -16,8 +16,7 @@ import type { Agent } from "@/agent/agent"
import { Permission } from "@/permission" import { Permission } from "@/permission"
import { Skill } from "@/skill" import { Skill } from "@/skill"
export namespace SystemPrompt { export function provider(model: Provider.Model) {
export function provider(model: Provider.Model) {
if (model.api.id.includes("gpt-4") || model.api.id.includes("o1") || model.api.id.includes("o3")) if (model.api.id.includes("gpt-4") || model.api.id.includes("o1") || model.api.id.includes("o3"))
return [PROMPT_BEAST] return [PROMPT_BEAST]
if (model.api.id.includes("gpt")) { if (model.api.id.includes("gpt")) {
@@ -31,16 +30,16 @@ export namespace SystemPrompt {
if (model.api.id.toLowerCase().includes("trinity")) return [PROMPT_TRINITY] if (model.api.id.toLowerCase().includes("trinity")) return [PROMPT_TRINITY]
if (model.api.id.toLowerCase().includes("kimi")) return [PROMPT_KIMI] if (model.api.id.toLowerCase().includes("kimi")) return [PROMPT_KIMI]
return [PROMPT_DEFAULT] return [PROMPT_DEFAULT]
} }
export interface Interface { export interface Interface {
readonly environment: (model: Provider.Model) => string[] readonly environment: (model: Provider.Model) => string[]
readonly skills: (agent: Agent.Info) => Effect.Effect<string | undefined> readonly skills: (agent: Agent.Info) => Effect.Effect<string | undefined>
} }
export class Service extends Context.Service<Service, Interface>()("@opencode/SystemPrompt") {} export class Service extends Context.Service<Service, Interface>()("@opencode/SystemPrompt") {}
export const layer = Layer.effect( export const layer = Layer.effect(
Service, Service,
Effect.gen(function* () { Effect.gen(function* () {
const skill = yield* Skill.Service const skill = yield* Skill.Service
@@ -78,7 +77,6 @@ export namespace SystemPrompt {
}), }),
}) })
}), }),
) )
export const defaultLayer = layer.pipe(Layer.provide(Skill.defaultLayer)) export const defaultLayer = layer.pipe(Layer.provide(Skill.defaultLayer))
}
+10 -12
View File
@@ -6,17 +6,16 @@ import z from "zod"
import { Database, eq, asc } from "../storage/db" import { Database, eq, asc } from "../storage/db"
import { TodoTable } from "./session.sql" import { TodoTable } from "./session.sql"
export namespace Todo { export const Info = z
export const Info = z
.object({ .object({
content: z.string().describe("Brief description of the task"), content: z.string().describe("Brief description of the task"),
status: z.string().describe("Current status of the task: pending, in_progress, completed, cancelled"), status: z.string().describe("Current status of the task: pending, in_progress, completed, cancelled"),
priority: z.string().describe("Priority level of the task: high, medium, low"), priority: z.string().describe("Priority level of the task: high, medium, low"),
}) })
.meta({ ref: "Todo" }) .meta({ ref: "Todo" })
export type Info = z.infer<typeof Info> export type Info = z.infer<typeof Info>
export const Event = { export const Event = {
Updated: BusEvent.define( Updated: BusEvent.define(
"todo.updated", "todo.updated",
z.object({ z.object({
@@ -24,16 +23,16 @@ export namespace Todo {
todos: z.array(Info), todos: z.array(Info),
}), }),
), ),
} }
export interface Interface { export interface Interface {
readonly update: (input: { sessionID: SessionID; todos: Info[] }) => Effect.Effect<void> readonly update: (input: { sessionID: SessionID; todos: Info[] }) => Effect.Effect<void>
readonly get: (sessionID: SessionID) => Effect.Effect<Info[]> readonly get: (sessionID: SessionID) => Effect.Effect<Info[]>
} }
export class Service extends Context.Service<Service, Interface>()("@opencode/SessionTodo") {} export class Service extends Context.Service<Service, Interface>()("@opencode/SessionTodo") {}
export const layer = Layer.effect( export const layer = Layer.effect(
Service, Service,
Effect.gen(function* () { Effect.gen(function* () {
const bus = yield* Bus.Service const bus = yield* Bus.Service
@@ -79,7 +78,6 @@ export namespace Todo {
return Service.of({ update, get }) return Service.of({ update, get })
}), }),
) )
export const defaultLayer = layer.pipe(Layer.provide(Bus.layer)) export const defaultLayer = layer.pipe(Layer.provide(Bus.layer))
}
+1 -1
View File
@@ -7,7 +7,7 @@ import { InstanceState } from "@/effect"
import { Provider } from "@/provider" import { Provider } from "@/provider"
import { ModelID, ProviderID } from "@/provider/schema" import { ModelID, ProviderID } from "@/provider/schema"
import { Session } from "@/session" import { Session } from "@/session"
import { MessageV2 } from "@/session/message-v2" import { MessageV2 } from "@/session"
import type { SessionID } from "@/session/schema" import type { SessionID } from "@/session/schema"
import { Database, eq } from "@/storage/db" import { Database, eq } from "@/storage/db"
import { Config } from "@/config" import { Config } from "@/config"
+1 -1
View File
@@ -4,7 +4,7 @@ import { Effect } from "effect"
import { Tool } from "./tool" import { Tool } from "./tool"
import { Question } from "../question" import { Question } from "../question"
import { Session } from "../session" import { Session } from "../session"
import { MessageV2 } from "../session/message-v2" import { MessageV2 } from "../session"
import { Provider } from "../provider" import { Provider } from "../provider"
import { Instance } from "../project/instance" import { Instance } from "../project/instance"
import { type SessionID, MessageID, PartID } from "../session/schema" import { type SessionID, MessageID, PartID } from "../session/schema"
+1 -1
View File
@@ -11,7 +11,7 @@ import { FileTime } from "../file/time"
import DESCRIPTION from "./read.txt" import DESCRIPTION from "./read.txt"
import { Instance } from "../project/instance" import { Instance } from "../project/instance"
import { assertExternalDirectoryEffect } from "./external-directory" import { assertExternalDirectoryEffect } from "./external-directory"
import { Instruction } from "../session/instruction" import { Instruction } from "../session"
const DEFAULT_READ_LIMIT = 2000 const DEFAULT_READ_LIMIT = 2000
const MAX_LINE_LENGTH = 2000 const MAX_LINE_LENGTH = 2000
+2 -2
View File
@@ -37,10 +37,10 @@ import { Ripgrep } from "../file/ripgrep"
import { Format } from "../format" import { Format } from "../format"
import { InstanceState } from "@/effect" import { InstanceState } from "@/effect"
import { Question } from "../question" import { Question } from "../question"
import { Todo } from "../session/todo" import { Todo } from "../session"
import { LSP } from "../lsp" import { LSP } from "../lsp"
import { FileTime } from "../file/time" import { FileTime } from "../file/time"
import { Instruction } from "../session/instruction" import { Instruction } from "../session"
import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { AppFileSystem } from "@opencode-ai/shared/filesystem"
import { Bus } from "../bus" import { Bus } from "../bus"
import { Agent } from "../agent/agent" import { Agent } from "../agent/agent"
+2 -2
View File
@@ -3,9 +3,9 @@ import DESCRIPTION from "./task.txt"
import z from "zod" import z from "zod"
import { Session } from "../session" import { Session } from "../session"
import { SessionID, MessageID } from "../session/schema" import { SessionID, MessageID } from "../session/schema"
import { MessageV2 } from "../session/message-v2" import { MessageV2 } from "../session"
import { Agent } from "../agent/agent" import { Agent } from "../agent/agent"
import type { SessionPrompt } from "../session/prompt" import type { SessionPrompt } from "../session"
import { Config } from "../config" import { Config } from "../config"
import { Effect } from "effect" import { Effect } from "effect"
+1 -1
View File
@@ -2,7 +2,7 @@ import z from "zod"
import { Effect } from "effect" import { Effect } from "effect"
import { Tool } from "./tool" import { Tool } from "./tool"
import DESCRIPTION_WRITE from "./todowrite.txt" import DESCRIPTION_WRITE from "./todowrite.txt"
import { Todo } from "../session/todo" import { Todo } from "../session"
const parameters = z.object({ const parameters = z.object({
todos: z.array(z.object(Todo.Info.shape)).describe("The updated todo list"), todos: z.array(z.object(Todo.Info.shape)).describe("The updated todo list"),
+1 -1
View File
@@ -1,6 +1,6 @@
import z from "zod" import z from "zod"
import { Effect } from "effect" import { Effect } from "effect"
import type { MessageV2 } from "../session/message-v2" import type { MessageV2 } from "../session"
import type { Permission } from "../permission" import type { Permission } from "../permission"
import type { SessionID, MessageID } from "../session/schema" import type { SessionID, MessageID } from "../session/schema"
import { Truncate } from "./truncate" import { Truncate } from "./truncate"
@@ -1,6 +1,6 @@
import { test, expect, describe } from "bun:test" import { test, expect, describe } from "bun:test"
import { extractResponseText, formatPromptTooLargeError } from "../../src/cli/cmd/github" import { extractResponseText, formatPromptTooLargeError } from "../../src/cli/cmd/github"
import type { MessageV2 } from "../../src/session/message-v2" import type { MessageV2 } from "../../src/session"
import { SessionID, MessageID, PartID } from "../../src/session/schema" import { SessionID, MessageID, PartID } from "../../src/session/schema"
// Helper to create minimal valid parts // Helper to create minimal valid parts
@@ -3,7 +3,7 @@ import { Effect } from "effect"
import { Instance } from "../../src/project/instance" import { Instance } from "../../src/project/instance"
import { Server } from "../../src/server/server" import { Server } from "../../src/server/server"
import { Session as SessionNs } from "../../src/session" import { Session as SessionNs } from "../../src/session"
import { MessageV2 } from "../../src/session/message-v2" import { MessageV2 } from "../../src/session"
import { MessageID, PartID, type SessionID } from "../../src/session/schema" import { MessageID, PartID, type SessionID } from "../../src/session/schema"
import { Log } from "../../src/util/log" import { Log } from "../../src/util/log"
import { tmpdir } from "../fixture/fixture" import { tmpdir } from "../fixture/fixture"
@@ -6,8 +6,8 @@ import z from "zod"
import { Bus } from "../../src/bus" import { Bus } from "../../src/bus"
import { Config } from "../../src/config" import { Config } from "../../src/config"
import { Agent } from "../../src/agent/agent" import { Agent } from "../../src/agent/agent"
import { LLM } from "../../src/session/llm" import { LLM } from "../../src/session"
import { SessionCompaction } from "../../src/session/compaction" import { SessionCompaction } from "../../src/session"
import { Token } from "../../src/util/token" import { Token } from "../../src/util/token"
import { Instance } from "../../src/project/instance" import { Instance } from "../../src/project/instance"
import { Log } from "../../src/util/log" import { Log } from "../../src/util/log"
@@ -15,13 +15,13 @@ import { Permission } from "../../src/permission"
import { Plugin } from "../../src/plugin" import { Plugin } from "../../src/plugin"
import { provideTmpdirInstance, tmpdir } from "../fixture/fixture" import { provideTmpdirInstance, tmpdir } from "../fixture/fixture"
import { Session as SessionNs } from "../../src/session" import { Session as SessionNs } from "../../src/session"
import { MessageV2 } from "../../src/session/message-v2" import { MessageV2 } from "../../src/session"
import { MessageID, PartID, SessionID } from "../../src/session/schema" import { MessageID, PartID, SessionID } from "../../src/session/schema"
import { SessionStatus } from "../../src/session/status" import { SessionStatus } from "../../src/session"
import { SessionSummary } from "../../src/session/summary" import { SessionSummary } from "../../src/session"
import { ModelID, ProviderID } from "../../src/provider/schema" import { ModelID, ProviderID } from "../../src/provider/schema"
import type { Provider } from "../../src/provider" import type { Provider } from "../../src/provider"
import * as SessionProcessorModule from "../../src/session/processor" import * as SessionProcessorModule from "../../src/session"
import { Snapshot } from "../../src/snapshot" import { Snapshot } from "../../src/snapshot"
import { ProviderTest } from "../fake/provider" import { ProviderTest } from "../fake/provider"
import { testEffect } from "../lib/effect" import { testEffect } from "../lib/effect"
@@ -2,8 +2,8 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test"
import path from "path" import path from "path"
import { Effect } from "effect" import { Effect } from "effect"
import { ModelID, ProviderID } from "../../src/provider/schema" import { ModelID, ProviderID } from "../../src/provider/schema"
import { Instruction } from "../../src/session/instruction" import { Instruction } from "../../src/session"
import type { MessageV2 } from "../../src/session/message-v2" import type { MessageV2 } from "../../src/session"
import { Instance } from "../../src/project/instance" import { Instance } from "../../src/project/instance"
import { MessageID, PartID, SessionID } from "../../src/session/schema" import { MessageID, PartID, SessionID } from "../../src/session/schema"
import { Global } from "../../src/global" import { Global } from "../../src/global"
+2 -2
View File
@@ -4,7 +4,7 @@ import { tool, type ModelMessage } from "ai"
import { Cause, Effect, Exit, Stream } from "effect" import { Cause, Effect, Exit, Stream } from "effect"
import z from "zod" import z from "zod"
import { makeRuntime } from "../../src/effect/run-service" import { makeRuntime } from "../../src/effect/run-service"
import { LLM } from "../../src/session/llm" import { LLM } from "../../src/session"
import { Instance } from "../../src/project/instance" import { Instance } from "../../src/project/instance"
import { Provider } from "../../src/provider" import { Provider } from "../../src/provider"
import { ProviderTransform } from "../../src/provider/transform" import { ProviderTransform } from "../../src/provider/transform"
@@ -13,7 +13,7 @@ import { ProviderID, ModelID } from "../../src/provider/schema"
import { Filesystem } from "../../src/util/filesystem" import { Filesystem } from "../../src/util/filesystem"
import { tmpdir } from "../fixture/fixture" import { tmpdir } from "../fixture/fixture"
import type { Agent } from "../../src/agent/agent" import type { Agent } from "../../src/agent/agent"
import { MessageV2 } from "../../src/session/message-v2" import { MessageV2 } from "../../src/session"
import { SessionID, MessageID } from "../../src/session/schema" import { SessionID, MessageID } from "../../src/session/schema"
import { AppRuntime } from "../../src/effect/app-runtime" import { AppRuntime } from "../../src/effect/app-runtime"
@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test" import { describe, expect, test } from "bun:test"
import { APICallError } from "ai" import { APICallError } from "ai"
import { MessageV2 } from "../../src/session/message-v2" import { MessageV2 } from "../../src/session"
import type { Provider } from "../../src/provider" import type { Provider } from "../../src/provider"
import { ModelID, ProviderID } from "../../src/provider/schema" import { ModelID, ProviderID } from "../../src/provider/schema"
import { SessionID, MessageID, PartID } from "../../src/session/schema" import { SessionID, MessageID, PartID } from "../../src/session/schema"
@@ -3,7 +3,7 @@ import { Effect } from "effect"
import path from "path" import path from "path"
import { Instance } from "../../src/project/instance" import { Instance } from "../../src/project/instance"
import { Session as SessionNs } from "../../src/session" import { Session as SessionNs } from "../../src/session"
import { MessageV2 } from "../../src/session/message-v2" import { MessageV2 } from "../../src/session"
import { MessageID, PartID, type SessionID } from "../../src/session/schema" import { MessageID, PartID, type SessionID } from "../../src/session/schema"
import { ModelID, ProviderID } from "../../src/provider/schema" import { ModelID, ProviderID } from "../../src/provider/schema"
import { Log } from "../../src/util/log" import { Log } from "../../src/util/log"
@@ -11,12 +11,12 @@ import { Plugin } from "../../src/plugin"
import { Provider } from "../../src/provider" import { Provider } from "../../src/provider"
import { ModelID, ProviderID } from "../../src/provider/schema" import { ModelID, ProviderID } from "../../src/provider/schema"
import { Session } from "../../src/session" import { Session } from "../../src/session"
import { LLM } from "../../src/session/llm" import { LLM } from "../../src/session"
import { MessageV2 } from "../../src/session/message-v2" import { MessageV2 } from "../../src/session"
import { SessionProcessor } from "../../src/session/processor" import { SessionProcessor } from "../../src/session"
import { MessageID, PartID, SessionID } from "../../src/session/schema" import { MessageID, PartID, SessionID } from "../../src/session/schema"
import { SessionStatus } from "../../src/session/status" import { SessionStatus } from "../../src/session"
import { SessionSummary } from "../../src/session/summary" import { SessionSummary } from "../../src/session"
import { Snapshot } from "../../src/snapshot" import { Snapshot } from "../../src/snapshot"
import { Log } from "../../src/util/log" import { Log } from "../../src/util/log"
import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
@@ -16,22 +16,22 @@ import { Provider as ProviderSvc } from "../../src/provider"
import { Env } from "../../src/env" import { Env } from "../../src/env"
import { ModelID, ProviderID } from "../../src/provider/schema" import { ModelID, ProviderID } from "../../src/provider/schema"
import { Question } from "../../src/question" import { Question } from "../../src/question"
import { Todo } from "../../src/session/todo" import { Todo } from "../../src/session"
import { Session } from "../../src/session" import { Session } from "../../src/session"
import { LLM } from "../../src/session/llm" import { LLM } from "../../src/session"
import { MessageV2 } from "../../src/session/message-v2" import { MessageV2 } from "../../src/session"
import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { AppFileSystem } from "@opencode-ai/shared/filesystem"
import { SessionCompaction } from "../../src/session/compaction" import { SessionCompaction } from "../../src/session"
import { SessionSummary } from "../../src/session/summary" import { SessionSummary } from "../../src/session"
import { Instruction } from "../../src/session/instruction" import { Instruction } from "../../src/session"
import { SessionProcessor } from "../../src/session/processor" import { SessionProcessor } from "../../src/session"
import { SessionPrompt } from "../../src/session/prompt" import { SessionPrompt } from "../../src/session"
import { SessionRevert } from "../../src/session/revert" import { SessionRevert } from "../../src/session"
import { SessionRunState } from "../../src/session/run-state" import { SessionRunState } from "../../src/session"
import { MessageID, PartID, SessionID } from "../../src/session/schema" import { MessageID, PartID, SessionID } from "../../src/session/schema"
import { SessionStatus } from "../../src/session/status" import { SessionStatus } from "../../src/session"
import { Skill } from "../../src/skill" import { Skill } from "../../src/skill"
import { SystemPrompt } from "../../src/session/system" import { SystemPrompt } from "../../src/session"
import { Shell } from "../../src/shell/shell" import { Shell } from "../../src/shell/shell"
import { Snapshot } from "../../src/snapshot" import { Snapshot } from "../../src/snapshot"
import { ToolRegistry } from "../../src/tool/registry" import { ToolRegistry } from "../../src/tool/registry"
@@ -6,8 +6,8 @@ import { Effect, Layer } from "effect"
import { Instance } from "../../src/project/instance" import { Instance } from "../../src/project/instance"
import { ModelID, ProviderID } from "../../src/provider/schema" import { ModelID, ProviderID } from "../../src/provider/schema"
import { Session } from "../../src/session" import { Session } from "../../src/session"
import { MessageV2 } from "../../src/session/message-v2" import { MessageV2 } from "../../src/session"
import { SessionPrompt } from "../../src/session/prompt" import { SessionPrompt } from "../../src/session"
import { Log } from "../../src/util/log" import { Log } from "../../src/util/log"
import { tmpdir } from "../fixture/fixture" import { tmpdir } from "../fixture/fixture"
+3 -3
View File
@@ -3,12 +3,12 @@ import type { NamedError } from "@opencode-ai/shared/util/error"
import { APICallError } from "ai" import { APICallError } from "ai"
import { setTimeout as sleep } from "node:timers/promises" import { setTimeout as sleep } from "node:timers/promises"
import { Effect, Schedule } from "effect" import { Effect, Schedule } from "effect"
import { SessionRetry } from "../../src/session/retry" import { SessionRetry } from "../../src/session"
import { MessageV2 } from "../../src/session/message-v2" import { MessageV2 } from "../../src/session"
import { ProviderID } from "../../src/provider/schema" import { ProviderID } from "../../src/provider/schema"
import { AppRuntime } from "../../src/effect/app-runtime" import { AppRuntime } from "../../src/effect/app-runtime"
import { SessionID } from "../../src/session/schema" import { SessionID } from "../../src/session/schema"
import { SessionStatus } from "../../src/session/status" import { SessionStatus } from "../../src/session"
import { Instance } from "../../src/project/instance" import { Instance } from "../../src/project/instance"
import { tmpdir } from "../fixture/fixture" import { tmpdir } from "../fixture/fixture"
@@ -4,8 +4,8 @@ import path from "path"
import { Effect, Layer } from "effect" import { Effect, Layer } from "effect"
import { Session } from "../../src/session" import { Session } from "../../src/session"
import { ModelID, ProviderID } from "../../src/provider/schema" import { ModelID, ProviderID } from "../../src/provider/schema"
import { SessionRevert } from "../../src/session/revert" import { SessionRevert } from "../../src/session"
import { MessageV2 } from "../../src/session/message-v2" import { MessageV2 } from "../../src/session"
import { Snapshot } from "../../src/snapshot" import { Snapshot } from "../../src/snapshot"
import { Log } from "../../src/util/log" import { Log } from "../../src/util/log"
import { MessageID, PartID, SessionID } from "../../src/session/schema" import { MessageID, PartID, SessionID } from "../../src/session/schema"
@@ -4,7 +4,7 @@ import { Session as SessionNs } from "../../src/session"
import { Bus } from "../../src/bus" import { Bus } from "../../src/bus"
import { Log } from "../../src/util/log" import { Log } from "../../src/util/log"
import { Instance } from "../../src/project/instance" import { Instance } from "../../src/project/instance"
import { MessageV2 } from "../../src/session/message-v2" import { MessageV2 } from "../../src/session"
import { MessageID, PartID, type SessionID } from "../../src/session/schema" import { MessageID, PartID, type SessionID } from "../../src/session/schema"
import { AppRuntime } from "../../src/effect/app-runtime" import { AppRuntime } from "../../src/effect/app-runtime"
import { tmpdir } from "../fixture/fixture" import { tmpdir } from "../fixture/fixture"
@@ -17,11 +17,11 @@ import { FetchHttpClient } from "effect/unstable/http"
import fs from "fs/promises" import fs from "fs/promises"
import path from "path" import path from "path"
import { Session } from "../../src/session" import { Session } from "../../src/session"
import { LLM } from "../../src/session/llm" import { LLM } from "../../src/session"
import { SessionPrompt } from "../../src/session/prompt" import { SessionPrompt } from "../../src/session"
import { SessionRevert } from "../../src/session/revert" import { SessionRevert } from "../../src/session"
import { SessionSummary } from "../../src/session/summary" import { SessionSummary } from "../../src/session"
import { MessageV2 } from "../../src/session/message-v2" import { MessageV2 } from "../../src/session"
import { Log } from "../../src/util/log" import { Log } from "../../src/util/log"
import { provideTmpdirServer } from "../fixture/fixture" import { provideTmpdirServer } from "../fixture/fixture"
import { testEffect } from "../lib/effect" import { testEffect } from "../lib/effect"
@@ -42,13 +42,13 @@ import { Provider as ProviderSvc } from "../../src/provider"
import { Env } from "../../src/env" import { Env } from "../../src/env"
import { Question } from "../../src/question" import { Question } from "../../src/question"
import { Skill } from "../../src/skill" import { Skill } from "../../src/skill"
import { SystemPrompt } from "../../src/session/system" import { SystemPrompt } from "../../src/session"
import { Todo } from "../../src/session/todo" import { Todo } from "../../src/session"
import { SessionCompaction } from "../../src/session/compaction" import { SessionCompaction } from "../../src/session"
import { Instruction } from "../../src/session/instruction" import { Instruction } from "../../src/session"
import { SessionProcessor } from "../../src/session/processor" import { SessionProcessor } from "../../src/session"
import { SessionRunState } from "../../src/session/run-state" import { SessionRunState } from "../../src/session"
import { SessionStatus } from "../../src/session/status" import { SessionStatus } from "../../src/session"
import { Snapshot } from "../../src/snapshot" import { Snapshot } from "../../src/snapshot"
import { ToolRegistry } from "../../src/tool/registry" import { ToolRegistry } from "../../src/tool/registry"
import { Truncate } from "../../src/tool/truncate" import { Truncate } from "../../src/tool/truncate"
@@ -2,10 +2,10 @@ import { describe, expect, test } from "bun:test"
import path from "path" import path from "path"
import { Effect, Layer } from "effect" import { Effect, Layer } from "effect"
import { Session } from "../../src/session" import { Session } from "../../src/session"
import { SessionPrompt } from "../../src/session/prompt" import { SessionPrompt } from "../../src/session"
import { Log } from "../../src/util/log" import { Log } from "../../src/util/log"
import { Instance } from "../../src/project/instance" import { Instance } from "../../src/project/instance"
import { MessageV2 } from "../../src/session/message-v2" import { MessageV2 } from "../../src/session"
const projectRoot = path.join(__dirname, "../..") const projectRoot = path.join(__dirname, "../..")
Log.init({ print: false }) Log.init({ print: false })
@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test" import { describe, expect, test } from "bun:test"
import { MessageV2 } from "../../src/session/message-v2" import { MessageV2 } from "../../src/session"
import { SessionPrompt } from "../../src/session/prompt" import { SessionPrompt } from "../../src/session"
import { SessionID, MessageID } from "../../src/session/schema" import { SessionID, MessageID } from "../../src/session/schema"
describe("structured-output.OutputFormat", () => { describe("structured-output.OutputFormat", () => {
@@ -3,7 +3,7 @@ import path from "path"
import { Effect } from "effect" import { Effect } from "effect"
import { Agent } from "../../src/agent/agent" import { Agent } from "../../src/agent/agent"
import { Instance } from "../../src/project/instance" import { Instance } from "../../src/project/instance"
import { SystemPrompt } from "../../src/session/system" import { SystemPrompt } from "../../src/session"
import { provideInstance, tmpdir } from "../fixture/fixture" import { provideInstance, tmpdir } from "../fixture/fixture"
function load<A>(dir: string, fn: (svc: Agent.Interface) => Effect.Effect<A>) { function load<A>(dir: string, fn: (svc: Agent.Interface) => Effect.Effect<A>) {
+1 -1
View File
@@ -9,7 +9,7 @@ import { LSP } from "../../src/lsp"
import { Permission } from "../../src/permission" import { Permission } from "../../src/permission"
import { Instance } from "../../src/project/instance" import { Instance } from "../../src/project/instance"
import { SessionID, MessageID } from "../../src/session/schema" import { SessionID, MessageID } from "../../src/session/schema"
import { Instruction } from "../../src/session/instruction" import { Instruction } from "../../src/session"
import { ReadTool } from "../../src/tool/read" import { ReadTool } from "../../src/tool/read"
import { Truncate } from "../../src/tool/truncate" import { Truncate } from "../../src/tool/truncate"
import { Tool } from "../../src/tool/tool" import { Tool } from "../../src/tool/tool"
+2 -2
View File
@@ -5,8 +5,8 @@ import { Config } from "../../src/config"
import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
import { Instance } from "../../src/project/instance" import { Instance } from "../../src/project/instance"
import { Session } from "../../src/session" import { Session } from "../../src/session"
import { MessageV2 } from "../../src/session/message-v2" import { MessageV2 } from "../../src/session"
import type { SessionPrompt } from "../../src/session/prompt" import type { SessionPrompt } from "../../src/session"
import { MessageID, PartID } from "../../src/session/schema" import { MessageID, PartID } from "../../src/session/schema"
import { ModelID, ProviderID } from "../../src/provider/schema" import { ModelID, ProviderID } from "../../src/provider/schema"
import { TaskTool, type TaskPromptOps } from "../../src/tool/task" import { TaskTool, type TaskPromptOps } from "../../src/tool/task"