Use SyncEvent service at event call sites (#26782)

This commit is contained in:
Kit Langton
2026-05-10 21:20:13 -04:00
committed by GitHub
parent 5801cce1b5
commit effd96755e
9 changed files with 250 additions and 205 deletions
@@ -676,14 +676,12 @@ export const layer = Layer.effect(
} }
if (input.workspaceID === null) { if (input.workspaceID === null) {
yield* Effect.sync(() => yield* sync.run(Session.Event.Updated, {
SyncEvent.run(Session.Event.Updated, { sessionID: input.sessionID,
sessionID: input.sessionID, info: {
info: { workspaceID: null,
workspaceID: null, },
}, })
}),
)
log.info("session warp complete", { log.info("session warp complete", {
workspaceID: input.workspaceID, workspaceID: input.workspaceID,
+20 -12
View File
@@ -18,8 +18,9 @@ import { InstanceState } from "@/effect/instance-state"
import { isOverflow as overflow, usable } from "./overflow" import { isOverflow as overflow, usable } from "./overflow"
import { makeRuntime } from "@/effect/run-service" import { makeRuntime } from "@/effect/run-service"
import { serviceUse } from "@/effect/service-use" import { serviceUse } from "@/effect/service-use"
import { EventV2 } from "@/v2/event" import { SyncEvent } from "@/sync"
import { SessionEvent } from "@/v2/session-event" import { SessionEvent } from "@/v2/session-event"
import { Flag } from "@opencode-ai/core/flag/flag"
const log = Log.create({ service: "session.compaction" }) const log = Log.create({ service: "session.compaction" })
@@ -219,6 +220,7 @@ export const layer: Layer.Layer<
| Plugin.Service | Plugin.Service
| SessionProcessor.Service | SessionProcessor.Service
| Provider.Service | Provider.Service
| SyncEvent.Service
> = Layer.effect( > = Layer.effect(
Service, Service,
Effect.gen(function* () { Effect.gen(function* () {
@@ -229,6 +231,7 @@ export const layer: Layer.Layer<
const plugin = yield* Plugin.Service const plugin = yield* Plugin.Service
const processors = yield* SessionProcessor.Service const processors = yield* SessionProcessor.Service
const provider = yield* Provider.Service const provider = yield* Provider.Service
const sync = yield* SyncEvent.Service
const isOverflow = Effect.fn("SessionCompaction.isOverflow")(function* (input: { const isOverflow = Effect.fn("SessionCompaction.isOverflow")(function* (input: {
tokens: MessageV2.Assistant["tokens"] tokens: MessageV2.Assistant["tokens"]
@@ -567,12 +570,14 @@ export const layer: Layer.Layer<
parts: [], parts: [],
}, },
) )
EventV2.run(SessionEvent.Compaction.Ended.Sync, { if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) {
sessionID: input.sessionID, yield* sync.run(SessionEvent.Compaction.Ended.Sync, {
timestamp: DateTime.makeUnsafe(Date.now()), sessionID: input.sessionID,
text: summary ?? "", timestamp: DateTime.makeUnsafe(Date.now()),
include: selected.tail_start_id, text: summary ?? "",
}) include: selected.tail_start_id,
})
}
yield* bus.publish(Event.Compacted, { sessionID: input.sessionID }) yield* bus.publish(Event.Compacted, { sessionID: input.sessionID })
} }
return result return result
@@ -601,11 +606,13 @@ export const layer: Layer.Layer<
auto: input.auto, auto: input.auto,
overflow: input.overflow, overflow: input.overflow,
}) })
EventV2.run(SessionEvent.Compaction.Started.Sync, { if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) {
sessionID: input.sessionID, yield* sync.run(SessionEvent.Compaction.Started.Sync, {
timestamp: DateTime.makeUnsafe(Date.now()), sessionID: input.sessionID,
reason: input.auto ? "auto" : "manual", timestamp: DateTime.makeUnsafe(Date.now()),
}) reason: input.auto ? "auto" : "manual",
})
}
}) })
return Service.of({ return Service.of({
@@ -626,6 +633,7 @@ export const defaultLayer = Layer.suspend(() =>
Layer.provide(Plugin.defaultLayer), Layer.provide(Plugin.defaultLayer),
Layer.provide(Bus.layer), Layer.provide(Bus.layer),
Layer.provide(Config.defaultLayer), Layer.provide(Config.defaultLayer),
Layer.provide(SyncEvent.defaultLayer),
), ),
) )
+153 -119
View File
@@ -21,10 +21,11 @@ import { Question } from "@/question"
import { errorMessage } from "@/util/error" import { errorMessage } from "@/util/error"
import * as Log from "@opencode-ai/core/util/log" import * as Log from "@opencode-ai/core/util/log"
import { isRecord } from "@/util/record" import { isRecord } from "@/util/record"
import { EventV2 } from "@/v2/event" import { SyncEvent } from "@/sync"
import { SessionEvent } from "@/v2/session-event" import { SessionEvent } from "@/v2/session-event"
import { Modelv2 } from "@/v2/model" import { Modelv2 } from "@/v2/model"
import * as DateTime from "effect/DateTime" import * as DateTime from "effect/DateTime"
import { Flag } from "@opencode-ai/core/flag/flag"
const DOOM_LOOP_THRESHOLD = 3 const DOOM_LOOP_THRESHOLD = 3
const log = Log.create({ service: "session.processor" }) const log = Log.create({ service: "session.processor" })
@@ -96,6 +97,7 @@ export const layer: Layer.Layer<
| Image.Service | Image.Service
| SessionSummary.Service | SessionSummary.Service
| SessionStatus.Service | SessionStatus.Service
| SyncEvent.Service
> = Layer.effect( > = Layer.effect(
Service, Service,
Effect.gen(function* () { Effect.gen(function* () {
@@ -111,6 +113,7 @@ export const layer: Layer.Layer<
const scope = yield* Scope.Scope const scope = yield* Scope.Scope
const status = yield* SessionStatus.Service const status = yield* SessionStatus.Service
const image = yield* Image.Service const image = yield* Image.Service
const sync = yield* SyncEvent.Service
const create = Effect.fn("SessionProcessor.create")(function* (input: Input) { const create = Effect.fn("SessionProcessor.create")(function* (input: Input) {
// Pre-capture snapshot before the LLM stream starts. The AI SDK // Pre-capture snapshot before the LLM stream starts. The AI SDK
@@ -229,11 +232,13 @@ export const layer: Layer.Layer<
case "reasoning-start": case "reasoning-start":
if (value.id in ctx.reasoningMap) return if (value.id in ctx.reasoningMap) return
// TODO(v2): Temporary dual-write while migrating session messages to v2 events. // TODO(v2): Temporary dual-write while migrating session messages to v2 events.
EventV2.run(SessionEvent.Reasoning.Started.Sync, { if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) {
sessionID: ctx.sessionID, yield* sync.run(SessionEvent.Reasoning.Started.Sync, {
reasoningID: value.id, sessionID: ctx.sessionID,
timestamp: DateTime.makeUnsafe(Date.now()), reasoningID: value.id,
}) timestamp: DateTime.makeUnsafe(Date.now()),
})
}
ctx.reasoningMap[value.id] = { ctx.reasoningMap[value.id] = {
id: PartID.ascending(), id: PartID.ascending(),
messageID: ctx.assistantMessage.id, messageID: ctx.assistantMessage.id,
@@ -262,12 +267,14 @@ export const layer: Layer.Layer<
case "reasoning-end": case "reasoning-end":
if (!(value.id in ctx.reasoningMap)) return if (!(value.id in ctx.reasoningMap)) return
// TODO(v2): Temporary dual-write while migrating session messages to v2 events. // TODO(v2): Temporary dual-write while migrating session messages to v2 events.
EventV2.run(SessionEvent.Reasoning.Ended.Sync, { if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) {
sessionID: ctx.sessionID, yield* sync.run(SessionEvent.Reasoning.Ended.Sync, {
reasoningID: value.id, sessionID: ctx.sessionID,
text: ctx.reasoningMap[value.id].text, reasoningID: value.id,
timestamp: DateTime.makeUnsafe(Date.now()), text: ctx.reasoningMap[value.id].text,
}) timestamp: DateTime.makeUnsafe(Date.now()),
})
}
// oxlint-disable-next-line no-self-assign -- reactivity trigger // oxlint-disable-next-line no-self-assign -- reactivity trigger
ctx.reasoningMap[value.id].text = ctx.reasoningMap[value.id].text ctx.reasoningMap[value.id].text = ctx.reasoningMap[value.id].text
ctx.reasoningMap[value.id].time = { ...ctx.reasoningMap[value.id].time, end: Date.now() } ctx.reasoningMap[value.id].time = { ...ctx.reasoningMap[value.id].time, end: Date.now() }
@@ -281,12 +288,14 @@ export const layer: Layer.Layer<
throw new Error(`Tool call not allowed while generating summary: ${value.toolName}`) throw new Error(`Tool call not allowed while generating summary: ${value.toolName}`)
} }
// TODO(v2): Temporary dual-write while migrating session messages to v2 events. // TODO(v2): Temporary dual-write while migrating session messages to v2 events.
EventV2.run(SessionEvent.Tool.Input.Started.Sync, { if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) {
sessionID: ctx.sessionID, yield* sync.run(SessionEvent.Tool.Input.Started.Sync, {
callID: value.id, sessionID: ctx.sessionID,
name: value.toolName, callID: value.id,
timestamp: DateTime.makeUnsafe(Date.now()), name: value.toolName,
}) timestamp: DateTime.makeUnsafe(Date.now()),
})
}
const part = yield* session.updatePart({ const part = yield* session.updatePart({
id: ctx.toolcalls[value.id]?.partID ?? PartID.ascending(), id: ctx.toolcalls[value.id]?.partID ?? PartID.ascending(),
messageID: ctx.assistantMessage.id, messageID: ctx.assistantMessage.id,
@@ -310,12 +319,14 @@ export const layer: Layer.Layer<
case "tool-input-end": { case "tool-input-end": {
// TODO(v2): Temporary dual-write while migrating session messages to v2 events. // TODO(v2): Temporary dual-write while migrating session messages to v2 events.
EventV2.run(SessionEvent.Tool.Input.Ended.Sync, { if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) {
sessionID: ctx.sessionID, yield* sync.run(SessionEvent.Tool.Input.Ended.Sync, {
callID: value.id, sessionID: ctx.sessionID,
text: "", callID: value.id,
timestamp: DateTime.makeUnsafe(Date.now()), text: "",
}) timestamp: DateTime.makeUnsafe(Date.now()),
})
}
return return
} }
@@ -325,17 +336,19 @@ export const layer: Layer.Layer<
} }
const toolCall = yield* readToolCall(value.toolCallId) const toolCall = yield* readToolCall(value.toolCallId)
// TODO(v2): Temporary dual-write while migrating session messages to v2 events. // TODO(v2): Temporary dual-write while migrating session messages to v2 events.
EventV2.run(SessionEvent.Tool.Called.Sync, { if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) {
sessionID: ctx.sessionID, yield* sync.run(SessionEvent.Tool.Called.Sync, {
callID: value.toolCallId, sessionID: ctx.sessionID,
tool: value.toolName, callID: value.toolCallId,
input: value.input, tool: value.toolName,
provider: { input: value.input,
executed: toolCall?.part.metadata?.providerExecuted === true, provider: {
...(value.providerMetadata ? { metadata: value.providerMetadata } : {}), executed: toolCall?.part.metadata?.providerExecuted === true,
}, ...(value.providerMetadata ? { metadata: value.providerMetadata } : {}),
timestamp: DateTime.makeUnsafe(Date.now()), },
}) timestamp: DateTime.makeUnsafe(Date.now()),
})
}
yield* updateToolCall(value.toolCallId, (match) => ({ yield* updateToolCall(value.toolCallId, (match) => ({
...match, ...match,
tool: value.toolName, tool: value.toolName,
@@ -405,27 +418,29 @@ export const layer: Layer.Layer<
attachments: attachments?.length ? attachments : undefined, attachments: attachments?.length ? attachments : undefined,
} }
// TODO(v2): Temporary dual-write while migrating session messages to v2 events. // TODO(v2): Temporary dual-write while migrating session messages to v2 events.
EventV2.run(SessionEvent.Tool.Success.Sync, { if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) {
sessionID: ctx.sessionID, yield* sync.run(SessionEvent.Tool.Success.Sync, {
callID: value.toolCallId, sessionID: ctx.sessionID,
structured: output.metadata, callID: value.toolCallId,
content: [ structured: output.metadata,
{ content: [
type: "text", {
text: output.output, type: "text",
text: output.output,
},
...(output.attachments?.map((item: MessageV2.FilePart) => ({
type: "file",
uri: item.url,
mime: item.mime,
name: item.filename,
})) ?? []),
],
provider: {
executed: toolCall?.part.metadata?.providerExecuted === true,
}, },
...(output.attachments?.map((item: MessageV2.FilePart) => ({ timestamp: DateTime.makeUnsafe(Date.now()),
type: "file", })
uri: item.url, }
mime: item.mime,
name: item.filename,
})) ?? []),
],
provider: {
executed: toolCall?.part.metadata?.providerExecuted === true,
},
timestamp: DateTime.makeUnsafe(Date.now()),
})
yield* completeToolCall(value.toolCallId, output) yield* completeToolCall(value.toolCallId, output)
return return
} }
@@ -433,18 +448,20 @@ export const layer: Layer.Layer<
case "tool-error": { case "tool-error": {
const toolCall = yield* readToolCall(value.toolCallId) const toolCall = yield* readToolCall(value.toolCallId)
// TODO(v2): Temporary dual-write while migrating session messages to v2 events. // TODO(v2): Temporary dual-write while migrating session messages to v2 events.
EventV2.run(SessionEvent.Tool.Failed.Sync, { if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) {
sessionID: ctx.sessionID, yield* sync.run(SessionEvent.Tool.Failed.Sync, {
callID: value.toolCallId, sessionID: ctx.sessionID,
error: { callID: value.toolCallId,
type: "unknown", error: {
message: errorMessage(value.error), type: "unknown",
}, message: errorMessage(value.error),
provider: { },
executed: toolCall?.part.metadata?.providerExecuted === true, provider: {
}, executed: toolCall?.part.metadata?.providerExecuted === true,
timestamp: DateTime.makeUnsafe(Date.now()), },
}) timestamp: DateTime.makeUnsafe(Date.now()),
})
}
yield* failToolCall(value.toolCallId, value.error) yield* failToolCall(value.toolCallId, value.error)
return return
} }
@@ -456,17 +473,19 @@ export const layer: Layer.Layer<
if (!ctx.snapshot) ctx.snapshot = yield* snapshot.track() if (!ctx.snapshot) ctx.snapshot = yield* snapshot.track()
if (!ctx.assistantMessage.summary) { if (!ctx.assistantMessage.summary) {
// TODO(v2): Temporary dual-write while migrating session messages to v2 events. // TODO(v2): Temporary dual-write while migrating session messages to v2 events.
EventV2.run(SessionEvent.Step.Started.Sync, { if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) {
sessionID: ctx.sessionID, yield* sync.run(SessionEvent.Step.Started.Sync, {
agent: input.assistantMessage.agent, sessionID: ctx.sessionID,
model: { agent: input.assistantMessage.agent,
id: Modelv2.ID.make(ctx.model.id), model: {
providerID: Modelv2.ProviderID.make(ctx.model.providerID), id: Modelv2.ID.make(ctx.model.id),
variant: Modelv2.VariantID.make(input.assistantMessage.variant ?? "default"), providerID: Modelv2.ProviderID.make(ctx.model.providerID),
}, variant: Modelv2.VariantID.make(input.assistantMessage.variant ?? "default"),
snapshot: ctx.snapshot, },
timestamp: DateTime.makeUnsafe(Date.now()), snapshot: ctx.snapshot,
}) timestamp: DateTime.makeUnsafe(Date.now()),
})
}
} }
yield* session.updatePart({ yield* session.updatePart({
id: PartID.ascending(), id: PartID.ascending(),
@@ -486,14 +505,16 @@ export const layer: Layer.Layer<
}) })
if (!ctx.assistantMessage.summary) { if (!ctx.assistantMessage.summary) {
// TODO(v2): Temporary dual-write while migrating session messages to v2 events. // TODO(v2): Temporary dual-write while migrating session messages to v2 events.
EventV2.run(SessionEvent.Step.Ended.Sync, { if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) {
sessionID: ctx.sessionID, yield* sync.run(SessionEvent.Step.Ended.Sync, {
finish: value.finishReason, sessionID: ctx.sessionID,
cost: usage.cost, finish: value.finishReason,
tokens: usage.tokens, cost: usage.cost,
snapshot: completedSnapshot, tokens: usage.tokens,
timestamp: DateTime.makeUnsafe(Date.now()), snapshot: completedSnapshot,
}) timestamp: DateTime.makeUnsafe(Date.now()),
})
}
} }
ctx.assistantMessage.finish = value.finishReason ctx.assistantMessage.finish = value.finishReason
ctx.assistantMessage.cost += usage.cost ctx.assistantMessage.cost += usage.cost
@@ -541,10 +562,12 @@ export const layer: Layer.Layer<
case "text-start": case "text-start":
if (!ctx.assistantMessage.summary) { if (!ctx.assistantMessage.summary) {
// TODO(v2): Temporary dual-write while migrating session messages to v2 events. // TODO(v2): Temporary dual-write while migrating session messages to v2 events.
EventV2.run(SessionEvent.Text.Started.Sync, { if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) {
sessionID: ctx.sessionID, yield* sync.run(SessionEvent.Text.Started.Sync, {
timestamp: DateTime.makeUnsafe(Date.now()), sessionID: ctx.sessionID,
}) timestamp: DateTime.makeUnsafe(Date.now()),
})
}
} }
ctx.currentText = { ctx.currentText = {
id: PartID.ascending(), id: PartID.ascending(),
@@ -586,11 +609,13 @@ export const layer: Layer.Layer<
)).text )).text
if (!ctx.assistantMessage.summary) { if (!ctx.assistantMessage.summary) {
// TODO(v2): Temporary dual-write while migrating session messages to v2 events. // TODO(v2): Temporary dual-write while migrating session messages to v2 events.
EventV2.run(SessionEvent.Text.Ended.Sync, { if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) {
sessionID: ctx.sessionID, yield* sync.run(SessionEvent.Text.Ended.Sync, {
text: ctx.currentText.text, sessionID: ctx.sessionID,
timestamp: DateTime.makeUnsafe(Date.now()), text: ctx.currentText.text,
}) timestamp: DateTime.makeUnsafe(Date.now()),
})
}
} }
{ {
const end = Date.now() const end = Date.now()
@@ -680,14 +705,16 @@ export const layer: Layer.Layer<
} }
if (!ctx.assistantMessage.summary) { if (!ctx.assistantMessage.summary) {
// TODO(v2): Temporary dual-write while migrating session messages to v2 events. // TODO(v2): Temporary dual-write while migrating session messages to v2 events.
EventV2.run(SessionEvent.Step.Failed.Sync, { if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) {
sessionID: ctx.sessionID, yield* sync.run(SessionEvent.Step.Failed.Sync, {
error: { sessionID: ctx.sessionID,
type: "unknown", error: {
message: errorMessage(e), type: "unknown",
}, message: errorMessage(e),
timestamp: DateTime.makeUnsafe(Date.now()), },
}) timestamp: DateTime.makeUnsafe(Date.now()),
})
}
} }
ctx.assistantMessage.error = error ctx.assistantMessage.error = error
yield* bus.publish(Session.Event.Error, { yield* bus.publish(Session.Event.Error, {
@@ -732,22 +759,28 @@ export const layer: Layer.Layer<
parse, parse,
set: (info) => { set: (info) => {
// TODO(v2): Temporary dual-write while migrating session messages to v2 events. // TODO(v2): Temporary dual-write while migrating session messages to v2 events.
EventV2.run(SessionEvent.Retried.Sync, { const event = Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM
sessionID: ctx.sessionID, ? sync.run(SessionEvent.Retried.Sync, {
attempt: info.attempt, sessionID: ctx.sessionID,
error: { attempt: info.attempt,
message: info.message, error: {
isRetryable: true, message: info.message,
}, isRetryable: true,
timestamp: DateTime.makeUnsafe(Date.now()), },
}) timestamp: DateTime.makeUnsafe(Date.now()),
return status.set(ctx.sessionID, { })
type: "retry", : Effect.void
attempt: info.attempt, return event.pipe(
message: info.message, Effect.andThen(
action: info.action, status.set(ctx.sessionID, {
next: info.next, type: "retry",
}) attempt: info.attempt,
message: info.message,
action: info.action,
next: info.next,
}),
),
)
}, },
}), }),
), ),
@@ -788,6 +821,7 @@ export const defaultLayer = Layer.suspend(() =>
Layer.provide(Image.defaultLayer), Layer.provide(Image.defaultLayer),
Layer.provide(Bus.layer), Layer.provide(Bus.layer),
Layer.provide(Config.defaultLayer), Layer.provide(Config.defaultLayer),
Layer.provide(SyncEvent.defaultLayer),
), ),
) )
+49 -47
View File
@@ -54,7 +54,7 @@ import { InstanceState } from "@/effect/instance-state"
import { TaskTool, type TaskPromptOps } from "@/tool/task" import { TaskTool, type TaskPromptOps } from "@/tool/task"
import { SessionRunState } from "./run-state" import { SessionRunState } from "./run-state"
import { EffectBridge } from "@/effect/bridge" import { EffectBridge } from "@/effect/bridge"
import { EventV2 } from "@/v2/event" import { SyncEvent } from "@/sync"
import { SessionEvent } from "@/v2/session-event" import { SessionEvent } from "@/v2/session-event"
import { Modelv2 } from "@/v2/model" import { Modelv2 } from "@/v2/model"
import { AgentAttachment, FileAttachment, Source } from "@/v2/session-prompt" import { AgentAttachment, FileAttachment, Source } from "@/v2/session-prompt"
@@ -118,6 +118,7 @@ export const layer = Layer.effect(
const summary = yield* SessionSummary.Service const summary = yield* SessionSummary.Service
const sys = yield* SystemPrompt.Service const sys = yield* SystemPrompt.Service
const llm = yield* LLM.Service const llm = yield* LLM.Service
const sync = yield* SyncEvent.Service
const runner = Effect.fn("SessionPrompt.runner")(function* () { const runner = Effect.fn("SessionPrompt.runner")(function* () {
return yield* EffectBridge.make() return yield* EffectBridge.make()
}) })
@@ -809,12 +810,14 @@ NOTE: At any point in time through this workflow you should feel free to ask the
}, },
} }
yield* sessions.updatePart(part) yield* sessions.updatePart(part)
EventV2.run(SessionEvent.Shell.Started.Sync, { if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) {
sessionID: input.sessionID, yield* sync.run(SessionEvent.Shell.Started.Sync, {
timestamp: DateTime.makeUnsafe(started), sessionID: input.sessionID,
callID, timestamp: DateTime.makeUnsafe(started),
command: input.command, callID,
}) command: input.command,
})
}
return { msg, part, cwd: ctx.directory } return { msg, part, cwd: ctx.directory }
}).pipe(Effect.ensuring(markReady)) }).pipe(Effect.ensuring(markReady))
@@ -830,12 +833,14 @@ NOTE: At any point in time through this workflow you should feel free to ask the
output += "\n\n" + ["<metadata>", "User aborted the command", "</metadata>"].join("\n") output += "\n\n" + ["<metadata>", "User aborted the command", "</metadata>"].join("\n")
} }
const completed = Date.now() const completed = Date.now()
EventV2.run(SessionEvent.Shell.Ended.Sync, { if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) {
sessionID: input.sessionID, yield* sync.run(SessionEvent.Shell.Ended.Sync, {
timestamp: DateTime.makeUnsafe(completed), sessionID: input.sessionID,
callID: part.callID, timestamp: DateTime.makeUnsafe(completed),
output, callID: part.callID,
}) output,
})
}
if (!msg.time.completed) { if (!msg.time.completed) {
msg.time.completed = completed msg.time.completed = completed
yield* sessions.updateMessage(msg) yield* sessions.updateMessage(msg)
@@ -975,34 +980,26 @@ NOTE: At any point in time through this workflow you should feel free to ask the
} }
if (current?.agent !== info.agent) { if (current?.agent !== info.agent) {
EventV2.run( yield* sync.run(SessionEvent.AgentSwitched.Sync, {
SessionEvent.AgentSwitched.Sync, sessionID: input.sessionID,
{ timestamp: DateTime.makeUnsafe(info.time.created),
sessionID: input.sessionID, agent: info.agent,
timestamp: DateTime.makeUnsafe(info.time.created), })
agent: info.agent,
},
{ bypassExperimentalEventSystem: true },
)
} }
if ( if (
current?.model?.providerID !== info.model.providerID || current?.model?.providerID !== info.model.providerID ||
current.model.id !== info.model.modelID || current.model.id !== info.model.modelID ||
(current.model.variant === "default" ? undefined : current.model.variant) !== info.model.variant (current.model.variant === "default" ? undefined : current.model.variant) !== info.model.variant
) { ) {
EventV2.run( yield* sync.run(SessionEvent.ModelSwitched.Sync, {
SessionEvent.ModelSwitched.Sync, sessionID: input.sessionID,
{ timestamp: DateTime.makeUnsafe(info.time.created),
sessionID: input.sessionID, model: {
timestamp: DateTime.makeUnsafe(info.time.created), id: Modelv2.ID.make(info.model.modelID),
model: { providerID: Modelv2.ProviderID.make(info.model.providerID),
id: Modelv2.ID.make(info.model.modelID), variant: Modelv2.VariantID.make(info.model.variant ?? "default"),
providerID: Modelv2.ProviderID.make(info.model.providerID),
variant: Modelv2.VariantID.make(info.model.variant ?? "default"),
},
}, },
{ bypassExperimentalEventSystem: true }, })
)
} }
yield* Effect.addFinalizer(() => instruction.clear(info.id)) yield* Effect.addFinalizer(() => instruction.clear(info.id))
@@ -1371,23 +1368,27 @@ NOTE: At any point in time through this workflow you should feel free to ask the
}, },
) )
// TODO(v2): Temporary dual-write while migrating session messages to v2 events. // TODO(v2): Temporary dual-write while migrating session messages to v2 events.
EventV2.run(SessionEvent.Prompted.Sync, { if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) {
sessionID: input.sessionID, yield* sync.run(SessionEvent.Prompted.Sync, {
timestamp: DateTime.makeUnsafe(info.time.created),
prompt: {
text: nextPrompt.text.join("\n"),
files: nextPrompt.files,
agents: nextPrompt.agents,
},
})
for (const text of nextPrompt.synthetic) {
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
EventV2.run(SessionEvent.Synthetic.Sync, {
sessionID: input.sessionID, sessionID: input.sessionID,
timestamp: DateTime.makeUnsafe(info.time.created), timestamp: DateTime.makeUnsafe(info.time.created),
text, prompt: {
text: nextPrompt.text.join("\n"),
files: nextPrompt.files,
agents: nextPrompt.agents,
},
}) })
} }
for (const text of nextPrompt.synthetic) {
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) {
yield* sync.run(SessionEvent.Synthetic.Sync, {
sessionID: input.sessionID,
timestamp: DateTime.makeUnsafe(info.time.created),
text,
})
}
}
return { info, parts } return { info, parts }
}, Effect.scoped) }, Effect.scoped)
@@ -1820,6 +1821,7 @@ export const defaultLayer = Layer.suspend(() =>
LLM.defaultLayer, LLM.defaultLayer,
Bus.layer, Bus.layer,
CrossSpawnSpawner.defaultLayer, CrossSpawnSpawner.defaultLayer,
SyncEvent.defaultLayer,
), ),
), ),
), ),
+13 -19
View File
@@ -12,6 +12,7 @@ import { SessionEvent } from "./session-event"
import { V2Schema } from "./schema" import { V2Schema } from "./schema"
import { optionalOmitUndefined } from "@opencode-ai/core/schema" import { optionalOmitUndefined } from "@opencode-ai/core/schema"
import { Modelv2 } from "./model" import { Modelv2 } from "./model"
import { SyncEvent } from "@/sync"
export const Delivery = Schema.Literals(["immediate", "deferred"]).annotate({ export const Delivery = Schema.Literals(["immediate", "deferred"]).annotate({
identifier: "Session.Delivery", identifier: "Session.Delivery",
@@ -113,6 +114,7 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/v2
export const layer = Layer.effect( export const layer = Layer.effect(
Service, Service,
Effect.gen(function* () { Effect.gen(function* () {
const sync = yield* SyncEvent.Service
const decodeMessage = Schema.decodeUnknownSync(SessionMessage.Message) const decodeMessage = Schema.decodeUnknownSync(SessionMessage.Message)
const decode = (row: typeof SessionMessageTable.$inferSelect) => const decode = (row: typeof SessionMessageTable.$inferSelect) =>
@@ -269,26 +271,18 @@ export const layer = Layer.effect(
shell: Effect.fn("V2Session.shell")(function* (_input) {}), shell: Effect.fn("V2Session.shell")(function* (_input) {}),
skill: Effect.fn("V2Session.skill")(function* (_input) {}), skill: Effect.fn("V2Session.skill")(function* (_input) {}),
switchAgent: Effect.fn("V2Session.switchAgent")(function* (input) { switchAgent: Effect.fn("V2Session.switchAgent")(function* (input) {
EventV2.run( yield* sync.run(SessionEvent.AgentSwitched.Sync, {
SessionEvent.AgentSwitched.Sync, sessionID: input.sessionID,
{ timestamp: DateTime.makeUnsafe(Date.now()),
sessionID: input.sessionID, agent: input.agent,
timestamp: DateTime.makeUnsafe(Date.now()), })
agent: input.agent,
},
{ bypassExperimentalEventSystem: true },
)
}), }),
switchModel: Effect.fn("V2Session.switchModel")(function* (input) { switchModel: Effect.fn("V2Session.switchModel")(function* (input) {
EventV2.run( yield* sync.run(SessionEvent.ModelSwitched.Sync, {
SessionEvent.ModelSwitched.Sync, sessionID: input.sessionID,
{ timestamp: DateTime.makeUnsafe(Date.now()),
sessionID: input.sessionID, model: input.model,
timestamp: DateTime.makeUnsafe(Date.now()), })
model: input.model,
},
{ bypassExperimentalEventSystem: true },
)
}), }),
subagent: Effect.fn("V2Session.subagent")(function* (input) { subagent: Effect.fn("V2Session.subagent")(function* (input) {
const parent = yield* result.get(input.parentID) const parent = yield* result.get(input.parentID)
@@ -319,6 +313,6 @@ export const layer = Layer.effect(
}), }),
) )
export const defaultLayer = layer export const defaultLayer = layer.pipe(Layer.provide(SyncEvent.defaultLayer))
export * as SessionV2 from "./session" export * as SessionV2 from "./session"
@@ -27,6 +27,7 @@ import { ProviderTest } from "../fake/provider"
import { testEffect } from "../lib/effect" import { testEffect } from "../lib/effect"
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
import { TestConfig } from "../fixture/config" import { TestConfig } from "../fixture/config"
import { SyncEvent } from "@/sync"
void Log.init({ print: false }) void Log.init({ print: false })
@@ -223,6 +224,7 @@ const deps = Layer.mergeAll(
Plugin.defaultLayer, Plugin.defaultLayer,
Bus.layer, Bus.layer,
Config.defaultLayer, Config.defaultLayer,
SyncEvent.defaultLayer,
) )
const env = Layer.mergeAll( const env = Layer.mergeAll(
@@ -269,6 +271,7 @@ function compactionProcessLayer(options?: CompactionProcessOptions) {
Layer.provide(status), Layer.provide(status),
Layer.provide(bus), Layer.provide(bus),
Layer.provide(options?.config ?? Config.defaultLayer), Layer.provide(options?.config ?? Config.defaultLayer),
Layer.provide(SyncEvent.defaultLayer),
) )
} }
@@ -24,6 +24,7 @@ import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
import { provideTmpdirServer } from "../fixture/fixture" import { provideTmpdirServer } from "../fixture/fixture"
import { testEffect } from "../lib/effect" import { testEffect } from "../lib/effect"
import { raw, reply, TestLLMServer } from "../lib/llm-server" import { raw, reply, TestLLMServer } from "../lib/llm-server"
import { SyncEvent } from "@/sync"
void Log.init({ print: false }) void Log.init({ print: false })
@@ -166,6 +167,7 @@ const deps = Layer.mergeAll(
LLM.defaultLayer, LLM.defaultLayer,
Provider.defaultLayer, Provider.defaultLayer,
status, status,
SyncEvent.defaultLayer,
).pipe(Layer.provideMerge(infra)) ).pipe(Layer.provideMerge(infra))
const env = Layer.mergeAll( const env = Layer.mergeAll(
TestLLMServer.layer, TestLLMServer.layer,
@@ -50,6 +50,7 @@ import { Reference } from "../../src/reference/reference"
import { provideTmpdirInstance, provideTmpdirServer } from "../fixture/fixture" import { provideTmpdirInstance, provideTmpdirServer } from "../fixture/fixture"
import { testEffect } from "../lib/effect" import { testEffect } from "../lib/effect"
import { reply, TestLLMServer } from "../lib/llm-server" import { reply, TestLLMServer } from "../lib/llm-server"
import { SyncEvent } from "@/sync"
void Log.init({ print: false }) void Log.init({ print: false })
@@ -174,6 +175,7 @@ function makeHttp() {
mcp, mcp,
AppFileSystem.defaultLayer, AppFileSystem.defaultLayer,
status, status,
SyncEvent.defaultLayer,
).pipe(Layer.provideMerge(infra)) ).pipe(Layer.provideMerge(infra))
const question = Question.layer.pipe(Layer.provideMerge(deps)) const question = Question.layer.pipe(Layer.provideMerge(deps))
const todo = Todo.layer.pipe(Layer.provideMerge(deps)) const todo = Todo.layer.pipe(Layer.provideMerge(deps))
@@ -58,6 +58,7 @@ import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
import { Ripgrep } from "../../src/file/ripgrep" import { Ripgrep } from "../../src/file/ripgrep"
import { Format } from "../../src/format" import { Format } from "../../src/format"
import { Reference } from "../../src/reference/reference" import { Reference } from "../../src/reference/reference"
import { SyncEvent } from "@/sync"
void Log.init({ print: false }) void Log.init({ print: false })
@@ -124,6 +125,7 @@ function makeHttp() {
mcp, mcp,
AppFileSystem.defaultLayer, AppFileSystem.defaultLayer,
status, status,
SyncEvent.defaultLayer,
).pipe(Layer.provideMerge(infra)) ).pipe(Layer.provideMerge(infra))
const question = Question.layer.pipe(Layer.provideMerge(deps)) const question = Question.layer.pipe(Layer.provideMerge(deps))
const todo = Todo.layer.pipe(Layer.provideMerge(deps)) const todo = Todo.layer.pipe(Layer.provideMerge(deps))