From 459af775e8b5df120d2b18a83674a4186ac751e9 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Fri, 24 Apr 2026 18:19:59 -0400 Subject: [PATCH] fix session event typechecks and shell cwd --- packages/opencode/src/session/projectors.ts | 1 + packages/opencode/src/session/prompt.ts | 6 +- packages/opencode/src/session/schema.ts | 1 + packages/opencode/src/v2/session-event.ts | 590 +++++++----------- .../session/session-entry-stepper.test.ts | 159 +++-- 5 files changed, 323 insertions(+), 434 deletions(-) diff --git a/packages/opencode/src/session/projectors.ts b/packages/opencode/src/session/projectors.ts index 3a5fd0d8c9..cfa2c2835a 100644 --- a/packages/opencode/src/session/projectors.ts +++ b/packages/opencode/src/session/projectors.ts @@ -2,6 +2,7 @@ import { NotFoundError, eq, and } from "../storage" import { SyncEvent } from "@/sync" import * as Session from "./session" import { MessageV2 } from "./message-v2" +import "../v2/session-event" import { SessionTable, MessageTable, PartTable } from "./session.sql" import { Log } from "../util" diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 5f3530bcef..0476159055 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -795,7 +795,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the "-l", "-c", ` - __oc_cwd=$PWD + __oc_cwd=$OPENCODE_CWD [[ -f ~/.zshenv ]] && source ~/.zshenv >/dev/null 2>&1 || true [[ -f "\${ZDOTDIR:-$HOME}/.zshrc" ]] && source "\${ZDOTDIR:-$HOME}/.zshrc" >/dev/null 2>&1 || true cd "$__oc_cwd" @@ -808,7 +808,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the "-l", "-c", ` - __oc_cwd=$PWD + __oc_cwd=$OPENCODE_CWD shopt -s expand_aliases [[ -f ~/.bashrc ]] && source ~/.bashrc >/dev/null 2>&1 || true cd "$__oc_cwd" @@ -833,7 +833,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the const cmd = ChildProcess.make(sh, args, { cwd, extendEnv: true, - env: { ...shellEnv.env, TERM: "dumb" }, + env: { ...shellEnv.env, OPENCODE_CWD: cwd, TERM: "dumb" }, stdin: "ignore", forceKillAfter: "3 seconds", }) diff --git a/packages/opencode/src/session/schema.ts b/packages/opencode/src/session/schema.ts index 487cbcd34a..a59625092e 100644 --- a/packages/opencode/src/session/schema.ts +++ b/packages/opencode/src/session/schema.ts @@ -8,6 +8,7 @@ export const SessionID = Schema.String.annotate({ [ZodOverride]: Identifier.sche Schema.brand("SessionID"), withStatics((s) => ({ descending: (id?: string) => s.make(Identifier.descending("session", id)), + empty: () => s.make("ses_empty"), zod: zod(s), })), ) diff --git a/packages/opencode/src/v2/session-event.ts b/packages/opencode/src/v2/session-event.ts index f922becf3a..49ec74b88b 100644 --- a/packages/opencode/src/v2/session-event.ts +++ b/packages/opencode/src/v2/session-event.ts @@ -1,127 +1,132 @@ import { Identifier } from "@/id/id" import { withStatics } from "@/util/schema" -import * as DateTime from "effect/DateTime" import { Schema } from "effect" +import { SyncEvent } from "@/sync" +import { SessionID } from "@/session/schema" +import * as DateTime from "effect/DateTime" -export namespace SessionEvent { - export const ID = Schema.String.pipe( - Schema.brand("Session.Event.ID"), - withStatics((s) => ({ - create: () => s.make(Identifier.create("evt", "ascending")), - })), - ) - export type ID = Schema.Schema.Type - type Stamp = Schema.Schema.Type - type BaseInput = { - id?: ID - metadata?: Record - timestamp?: Stamp +export const ID = Schema.String.pipe( + Schema.brand("Session.Event.ID"), + withStatics((s) => ({ + create: () => s.make(Identifier.create("evt", "ascending")), + })), +) +export type ID = Schema.Schema.Type +type Stamp = Schema.Schema.Type +type BaseInput = { + id?: ID + sessionID: SessionID + metadata?: Record + timestamp?: Stamp +} + +function defineEvent(identifier: string) { + return (input: { + type: Type + schema: Fields + version?: number + }) => { + const RawEvent = Schema.Class(identifier)({ + id: ID, + sessionID: SessionID, + metadata: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional), + timestamp: Schema.DateTimeUtc, + type: Schema.Literal(input.type), + ...input.schema, + }) + const Event = RawEvent as Exclude + + const Sync = SyncEvent.define({ + type: input.type, + version: input.version ?? 1, + aggregate: "sessionID", + schema: Event, + }) + + return Object.assign(Event, { + Sync, + create(value: BaseInput & Record) { + return new (Event as unknown as new (value: Record) => Self)({ + ...value, + id: value.id ?? ID.create(), + sessionID: value.sessionID, + timestamp: value.timestamp ?? DateTime.makeUnsafe(Date.now()), + type: input.type, + }) + }, + }) } +} - const Base = { - id: ID, - metadata: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional), - timestamp: Schema.DateTimeUtc, +export class Source extends Schema.Class("Session.Event.Source")({ + start: Schema.Number, + end: Schema.Number, + text: Schema.String, +}) {} + +export class FileAttachment extends Schema.Class("Session.Event.FileAttachment")({ + uri: Schema.String, + mime: Schema.String, + name: Schema.String.pipe(Schema.optional), + description: Schema.String.pipe(Schema.optional), + source: Source.pipe(Schema.optional), +}) { + static create(input: FileAttachment) { + return new FileAttachment({ + uri: input.uri, + mime: input.mime, + name: input.name, + description: input.description, + source: input.source, + }) } +} - export class Source extends Schema.Class("Session.Event.Source")({ - start: Schema.Number, - end: Schema.Number, - text: Schema.String, - }) {} +export class AgentAttachment extends Schema.Class("Session.Event.AgentAttachment")({ + name: Schema.String, + source: Source.pipe(Schema.optional), +}) {} - export class FileAttachment extends Schema.Class("Session.Event.FileAttachment")({ - uri: Schema.String, - mime: Schema.String, - name: Schema.String.pipe(Schema.optional), - description: Schema.String.pipe(Schema.optional), - source: Source.pipe(Schema.optional), - }) { - static create(input: FileAttachment) { - return new FileAttachment({ - uri: input.uri, - mime: input.mime, - name: input.name, - description: input.description, - source: input.source, - }) - } - } +export class RetryError extends Schema.Class("Session.Event.Retry.Error")({ + message: Schema.String, + statusCode: Schema.Number.pipe(Schema.optional), + isRetryable: Schema.Boolean, + responseHeaders: Schema.Record(Schema.String, Schema.String).pipe(Schema.optional), + responseBody: Schema.String.pipe(Schema.optional), + metadata: Schema.Record(Schema.String, Schema.String).pipe(Schema.optional), +}) {} - export class AgentAttachment extends Schema.Class("Session.Event.AgentAttachment")({ - name: Schema.String, - source: Source.pipe(Schema.optional), - }) {} - - export class RetryError extends Schema.Class("Session.Event.Retry.Error")({ - message: Schema.String, - statusCode: Schema.Number.pipe(Schema.optional), - isRetryable: Schema.Boolean, - responseHeaders: Schema.Record(Schema.String, Schema.String).pipe(Schema.optional), - responseBody: Schema.String.pipe(Schema.optional), - metadata: Schema.Record(Schema.String, Schema.String).pipe(Schema.optional), - }) {} - - export class Prompt extends Schema.Class("Session.Event.Prompt")({ - ...Base, - type: Schema.Literal("prompt"), +export class Prompt extends defineEvent("Session.Event.Prompt")({ + type: "prompt", + schema: { text: Schema.String, files: Schema.Array(FileAttachment).pipe(Schema.optional), agents: Schema.Array(AgentAttachment).pipe(Schema.optional), - }) { - static create(input: BaseInput & { text: string; files?: FileAttachment[]; agents?: AgentAttachment[] }) { - return new Prompt({ - id: input.id ?? ID.create(), - type: "prompt", - timestamp: input.timestamp ?? DateTime.makeUnsafe(Date.now()), - metadata: input.metadata, - text: input.text, - files: input.files, - agents: input.agents, - }) - } - } + }, +}) {} - export class Synthetic extends Schema.Class("Session.Event.Synthetic")({ - ...Base, - type: Schema.Literal("synthetic"), +export class Synthetic extends defineEvent("Session.Event.Synthetic")({ + type: "synthetic", + schema: { text: Schema.String, - }) { - static create(input: BaseInput & { text: string }) { - return new Synthetic({ - id: input.id ?? ID.create(), - type: "synthetic", - timestamp: input.timestamp ?? DateTime.makeUnsafe(Date.now()), - metadata: input.metadata, - text: input.text, - }) - } - } + }, +}) {} - export namespace Step { - export class Started extends Schema.Class("Session.Event.Step.Started")({ - ...Base, - type: Schema.Literal("step.started"), +export namespace Step { + export class Started extends defineEvent("Session.Event.Step.Started")({ + type: "step.started", + schema: { model: Schema.Struct({ id: Schema.String, providerID: Schema.String, variant: Schema.String.pipe(Schema.optional), }), - }) { - static create(input: BaseInput & { model: { id: string; providerID: string; variant?: string } }) { - return new Started({ - id: input.id ?? ID.create(), - type: "step.started", - timestamp: input.timestamp ?? DateTime.makeUnsafe(Date.now()), - metadata: input.metadata, - model: input.model, - }) - } - } + }, + }) {} - export class Ended extends Schema.Class("Session.Event.Step.Ended")({ - ...Base, - type: Schema.Literal("step.ended"), + export class Ended extends defineEvent("Session.Event.Step.Ended")({ + type: "step.ended", + schema: { reason: Schema.String, cost: Schema.Number, tokens: Schema.Struct({ @@ -133,177 +138,82 @@ export namespace SessionEvent { write: Schema.Number, }), }), - }) { - static create(input: BaseInput & { reason: string; cost: number; tokens: Ended["tokens"] }) { - return new Ended({ - id: input.id ?? ID.create(), - type: "step.ended", - timestamp: input.timestamp ?? DateTime.makeUnsafe(Date.now()), - metadata: input.metadata, - reason: input.reason, - cost: input.cost, - tokens: input.tokens, - }) - } - } - } + }, + }) {} +} - export namespace Text { - export class Started extends Schema.Class("Session.Event.Text.Started")({ - ...Base, - type: Schema.Literal("text.started"), - }) { - static create(input: BaseInput = {}) { - return new Started({ - id: input.id ?? ID.create(), - type: "text.started", - timestamp: input.timestamp ?? DateTime.makeUnsafe(Date.now()), - metadata: input.metadata, - }) - } - } +export namespace Text { + export class Started extends defineEvent("Session.Event.Text.Started")({ + type: "text.started", + schema: {}, + }) {} - export class Delta extends Schema.Class("Session.Event.Text.Delta")({ - ...Base, - type: Schema.Literal("text.delta"), + export class Delta extends defineEvent("Session.Event.Text.Delta")({ + type: "text.delta", + schema: { delta: Schema.String, - }) { - static create(input: BaseInput & { delta: string }) { - return new Delta({ - id: input.id ?? ID.create(), - type: "text.delta", - timestamp: input.timestamp ?? DateTime.makeUnsafe(Date.now()), - metadata: input.metadata, - delta: input.delta, - }) - } - } + }, + }) {} - export class Ended extends Schema.Class("Session.Event.Text.Ended")({ - ...Base, - type: Schema.Literal("text.ended"), + export class Ended extends defineEvent("Session.Event.Text.Ended")({ + type: "text.ended", + schema: { text: Schema.String, - }) { - static create(input: BaseInput & { text: string }) { - return new Ended({ - id: input.id ?? ID.create(), - type: "text.ended", - timestamp: input.timestamp ?? DateTime.makeUnsafe(Date.now()), - metadata: input.metadata, - text: input.text, - }) - } - } - } + }, + }) {} +} - export namespace Reasoning { - export class Started extends Schema.Class("Session.Event.Reasoning.Started")({ - ...Base, - type: Schema.Literal("reasoning.started"), - }) { - static create(input: BaseInput = {}) { - return new Started({ - id: input.id ?? ID.create(), - type: "reasoning.started", - timestamp: input.timestamp ?? DateTime.makeUnsafe(Date.now()), - metadata: input.metadata, - }) - } - } +export namespace Reasoning { + export class Started extends defineEvent("Session.Event.Reasoning.Started")({ + type: "reasoning.started", + schema: {}, + }) {} - export class Delta extends Schema.Class("Session.Event.Reasoning.Delta")({ - ...Base, - type: Schema.Literal("reasoning.delta"), + export class Delta extends defineEvent("Session.Event.Reasoning.Delta")({ + type: "reasoning.delta", + schema: { delta: Schema.String, - }) { - static create(input: BaseInput & { delta: string }) { - return new Delta({ - id: input.id ?? ID.create(), - type: "reasoning.delta", - timestamp: input.timestamp ?? DateTime.makeUnsafe(Date.now()), - metadata: input.metadata, - delta: input.delta, - }) - } - } + }, + }) {} - export class Ended extends Schema.Class("Session.Event.Reasoning.Ended")({ - ...Base, - type: Schema.Literal("reasoning.ended"), + export class Ended extends defineEvent("Session.Event.Reasoning.Ended")({ + type: "reasoning.ended", + schema: { text: Schema.String, - }) { - static create(input: BaseInput & { text: string }) { - return new Ended({ - id: input.id ?? ID.create(), - type: "reasoning.ended", - timestamp: input.timestamp ?? DateTime.makeUnsafe(Date.now()), - metadata: input.metadata, - text: input.text, - }) - } - } - } + }, + }) {} +} - export namespace Tool { - export namespace Input { - export class Started extends Schema.Class("Session.Event.Tool.Input.Started")({ - ...Base, +export namespace Tool { + export namespace Input { + export class Started extends defineEvent("Session.Event.Tool.Input.Started")({ + type: "tool.input.started", + schema: { callID: Schema.String, name: Schema.String, - type: Schema.Literal("tool.input.started"), - }) { - static create(input: BaseInput & { callID: string; name: string }) { - return new Started({ - id: input.id ?? ID.create(), - type: "tool.input.started", - timestamp: input.timestamp ?? DateTime.makeUnsafe(Date.now()), - metadata: input.metadata, - callID: input.callID, - name: input.name, - }) - } - } + }, + }) {} - export class Delta extends Schema.Class("Session.Event.Tool.Input.Delta")({ - ...Base, + export class Delta extends defineEvent("Session.Event.Tool.Input.Delta")({ + type: "tool.input.delta", + schema: { callID: Schema.String, - type: Schema.Literal("tool.input.delta"), delta: Schema.String, - }) { - static create(input: BaseInput & { callID: string; delta: string }) { - return new Delta({ - id: input.id ?? ID.create(), - type: "tool.input.delta", - timestamp: input.timestamp ?? DateTime.makeUnsafe(Date.now()), - metadata: input.metadata, - callID: input.callID, - delta: input.delta, - }) - } - } + }, + }) {} - export class Ended extends Schema.Class("Session.Event.Tool.Input.Ended")({ - ...Base, + export class Ended extends defineEvent("Session.Event.Tool.Input.Ended")({ + type: "tool.input.ended", + schema: { callID: Schema.String, - type: Schema.Literal("tool.input.ended"), text: Schema.String, - }) { - static create(input: BaseInput & { callID: string; text: string }) { - return new Ended({ - id: input.id ?? ID.create(), - type: "tool.input.ended", - timestamp: input.timestamp ?? DateTime.makeUnsafe(Date.now()), - metadata: input.metadata, - callID: input.callID, - text: input.text, - }) - } - } - } + }, + }) {} + } - export class Called extends Schema.Class("Session.Event.Tool.Called")({ - ...Base, - type: Schema.Literal("tool.called"), + export class Called extends defineEvent("Session.Event.Tool.Called")({ + type: "tool.called", + schema: { callID: Schema.String, tool: Schema.String, input: Schema.Record(Schema.String, Schema.Unknown), @@ -311,31 +221,12 @@ export namespace SessionEvent { executed: Schema.Boolean, metadata: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional), }), - }) { - static create( - input: BaseInput & { - callID: string - tool: string - input: Record - provider: Called["provider"] - }, - ) { - return new Called({ - id: input.id ?? ID.create(), - type: "tool.called", - timestamp: input.timestamp ?? DateTime.makeUnsafe(Date.now()), - metadata: input.metadata, - callID: input.callID, - tool: input.tool, - input: input.input, - provider: input.provider, - }) - } - } + }, + }) {} - export class Success extends Schema.Class("Session.Event.Tool.Success")({ - ...Base, - type: Schema.Literal("tool.success"), + export class Success extends defineEvent("Session.Event.Tool.Success")({ + type: "tool.success", + schema: { callID: Schema.String, title: Schema.String, output: Schema.String.pipe(Schema.optional), @@ -344,115 +235,64 @@ export namespace SessionEvent { executed: Schema.Boolean, metadata: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional), }), - }) { - static create( - input: BaseInput & { - callID: string - title: string - output?: string - attachments?: FileAttachment[] - provider: Success["provider"] - }, - ) { - return new Success({ - id: input.id ?? ID.create(), - type: "tool.success", - timestamp: input.timestamp ?? DateTime.makeUnsafe(Date.now()), - metadata: input.metadata, - callID: input.callID, - title: input.title, - output: input.output, - attachments: input.attachments, - provider: input.provider, - }) - } - } + }, + }) {} - export class Error extends Schema.Class("Session.Event.Tool.Error")({ - ...Base, - type: Schema.Literal("tool.error"), + export class Error extends defineEvent("Session.Event.Tool.Error")({ + type: "tool.error", + schema: { callID: Schema.String, error: Schema.String, provider: Schema.Struct({ executed: Schema.Boolean, metadata: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional), }), - }) { - static create(input: BaseInput & { callID: string; error: string; provider: Error["provider"] }) { - return new Error({ - id: input.id ?? ID.create(), - type: "tool.error", - timestamp: input.timestamp ?? DateTime.makeUnsafe(Date.now()), - metadata: input.metadata, - callID: input.callID, - error: input.error, - provider: input.provider, - }) - } - } - } + }, + }) {} +} - export class Retried extends Schema.Class("Session.Event.Retried")({ - ...Base, - type: Schema.Literal("retried"), +export class Retried extends defineEvent("Session.Event.Retried")({ + type: "retried", + schema: { attempt: Schema.Number, error: RetryError, - }) { - static create(input: BaseInput & { attempt: number; error: RetryError }) { - return new Retried({ - id: input.id ?? ID.create(), - type: "retried", - timestamp: input.timestamp ?? DateTime.makeUnsafe(Date.now()), - metadata: input.metadata, - attempt: input.attempt, - error: input.error, - }) - } - } + }, +}) {} - export class Compacted extends Schema.Class("Session.Event.Compated")({ - ...Base, - type: Schema.Literal("compacted"), +export class Compacted extends defineEvent("Session.Event.Compacted")({ + type: "compacted", + schema: { auto: Schema.Boolean, overflow: Schema.Boolean.pipe(Schema.optional), - }) { - static create(input: BaseInput & { auto: boolean; overflow?: boolean }) { - return new Compacted({ - id: input.id ?? ID.create(), - type: "compacted", - timestamp: input.timestamp ?? DateTime.makeUnsafe(Date.now()), - metadata: input.metadata, - auto: input.auto, - overflow: input.overflow, - }) - } - } + }, +}) {} - export const Event = Schema.Union( - [ - Prompt, - Synthetic, - Step.Started, - Step.Ended, - Text.Started, - Text.Delta, - Text.Ended, - Tool.Input.Started, - Tool.Input.Delta, - Tool.Input.Ended, - Tool.Called, - Tool.Success, - Tool.Error, - Reasoning.Started, - Reasoning.Delta, - Reasoning.Ended, - Retried, - Compacted, - ], - { - mode: "oneOf", - }, - ).pipe(Schema.toTaggedUnion("type")) - export type Event = Schema.Schema.Type - export type Type = Event["type"] -} +export const Event = Schema.Union( + [ + Prompt, + Synthetic, + Step.Started, + Step.Ended, + Text.Started, + Text.Delta, + Text.Ended, + Tool.Input.Started, + Tool.Input.Delta, + Tool.Input.Ended, + Tool.Called, + Tool.Success, + Tool.Error, + Reasoning.Started, + Reasoning.Delta, + Reasoning.Ended, + Retried, + Compacted, + ], + { + mode: "oneOf", + }, +).pipe(Schema.toTaggedUnion("type")) +export type Event = Schema.Schema.Type +export type Type = Event["type"] + +export * as SessionEvent from "./session-event" diff --git a/packages/opencode/test/session/session-entry-stepper.test.ts b/packages/opencode/test/session/session-entry-stepper.test.ts index defce40c14..aa5dbf3057 100644 --- a/packages/opencode/test/session/session-entry-stepper.test.ts +++ b/packages/opencode/test/session/session-entry-stepper.test.ts @@ -4,8 +4,10 @@ import * as FastCheck from "effect/testing/FastCheck" import { SessionEntry } from "../../src/v2/session-entry" import { SessionEntryStepper } from "../../src/v2/session-entry-stepper" import { SessionEvent } from "../../src/v2/session-event" +import { SessionID } from "../../src/session/schema" const time = (n: number) => DateTime.makeUnsafe(n) +const sessionID = SessionID.empty() const word = FastCheck.string({ minLength: 1, maxLength: 8 }) const text = FastCheck.string({ maxLength: 16 }) @@ -147,24 +149,34 @@ describe("session-entry-stepper", () => { const store = adapterStore() store.committed.push(assistant()) - SessionEntryStepper.stepWith(adapterFor(store), SessionEvent.Prompt.create({ text: "hello", timestamp: time(1) })) - SessionEntryStepper.stepWith(adapterFor(store), SessionEvent.Reasoning.Started.create({ timestamp: time(2) })) SessionEntryStepper.stepWith( adapterFor(store), - SessionEvent.Reasoning.Delta.create({ delta: "thinking", timestamp: time(3) }), + SessionEvent.Prompt.create({ sessionID, text: "hello", timestamp: time(1) }), ) SessionEntryStepper.stepWith( adapterFor(store), - SessionEvent.Reasoning.Ended.create({ text: "thought", timestamp: time(4) }), + SessionEvent.Reasoning.Started.create({ sessionID, timestamp: time(2) }), ) - SessionEntryStepper.stepWith(adapterFor(store), SessionEvent.Text.Started.create({ timestamp: time(5) })) SessionEntryStepper.stepWith( adapterFor(store), - SessionEvent.Text.Delta.create({ delta: "world", timestamp: time(6) }), + SessionEvent.Reasoning.Delta.create({ sessionID, delta: "thinking", timestamp: time(3) }), + ) + SessionEntryStepper.stepWith( + adapterFor(store), + SessionEvent.Reasoning.Ended.create({ sessionID, text: "thought", timestamp: time(4) }), + ) + SessionEntryStepper.stepWith( + adapterFor(store), + SessionEvent.Text.Started.create({ sessionID, timestamp: time(5) }), + ) + SessionEntryStepper.stepWith( + adapterFor(store), + SessionEvent.Text.Delta.create({ sessionID, delta: "world", timestamp: time(6) }), ) SessionEntryStepper.stepWith( adapterFor(store), SessionEvent.Step.Ended.create({ + sessionID, reason: "stop", cost: 1, tokens: { @@ -199,15 +211,12 @@ describe("session-entry-stepper", () => { SessionEntryStepper.stepWith( adapterFor(store), - SessionEvent.Retried.create({ - attempt: 1, - error: retryError("rate limited"), - timestamp: time(1), - }), + SessionEvent.Retried.create({ sessionID, attempt: 1, error: retryError("rate limited"), timestamp: time(1) }), ) SessionEntryStepper.stepWith( adapterFor(store), SessionEvent.Retried.create({ + sessionID, attempt: 2, error: retryError("provider overloaded"), timestamp: time(2), @@ -253,9 +262,11 @@ describe("session-entry-stepper", () => { const state = memoryState() const adapter = SessionEntryStepper.memory(state) const committed = SessionEntry.User.fromEvent( - SessionEvent.Prompt.create({ text: "committed", timestamp: time(1) }), + SessionEvent.Prompt.create({ sessionID, text: "committed", timestamp: time(1) }), + ) + const pending = SessionEntry.User.fromEvent( + SessionEvent.Prompt.create({ sessionID, text: "pending", timestamp: time(2) }), ) - const pending = SessionEntry.User.fromEvent(SessionEvent.Prompt.create({ text: "pending", timestamp: time(2) })) adapter.appendEntry(committed) adapter.appendPending(pending) @@ -269,15 +280,15 @@ describe("session-entry-stepper", () => { SessionEntryStepper.stepWith( SessionEntryStepper.memory(state), - SessionEvent.Reasoning.Started.create({ timestamp: time(1) }), + SessionEvent.Reasoning.Started.create({ sessionID, timestamp: time(1) }), ) SessionEntryStepper.stepWith( SessionEntryStepper.memory(state), - SessionEvent.Reasoning.Delta.create({ delta: "draft", timestamp: time(2) }), + SessionEvent.Reasoning.Delta.create({ sessionID, delta: "draft", timestamp: time(2) }), ) SessionEntryStepper.stepWith( SessionEntryStepper.memory(state), - SessionEvent.Reasoning.Ended.create({ text: "final", timestamp: time(3) }), + SessionEvent.Reasoning.Ended.create({ sessionID, text: "final", timestamp: time(3) }), ) expect(reasons(state)).toEqual([{ type: "reasoning", text: "final" }]) @@ -288,11 +299,7 @@ describe("session-entry-stepper", () => { SessionEntryStepper.stepWith( SessionEntryStepper.memory(state), - SessionEvent.Retried.create({ - attempt: 1, - error: retryError("rate limited"), - timestamp: time(1), - }), + SessionEvent.Retried.create({ sessionID, attempt: 1, error: retryError("rate limited"), timestamp: time(1) }), ) expect(retriesOf(state)).toEqual([retry(1, "rate limited", 1)]) @@ -306,7 +313,7 @@ describe("session-entry-stepper", () => { FastCheck.property(word, (body) => { const next = SessionEntryStepper.step( memoryState(), - SessionEvent.Prompt.create({ text: body, timestamp: time(1) }), + SessionEvent.Prompt.create({ sessionID, text: body, timestamp: time(1) }), ) expect(next.entries).toHaveLength(1) expect(next.entries[0]?.type).toBe("user") @@ -322,7 +329,7 @@ describe("session-entry-stepper", () => { FastCheck.property(word, (body) => { const next = SessionEntryStepper.step( active(), - SessionEvent.Prompt.create({ text: body, timestamp: time(1) }), + SessionEvent.Prompt.create({ sessionID, text: body, timestamp: time(1) }), ) expect(next.pending).toHaveLength(1) expect(next.pending[0]?.type).toBe("user") @@ -340,9 +347,9 @@ describe("session-entry-stepper", () => { (state, part, i) => SessionEntryStepper.step( state, - SessionEvent.Text.Delta.create({ delta: part, timestamp: time(i + 2) }), + SessionEvent.Text.Delta.create({ sessionID, delta: part, timestamp: time(i + 2) }), ), - SessionEntryStepper.step(active(), SessionEvent.Text.Started.create({ timestamp: time(1) })), + SessionEntryStepper.step(active(), SessionEvent.Text.Started.create({ sessionID, timestamp: time(1) })), ) expect(texts_of(next)).toEqual([ @@ -361,10 +368,12 @@ describe("session-entry-stepper", () => { FastCheck.property(texts, texts, (a, b) => { const next = run( [ - SessionEvent.Text.Started.create({ timestamp: time(1) }), - ...a.map((x, i) => SessionEvent.Text.Delta.create({ delta: x, timestamp: time(i + 2) })), - SessionEvent.Text.Started.create({ timestamp: time(a.length + 2) }), - ...b.map((x, i) => SessionEvent.Text.Delta.create({ delta: x, timestamp: time(i + a.length + 3) })), + SessionEvent.Text.Started.create({ sessionID, timestamp: time(1) }), + ...a.map((x, i) => SessionEvent.Text.Delta.create({ sessionID, delta: x, timestamp: time(i + 2) })), + SessionEvent.Text.Started.create({ sessionID, timestamp: time(a.length + 2) }), + ...b.map((x, i) => + SessionEvent.Text.Delta.create({ sessionID, delta: x, timestamp: time(i + a.length + 3) }), + ), ], active(), ) @@ -383,9 +392,11 @@ describe("session-entry-stepper", () => { FastCheck.property(texts, text, (parts, end) => { const next = run( [ - SessionEvent.Reasoning.Started.create({ timestamp: time(1) }), - ...parts.map((x, i) => SessionEvent.Reasoning.Delta.create({ delta: x, timestamp: time(i + 2) })), - SessionEvent.Reasoning.Ended.create({ text: end, timestamp: time(parts.length + 2) }), + SessionEvent.Reasoning.Started.create({ sessionID, timestamp: time(1) }), + ...parts.map((x, i) => + SessionEvent.Reasoning.Delta.create({ sessionID, delta: x, timestamp: time(i + 2) }), + ), + SessionEvent.Reasoning.Ended.create({ sessionID, text: end, timestamp: time(parts.length + 2) }), ], active(), ) @@ -414,11 +425,12 @@ describe("session-entry-stepper", () => { (callID, title, input, output, metadata, attachments, parts) => { const next = run( [ - SessionEvent.Tool.Input.Started.create({ callID, name: "bash", timestamp: time(1) }), + SessionEvent.Tool.Input.Started.create({ sessionID, callID, name: "bash", timestamp: time(1) }), ...parts.map((x, i) => - SessionEvent.Tool.Input.Delta.create({ callID, delta: x, timestamp: time(i + 2) }), + SessionEvent.Tool.Input.Delta.create({ sessionID, callID, delta: x, timestamp: time(i + 2) }), ), SessionEvent.Tool.Called.create({ + sessionID, callID, tool: "bash", input, @@ -426,6 +438,7 @@ describe("session-entry-stepper", () => { timestamp: time(parts.length + 2), }), SessionEvent.Tool.Success.create({ + sessionID, callID, title, output, @@ -459,8 +472,9 @@ describe("session-entry-stepper", () => { FastCheck.property(word, dict, word, maybe(dict), (callID, input, error, metadata) => { const next = run( [ - SessionEvent.Tool.Input.Started.create({ callID, name: "bash", timestamp: time(1) }), + SessionEvent.Tool.Input.Started.create({ sessionID, callID, name: "bash", timestamp: time(1) }), SessionEvent.Tool.Called.create({ + sessionID, callID, tool: "bash", input, @@ -468,6 +482,7 @@ describe("session-entry-stepper", () => { timestamp: time(2), }), SessionEvent.Tool.Error.create({ + sessionID, callID, error, metadata, @@ -496,8 +511,9 @@ describe("session-entry-stepper", () => { FastCheck.property(word, word, (callID, title) => { const next = run( [ - SessionEvent.Tool.Input.Started.create({ callID, name: "bash", timestamp: time(1) }), + SessionEvent.Tool.Input.Started.create({ sessionID, callID, name: "bash", timestamp: time(1) }), SessionEvent.Tool.Success.create({ + sessionID, callID, title, provider: { executed: true }, @@ -520,6 +536,7 @@ describe("session-entry-stepper", () => { FastCheck.assert( FastCheck.property(FastCheck.integer({ min: 1, max: 1000 }), (n) => { const event = SessionEvent.Step.Ended.create({ + sessionID, reason: "stop", cost: 1, tokens: { @@ -552,7 +569,10 @@ describe("session-entry-stepper", () => { FastCheck.assert( FastCheck.property(word, (body) => { const old = memoryState() - const next = SessionEntryStepper.step(old, SessionEvent.Prompt.create({ text: body, timestamp: time(1) })) + const next = SessionEntryStepper.step( + old, + SessionEvent.Prompt.create({ sessionID, text: body, timestamp: time(1) }), + ) expect(old).not.toBe(next) expect(old.entries).toHaveLength(0) expect(next.entries).toHaveLength(1) @@ -565,7 +585,10 @@ describe("session-entry-stepper", () => { FastCheck.assert( FastCheck.property(word, (body) => { const old = active() - const next = SessionEntryStepper.step(old, SessionEvent.Prompt.create({ text: body, timestamp: time(1) })) + const next = SessionEntryStepper.step( + old, + SessionEvent.Prompt.create({ sessionID, text: body, timestamp: time(1) }), + ) expect(old).not.toBe(next) expect(old.pending).toHaveLength(0) expect(next.pending).toHaveLength(1) @@ -579,15 +602,17 @@ describe("session-entry-stepper", () => { FastCheck.property(texts, (parts) => { const next = run([ SessionEvent.Step.Started.create({ + sessionID, model: { id: "model", providerID: "provider", }, timestamp: time(1), }), - SessionEvent.Text.Started.create({ timestamp: time(2) }), - ...parts.map((x, i) => SessionEvent.Text.Delta.create({ delta: x, timestamp: time(i + 3) })), + SessionEvent.Text.Started.create({ sessionID, timestamp: time(2) }), + ...parts.map((x, i) => SessionEvent.Text.Delta.create({ sessionID, delta: x, timestamp: time(i + 3) })), SessionEvent.Step.Ended.create({ + sessionID, reason: "stop", cost: 1, tokens: { @@ -623,17 +648,19 @@ describe("session-entry-stepper", () => { FastCheck.assert( FastCheck.property(word, texts, (body, parts) => { const next = run([ - SessionEvent.Prompt.create({ text: body, timestamp: time(0) }), + SessionEvent.Prompt.create({ sessionID, text: body, timestamp: time(0) }), SessionEvent.Step.Started.create({ + sessionID, model: { id: "model", providerID: "provider", }, timestamp: time(1), }), - SessionEvent.Text.Started.create({ timestamp: time(2) }), - ...parts.map((x, i) => SessionEvent.Text.Delta.create({ delta: x, timestamp: time(i + 3) })), + SessionEvent.Text.Started.create({ sessionID, timestamp: time(2) }), + ...parts.map((x, i) => SessionEvent.Text.Delta.create({ sessionID, delta: x, timestamp: time(i + 3) })), SessionEvent.Step.Ended.create({ + sessionID, reason: "stop", cost: 1, tokens: { @@ -680,19 +707,28 @@ describe("session-entry-stepper", () => { (body, reason, end, input, title, output, metadata, attachments) => { const callID = "call" const next = run([ - SessionEvent.Prompt.create({ text: body, timestamp: time(0) }), + SessionEvent.Prompt.create({ sessionID, text: body, timestamp: time(0) }), SessionEvent.Step.Started.create({ + sessionID, model: { id: "model", providerID: "provider", }, timestamp: time(1), }), - SessionEvent.Reasoning.Started.create({ timestamp: time(2) }), - ...reason.map((x, i) => SessionEvent.Reasoning.Delta.create({ delta: x, timestamp: time(i + 3) })), - SessionEvent.Reasoning.Ended.create({ text: end, timestamp: time(reason.length + 3) }), - SessionEvent.Tool.Input.Started.create({ callID, name: "bash", timestamp: time(reason.length + 4) }), + SessionEvent.Reasoning.Started.create({ sessionID, timestamp: time(2) }), + ...reason.map((x, i) => + SessionEvent.Reasoning.Delta.create({ sessionID, delta: x, timestamp: time(i + 3) }), + ), + SessionEvent.Reasoning.Ended.create({ sessionID, text: end, timestamp: time(reason.length + 3) }), + SessionEvent.Tool.Input.Started.create({ + sessionID, + callID, + name: "bash", + timestamp: time(reason.length + 4), + }), SessionEvent.Tool.Called.create({ + sessionID, callID, tool: "bash", input, @@ -700,6 +736,7 @@ describe("session-entry-stepper", () => { timestamp: time(reason.length + 5), }), SessionEvent.Tool.Success.create({ + sessionID, callID, title, output, @@ -709,6 +746,7 @@ describe("session-entry-stepper", () => { timestamp: time(reason.length + 6), }), SessionEvent.Step.Ended.create({ + sessionID, reason: "stop", cost: 1, tokens: { @@ -747,6 +785,7 @@ describe("session-entry-stepper", () => { const next = run( [ SessionEvent.Step.Started.create({ + sessionID, model: { id: "model", providerID: "provider", @@ -771,8 +810,9 @@ describe("session-entry-stepper", () => { FastCheck.property(dict, dict, word, word, (a, b, title, error) => { const next = run( [ - SessionEvent.Tool.Input.Started.create({ callID: "a", name: "bash", timestamp: time(1) }), + SessionEvent.Tool.Input.Started.create({ sessionID, callID: "a", name: "bash", timestamp: time(1) }), SessionEvent.Tool.Called.create({ + sessionID, callID: "a", tool: "bash", input: a, @@ -780,14 +820,16 @@ describe("session-entry-stepper", () => { timestamp: time(2), }), SessionEvent.Tool.Success.create({ + sessionID, callID: "a", title, output: "done", provider: { executed: true }, timestamp: time(3), }), - SessionEvent.Tool.Input.Started.create({ callID: "b", name: "grep", timestamp: time(4) }), + SessionEvent.Tool.Input.Started.create({ sessionID, callID: "b", name: "grep", timestamp: time(4) }), SessionEvent.Tool.Called.create({ + sessionID, callID: "b", tool: "bash", input: b, @@ -795,6 +837,7 @@ describe("session-entry-stepper", () => { timestamp: time(5), }), SessionEvent.Tool.Error.create({ + sessionID, callID: "b", error, provider: { executed: true }, @@ -827,11 +870,12 @@ describe("session-entry-stepper", () => { FastCheck.property(dict, dict, word, word, text, text, (a, b, titleA, titleB, deltaA, deltaB) => { const next = run( [ - SessionEvent.Tool.Input.Started.create({ callID: "a", name: "bash", timestamp: time(1) }), - SessionEvent.Tool.Input.Started.create({ callID: "b", name: "grep", timestamp: time(2) }), - SessionEvent.Tool.Input.Delta.create({ callID: "a", delta: deltaA, timestamp: time(3) }), - SessionEvent.Tool.Input.Delta.create({ callID: "b", delta: deltaB, timestamp: time(4) }), + SessionEvent.Tool.Input.Started.create({ sessionID, callID: "a", name: "bash", timestamp: time(1) }), + SessionEvent.Tool.Input.Started.create({ sessionID, callID: "b", name: "grep", timestamp: time(2) }), + SessionEvent.Tool.Input.Delta.create({ sessionID, callID: "a", delta: deltaA, timestamp: time(3) }), + SessionEvent.Tool.Input.Delta.create({ sessionID, callID: "b", delta: deltaB, timestamp: time(4) }), SessionEvent.Tool.Called.create({ + sessionID, callID: "a", tool: "bash", input: a, @@ -839,6 +883,7 @@ describe("session-entry-stepper", () => { timestamp: time(5), }), SessionEvent.Tool.Called.create({ + sessionID, callID: "b", tool: "grep", input: b, @@ -846,6 +891,7 @@ describe("session-entry-stepper", () => { timestamp: time(6), }), SessionEvent.Tool.Success.create({ + sessionID, callID: "a", title: titleA, output: "done-a", @@ -853,6 +899,7 @@ describe("session-entry-stepper", () => { timestamp: time(7), }), SessionEvent.Tool.Success.create({ + sessionID, callID: "b", title: titleB, output: "done-b", @@ -884,7 +931,7 @@ describe("session-entry-stepper", () => { FastCheck.property(word, (body) => { const next = SessionEntryStepper.step( memoryState(), - SessionEvent.Synthetic.create({ text: body, timestamp: time(1) }), + SessionEvent.Synthetic.create({ sessionID, text: body, timestamp: time(1) }), ) expect(next.entries).toHaveLength(1) expect(next.entries[0]?.type).toBe("synthetic") @@ -900,7 +947,7 @@ describe("session-entry-stepper", () => { FastCheck.property(FastCheck.boolean(), maybe(FastCheck.boolean()), (auto, overflow) => { const next = SessionEntryStepper.step( memoryState(), - SessionEvent.Compacted.create({ auto, overflow, timestamp: time(1) }), + SessionEvent.Compacted.create({ sessionID, auto, overflow, timestamp: time(1) }), ) expect(next.entries).toHaveLength(1) expect(next.entries[0]?.type).toBe("compaction")