chore: effect pattern lint infrastructure (#35210)
This commit is contained in:
parent
1b88ff8d53
commit
1401901529
70 changed files with 524 additions and 234 deletions
|
|
@ -2,10 +2,8 @@ export * as SessionCompaction from "./compaction"
|
|||
|
||||
import { LLM, LLMClient, LLMError, LLMEvent, Message, type LLMRequest, type Model } from "@opencode-ai/llm"
|
||||
import { Context, DateTime, Effect, Layer, Stream } from "effect"
|
||||
import type { Config } from "../config"
|
||||
import { Config as ConfigV2 } from "../config"
|
||||
import type { EventV2 } from "../event"
|
||||
import { EventV2 as EventV2Service } from "../event"
|
||||
import { Config } from "../config"
|
||||
import { EventV2 } from "../event"
|
||||
import { makeLocationNode } from "../effect/app-node"
|
||||
import { llmClient } from "../effect/app-node-platform"
|
||||
import { SessionEvent } from "./event"
|
||||
|
|
@ -311,9 +309,9 @@ const make = (dependencies: Dependencies) => {
|
|||
export const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const events = yield* EventV2Service.Service
|
||||
const events = yield* EventV2.Service
|
||||
const llm = yield* LLMClient.Service
|
||||
const config = yield* ConfigV2.Service
|
||||
const config = yield* Config.Service
|
||||
const models = yield* SessionRunnerModel.Service
|
||||
const compaction = make({ events, llm, config: yield* config.entries() })
|
||||
|
||||
|
|
@ -336,5 +334,5 @@ export const layer = Layer.effect(
|
|||
export const node = makeLocationNode({
|
||||
service: Service,
|
||||
layer,
|
||||
deps: [EventV2Service.node, llmClient, ConfigV2.node, SessionRunnerModel.node],
|
||||
deps: [EventV2.node, llmClient, Config.node, SessionRunnerModel.node],
|
||||
})
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ const rewrite = Effect.fnUntraced(function* (
|
|||
.returning({ sessionID: SessionContextCheckpointTable.session_id })
|
||||
.get()
|
||||
.pipe(Effect.orDie)
|
||||
if (!updated) return yield* Effect.die("Context checkpoint not found")
|
||||
if (!updated) return yield* Effect.die(new Error("Context checkpoint not found"))
|
||||
})
|
||||
|
||||
const advance = Effect.fnUntraced(function* (
|
||||
|
|
@ -127,5 +127,5 @@ const advance = Effect.fnUntraced(function* (
|
|||
.returning({ sessionID: SessionContextCheckpointTable.session_id })
|
||||
.get()
|
||||
.pipe(Effect.orDie)
|
||||
if (!updated) return yield* Effect.die("Context checkpoint not found")
|
||||
if (!updated) return yield* Effect.die(new Error("Context checkpoint not found"))
|
||||
})
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ const layer = Layer.effect(
|
|||
const coordinator = yield* SessionRunCoordinator.make<SessionSchema.ID, SessionRunner.RunError>({
|
||||
drain: Effect.fnUntraced(function* (sessionID: SessionSchema.ID, force) {
|
||||
const session = yield* store.get(sessionID)
|
||||
if (!session) return yield* Effect.die(`Session not found: ${sessionID}`)
|
||||
if (!session) return yield* Effect.die(new Error(`Session not found: ${sessionID}`))
|
||||
return yield* SessionRunner.Service.use((runner) => runner.run({ sessionID, force })).pipe(
|
||||
Effect.provide(locations.get(session.location)),
|
||||
Effect.tapCause((cause) =>
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ export const admit = Effect.fn("SessionInput.admit")(function* (
|
|||
.pipe(
|
||||
Effect.flatMap((event) =>
|
||||
event.durable === undefined
|
||||
? Effect.die("Prompt admission event is missing aggregate sequence")
|
||||
? Effect.die(new Error("Prompt admission event is missing aggregate sequence"))
|
||||
: Effect.succeed(
|
||||
Admitted.make({
|
||||
admittedSeq: event.durable.seq,
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ const projectFork = Effect.fn("SessionProjector.projectFork")(function* (
|
|||
.where(eq(SessionTable.id, event.data.parentID))
|
||||
.get()
|
||||
.pipe(Effect.orDie)
|
||||
if (!parent) return yield* Effect.die(`Fork parent session not found: ${event.data.parentID}`)
|
||||
if (!parent) return yield* Effect.die(new Error(`Fork parent session not found: ${event.data.parentID}`))
|
||||
const boundary = event.data.messageID
|
||||
? yield* db
|
||||
.select({ seq: SessionMessageTable.seq })
|
||||
|
|
@ -172,7 +172,7 @@ const projectFork = Effect.fn("SessionProjector.projectFork")(function* (
|
|||
.pipe(Effect.orDie)
|
||||
: undefined
|
||||
if (event.data.messageID && !boundary)
|
||||
return yield* Effect.die(`Fork boundary message not found: ${event.data.messageID}`)
|
||||
return yield* Effect.die(new Error(`Fork boundary message not found: ${event.data.messageID}`))
|
||||
const copied = yield* db
|
||||
.select({ seq: SessionMessageTable.seq })
|
||||
.from(SessionMessageTable)
|
||||
|
|
@ -341,7 +341,8 @@ function run(db: DatabaseService, event: MessageEvent) {
|
|||
const decodeRow = (row: typeof SessionMessageTable.$inferSelect) =>
|
||||
decodeMessage({ ...row.data, id: row.id, type: row.type })
|
||||
const updateMessage = (message: SessionMessage.Message) => {
|
||||
if (event.durable === undefined) return Effect.die("Durable Session event is missing aggregate sequence")
|
||||
if (event.durable === undefined)
|
||||
return Effect.die(new Error("Durable Session event is missing aggregate sequence"))
|
||||
const encoded = encodeMessage(message)
|
||||
const { id, type, ...data } = encoded
|
||||
return db
|
||||
|
|
@ -418,7 +419,7 @@ function run(db: DatabaseService, event: MessageEvent) {
|
|||
}
|
||||
|
||||
function insertMessage(db: DatabaseService, event: SessionEvent.DurableEvent, message: SessionMessage.Message) {
|
||||
if (event.durable === undefined) return Effect.die("Durable Session event is missing aggregate sequence")
|
||||
if (event.durable === undefined) return Effect.die(new Error("Durable Session event is missing aggregate sequence"))
|
||||
const encoded = encodeMessage(message)
|
||||
const { id, type, ...data } = encoded
|
||||
return db
|
||||
|
|
@ -585,7 +586,8 @@ const layer = Layer.effectDiscard(
|
|||
yield* events.project(SessionEvent.Forked, (event) => projectFork(db, event))
|
||||
yield* events.project(SessionEvent.Prompted, (event) =>
|
||||
Effect.gen(function* () {
|
||||
if (event.durable === undefined) return yield* Effect.die("Durable Session event is missing aggregate sequence")
|
||||
if (event.durable === undefined)
|
||||
return yield* Effect.die(new Error("Durable Session event is missing aggregate sequence"))
|
||||
yield* SessionInput.projectPrompted(db, {
|
||||
id: event.data.messageID,
|
||||
sessionID: event.data.sessionID,
|
||||
|
|
@ -599,7 +601,8 @@ const layer = Layer.effectDiscard(
|
|||
)
|
||||
yield* events.project(SessionEvent.PromptAdmitted, (event) =>
|
||||
Effect.gen(function* () {
|
||||
if (event.durable === undefined) return yield* Effect.die("Durable Session event is missing aggregate sequence")
|
||||
if (event.durable === undefined)
|
||||
return yield* Effect.die(new Error("Durable Session event is missing aggregate sequence"))
|
||||
yield* SessionInput.projectAdmitted(db, {
|
||||
admittedSeq: event.durable.seq,
|
||||
id: event.data.messageID,
|
||||
|
|
@ -670,7 +673,7 @@ const layer = Layer.effectDiscard(
|
|||
)
|
||||
.get()
|
||||
.pipe(Effect.orDie)
|
||||
if (!boundary) return yield* Effect.die(`Revert boundary message not found: ${event.data.messageID}`)
|
||||
if (!boundary) return yield* Effect.die(new Error(`Revert boundary message not found: ${event.data.messageID}`))
|
||||
yield* db
|
||||
.delete(SessionMessageTable)
|
||||
.where(
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
export * as SessionRunnerLLM from "./llm"
|
||||
|
||||
import {
|
||||
LLM,
|
||||
LLMClient,
|
||||
|
|
@ -119,7 +121,7 @@ const layer = Layer.effect(
|
|||
const forkTitle = yield* FiberSet.makeRuntime<never, void, never>()
|
||||
const getSession = Effect.fn("SessionRunner.getSession")(function* (sessionID: SessionSchema.ID) {
|
||||
const session = yield* store.get(sessionID)
|
||||
if (!session) return yield* Effect.die(`Session not found: ${sessionID}`)
|
||||
if (!session) return yield* Effect.die(new Error(`Session not found: ${sessionID}`))
|
||||
return session
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,11 @@ export * as SessionRunnerModel from "./model"
|
|||
|
||||
import { makeLocationNode } from "../../effect/app-node"
|
||||
import { type Model } from "@opencode-ai/llm"
|
||||
// ast-grep-ignore: no-star-import
|
||||
import * as AnthropicMessages from "@opencode-ai/llm/protocols/anthropic-messages"
|
||||
// ast-grep-ignore: no-star-import
|
||||
import * as OpenAICompatibleChat from "@opencode-ai/llm/protocols/openai-compatible-chat"
|
||||
// ast-grep-ignore: no-star-import
|
||||
import * as OpenAIResponses from "@opencode-ai/llm/protocols/openai-responses"
|
||||
import { Auth, type AnyRoute } from "@opencode-ai/llm/route"
|
||||
import { Context, Effect, Layer, Schema } from "effect"
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
|||
})
|
||||
const currentAssistantMessageID = () =>
|
||||
assistantMessageID === undefined
|
||||
? Effect.die("Tool event before assistant step start")
|
||||
? Effect.die(new Error("Tool event before assistant step start"))
|
||||
: Effect.succeed(assistantMessageID)
|
||||
|
||||
const fragments = (
|
||||
|
|
@ -95,20 +95,20 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
|||
const chunks = new Map<string, string[]>()
|
||||
const start = (id: string) =>
|
||||
Effect.suspend(() => {
|
||||
if (chunks.has(id)) return Effect.die(`Duplicate ${name} start: ${id}`)
|
||||
if (chunks.has(id)) return Effect.die(new Error(`Duplicate ${name} start: ${id}`))
|
||||
chunks.set(id, [])
|
||||
return Effect.void
|
||||
})
|
||||
const append = (id: string, value: string) =>
|
||||
Effect.suspend(() => {
|
||||
const current = chunks.get(id)
|
||||
if (!current) return Effect.die(`${name} delta before start: ${id}`)
|
||||
if (!current) return Effect.die(new Error(`${name} delta before start: ${id}`))
|
||||
current.push(value)
|
||||
return Effect.void
|
||||
})
|
||||
const end = Effect.fnUntraced(function* (id: string, providerMetadata?: ProviderMetadata) {
|
||||
const current = chunks.get(id)
|
||||
if (!current) return yield* Effect.die(`${name} end before start: ${id}`)
|
||||
if (!current) return yield* Effect.die(new Error(`${name} end before start: ${id}`))
|
||||
yield* ended(id, current.join(""), providerMetadata)
|
||||
chunks.delete(id)
|
||||
})
|
||||
|
|
@ -144,7 +144,7 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
|||
const toolInput = fragments("tool input", (callID, value) =>
|
||||
Effect.gen(function* () {
|
||||
const tool = tools.get(callID)
|
||||
if (!tool) return yield* Effect.die(`Tool input end before start: ${callID}`)
|
||||
if (!tool) return yield* Effect.die(new Error(`Tool input end before start: ${callID}`))
|
||||
yield* events.publish(SessionEvent.Tool.Input.Ended, {
|
||||
sessionID: input.sessionID,
|
||||
timestamp: yield* timestamp,
|
||||
|
|
@ -163,7 +163,7 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
|||
})
|
||||
|
||||
const startToolInput = Effect.fnUntraced(function* (event: { readonly id: string; readonly name: string }) {
|
||||
if (tools.has(event.id)) return yield* Effect.die(`Duplicate tool input start: ${event.id}`)
|
||||
if (tools.has(event.id)) return yield* Effect.die(new Error(`Duplicate tool input start: ${event.id}`))
|
||||
const assistantMessageID = yield* startAssistant()
|
||||
tools.set(event.id, {
|
||||
assistantMessageID,
|
||||
|
|
@ -185,10 +185,10 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
|||
|
||||
const endToolInput = Effect.fnUntraced(function* (event: { readonly id: string; readonly name: string }) {
|
||||
const tool = tools.get(event.id)
|
||||
if (!tool) return yield* Effect.die(`Tool input end before start: ${event.id}`)
|
||||
if (!tool) return yield* Effect.die(new Error(`Tool input end before start: ${event.id}`))
|
||||
if (tool.name !== event.name)
|
||||
return yield* Effect.die(`Tool input name changed for ${event.id}: ${tool.name} -> ${event.name}`)
|
||||
if (tool.inputEnded) return yield* Effect.die(`Duplicate tool input end: ${event.id}`)
|
||||
return yield* Effect.die(new Error(`Tool input name changed for ${event.id}: ${tool.name} -> ${event.name}`))
|
||||
if (tool.inputEnded) return yield* Effect.die(new Error(`Duplicate tool input end: ${event.id}`))
|
||||
yield* toolInput.end(event.id)
|
||||
})
|
||||
|
||||
|
|
@ -233,7 +233,7 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
|||
|
||||
const assistantMessageIDForTool = (callID: string) => {
|
||||
const tool = tools.get(callID)
|
||||
return tool ? Effect.succeed(tool.assistantMessageID) : Effect.die(`Unknown tool call: ${callID}`)
|
||||
return tool ? Effect.succeed(tool.assistantMessageID) : Effect.die(new Error(`Unknown tool call: ${callID}`))
|
||||
}
|
||||
|
||||
const publish = Effect.fn("SessionRunner.publishLLMEvent")(function* (
|
||||
|
|
@ -293,10 +293,10 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
|||
return
|
||||
case "tool-input-delta": {
|
||||
const tool = tools.get(event.id)
|
||||
if (!tool) return yield* Effect.die(`Tool input delta before start: ${event.id}`)
|
||||
if (!tool) return yield* Effect.die(new Error(`Tool input delta before start: ${event.id}`))
|
||||
if (tool.name !== event.name)
|
||||
return yield* Effect.die(`Tool input name changed for ${event.id}: ${tool.name} -> ${event.name}`)
|
||||
if (tool.inputEnded) return yield* Effect.die(`Tool input delta after end: ${event.id}`)
|
||||
return yield* Effect.die(new Error(`Tool input name changed for ${event.id}: ${tool.name} -> ${event.name}`))
|
||||
if (tool.inputEnded) return yield* Effect.die(new Error(`Tool input delta after end: ${event.id}`))
|
||||
yield* toolInput.append(event.id, event.text)
|
||||
yield* events.publish(SessionEvent.Tool.Input.Delta, {
|
||||
sessionID: input.sessionID,
|
||||
|
|
@ -315,8 +315,8 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
|||
const tool = tools.get(event.id)!
|
||||
if (!tool.inputEnded) yield* endToolInput(event)
|
||||
if (tool.name !== event.name)
|
||||
return yield* Effect.die(`Tool call name changed for ${event.id}: ${tool.name} -> ${event.name}`)
|
||||
if (tool.called) return yield* Effect.die(`Duplicate tool call: ${event.id}`)
|
||||
return yield* Effect.die(new Error(`Tool call name changed for ${event.id}: ${tool.name} -> ${event.name}`))
|
||||
if (tool.called) return yield* Effect.die(new Error(`Duplicate tool call: ${event.id}`))
|
||||
tool.called = true
|
||||
tool.providerExecuted = event.providerExecuted === true
|
||||
tool.providerMetadata = event.providerMetadata
|
||||
|
|
@ -336,12 +336,12 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
|||
}
|
||||
case "tool-result": {
|
||||
const tool = tools.get(event.id)
|
||||
if (!tool?.called) return yield* Effect.die(`Tool result before call: ${event.id}`)
|
||||
if (!tool?.called) return yield* Effect.die(new Error(`Tool result before call: ${event.id}`))
|
||||
if (tool.name !== event.name)
|
||||
return yield* Effect.die(`Tool result name changed for ${event.id}: ${tool.name} -> ${event.name}`)
|
||||
return yield* Effect.die(new Error(`Tool result name changed for ${event.id}: ${tool.name} -> ${event.name}`))
|
||||
if (tool.settled) {
|
||||
if (event.result.type === "error") return
|
||||
return yield* Effect.die(`Duplicate tool result: ${event.id}`)
|
||||
return yield* Effect.die(new Error(`Duplicate tool result: ${event.id}`))
|
||||
}
|
||||
tool.settled = true
|
||||
const result = settledOutput(event.output, event.result)
|
||||
|
|
@ -375,10 +375,10 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
|||
}
|
||||
case "tool-error": {
|
||||
const tool = tools.get(event.id)
|
||||
if (!tool?.called) return yield* Effect.die(`Tool error before call: ${event.id}`)
|
||||
if (!tool?.called) return yield* Effect.die(new Error(`Tool error before call: ${event.id}`))
|
||||
if (tool.name !== event.name)
|
||||
return yield* Effect.die(`Tool error name changed for ${event.id}: ${tool.name} -> ${event.name}`)
|
||||
if (tool.settled) return yield* Effect.die(`Duplicate tool error: ${event.id}`)
|
||||
return yield* Effect.die(new Error(`Tool error name changed for ${event.id}: ${tool.name} -> ${event.name}`))
|
||||
if (tool.settled) return yield* Effect.die(new Error(`Duplicate tool error: ${event.id}`))
|
||||
tool.settled = true
|
||||
yield* events.publish(SessionEvent.Tool.Failed, {
|
||||
sessionID: input.sessionID,
|
||||
|
|
@ -396,7 +396,7 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
|||
case "step-finish":
|
||||
yield* flush()
|
||||
assistantActive = false
|
||||
if (stepSettlement) return yield* Effect.die("Duplicate step finish")
|
||||
if (stepSettlement) return yield* Effect.die(new Error("Duplicate step finish"))
|
||||
stepSettlement = { finish: event.reason, tokens: tokens(event.usage) }
|
||||
return
|
||||
case "finish":
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { sqliteTable, text, integer, index, primaryKey, real, uniqueIndex } from "drizzle-orm/sqlite-core"
|
||||
import * as DatabasePath from "../database/path"
|
||||
import { directoryColumn, pathColumn } from "../database/path"
|
||||
import { ProjectTable } from "../project/sql"
|
||||
import type { SessionMessage } from "./message"
|
||||
import type { Prompt } from "./prompt"
|
||||
|
|
@ -31,8 +31,8 @@ export const SessionTable = sqliteTable(
|
|||
workspace_id: text().$type<WorkspaceV2.ID>(),
|
||||
parent_id: text().$type<SessionSchema.ID>(),
|
||||
slug: text().notNull(),
|
||||
directory: DatabasePath.directoryColumn().notNull(),
|
||||
path: DatabasePath.pathColumn(),
|
||||
directory: directoryColumn().notNull(),
|
||||
path: pathColumn(),
|
||||
title: text().notNull(),
|
||||
version: text().notNull(),
|
||||
share_url: text(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue