refactor(core): make v2 session inputs event sourced (#30785)
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { useEvent } from "@tui/context/event"
|
||||
import type {
|
||||
Event,
|
||||
SessionMessage,
|
||||
SessionMessageAssistant,
|
||||
SessionMessageAssistantReasoning,
|
||||
@@ -54,6 +55,11 @@ function latestReasoning(assistant: SessionMessageAssistant | undefined, reasoni
|
||||
)
|
||||
}
|
||||
|
||||
function prepend(messages: SessionMessage[], message: SessionMessage) {
|
||||
if (messages.some((item) => item.id === message.id)) return
|
||||
messages.unshift(message)
|
||||
}
|
||||
|
||||
export const { use: useSyncV2, provider: SyncProviderV2 } = createSimpleContext({
|
||||
name: "SyncV2",
|
||||
init: () => {
|
||||
@@ -67,6 +73,18 @@ export const { use: useSyncV2, provider: SyncProviderV2 } = createSimpleContext(
|
||||
|
||||
const event = useEvent()
|
||||
const sdk = useSDK()
|
||||
const applied = new Set<string>()
|
||||
const buffering = new Map<string, Event[]>()
|
||||
const syncing = new Map<string, Promise<void>>()
|
||||
|
||||
function duplicate(id: string) {
|
||||
if (applied.has(id)) return true
|
||||
applied.add(id)
|
||||
if (applied.size <= 1000) return false
|
||||
const oldest = applied.values().next()
|
||||
if (!oldest.done) applied.delete(oldest.value)
|
||||
return false
|
||||
}
|
||||
|
||||
function update(sessionID: string, fn: (messages: SessionMessage[]) => void) {
|
||||
setStore(
|
||||
@@ -77,12 +95,41 @@ export const { use: useSyncV2, provider: SyncProviderV2 } = createSimpleContext(
|
||||
)
|
||||
}
|
||||
|
||||
event.subscribe((event) => {
|
||||
async function hydrate(sessionID: string) {
|
||||
const pending: Event[] = []
|
||||
const before = JSON.parse(JSON.stringify(store.messages[sessionID] ?? [])) as SessionMessage[]
|
||||
buffering.set(sessionID, pending)
|
||||
try {
|
||||
const response = await sdk.client.v2.session.messages({ sessionID })
|
||||
const messages = response.data?.data ?? []
|
||||
const snapshotIDs = new Set(messages.map((message) => message.id))
|
||||
setStore(
|
||||
"messages",
|
||||
sessionID,
|
||||
reconcile([...messages, ...before.filter((message) => !snapshotIDs.has(message.id))]),
|
||||
)
|
||||
buffering.delete(sessionID)
|
||||
for (const event of pending) apply(event)
|
||||
} catch (error) {
|
||||
buffering.delete(sessionID)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
function sync(sessionID: string) {
|
||||
const existing = syncing.get(sessionID)
|
||||
if (existing) return existing
|
||||
const result = hydrate(sessionID).finally(() => syncing.delete(sessionID))
|
||||
syncing.set(sessionID, result)
|
||||
return result
|
||||
}
|
||||
|
||||
function apply(event: Event) {
|
||||
switch (event.type) {
|
||||
case "session.next.agent.switched":
|
||||
update(event.properties.sessionID, (draft) => {
|
||||
draft.unshift({
|
||||
id: event.id,
|
||||
prepend(draft, {
|
||||
id: event.properties.messageID,
|
||||
type: "agent-switched",
|
||||
agent: event.properties.agent,
|
||||
time: { created: event.properties.timestamp },
|
||||
@@ -91,8 +138,8 @@ export const { use: useSyncV2, provider: SyncProviderV2 } = createSimpleContext(
|
||||
break
|
||||
case "session.next.model.switched":
|
||||
update(event.properties.sessionID, (draft) => {
|
||||
draft.unshift({
|
||||
id: event.id,
|
||||
prepend(draft, {
|
||||
id: event.properties.messageID,
|
||||
type: "model-switched",
|
||||
model: event.properties.model,
|
||||
time: { created: event.properties.timestamp },
|
||||
@@ -101,8 +148,8 @@ export const { use: useSyncV2, provider: SyncProviderV2 } = createSimpleContext(
|
||||
break
|
||||
case "session.next.prompted": {
|
||||
update(event.properties.sessionID, (draft) => {
|
||||
draft.unshift({
|
||||
id: event.id,
|
||||
prepend(draft, {
|
||||
id: event.properties.messageID,
|
||||
type: "user",
|
||||
text: event.properties.prompt.text,
|
||||
files: event.properties.prompt.files,
|
||||
@@ -113,10 +160,25 @@ export const { use: useSyncV2, provider: SyncProviderV2 } = createSimpleContext(
|
||||
})
|
||||
break
|
||||
}
|
||||
case "session.next.prompt.admitted":
|
||||
break
|
||||
case "session.next.prompt.promoted":
|
||||
update(event.properties.sessionID, (draft) => {
|
||||
prepend(draft, {
|
||||
id: event.properties.messageID,
|
||||
type: "user",
|
||||
text: event.properties.prompt.text,
|
||||
files: event.properties.prompt.files,
|
||||
agents: event.properties.prompt.agents,
|
||||
references: event.properties.prompt.references,
|
||||
time: { created: event.properties.timeCreated },
|
||||
})
|
||||
})
|
||||
break
|
||||
case "session.next.synthetic":
|
||||
update(event.properties.sessionID, (draft) => {
|
||||
draft.unshift({
|
||||
id: event.id,
|
||||
prepend(draft, {
|
||||
id: event.properties.messageID,
|
||||
type: "synthetic",
|
||||
sessionID: event.properties.sessionID,
|
||||
text: event.properties.text,
|
||||
@@ -126,8 +188,8 @@ export const { use: useSyncV2, provider: SyncProviderV2 } = createSimpleContext(
|
||||
break
|
||||
case "session.next.shell.started":
|
||||
update(event.properties.sessionID, (draft) => {
|
||||
draft.unshift({
|
||||
id: event.id,
|
||||
prepend(draft, {
|
||||
id: event.properties.messageID,
|
||||
type: "shell",
|
||||
callID: event.properties.callID,
|
||||
command: event.properties.command,
|
||||
@@ -146,10 +208,11 @@ export const { use: useSyncV2, provider: SyncProviderV2 } = createSimpleContext(
|
||||
break
|
||||
case "session.next.step.started":
|
||||
update(event.properties.sessionID, (draft) => {
|
||||
if (draft.some((message) => message.id === event.properties.assistantMessageID)) return
|
||||
const currentAssistant = activeAssistant(draft)
|
||||
if (currentAssistant) currentAssistant.time.completed = event.properties.timestamp
|
||||
draft.unshift({
|
||||
id: event.id,
|
||||
prepend(draft, {
|
||||
id: event.properties.assistantMessageID,
|
||||
type: "assistant",
|
||||
agent: event.properties.agent,
|
||||
model: event.properties.model,
|
||||
@@ -182,18 +245,28 @@ export const { use: useSyncV2, provider: SyncProviderV2 } = createSimpleContext(
|
||||
break
|
||||
case "session.next.text.started":
|
||||
update(event.properties.sessionID, (draft) => {
|
||||
activeAssistant(draft)?.content.push({ type: "text", id: event.properties.textID, text: "" })
|
||||
ownedAssistant(draft, event.properties.assistantMessageID)?.content.push({
|
||||
type: "text",
|
||||
id: event.properties.textID,
|
||||
text: "",
|
||||
})
|
||||
})
|
||||
break
|
||||
case "session.next.text.delta":
|
||||
update(event.properties.sessionID, (draft) => {
|
||||
const match = latestText(activeAssistant(draft), event.properties.textID)
|
||||
const match = latestText(
|
||||
ownedAssistant(draft, event.properties.assistantMessageID),
|
||||
event.properties.textID,
|
||||
)
|
||||
if (match) match.text += event.properties.delta
|
||||
})
|
||||
break
|
||||
case "session.next.text.ended":
|
||||
update(event.properties.sessionID, (draft) => {
|
||||
const match = latestText(activeAssistant(draft), event.properties.textID)
|
||||
const match = latestText(
|
||||
ownedAssistant(draft, event.properties.assistantMessageID),
|
||||
event.properties.textID,
|
||||
)
|
||||
if (match) match.text = event.properties.text
|
||||
})
|
||||
break
|
||||
@@ -263,7 +336,11 @@ export const { use: useSyncV2, provider: SyncProviderV2 } = createSimpleContext(
|
||||
content: [...event.properties.content],
|
||||
result: event.properties.result,
|
||||
}
|
||||
match.provider = event.properties.provider
|
||||
match.provider = {
|
||||
executed: event.properties.provider.executed || match.provider?.executed === true,
|
||||
metadata: match.provider?.metadata,
|
||||
resultMetadata: event.properties.provider.metadata,
|
||||
}
|
||||
match.time.completed = event.properties.timestamp
|
||||
})
|
||||
break
|
||||
@@ -282,13 +359,17 @@ export const { use: useSyncV2, provider: SyncProviderV2 } = createSimpleContext(
|
||||
content: match.state.status === "running" ? match.state.content : [],
|
||||
result: event.properties.result,
|
||||
}
|
||||
match.provider = event.properties.provider
|
||||
match.provider = {
|
||||
executed: event.properties.provider.executed || match.provider?.executed === true,
|
||||
metadata: match.provider?.metadata,
|
||||
resultMetadata: event.properties.provider.metadata,
|
||||
}
|
||||
match.time.completed = event.properties.timestamp
|
||||
})
|
||||
break
|
||||
case "session.next.reasoning.started":
|
||||
update(event.properties.sessionID, (draft) => {
|
||||
activeAssistant(draft)?.content.push({
|
||||
ownedAssistant(draft, event.properties.assistantMessageID)?.content.push({
|
||||
type: "reasoning",
|
||||
id: event.properties.reasoningID,
|
||||
text: "",
|
||||
@@ -298,13 +379,19 @@ export const { use: useSyncV2, provider: SyncProviderV2 } = createSimpleContext(
|
||||
break
|
||||
case "session.next.reasoning.delta":
|
||||
update(event.properties.sessionID, (draft) => {
|
||||
const match = latestReasoning(activeAssistant(draft), event.properties.reasoningID)
|
||||
const match = latestReasoning(
|
||||
ownedAssistant(draft, event.properties.assistantMessageID),
|
||||
event.properties.reasoningID,
|
||||
)
|
||||
if (match) match.text += event.properties.delta
|
||||
})
|
||||
break
|
||||
case "session.next.reasoning.ended":
|
||||
update(event.properties.sessionID, (draft) => {
|
||||
const match = latestReasoning(activeAssistant(draft), event.properties.reasoningID)
|
||||
const match = latestReasoning(
|
||||
ownedAssistant(draft, event.properties.assistantMessageID),
|
||||
event.properties.reasoningID,
|
||||
)
|
||||
if (match) {
|
||||
match.text = event.properties.text
|
||||
if (event.properties.providerMetadata !== undefined)
|
||||
@@ -316,8 +403,8 @@ export const { use: useSyncV2, provider: SyncProviderV2 } = createSimpleContext(
|
||||
break
|
||||
case "session.next.compaction.started":
|
||||
update(event.properties.sessionID, (draft) => {
|
||||
draft.unshift({
|
||||
id: event.id,
|
||||
prepend(draft, {
|
||||
id: event.properties.messageID,
|
||||
type: "compaction",
|
||||
reason: event.properties.reason,
|
||||
summary: "",
|
||||
@@ -340,16 +427,20 @@ export const { use: useSyncV2, provider: SyncProviderV2 } = createSimpleContext(
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
event.subscribe((event) => {
|
||||
if (duplicate(event.id)) return
|
||||
if ("sessionID" in event.properties && typeof event.properties.sessionID === "string")
|
||||
buffering.get(event.properties.sessionID)?.push(event)
|
||||
apply(event)
|
||||
})
|
||||
|
||||
const result = {
|
||||
data: store,
|
||||
session: {
|
||||
message: {
|
||||
async sync(sessionID: string) {
|
||||
const response = await sdk.client.v2.session.messages({ sessionID })
|
||||
setStore("messages", sessionID, reconcile(response.data?.data ?? []))
|
||||
},
|
||||
sync,
|
||||
fromSession(sessionID: string) {
|
||||
const messages = store.messages[sessionID]
|
||||
if (!messages) return []
|
||||
|
||||
@@ -32,7 +32,7 @@ const EventSchema = Schema.Union([
|
||||
.values()
|
||||
.map((definition) =>
|
||||
Schema.Struct({
|
||||
id: Schema.String,
|
||||
id: EventV2.ID,
|
||||
type: Schema.Literal(definition.type),
|
||||
properties: definition.data,
|
||||
}).annotate({ identifier: `Event.${definition.type}` }),
|
||||
|
||||
@@ -20,10 +20,10 @@ const SyncEventSchemas = EventV2.registry
|
||||
return [
|
||||
Schema.Struct({
|
||||
type: Schema.Literal("sync"),
|
||||
id: Schema.String,
|
||||
id: EventV2.ID,
|
||||
syncEvent: Schema.Struct({
|
||||
type: Schema.Literal(EventV2.versionedType(definition.type, definition.sync.version)),
|
||||
id: Schema.String,
|
||||
id: EventV2.ID,
|
||||
seq: Schema.Finite,
|
||||
aggregateID: Schema.String,
|
||||
data: definition.data,
|
||||
@@ -41,7 +41,7 @@ const GlobalEventSchema = Schema.Struct({
|
||||
...EventV2.registry
|
||||
.values()
|
||||
.map((definition) =>
|
||||
Schema.Struct({ id: Schema.String, type: Schema.Literal(definition.type), properties: definition.data }),
|
||||
Schema.Struct({ id: EventV2.ID, type: Schema.Literal(definition.type), properties: definition.data }),
|
||||
)
|
||||
.toArray(),
|
||||
InstanceDisposed,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { NonNegativeInt } from "@opencode-ai/core/schema"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { SessionID } from "@/session/schema"
|
||||
import { Schema } from "effect"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
@@ -9,7 +10,7 @@ import { described } from "./metadata"
|
||||
|
||||
const root = "/sync"
|
||||
export const ReplayEvent = Schema.Struct({
|
||||
id: Schema.String,
|
||||
id: EventV2.ID,
|
||||
aggregateID: Schema.String,
|
||||
seq: NonNegativeInt,
|
||||
type: Schema.String,
|
||||
@@ -27,7 +28,7 @@ export const SessionPayload = Schema.Struct({
|
||||
})
|
||||
export const HistoryPayload = Schema.Record(Schema.String, NonNegativeInt)
|
||||
export const HistoryEvent = Schema.Struct({
|
||||
id: Schema.String,
|
||||
id: EventV2.ID,
|
||||
aggregate_id: Schema.String,
|
||||
seq: NonNegativeInt,
|
||||
type: Schema.String,
|
||||
|
||||
@@ -36,7 +36,7 @@ export const syncHandlers = HttpApiBuilder.group(InstanceHttpApi, "sync", (handl
|
||||
|
||||
const replay = Effect.fn("SyncHttpApi.replay")(function* (ctx: { payload: typeof ReplayPayload.Type }) {
|
||||
const payload: EventV2.SerializedEvent[] = ctx.payload.events.map((event) => ({
|
||||
id: EventV2.ID.make(event.id),
|
||||
id: event.id,
|
||||
aggregateID: event.aggregateID,
|
||||
seq: event.seq,
|
||||
type: event.type,
|
||||
|
||||
@@ -12,7 +12,7 @@ import { Plugin } from "@/plugin"
|
||||
import { Config } from "@/config/config"
|
||||
import { NotFoundError } from "@/storage/storage"
|
||||
|
||||
import { Effect, Layer, Context, Schema } from "effect"
|
||||
import { Effect, Layer, Context } from "effect"
|
||||
import * as DateTime from "effect/DateTime"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import { isOverflow as overflow, usable } from "./overflow"
|
||||
@@ -20,6 +20,7 @@ import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { SessionEvent } from "@opencode-ai/core/session/event"
|
||||
import { SessionMessage } from "@opencode-ai/core/session/message"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { ModelV2 } from "@opencode-ai/core/model"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
@@ -609,6 +610,7 @@ export const layer = Layer.effect(
|
||||
if (flags.experimentalEventSystem) {
|
||||
yield* events.publish(SessionEvent.Compaction.Started, {
|
||||
sessionID: input.sessionID,
|
||||
messageID: SessionMessage.ID.create(),
|
||||
timestamp: DateTime.makeUnsafe(Date.now()),
|
||||
reason: input.auto ? "auto" : "manual",
|
||||
})
|
||||
|
||||
@@ -25,13 +25,13 @@ import { isRecord } from "@/util/record"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { Database } from "@opencode-ai/core/database/database"
|
||||
import { SessionEvent } from "@opencode-ai/core/session/event"
|
||||
import { SessionMessage } from "@opencode-ai/core/session/message"
|
||||
import { ModelV2 } from "@opencode-ai/core/model"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import * as DateTime from "effect/DateTime"
|
||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
import { toolFileSourceFromUri, Usage, type LLMEvent } from "@opencode-ai/llm"
|
||||
import { ToolOutput } from "@opencode-ai/core/tool-output"
|
||||
import type { EventV2 } from "@opencode-ai/core/event"
|
||||
|
||||
const DOOM_LOOP_THRESHOLD = 3
|
||||
const log = Log.create({ service: "session.processor" })
|
||||
@@ -67,7 +67,7 @@ export interface Interface {
|
||||
}
|
||||
|
||||
type ToolCall = {
|
||||
assistantMessageID?: EventV2.ID
|
||||
assistantMessageID?: SessionMessage.ID
|
||||
partID: SessionV1.ToolPart["id"]
|
||||
messageID: SessionV1.ToolPart["messageID"]
|
||||
sessionID: SessionV1.ToolPart["sessionID"]
|
||||
@@ -85,7 +85,7 @@ interface ProcessorContext extends Input {
|
||||
currentText: SessionV1.TextPart | undefined
|
||||
currentTextID: string | undefined
|
||||
reasoningMap: Record<string, SessionV1.ReasoningPart>
|
||||
v2AssistantMessageID: EventV2.ID | undefined
|
||||
v2AssistantMessageID: SessionMessage.ID | undefined
|
||||
}
|
||||
|
||||
type StreamEvent = LLMEvent
|
||||
@@ -129,6 +129,7 @@ export const layer = Layer.effect(
|
||||
reasoningMap: {},
|
||||
v2AssistantMessageID: undefined,
|
||||
}
|
||||
const mirrorAssistant = flags.experimentalEventSystem && !input.assistantMessage.summary
|
||||
let aborted = false
|
||||
const slog = log.clone().tag("session.id", input.sessionID).tag("messageID", input.assistantMessage.id)
|
||||
|
||||
@@ -146,8 +147,10 @@ export const layer = Layer.effect(
|
||||
|
||||
const ensureV2AssistantMessage = Effect.fn("SessionProcessor.ensureV2AssistantMessage")(function* () {
|
||||
if (ctx.v2AssistantMessageID) return ctx.v2AssistantMessageID
|
||||
ctx.v2AssistantMessageID = (yield* events.publish(SessionEvent.Step.Started, {
|
||||
ctx.v2AssistantMessageID = SessionMessage.ID.create()
|
||||
yield* events.publish(SessionEvent.Step.Started, {
|
||||
sessionID: ctx.sessionID,
|
||||
assistantMessageID: ctx.v2AssistantMessageID,
|
||||
agent: input.assistantMessage.agent,
|
||||
model: {
|
||||
id: ModelV2.ID.make(ctx.model.id),
|
||||
@@ -156,7 +159,7 @@ export const layer = Layer.effect(
|
||||
},
|
||||
snapshot: ctx.snapshot,
|
||||
timestamp: DateTime.makeUnsafe(Date.now()),
|
||||
})).id
|
||||
})
|
||||
return ctx.v2AssistantMessageID
|
||||
})
|
||||
|
||||
@@ -249,9 +252,10 @@ export const layer = Layer.effect(
|
||||
const finishReasoning = Effect.fn("SessionProcessor.finishReasoning")(function* (reasoningID: string) {
|
||||
if (!(reasoningID in ctx.reasoningMap)) return
|
||||
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
||||
if (flags.experimentalEventSystem) {
|
||||
if (mirrorAssistant) {
|
||||
yield* events.publish(SessionEvent.Reasoning.Ended, {
|
||||
sessionID: ctx.sessionID,
|
||||
assistantMessageID: yield* currentV2AssistantMessage(),
|
||||
reasoningID,
|
||||
text: ctx.reasoningMap[reasoningID].text,
|
||||
providerMetadata: ctx.reasoningMap[reasoningID].metadata,
|
||||
@@ -266,23 +270,29 @@ export const layer = Layer.effect(
|
||||
})
|
||||
|
||||
const flushV2Fragments = Effect.fn("SessionProcessor.flushV2Fragments")(function* () {
|
||||
if (!flags.experimentalEventSystem) return
|
||||
if (!mirrorAssistant) return
|
||||
if (!ctx.assistantMessage.summary && ctx.currentText && ctx.currentTextID) {
|
||||
yield* events.publish(SessionEvent.Text.Ended, {
|
||||
sessionID: ctx.sessionID,
|
||||
assistantMessageID: yield* currentV2AssistantMessage(),
|
||||
textID: ctx.currentTextID,
|
||||
text: ctx.currentText.text,
|
||||
timestamp: DateTime.makeUnsafe(Date.now()),
|
||||
})
|
||||
}
|
||||
yield* Effect.forEach(Object.entries(ctx.reasoningMap), ([reasoningID, part]) =>
|
||||
events.publish(SessionEvent.Reasoning.Ended, {
|
||||
sessionID: ctx.sessionID,
|
||||
reasoningID,
|
||||
text: part.text,
|
||||
providerMetadata: part.metadata,
|
||||
timestamp: DateTime.makeUnsafe(Date.now()),
|
||||
}),
|
||||
currentV2AssistantMessage().pipe(
|
||||
Effect.flatMap((assistantMessageID) =>
|
||||
events.publish(SessionEvent.Reasoning.Ended, {
|
||||
sessionID: ctx.sessionID,
|
||||
assistantMessageID,
|
||||
reasoningID,
|
||||
text: part.text,
|
||||
providerMetadata: part.metadata,
|
||||
timestamp: DateTime.makeUnsafe(Date.now()),
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -307,7 +317,7 @@ export const layer = Layer.effect(
|
||||
return { call: ctx.toolcalls[input.id], part }
|
||||
}
|
||||
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
||||
const assistantMessageID = flags.experimentalEventSystem ? yield* ensureV2AssistantMessage() : undefined
|
||||
const assistantMessageID = mirrorAssistant ? yield* ensureV2AssistantMessage() : undefined
|
||||
if (assistantMessageID) {
|
||||
yield* events.publish(SessionEvent.Tool.Input.Started, {
|
||||
sessionID: ctx.sessionID,
|
||||
@@ -367,9 +377,10 @@ export const layer = Layer.effect(
|
||||
case "reasoning-start":
|
||||
if (value.id in ctx.reasoningMap) return
|
||||
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
||||
if (flags.experimentalEventSystem) {
|
||||
if (mirrorAssistant) {
|
||||
yield* events.publish(SessionEvent.Reasoning.Started, {
|
||||
sessionID: ctx.sessionID,
|
||||
assistantMessageID: yield* ensureV2AssistantMessage(),
|
||||
reasoningID: value.id,
|
||||
providerMetadata: value.providerMetadata,
|
||||
timestamp: DateTime.makeUnsafe(Date.now()),
|
||||
@@ -392,9 +403,10 @@ export const layer = Layer.effect(
|
||||
if (!(value.id in ctx.reasoningMap)) return
|
||||
ctx.reasoningMap[value.id].text += value.text
|
||||
if (value.providerMetadata) ctx.reasoningMap[value.id].metadata = value.providerMetadata
|
||||
if (flags.experimentalEventSystem) {
|
||||
if (mirrorAssistant) {
|
||||
yield* events.publish(SessionEvent.Reasoning.Delta, {
|
||||
sessionID: ctx.sessionID,
|
||||
assistantMessageID: yield* currentV2AssistantMessage(),
|
||||
reasoningID: value.id,
|
||||
delta: value.text,
|
||||
timestamp: DateTime.makeUnsafe(Date.now()),
|
||||
@@ -426,9 +438,7 @@ export const layer = Layer.effect(
|
||||
case "tool-input-delta":
|
||||
{
|
||||
const toolCall = yield* ensureToolCall(value)
|
||||
const assistantMessageID = flags.experimentalEventSystem
|
||||
? yield* requireV2AssistantMessage(toolCall.call)
|
||||
: undefined
|
||||
const assistantMessageID = mirrorAssistant ? yield* requireV2AssistantMessage(toolCall.call) : undefined
|
||||
if (assistantMessageID) {
|
||||
yield* events.publish(SessionEvent.Tool.Input.Delta, {
|
||||
sessionID: ctx.sessionID,
|
||||
@@ -445,7 +455,7 @@ export const layer = Layer.effect(
|
||||
case "tool-input-end": {
|
||||
const toolCall = yield* ensureToolCall(value)
|
||||
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
||||
if (flags.experimentalEventSystem) {
|
||||
if (mirrorAssistant) {
|
||||
const assistantMessageID = yield* requireV2AssistantMessage(toolCall.call)
|
||||
yield* events.publish(SessionEvent.Tool.Input.Ended, {
|
||||
sessionID: ctx.sessionID,
|
||||
@@ -467,7 +477,7 @@ export const layer = Layer.effect(
|
||||
const input = isRecord(value.input) ? value.input : { value: value.input }
|
||||
if (!toolCall.call.inputEnded) {
|
||||
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
||||
if (flags.experimentalEventSystem) {
|
||||
if (mirrorAssistant) {
|
||||
const assistantMessageID = yield* requireV2AssistantMessage(toolCall.call)
|
||||
yield* events.publish(SessionEvent.Tool.Input.Ended, {
|
||||
sessionID: ctx.sessionID,
|
||||
@@ -479,7 +489,7 @@ export const layer = Layer.effect(
|
||||
}
|
||||
}
|
||||
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
||||
if (flags.experimentalEventSystem) {
|
||||
if (mirrorAssistant) {
|
||||
const assistantMessageID = yield* requireV2AssistantMessage(toolCall.call)
|
||||
yield* events.publish(SessionEvent.Tool.Called, {
|
||||
sessionID: ctx.sessionID,
|
||||
@@ -545,7 +555,7 @@ export const layer = Layer.effect(
|
||||
if (!toolCall && value.result.type === "error") return
|
||||
if (value.result.type === "error") {
|
||||
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
||||
if (flags.experimentalEventSystem) {
|
||||
if (mirrorAssistant) {
|
||||
const assistantMessageID = yield* requireV2AssistantMessage(toolCall?.call)
|
||||
yield* events.publish(SessionEvent.Tool.Failed, {
|
||||
sessionID: ctx.sessionID,
|
||||
@@ -586,7 +596,7 @@ export const layer = Layer.effect(
|
||||
attachments: attachments.length ? attachments : undefined,
|
||||
}
|
||||
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
||||
if (flags.experimentalEventSystem) {
|
||||
if (mirrorAssistant) {
|
||||
const assistantMessageID = yield* requireV2AssistantMessage(toolCall?.call)
|
||||
const content = [
|
||||
ToolOutput.text({ type: "text", text: output.output }),
|
||||
@@ -642,7 +652,7 @@ export const layer = Layer.effect(
|
||||
case "tool-error": {
|
||||
const toolCall = yield* readToolCall(value.id)
|
||||
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
||||
if (flags.experimentalEventSystem) {
|
||||
if (mirrorAssistant) {
|
||||
const assistantMessageID = yield* requireV2AssistantMessage(toolCall?.call)
|
||||
yield* events.publish(SessionEvent.Tool.Failed, {
|
||||
sessionID: ctx.sessionID,
|
||||
@@ -670,7 +680,7 @@ export const layer = Layer.effect(
|
||||
if (!ctx.snapshot) ctx.snapshot = yield* snapshot.track()
|
||||
if (!ctx.assistantMessage.summary) {
|
||||
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
||||
if (flags.experimentalEventSystem) {
|
||||
if (mirrorAssistant) {
|
||||
yield* ensureV2AssistantMessage()
|
||||
}
|
||||
}
|
||||
@@ -693,7 +703,7 @@ export const layer = Layer.effect(
|
||||
})
|
||||
if (!ctx.assistantMessage.summary) {
|
||||
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
||||
if (flags.experimentalEventSystem) {
|
||||
if (mirrorAssistant) {
|
||||
yield* events.publish(SessionEvent.Step.Ended, {
|
||||
sessionID: ctx.sessionID,
|
||||
assistantMessageID: yield* currentV2AssistantMessage(),
|
||||
@@ -752,9 +762,10 @@ export const layer = Layer.effect(
|
||||
case "text-start":
|
||||
if (!ctx.assistantMessage.summary) {
|
||||
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
||||
if (flags.experimentalEventSystem) {
|
||||
if (mirrorAssistant) {
|
||||
yield* events.publish(SessionEvent.Text.Started, {
|
||||
sessionID: ctx.sessionID,
|
||||
assistantMessageID: yield* ensureV2AssistantMessage(),
|
||||
timestamp: DateTime.makeUnsafe(Date.now()),
|
||||
textID: value.id,
|
||||
})
|
||||
@@ -777,9 +788,10 @@ export const layer = Layer.effect(
|
||||
if (!ctx.currentText) return
|
||||
ctx.currentText.text += value.text
|
||||
if (value.providerMetadata) ctx.currentText.metadata = value.providerMetadata
|
||||
if (flags.experimentalEventSystem) {
|
||||
if (mirrorAssistant) {
|
||||
yield* events.publish(SessionEvent.Text.Delta, {
|
||||
sessionID: ctx.sessionID,
|
||||
assistantMessageID: yield* currentV2AssistantMessage(),
|
||||
textID: value.id,
|
||||
delta: value.text,
|
||||
timestamp: DateTime.makeUnsafe(Date.now()),
|
||||
@@ -809,9 +821,10 @@ export const layer = Layer.effect(
|
||||
)).text
|
||||
if (!ctx.assistantMessage.summary) {
|
||||
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
||||
if (flags.experimentalEventSystem) {
|
||||
if (mirrorAssistant) {
|
||||
yield* events.publish(SessionEvent.Text.Ended, {
|
||||
sessionID: ctx.sessionID,
|
||||
assistantMessageID: yield* currentV2AssistantMessage(),
|
||||
text: ctx.currentText.text,
|
||||
timestamp: DateTime.makeUnsafe(Date.now()),
|
||||
textID: value.id,
|
||||
@@ -876,7 +889,7 @@ export const layer = Layer.effect(
|
||||
const match = yield* readToolCall(toolCallID)
|
||||
if (!match) continue
|
||||
const part = match.part
|
||||
if (flags.experimentalEventSystem && match.call.assistantMessageID) {
|
||||
if (mirrorAssistant && match.call.assistantMessageID) {
|
||||
yield* events.publish(SessionEvent.Tool.Failed, {
|
||||
sessionID: ctx.sessionID,
|
||||
assistantMessageID: match.call.assistantMessageID,
|
||||
@@ -922,7 +935,7 @@ export const layer = Layer.effect(
|
||||
}
|
||||
if (!ctx.assistantMessage.summary) {
|
||||
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
||||
if (flags.experimentalEventSystem) {
|
||||
if (mirrorAssistant) {
|
||||
yield* events.publish(SessionEvent.Step.Failed, {
|
||||
sessionID: ctx.sessionID,
|
||||
assistantMessageID: yield* ensureV2AssistantMessage(),
|
||||
@@ -979,7 +992,7 @@ export const layer = Layer.effect(
|
||||
parse,
|
||||
set: (info) => {
|
||||
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
||||
const event = flags.experimentalEventSystem
|
||||
const event = mirrorAssistant
|
||||
? events.publish(SessionEvent.Retried, {
|
||||
sessionID: ctx.sessionID,
|
||||
attempt: info.attempt,
|
||||
|
||||
@@ -51,6 +51,7 @@ import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { Database } from "@opencode-ai/core/database/database"
|
||||
import { SessionEvent } from "@opencode-ai/core/session/event"
|
||||
import { SessionMessage } from "@opencode-ai/core/session/message"
|
||||
import { ModelV2 } from "@opencode-ai/core/model"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { AgentAttachment, FileAttachment, Prompt, ReferenceAttachment, Source } from "@opencode-ai/core/session/prompt"
|
||||
@@ -563,6 +564,7 @@ export const layer = Layer.effect(
|
||||
if (flags.experimentalEventSystem) {
|
||||
yield* events.publish(SessionEvent.Shell.Started, {
|
||||
sessionID: input.sessionID,
|
||||
messageID: SessionMessage.ID.create(),
|
||||
timestamp: DateTime.makeUnsafe(started),
|
||||
callID: part.callID,
|
||||
command: input.command,
|
||||
@@ -738,6 +740,7 @@ export const layer = Layer.effect(
|
||||
if (current?.agent !== info.agent) {
|
||||
yield* events.publish(SessionEvent.AgentSwitched, {
|
||||
sessionID: input.sessionID,
|
||||
messageID: SessionMessage.ID.create(),
|
||||
timestamp: DateTime.makeUnsafe(info.time.created),
|
||||
agent: info.agent,
|
||||
})
|
||||
@@ -749,6 +752,7 @@ export const layer = Layer.effect(
|
||||
) {
|
||||
yield* events.publish(SessionEvent.ModelSwitched, {
|
||||
sessionID: input.sessionID,
|
||||
messageID: SessionMessage.ID.create(),
|
||||
timestamp: DateTime.makeUnsafe(info.time.created),
|
||||
model: {
|
||||
id: ModelV2.ID.make(info.model.modelID),
|
||||
@@ -1190,6 +1194,7 @@ export const layer = Layer.effect(
|
||||
if (flags.experimentalEventSystem) {
|
||||
yield* events.publish(SessionEvent.Prompted, {
|
||||
sessionID: input.sessionID,
|
||||
messageID: SessionMessage.ID.create(),
|
||||
timestamp: DateTime.makeUnsafe(info.time.created),
|
||||
delivery: "steer",
|
||||
prompt: new Prompt({
|
||||
@@ -1205,6 +1210,7 @@ export const layer = Layer.effect(
|
||||
if (flags.experimentalEventSystem) {
|
||||
yield* events.publish(SessionEvent.Synthetic, {
|
||||
sessionID: input.sessionID,
|
||||
messageID: SessionMessage.ID.create(),
|
||||
timestamp: DateTime.makeUnsafe(info.time.created),
|
||||
text,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user