From 39daec9bec28df4e0dbf88101e1ffd86d90d1319 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Fri, 28 Aug 2026 13:05:16 -0400 Subject: [PATCH] refactor(core): narrow session internals (#45638) --- packages/core/src/session.ts | 6 +-- packages/core/src/session/info.ts | 14 +++---- packages/core/src/session/message-updater.ts | 40 +++++++++----------- packages/core/src/session/projector.ts | 28 +++++++------- packages/core/src/session/sql.ts | 6 +-- packages/core/src/session/stats.ts | 13 +++---- packages/core/src/session/usage.ts | 2 +- 7 files changed, 52 insertions(+), 57 deletions(-) diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index ba2f1b0fa4d..b80e575b357 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -5,8 +5,8 @@ import { Cause, Effect, Layer, Schema, Context, RcMap, Stream, Scope } from "eff import { ListAnchor } from "@opencode-ai/schema/session" import { and, asc, desc, eq, gt, isNull, like, lt, or, type SQL } from "drizzle-orm" import { Project } from "./project.js" -import { Workspace } from "./workspace.js" -import { Model } from "./model.js" +import { Workspace } from "@opencode-ai/schema/workspace" +import { Model } from "@opencode-ai/schema/model" import { Location } from "./location.js" import { SessionMessage } from "./session/message.js" import { Base64, FileAttachment, Prompt } from "@opencode-ai/schema/prompt" @@ -17,7 +17,7 @@ import { SessionProjector } from "./session/projector.js" import { SessionMessageTable, SessionTable } from "./session/sql.js" import { SessionSchema } from "./session/schema.js" import { AbsolutePath, PositiveInt, RelativePath } from "./schema.js" -import { Agent } from "./agent.js" +import { Agent } from "@opencode-ai/schema/agent" import { Money } from "@opencode-ai/schema/money" import { App } from "./app.js" import { Slug } from "./util/slug.js" diff --git a/packages/core/src/session/info.ts b/packages/core/src/session/info.ts index 06145b76b8f..d5fe96885fa 100644 --- a/packages/core/src/session/info.ts +++ b/packages/core/src/session/info.ts @@ -1,13 +1,13 @@ import { DateTime, Schema } from "effect" -import { Agent } from "../agent.js" -import { Location } from "../location.js" -import { Model } from "../model.js" -import { Project } from "../project.js" -import { Provider } from "../provider.js" +import { Agent } from "@opencode-ai/schema/agent" +import { Location } from "@opencode-ai/schema/location" +import { Model } from "@opencode-ai/schema/model" +import { Project } from "@opencode-ai/schema/project" +import { Provider } from "@opencode-ai/schema/provider" import { AbsolutePath, RelativePath } from "../schema.js" -import { Workspace } from "../workspace.js" +import { Workspace } from "@opencode-ai/schema/workspace" import { SessionSchema } from "./schema.js" -import { SessionTable } from "./sql.js" +import type { SessionTable } from "./sql.js" import { PersistedRevert } from "@opencode-ai/schema/session-revert" import { Money } from "@opencode-ai/schema/money" diff --git a/packages/core/src/session/message-updater.ts b/packages/core/src/session/message-updater.ts index 0788c474954..4aadfba36a0 100644 --- a/packages/core/src/session/message-updater.ts +++ b/packages/core/src/session/message-updater.ts @@ -4,21 +4,17 @@ import { SessionEvent } from "./event.js" import { SessionMessage } from "./message.js" export interface Adapter { - readonly getAgent: () => Effect.Effect - readonly getModel: () => Effect.Effect - readonly getLocation: () => Effect.Effect - readonly getCurrentAssistant: () => Effect.Effect - readonly getAssistant: ( - messageID: SessionMessage.ID, - ) => Effect.Effect - readonly getShell: ( - shellID: SessionMessage.Shell["shellID"], - ) => Effect.Effect - readonly getCompaction: () => Effect.Effect - readonly updateAssistant: (assistant: SessionMessage.Assistant) => Effect.Effect - readonly updateShell: (shell: SessionMessage.Shell) => Effect.Effect - readonly updateCompaction: (compaction: SessionMessage.Compaction) => Effect.Effect - readonly appendMessage: (message: SessionMessage.Info) => Effect.Effect + readonly getAgent: () => Effect.Effect + readonly getModel: () => Effect.Effect + readonly getLocation: () => Effect.Effect + readonly getCurrentAssistant: () => Effect.Effect + readonly getAssistant: (messageID: SessionMessage.ID) => Effect.Effect + readonly getShell: (shellID: SessionMessage.Shell["shellID"]) => Effect.Effect + readonly getCompaction: () => Effect.Effect + readonly updateAssistant: (assistant: SessionMessage.Assistant) => Effect.Effect + readonly updateShell: (shell: SessionMessage.Shell) => Effect.Effect + readonly updateCompaction: (compaction: SessionMessage.Compaction) => Effect.Effect + readonly appendMessage: (message: SessionMessage.Info) => Effect.Effect } type DraftAssistant = WritableDraft @@ -38,16 +34,14 @@ export function update(adapter: Adapter, event: SessionEvent.DurableEvent) { type DraftReasoning = WritableDraft const created = DateTime.makeUnsafe(event.created) - const latestTool = (assistant: DraftAssistant | undefined, id?: string) => - assistant?.content.findLast( - (item): item is DraftTool => item.type === "tool" && (id === undefined || item.id === id), - ) + const latestTool = (assistant: DraftAssistant, id: string) => + assistant.content.findLast((item): item is DraftTool => item.type === "tool" && item.id === id) - const latestText = (assistant: DraftAssistant | undefined) => - assistant?.content.findLast((item): item is DraftText => item.type === "text") + const latestText = (assistant: DraftAssistant) => + assistant.content.findLast((item): item is DraftText => item.type === "text") - const latestReasoning = (assistant: DraftAssistant | undefined) => - assistant?.content.findLast((item): item is DraftReasoning => item.type === "reasoning" && !item.time?.completed) + const latestReasoning = (assistant: DraftAssistant) => + assistant.content.findLast((item): item is DraftReasoning => item.type === "reasoning" && !item.time?.completed) const updateOwnedAssistant = (messageID: SessionMessage.ID, recipe: (draft: DraftAssistant) => void) => Effect.gen(function* () { diff --git a/packages/core/src/session/projector.ts b/packages/core/src/session/projector.ts index 652361179cf..8cebd9c1dab 100644 --- a/packages/core/src/session/projector.ts +++ b/packages/core/src/session/projector.ts @@ -6,13 +6,13 @@ import path from "path" import { Database } from "../database/database.js" import { Bus } from "../bus.js" import { makeGlobalNode } from "@opencode-ai/util/effect/app-node" -import { Agent } from "../agent.js" -import { Model } from "../model.js" +import { Agent } from "@opencode-ai/schema/agent" +import { Model } from "@opencode-ai/schema/model" import { SessionEvent } from "./event.js" import { SessionMessage } from "./message.js" import { SessionMessageUpdater } from "./message-updater.js" import { SessionInbox } from "./inbox.js" -import { Workspace } from "../workspace.js" +import { Workspace } from "@opencode-ai/schema/workspace" import { InstructionState } from "./instruction-state.js" import { SessionInboxTable, SessionMessageTable, SessionTable } from "./sql.js" import { InstructionEntry } from "./instruction-entry.js" @@ -26,8 +26,10 @@ import type { SessionSchema } from "./schema.js" import { ProjectTable } from "../project/sql.js" type DatabaseService = Database.Interface["db"] -type CurrentDurableEvent = Extract -type MessageEvent = Exclude +type MessageEvent = Exclude< + SessionEvent.DurableEvent, + typeof SessionEvent.Forked.Type | typeof SessionEvent.Deleted.Type +> const decodeMessage = Schema.decodeUnknownSync(SessionMessage.Info) const encodeMessage = Schema.encodeSync(SessionMessage.Info) @@ -53,16 +55,16 @@ const forkTitle = (value?: string) => { return `${value} (fork #1)` } -function applyUsage(db: DatabaseService, sessionID: SessionSchema.ID, value: Usage, sign = 1) { +function applyUsage(db: DatabaseService, sessionID: SessionSchema.ID, value: Usage) { return db .update(SessionTable) .set({ - cost: sql`${SessionTable.cost} + ${value.cost * sign}`, - tokens_input: sql`${SessionTable.tokens_input} + ${value.tokens.input * sign}`, - tokens_output: sql`${SessionTable.tokens_output} + ${value.tokens.output * sign}`, - tokens_reasoning: sql`${SessionTable.tokens_reasoning} + ${value.tokens.reasoning * sign}`, - tokens_cache_read: sql`${SessionTable.tokens_cache_read} + ${value.tokens.cache.read * sign}`, - tokens_cache_write: sql`${SessionTable.tokens_cache_write} + ${value.tokens.cache.write * sign}`, + cost: sql`${SessionTable.cost} + ${value.cost}`, + tokens_input: sql`${SessionTable.tokens_input} + ${value.tokens.input}`, + tokens_output: sql`${SessionTable.tokens_output} + ${value.tokens.output}`, + tokens_reasoning: sql`${SessionTable.tokens_reasoning} + ${value.tokens.reasoning}`, + tokens_cache_read: sql`${SessionTable.tokens_cache_read} + ${value.tokens.cache.read}`, + tokens_cache_write: sql`${SessionTable.tokens_cache_write} + ${value.tokens.cache.write}`, time_updated: sql`${SessionTable.time_updated}`, }) .where(eq(SessionTable.id, sessionID)) @@ -73,7 +75,7 @@ function applyUsage(db: DatabaseService, sessionID: SessionSchema.ID, value: Usa const publishSessionUsage = Effect.fn("SessionProjector.publishUsage")(function* ( db: DatabaseService, bus: Bus.Interface, - sessionID: (typeof SessionEvent.Step.Ended.Type)["data"]["sessionID"], + sessionID: SessionSchema.ID, ) { const row = yield* db .select({ diff --git a/packages/core/src/session/sql.ts b/packages/core/src/session/sql.ts index 04ada2ffb29..facad140a46 100644 --- a/packages/core/src/session/sql.ts +++ b/packages/core/src/session/sql.ts @@ -5,10 +5,10 @@ import { ProjectTable } from "../project/sql.js" import type { SessionMessage } from "./message.js" import type { SessionInbox } from "./inbox.js" import type { FileDiff } from "@opencode-ai/schema/file-diff" -import { PermissionV1 } from "../v1/permission.js" -import { Project } from "../project.js" +import type { PermissionV1 } from "@opencode-ai/schema/permission-v1" +import type { Project } from "@opencode-ai/schema/project" import type { SessionSchema } from "./schema.js" -import { Workspace } from "../workspace.js" +import type { Workspace } from "@opencode-ai/schema/workspace" import { Timestamps } from "../database/schema.sql.js" import type { Instruction } from "@opencode-ai/schema/instruction" import type { Session } from "@opencode-ai/schema/session" diff --git a/packages/core/src/session/stats.ts b/packages/core/src/session/stats.ts index 11cf585d8f0..0086668ad79 100644 --- a/packages/core/src/session/stats.ts +++ b/packages/core/src/session/stats.ts @@ -255,7 +255,7 @@ export const get = Effect.fn("SessionStats.get")(function* (input: Input = {}) { Effect.tap((rows) => Effect.sync(() => { rows.forEach((row) => { - addToolStatus(toolTotals, row.status, 1) + addToolStatus(toolTotals, row.status) if (!row.name) return const tool = tools.get(row.name) ?? { name: row.name, @@ -266,7 +266,7 @@ export const get = Effect.fn("SessionStats.get")(function* (input: Input = {}) { durations: [], } tools.set(row.name, tool) - addToolStatus(tool, row.status, 1) + addToolStatus(tool, row.status) if (row.duration !== null) tool.durations.push(row.duration) }) }), @@ -385,18 +385,17 @@ function tokenTotal(tokens: Tokens) { function addToolStatus( target: { calls: number; succeeded: number; failed: number; unfinished: number }, status: string | null, - count: number, ) { - target.calls += count + target.calls++ if (status === "completed") { - target.succeeded += count + target.succeeded++ return } if (status === "error") { - target.failed += count + target.failed++ return } - target.unfinished += count + target.unfinished++ } function makeDateKey(timezone = "UTC") { diff --git a/packages/core/src/session/usage.ts b/packages/core/src/session/usage.ts index 518415122cc..569da6df93f 100644 --- a/packages/core/src/session/usage.ts +++ b/packages/core/src/session/usage.ts @@ -3,7 +3,7 @@ export * as SessionUsage from "./usage.js" import type { Usage } from "@opencode-ai/ai" import { Money } from "@opencode-ai/schema/money" import type { TokenUsage } from "@opencode-ai/schema/token-usage" -import type { Model } from "../model.js" +import type { Model } from "@opencode-ai/schema/model" const finite = (value: number) => (Number.isFinite(value) ? value : 0) const safe = (value: number | undefined) => Math.max(0, finite(value ?? 0))