event.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. import { Schema } from "effect"
  2. import { ProviderMetadata, ToolContent } from "@opencode-ai/llm"
  3. import { EventV2 } from "../event"
  4. import { ModelV2 } from "../model"
  5. import { NonNegativeInt } from "../schema"
  6. import { V2Schema } from "../v2-schema"
  7. import { FileAttachment, Prompt } from "./prompt"
  8. import { SessionSchema } from "./schema"
  9. import { Location } from "../location"
  10. import { RelativePath } from "../schema"
  11. import { SessionMessageID } from "./message-id"
  12. export { FileAttachment }
  13. export const Source = Schema.Struct({
  14. start: NonNegativeInt,
  15. end: NonNegativeInt,
  16. text: Schema.String,
  17. }).annotate({
  18. identifier: "session.next.event.source",
  19. })
  20. export type Source = typeof Source.Type
  21. const Base = {
  22. timestamp: V2Schema.DateTimeUtcFromMillis,
  23. sessionID: SessionSchema.ID,
  24. }
  25. const options = {
  26. durable: {
  27. aggregate: "sessionID",
  28. version: 1,
  29. },
  30. } as const
  31. const stepSettlementOptions = {
  32. durable: {
  33. aggregate: "sessionID",
  34. version: 2,
  35. },
  36. } as const
  37. export const UnknownError = Schema.Struct({
  38. type: Schema.Literal("unknown"),
  39. message: Schema.String,
  40. }).annotate({
  41. identifier: "Session.Error.Unknown",
  42. })
  43. export type UnknownError = typeof UnknownError.Type
  44. export const AgentSwitched = EventV2.define({
  45. type: "session.next.agent.switched",
  46. ...options,
  47. schema: {
  48. ...Base,
  49. messageID: SessionMessageID.ID,
  50. agent: Schema.String,
  51. },
  52. })
  53. export type AgentSwitched = typeof AgentSwitched.Type
  54. export const ModelSwitched = EventV2.define({
  55. type: "session.next.model.switched",
  56. ...options,
  57. schema: {
  58. ...Base,
  59. messageID: SessionMessageID.ID,
  60. model: ModelV2.Ref,
  61. },
  62. })
  63. export type ModelSwitched = typeof ModelSwitched.Type
  64. export const Moved = EventV2.define({
  65. type: "session.next.moved",
  66. ...options,
  67. schema: {
  68. ...Base,
  69. location: Location.Ref,
  70. subdirectory: RelativePath.pipe(Schema.optional),
  71. },
  72. })
  73. export type Moved = typeof Moved.Type
  74. export const Prompted = EventV2.define({
  75. type: "session.next.prompted",
  76. ...options,
  77. schema: {
  78. ...Base,
  79. messageID: SessionMessageID.ID,
  80. prompt: Prompt,
  81. delivery: Schema.Literals(["steer", "queue"]),
  82. },
  83. })
  84. export type Prompted = typeof Prompted.Type
  85. export namespace PromptLifecycle {
  86. export const Admitted = EventV2.define({
  87. type: "session.next.prompt.admitted",
  88. ...options,
  89. schema: {
  90. ...Base,
  91. messageID: SessionMessageID.ID,
  92. prompt: Prompt,
  93. delivery: Schema.Literals(["steer", "queue"]),
  94. },
  95. })
  96. export type Admitted = typeof Admitted.Type
  97. export const Promoted = EventV2.define({
  98. type: "session.next.prompt.promoted",
  99. ...options,
  100. schema: {
  101. ...Base,
  102. messageID: SessionMessageID.ID,
  103. prompt: Prompt,
  104. timeCreated: V2Schema.DateTimeUtcFromMillis,
  105. },
  106. })
  107. export type Promoted = typeof Promoted.Type
  108. }
  109. export const InterruptRequested = EventV2.define({
  110. type: "session.next.interrupt.requested",
  111. ...options,
  112. schema: Base,
  113. })
  114. export type InterruptRequested = typeof InterruptRequested.Type
  115. export const ContextUpdated = EventV2.define({
  116. type: "session.next.context.updated",
  117. ...options,
  118. schema: {
  119. ...Base,
  120. messageID: SessionMessageID.ID,
  121. text: Schema.String,
  122. },
  123. })
  124. export type ContextUpdated = typeof ContextUpdated.Type
  125. export const Synthetic = EventV2.define({
  126. type: "session.next.synthetic",
  127. ...options,
  128. schema: {
  129. ...Base,
  130. messageID: SessionMessageID.ID,
  131. text: Schema.String,
  132. },
  133. })
  134. export type Synthetic = typeof Synthetic.Type
  135. export namespace Shell {
  136. export const Started = EventV2.define({
  137. type: "session.next.shell.started",
  138. ...options,
  139. schema: {
  140. ...Base,
  141. messageID: SessionMessageID.ID,
  142. callID: Schema.String,
  143. command: Schema.String,
  144. },
  145. })
  146. export type Started = typeof Started.Type
  147. export const Ended = EventV2.define({
  148. type: "session.next.shell.ended",
  149. ...options,
  150. schema: {
  151. ...Base,
  152. callID: Schema.String,
  153. output: Schema.String,
  154. },
  155. })
  156. export type Ended = typeof Ended.Type
  157. }
  158. export namespace Step {
  159. export const Started = EventV2.define({
  160. type: "session.next.step.started",
  161. ...options,
  162. schema: {
  163. ...Base,
  164. assistantMessageID: SessionMessageID.ID,
  165. agent: Schema.String,
  166. model: ModelV2.Ref,
  167. snapshot: Schema.String.pipe(Schema.optional),
  168. },
  169. })
  170. export type Started = typeof Started.Type
  171. export const Ended = EventV2.define({
  172. type: "session.next.step.ended",
  173. ...stepSettlementOptions,
  174. schema: {
  175. ...Base,
  176. assistantMessageID: SessionMessageID.ID,
  177. finish: Schema.String,
  178. cost: Schema.Finite,
  179. tokens: Schema.Struct({
  180. input: Schema.Finite,
  181. output: Schema.Finite,
  182. reasoning: Schema.Finite,
  183. cache: Schema.Struct({
  184. read: Schema.Finite,
  185. write: Schema.Finite,
  186. }),
  187. }),
  188. snapshot: Schema.String.pipe(Schema.optional),
  189. },
  190. })
  191. export type Ended = typeof Ended.Type
  192. export const Failed = EventV2.define({
  193. type: "session.next.step.failed",
  194. ...stepSettlementOptions,
  195. schema: {
  196. ...Base,
  197. assistantMessageID: SessionMessageID.ID,
  198. error: UnknownError,
  199. },
  200. })
  201. export type Failed = typeof Failed.Type
  202. }
  203. export namespace Text {
  204. export const Started = EventV2.define({
  205. type: "session.next.text.started",
  206. ...options,
  207. schema: {
  208. ...Base,
  209. assistantMessageID: SessionMessageID.ID,
  210. textID: Schema.String,
  211. },
  212. })
  213. export type Started = typeof Started.Type
  214. // Stream fragments are live-only; Text.Ended is the replayable full-value boundary.
  215. export const Delta = EventV2.define({
  216. type: "session.next.text.delta",
  217. schema: {
  218. ...Base,
  219. assistantMessageID: SessionMessageID.ID,
  220. textID: Schema.String,
  221. delta: Schema.String,
  222. },
  223. })
  224. export type Delta = typeof Delta.Type
  225. export const Ended = EventV2.define({
  226. type: "session.next.text.ended",
  227. ...options,
  228. schema: {
  229. ...Base,
  230. assistantMessageID: SessionMessageID.ID,
  231. textID: Schema.String,
  232. text: Schema.String,
  233. },
  234. })
  235. export type Ended = typeof Ended.Type
  236. }
  237. export namespace Reasoning {
  238. export const Started = EventV2.define({
  239. type: "session.next.reasoning.started",
  240. ...options,
  241. schema: {
  242. ...Base,
  243. assistantMessageID: SessionMessageID.ID,
  244. reasoningID: Schema.String,
  245. providerMetadata: ProviderMetadata.pipe(Schema.optional),
  246. },
  247. })
  248. export type Started = typeof Started.Type
  249. // Stream fragments are live-only; Reasoning.Ended is the replayable full-value boundary.
  250. export const Delta = EventV2.define({
  251. type: "session.next.reasoning.delta",
  252. schema: {
  253. ...Base,
  254. assistantMessageID: SessionMessageID.ID,
  255. reasoningID: Schema.String,
  256. delta: Schema.String,
  257. },
  258. })
  259. export type Delta = typeof Delta.Type
  260. export const Ended = EventV2.define({
  261. type: "session.next.reasoning.ended",
  262. ...options,
  263. schema: {
  264. ...Base,
  265. assistantMessageID: SessionMessageID.ID,
  266. reasoningID: Schema.String,
  267. text: Schema.String,
  268. providerMetadata: ProviderMetadata.pipe(Schema.optional),
  269. },
  270. })
  271. export type Ended = typeof Ended.Type
  272. }
  273. export namespace Tool {
  274. const ToolBase = {
  275. ...Base,
  276. assistantMessageID: SessionMessageID.ID,
  277. callID: Schema.String,
  278. }
  279. export namespace Input {
  280. export const Started = EventV2.define({
  281. type: "session.next.tool.input.started",
  282. ...options,
  283. schema: {
  284. ...ToolBase,
  285. name: Schema.String,
  286. },
  287. })
  288. export type Started = typeof Started.Type
  289. // Stream fragments are live-only; Input.Ended is the replayable raw-input boundary.
  290. export const Delta = EventV2.define({
  291. type: "session.next.tool.input.delta",
  292. schema: {
  293. ...ToolBase,
  294. delta: Schema.String,
  295. },
  296. })
  297. export type Delta = typeof Delta.Type
  298. export const Ended = EventV2.define({
  299. type: "session.next.tool.input.ended",
  300. ...options,
  301. schema: {
  302. ...ToolBase,
  303. text: Schema.String,
  304. },
  305. })
  306. export type Ended = typeof Ended.Type
  307. }
  308. export const Called = EventV2.define({
  309. type: "session.next.tool.called",
  310. ...options,
  311. schema: {
  312. ...ToolBase,
  313. tool: Schema.String,
  314. input: Schema.Record(Schema.String, Schema.Unknown),
  315. provider: Schema.Struct({
  316. executed: Schema.Boolean,
  317. metadata: ProviderMetadata.pipe(Schema.optional),
  318. }),
  319. },
  320. })
  321. export type Called = typeof Called.Type
  322. /**
  323. * Replayable bounded running-tool state. Tools should checkpoint semantic
  324. * transitions or at a bounded cadence, not persist every stdout/stderr chunk.
  325. */
  326. export const Progress = EventV2.define({
  327. type: "session.next.tool.progress",
  328. ...options,
  329. schema: {
  330. ...ToolBase,
  331. structured: Schema.Record(Schema.String, Schema.Any),
  332. content: Schema.Array(ToolContent),
  333. },
  334. })
  335. export type Progress = typeof Progress.Type
  336. export const Success = EventV2.define({
  337. type: "session.next.tool.success",
  338. ...options,
  339. schema: {
  340. ...ToolBase,
  341. structured: Schema.Record(Schema.String, Schema.Any),
  342. content: Schema.Array(ToolContent),
  343. outputPaths: Schema.Array(Schema.String).pipe(Schema.optional),
  344. result: Schema.Unknown.pipe(Schema.optional),
  345. provider: Schema.Struct({
  346. executed: Schema.Boolean,
  347. metadata: ProviderMetadata.pipe(Schema.optional),
  348. }),
  349. },
  350. })
  351. export type Success = typeof Success.Type
  352. export const Failed = EventV2.define({
  353. type: "session.next.tool.failed",
  354. ...options,
  355. schema: {
  356. ...ToolBase,
  357. error: UnknownError,
  358. result: Schema.Unknown.pipe(Schema.optional),
  359. provider: Schema.Struct({
  360. executed: Schema.Boolean,
  361. metadata: ProviderMetadata.pipe(Schema.optional),
  362. }),
  363. },
  364. })
  365. export type Failed = typeof Failed.Type
  366. }
  367. export const RetryError = Schema.Struct({
  368. message: Schema.String,
  369. statusCode: Schema.Finite.pipe(Schema.optional),
  370. isRetryable: Schema.Boolean,
  371. responseHeaders: Schema.Record(Schema.String, Schema.String).pipe(Schema.optional),
  372. responseBody: Schema.String.pipe(Schema.optional),
  373. metadata: Schema.Record(Schema.String, Schema.String).pipe(Schema.optional),
  374. }).annotate({
  375. identifier: "session.next.retry_error",
  376. })
  377. export type RetryError = typeof RetryError.Type
  378. export const Retried = EventV2.define({
  379. type: "session.next.retried",
  380. ...options,
  381. schema: {
  382. ...Base,
  383. attempt: Schema.Finite,
  384. error: RetryError,
  385. },
  386. })
  387. export type Retried = typeof Retried.Type
  388. export namespace Compaction {
  389. export const Started = EventV2.define({
  390. type: "session.next.compaction.started",
  391. ...options,
  392. schema: {
  393. ...Base,
  394. messageID: SessionMessageID.ID,
  395. reason: Schema.Union([Schema.Literal("auto"), Schema.Literal("manual")]),
  396. },
  397. })
  398. export type Started = typeof Started.Type
  399. export const Delta = EventV2.define({
  400. type: "session.next.compaction.delta",
  401. schema: {
  402. ...Base,
  403. messageID: SessionMessageID.ID,
  404. text: Schema.String,
  405. },
  406. })
  407. export type Delta = typeof Delta.Type
  408. // Retain the unpublished v1 decoder so stored beta events remain replayable.
  409. export const EndedV1 = EventV2.define({
  410. type: "session.next.compaction.ended",
  411. ...options,
  412. schema: {
  413. ...Base,
  414. text: Schema.String,
  415. include: Schema.String.pipe(Schema.optional),
  416. },
  417. })
  418. export const Ended = EventV2.define({
  419. type: "session.next.compaction.ended",
  420. durable: { aggregate: "sessionID", version: 2 },
  421. schema: {
  422. ...Base,
  423. messageID: SessionMessageID.ID,
  424. reason: Started.data.fields.reason,
  425. text: Schema.String,
  426. recent: Schema.String,
  427. },
  428. })
  429. export type Ended = typeof Ended.Type
  430. }
  431. const DurableDefinitions = [
  432. AgentSwitched,
  433. ModelSwitched,
  434. Moved,
  435. Prompted,
  436. PromptLifecycle.Admitted,
  437. PromptLifecycle.Promoted,
  438. InterruptRequested,
  439. ContextUpdated,
  440. Synthetic,
  441. Shell.Started,
  442. Shell.Ended,
  443. Step.Started,
  444. Step.Ended,
  445. Step.Failed,
  446. Text.Started,
  447. Text.Ended,
  448. Tool.Input.Started,
  449. Tool.Input.Ended,
  450. Tool.Called,
  451. Tool.Progress,
  452. Tool.Success,
  453. Tool.Failed,
  454. Reasoning.Started,
  455. Reasoning.Ended,
  456. Retried,
  457. Compaction.Started,
  458. Compaction.Ended,
  459. ] as const
  460. const EphemeralDefinitions = [Text.Delta, Tool.Input.Delta, Reasoning.Delta, Compaction.Delta] as const
  461. export const Durable = Schema.Union(DurableDefinitions, { mode: "oneOf" }).pipe(Schema.toTaggedUnion("type"))
  462. export type DurableEvent = typeof Durable.Type
  463. export const All = Schema.Union([...DurableDefinitions, ...EphemeralDefinitions], { mode: "oneOf" }).pipe(
  464. Schema.toTaggedUnion("type"),
  465. )
  466. export type Event = typeof All.Type
  467. export type Type = Event["type"]
  468. export * as SessionEvent from "./event"