@truefoundry/trueforge-core 0.2.0-rc.0 → 0.2.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/agent-session/SessionHandle.d.ts +1 -0
  2. package/dist/agent-session/SessionHandle.d.ts.map +1 -1
  3. package/dist/agent-session/SessionHandle.js +3 -0
  4. package/dist/agent-session/SessionHandle.js.map +1 -1
  5. package/dist/agent-session/SessionHandle.mjs +3 -0
  6. package/dist/agent-session/SessionHandle.mjs.map +1 -1
  7. package/dist/agent-session/Sessions.d.ts +17 -6
  8. package/dist/agent-session/Sessions.d.ts.map +1 -1
  9. package/dist/agent-session/Sessions.js +45 -1
  10. package/dist/agent-session/Sessions.js.map +1 -1
  11. package/dist/agent-session/Sessions.mjs +45 -1
  12. package/dist/agent-session/Sessions.mjs.map +1 -1
  13. package/dist/agent-session/index.d.ts +6 -4
  14. package/dist/agent-session/index.d.ts.map +1 -1
  15. package/dist/agent-session/index.js +9 -0
  16. package/dist/agent-session/index.js.map +1 -1
  17. package/dist/agent-session/index.mjs +7 -1
  18. package/dist/agent-session/index.mjs.map +1 -1
  19. package/dist/agent-session/models/SessionRecord.d.ts +10 -2
  20. package/dist/agent-session/models/SessionRecord.d.ts.map +1 -1
  21. package/dist/agent-session/models/SessionRecord.js.map +1 -1
  22. package/dist/agent-session/schemas/session.d.ts +19 -1
  23. package/dist/agent-session/schemas/session.d.ts.map +1 -1
  24. package/dist/agent-session/schemas/session.js +23 -3
  25. package/dist/agent-session/schemas/session.js.map +1 -1
  26. package/dist/agent-session/schemas/session.mjs +21 -3
  27. package/dist/agent-session/schemas/session.mjs.map +1 -1
  28. package/dist/agent-session/schemas/subject.d.ts +9 -0
  29. package/dist/agent-session/schemas/subject.d.ts.map +1 -0
  30. package/dist/agent-session/schemas/subject.js +36 -0
  31. package/dist/agent-session/schemas/subject.js.map +1 -0
  32. package/dist/agent-session/schemas/subject.mjs +11 -0
  33. package/dist/agent-session/schemas/subject.mjs.map +1 -0
  34. package/dist/agent-session/store/ISessionStore.d.ts +29 -20
  35. package/dist/agent-session/store/ISessionStore.d.ts.map +1 -1
  36. package/dist/agent-session/store/ISessionStore.js.map +1 -1
  37. package/dist/agent-session/store/InMemorySessionStore.d.ts +4 -1
  38. package/dist/agent-session/store/InMemorySessionStore.d.ts.map +1 -1
  39. package/dist/agent-session/store/InMemorySessionStore.js +47 -3
  40. package/dist/agent-session/store/InMemorySessionStore.js.map +1 -1
  41. package/dist/agent-session/store/InMemorySessionStore.mjs +48 -3
  42. package/dist/agent-session/store/InMemorySessionStore.mjs.map +1 -1
  43. package/dist/agent-session/store/SessionStoreErrors.d.ts +4 -0
  44. package/dist/agent-session/store/SessionStoreErrors.d.ts.map +1 -1
  45. package/dist/agent-session/store/SessionStoreErrors.js +10 -0
  46. package/dist/agent-session/store/SessionStoreErrors.js.map +1 -1
  47. package/dist/agent-session/store/SessionStoreErrors.mjs +9 -0
  48. package/dist/agent-session/store/SessionStoreErrors.mjs.map +1 -1
  49. package/dist/core/llm/VercelAILLM.d.ts +1 -1
  50. package/dist/core/llm/VercelAILLM.d.ts.map +1 -1
  51. package/dist/core/llm/VercelAILLM.js +4 -2
  52. package/dist/core/llm/VercelAILLM.js.map +1 -1
  53. package/dist/core/llm/VercelAILLM.mjs +4 -2
  54. package/dist/core/llm/VercelAILLM.mjs.map +1 -1
  55. package/dist/core/mcp/remoteMcpClient.js +1 -1
  56. package/dist/core/mcp/remoteMcpClient.js.map +1 -1
  57. package/dist/core/mcp/remoteMcpClient.mjs +1 -1
  58. package/dist/core/mcp/remoteMcpClient.mjs.map +1 -1
  59. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/agent-session/store/ISessionStore.ts"],"sourcesContent":["import type { JsonValue } from '../../core/capabilities/AgentCapability';\nimport type { MCPServerInitInfo, ThreadOverwriteContextEvent } from '../../core/events/schema';\nimport type {\n AgentThreadSnapshot,\n ContextMessage,\n SubAgentCompletionMarker,\n} from '../../core/runtime/AgentThread.types';\nimport type { CurrentContextUsage } from '../../core/runtime/contextUsage';\nimport type { SandboxInfo } from '../../core/sandbox/Sandbox';\nimport type { SessionRecord } from '../models/SessionRecord';\nimport type { TurnRecord } from '../models/TurnRecord';\nimport type { PersistedTurnEvent, SessionEventItem } from '../schemas/events';\nimport type { TokenPagination } from '../schemas/pagination';\nimport type { CancellationReason, TerminalTurnState } from '../schemas/turn';\n\n/**\n * Caller-supplied fields for creating a session; the store owns timestamps and tip state.\n * `agent` is a discriminated reference | inline binding.\n */\nexport type CreateSessionInput<TSessionCustom extends object = Record<string, never>> = Pick<\n SessionRecord<TSessionCustom>,\n 'tenant_id' | 'session_id' | 'agent' | 'created_by'\n> & {\n custom: TSessionCustom | null;\n};\n\n/**\n * PATCH fields for an existing session; `undefined` fields are left unchanged.\n * `agent` may be set only on inline sessions, and only as an inline arm (`{ type: 'inline', spec }`).\n */\nexport type UpdateSessionInput<TSessionCustom extends object = Record<string, never>> = Pick<\n SessionRecord<TSessionCustom>,\n 'tenant_id' | 'session_id'\n> & {\n agent: Extract<SessionRecord<TSessionCustom>['agent'], { type: 'inline' }> | undefined;\n title: SessionRecord<TSessionCustom>['title'] | undefined;\n};\n\nexport interface GetSessionInput {\n tenant_id: string;\n session_id: string;\n}\n\nexport interface DeleteSessionInput {\n tenant_id: string;\n session_id: string;\n}\n\nexport interface ListSessionsInput {\n tenant_id: string;\n limit: number;\n page_token: string | undefined;\n order: 'asc' | 'desc' | undefined;\n /** Inclusive lower bound on `created_at` (instant). */\n start_timestamp: Date | undefined;\n /** Inclusive upper bound on `created_at` (instant). */\n end_timestamp: Date | undefined;\n /** When set, only sessions bound to this named agent id. */\n agent_id: string | undefined;\n /** When set, only sessions created by this identity. */\n created_by: string | undefined;\n}\n\n/** Turn row fields without assembled snapshot (create input / listTurns). */\nexport type TurnRecordWithoutSnapshot<TTurnCustom extends object = Record<string, never>> = Omit<\n TurnRecord<TTurnCustom>,\n 'snapshot'\n>;\n\n/** Thread registration; mutable per-turn data arrives through dedicated createTurn fields. */\nexport type NewThreadInit = Omit<\n AgentThreadSnapshot,\n 'context' | 'current_context_usage' | 'completion' | 'capability_state'\n>;\n\n/** Full post-send capability map for one thread. */\nexport type ThreadCapabilityStateInit = Pick<AgentThreadSnapshot, 'thread_id' | 'capability_state'>;\n\n/** One drained send() append batch for one thread; writer = the new turn. */\nexport interface TurnContextAppend {\n thread_id: string;\n context: ContextMessage[];\n current_context_usage: CurrentContextUsage | null;\n}\n\nexport interface CreateTurnInput<TTurnCustom extends object = Record<string, never>> {\n turn: TurnRecordWithoutSnapshot<TTurnCustom>;\n /** Threads absent on the previous turn (turn 1: the root thread; else empty). */\n new_threads: NewThreadInit[];\n /**\n * All new messages, for new and carried-forward threads alike — collected by\n * draining orchestrator.send() BEFORE this call. Draining without persisting is\n * safe: send() mutates only in-memory threads, and if createTurn fails the whole\n * in-memory state (orchestrator, threads, resolver) is discarded — so either this\n * turn commits atomically with its appends, or nothing was persisted at all.\n */\n new_context_appends: TurnContextAppend[];\n /**\n * Complete post-send capability maps: exactly one entry per turn thread.\n * Authoritative for this turn; the store does not carry capability rows forward.\n */\n capability_states: ThreadCapabilityStateInit[];\n /** Set only if session.title is NULL (first write wins). */\n update_session_title_if_not_exist: string | null;\n}\n\nexport interface FreezeAndGetTurnInput {\n session_id: string;\n turn_id: string;\n /** Written onto the turn row when the freeze actually cancels a running turn. */\n reason: CancellationReason;\n /** Caller-built cancelled turn.done event; store persists atomically with state flip when it cancels. */\n turn_done_event: PersistedTurnEvent;\n}\n\nexport interface GetTurnInput {\n session_id: string;\n turn_id: string;\n}\n\nexport interface ListTurnsInput {\n session_id: string;\n limit: number;\n page_token: string | undefined;\n}\n\nexport interface UpdateTurnStateInput {\n session_id: string;\n turn_id: string;\n state: TerminalTurnState;\n /** Caller-built turn.done; written atomically with the state flip in the same tx. */\n turn_done_event: PersistedTurnEvent;\n}\n\nexport interface AppendToEventsInput {\n session_id: string;\n turn_id: string;\n events: PersistedTurnEvent[];\n}\n\nexport interface AddThreadsInput {\n session_id: string;\n turn_id: string;\n threads: AgentThreadSnapshot[];\n}\n\nexport interface RemoveThreadsInput {\n session_id: string;\n turn_id: string;\n thread_ids: string[];\n}\n\nexport interface AppendToThreadContextInput {\n session_id: string;\n turn_id: string;\n thread_id: string;\n context: ContextMessage[];\n current_context_usage: CurrentContextUsage | null;\n completion: SubAgentCompletionMarker | null;\n}\n\nexport interface OverwriteThreadContextInput {\n session_id: string;\n turn_id: string;\n event: ThreadOverwriteContextEvent;\n}\n\nexport interface PatchMCPServersInput {\n session_id: string;\n turn_id: string;\n mcp_servers: MCPServerInitInfo[];\n}\n\nexport interface PatchSandboxInfoInput {\n session_id: string;\n turn_id: string;\n sandbox_info: SandboxInfo;\n}\n\nexport interface PatchThreadCapabilityStateInput {\n session_id: string;\n turn_id: string;\n thread_id: string;\n key: string;\n state: JsonValue;\n}\n\nexport interface ListTurnEventsInput {\n session_id: string;\n turn_id: string;\n limit: number;\n page_token: string | undefined;\n order: 'asc' | 'desc' | undefined;\n}\n\nexport interface ListSessionEventsInput {\n session_id: string;\n limit: number;\n page_token: string | undefined;\n last_turn_id: string | undefined;\n}\n\n/**\n * Session/turn persistence contract. Pure durability: no streaming, SSE, or\n * subscription members. Callers authorize the parent session before using\n * turn-scoped operations, which rely on globally unique session_id values.\n * Capability maps are initialized atomically by createTurn and subsequently\n * updated through patchThreadCapabilityState. Agent binding is session-scoped\n * as a discriminated `agent` (ref | value); named agents are not hydrated on read.\n */\nexport interface ISessionStore<\n TSessionCustom extends object = Record<string, never>,\n TTurnCustom extends object = Record<string, never>,\n> {\n /**\n * Persists a discriminated `agent` (ref | value). SQL backends may flatten to columns.\n * `session_id` is globally unique across tenants.\n * Persists caller-supplied `created_by` (immutable after create).\n * Sets `last_activity_timestamp_ms` (= now) on create.\n */\n createSession(input: CreateSessionInput<TSessionCustom>): Promise<void>;\n\n /** Permanently removes a session and all related data; missing sessions are a no-op. */\n deleteSession(input: DeleteSessionInput): Promise<void>;\n\n /**\n * Returns the session as stored (ref agents are not hydrated to a value).\n * Does **not** bump `last_activity_timestamp_ms` (read path).\n */\n getSession(input: GetSessionInput): Promise<SessionRecord<TSessionCustom> | undefined>;\n\n /**\n * PATCH semantics — update only the provided fields:\n * - agent: replace inline binding (inline sessions only; reference → invariant error).\n * - title: set/replace the session title.\n * Bumps `last_activity_timestamp_ms` (= now) in the same update.\n */\n updateSession(input: UpdateSessionInput<TSessionCustom>): Promise<void>;\n\n /**\n * Paginated list of the tenant's sessions ordered by `updated_at`\n * (`order` defaults to `desc`, `session_id` tie-break). `page_token` is a\n * keyset cursor on `(updated_at, session_id)`. `start_timestamp` /\n * `end_timestamp` are inclusive instant bounds on `created_at`. Optional\n * `agent_id` filters ref-bound sessions; optional `created_by` filters by\n * creator identity. Does **not** bump `last_activity_timestamp_ms` (read path).\n */\n listSessions(\n input: ListSessionsInput,\n ): Promise<{ data: SessionRecord<TSessionCustom>[]; pagination: TokenPagination }>;\n\n /**\n * Creates the turn AND advances `session.last_turn_id`. Context merging is the\n * store's responsibility: `new_context_appends` and `new_threads` are applied\n * on top of the previous turn's snapshot when `turn.previous_turn_id` is set.\n * `capability_states` is the complete authoritative map for every resulting\n * thread and is persisted directly without store-side carry-forward.\n *\n * Atomicity (store contract): insert turn + set `last_turn_id` (+ session\n * turn-list append if the backend has one) MUST be one atomic unit per\n * session. Concurrent createTurn on the same session must not drop a turn\n * row or leave `last_turn_id` pointing at a turn that was never created.\n * The implementation supplies the mechanism (session lock, row lock/tx, …).\n *\n * Also bumps `session.last_activity_timestamp_ms` in that same atomic unit.\n *\n * Fork semantics for `turn.previous_turn_id`:\n * - `null` — new root turn (no parent); always allowed.\n * - string — fork/chain from that turn when it exists. Tip-equality is NOT\n * required; concurrent forks from the same tip both succeed. Unknown id is\n * allowed (relaxed) and treated as no inheritance. If that turn exists and\n * is still `running`, reject with {@link PreviousTurnRunningError} —\n * callers must {@link freezeAndGetTurn} first.\n * `last_turn_id` always advances to the new turn in the same atomic unit.\n *\n * `update_session_title_if_not_exist`: when set AND `session.title` is\n * unset/null, set it in the same atomic unit; an existing title is NEVER\n * overwritten (first write wins). The caller derives the value; the store\n * only conditionally sets it.\n */\n createTurn(input: CreateTurnInput<TTurnCustom>): Promise<void>;\n\n /**\n * One tx: (a) conditionally cancel a running turn with `reason`; (b) if it\n * did cancel, insert the caller-built `turn_done_event` — already-terminal\n * turns skip the event insert; (c) return the now-immutable turn record.\n * Missing turn → {@link TurnNotFoundError}.\n */\n freezeAndGetTurn(input: FreezeAndGetTurnInput): Promise<TurnRecord<TTurnCustom>>;\n\n /** Returns the turn record, or undefined if not found in this session. */\n getTurn(input: GetTurnInput): Promise<TurnRecord<TTurnCustom> | undefined>;\n\n /** Paginated list of turn rows (no snapshot); use getTurn for assembled snapshot. */\n listTurns(\n input: ListTurnsInput,\n ): Promise<{ data: TurnRecordWithoutSnapshot<TTurnCustom>[]; pagination: TokenPagination }>;\n\n /**\n * Writes the terminal state and `turn_done_event` atomically. Store contract —\n * **first terminal write wins**:\n * - Allowed: `running` → `done` | `cancelled` | `error`.\n * - Rejected with **409** (or equivalent conflict): any write when status is already\n * terminal — including done→cancelled, cancelled→done, error→*, terminal→running.\n * - Missing turn → 404 / not-found.\n *\n * Check under the same concurrency control as other turn mutations (lock/CAS) —\n * not a racy read-then-write outside the critical section.\n */\n updateTurnState(input: UpdateTurnStateInput): Promise<void>;\n\n /**\n * Durable event log for the turn. MUST include lifecycle rows: a\n * `TurnCreatedEvent` at the start of the stream and a terminal `TurnDoneEvent`\n * (or its cancelled/error shape) at the end. Every turn written through this\n * API persists lifecycle in the stream — implementations never synthesize\n * missing created/done rows on read. Every event MUST carry `id` and\n * `created_at`; `id` is a monotonic ULID and is the primary within-turn sort\n * key. `created_at` records event creation time but is not the order key.\n */\n appendToEvents(input: AppendToEventsInput): Promise<void>;\n\n /** Adds thread snapshots to the turn (sub-agent spawns). */\n addThreads(input: AddThreadsInput): Promise<void>;\n\n /** Removes threads from the turn by id. */\n removeThreads(input: RemoveThreadsInput): Promise<void>;\n\n /** Appends messages to a thread's context; optionally updates usage / completion marker. */\n appendToThreadContext(input: AppendToThreadContextInput): Promise<void>;\n\n /** Replaces a thread's context wholesale (context-overwrite event). */\n overwriteThreadContext(input: OverwriteThreadContextInput): Promise<void>;\n\n /** Patches the turn snapshot's MCP server init info (by source id). */\n patchMCPServers(input: PatchMCPServersInput): Promise<void>;\n\n /** Patches the turn snapshot's sandbox info (id for cross-turn reattach). */\n patchSandboxInfo(input: PatchSandboxInfoInput): Promise<void>;\n\n /** Generic capability KV — store has zero Plan/feature knowledge. */\n patchThreadCapabilityState(input: PatchThreadCapabilityStateInput): Promise<void>;\n\n /** Paginated read ordered lexically by monotonic `event.id` (asc default). */\n listTurnEvents(input: ListTurnEventsInput): Promise<{\n data: PersistedTurnEvent[];\n pagination: TokenPagination;\n }>;\n\n /**\n * Paginated session-wide feed of persisted events across turns, including\n * turn.created / turn.done lifecycle rows. Reads only — never synthesizes.\n *\n * Includes events from **running** turns (the HTTP layer may 409 separately;\n * that is not store semantics). Ordering: newest turn first, then monotonic\n * `event.id` descending within each turn. `last_turn_id` anchors the window\n * to that turn plus its ancestors; when omitted, the session's current\n * `last_turn_id` selects the active branch. Continuation page tokens encode\n * that anchor and take precedence over `last_turn_id`, so later session\n * activity cannot move an in-progress pagination walk to another branch.\n * `ancestor_ids` may include only the previous N ancestors and need not reach\n * the root — implementations must spill through older turns' own\n * `ancestor_ids` when needed. Sibling branches from forks are excluded.\n */\n listSessionEvents(input: ListSessionEventsInput): Promise<{\n data: SessionEventItem[];\n pagination: TokenPagination;\n }>;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
1
+ {"version":3,"sources":["../../../src/agent-session/store/ISessionStore.ts"],"sourcesContent":["import type { JsonValue } from '../../core/capabilities/AgentCapability';\nimport type { MCPServerInitInfo, ThreadOverwriteContextEvent } from '../../core/events/schema';\nimport type {\n AgentThreadSnapshot,\n ContextMessage,\n SubAgentCompletionMarker,\n} from '../../core/runtime/AgentThread.types';\nimport type { CurrentContextUsage } from '../../core/runtime/contextUsage';\nimport type { SandboxInfo } from '../../core/sandbox/Sandbox';\nimport type { SessionRecord } from '../models/SessionRecord';\nimport type { TurnRecord } from '../models/TurnRecord';\nimport type { PersistedTurnEvent, SessionEventItem } from '../schemas/events';\nimport type { TokenPagination } from '../schemas/pagination';\nimport type { CancellationReason, TerminalTurnState } from '../schemas/turn';\n\n/**\n * Caller-supplied fields for creating a session; the store owns timestamps and tip state.\n * `agent` is a discriminated reference | inline binding.\n */\nexport type CreateSessionInput<TSessionCustom extends object = Record<string, never>> = Pick<\n SessionRecord<TSessionCustom>,\n 'tenant_id' | 'session_id' | 'agent' | 'created_by_subject' | 'external_id' | 'metadata'\n> & {\n custom: TSessionCustom | null;\n};\n\n/**\n * PATCH fields for an existing session; `undefined` fields are left unchanged.\n * `agent` may be set only on inline sessions, and only as an inline arm (`{ type: 'inline', spec }`).\n * `metadata` when set fully replaces the stored map (`{}` clears).\n */\nexport type UpdateSessionInput<TSessionCustom extends object = Record<string, never>> = Pick<\n SessionRecord<TSessionCustom>,\n 'tenant_id' | 'session_id'\n> & {\n agent: Extract<SessionRecord<TSessionCustom>['agent'], { type: 'inline' }> | undefined;\n title: SessionRecord<TSessionCustom>['title'] | undefined;\n metadata: SessionRecord<TSessionCustom>['metadata'] | undefined;\n};\n\nexport interface GetSessionInput {\n tenant_id: string;\n session_id: string;\n}\n\nexport interface GetSessionByExternalIdInput {\n tenant_id: string;\n external_id: string;\n}\n\nexport interface DeleteSessionInput {\n tenant_id: string;\n session_id: string;\n}\n\nexport interface ListSessionsInput {\n tenant_id: string;\n limit: number;\n page_token: string | undefined;\n order: 'asc' | 'desc' | undefined;\n /** Inclusive lower bound on `created_at` (instant). */\n start_timestamp: Date | undefined;\n /** Inclusive upper bound on `created_at` (instant). */\n end_timestamp: Date | undefined;\n /** When set, only sessions bound to this named agent id. */\n agent_id: string | undefined;\n /** When set, match creator or named-agent binding. Empty `agent_ids` means creator-only. */\n created_by_or_agent_ids:\n | {\n created_by_subject_id: string;\n agent_ids: readonly string[];\n }\n | undefined;\n}\n\n/** Turn row fields without assembled snapshot (create input / listTurns). */\nexport type TurnRecordWithoutSnapshot<TTurnCustom extends object = Record<string, never>> = Omit<\n TurnRecord<TTurnCustom>,\n 'snapshot'\n>;\n\n/** Thread registration; mutable per-turn data arrives through dedicated createTurn fields. */\nexport type NewThreadInit = Omit<\n AgentThreadSnapshot,\n 'context' | 'current_context_usage' | 'completion' | 'capability_state'\n>;\n\n/** Full post-send capability map for one thread. */\nexport type ThreadCapabilityStateInit = Pick<AgentThreadSnapshot, 'thread_id' | 'capability_state'>;\n\n/** One drained send() append batch for one thread; writer = the new turn. */\nexport interface TurnContextAppend {\n thread_id: string;\n context: ContextMessage[];\n current_context_usage: CurrentContextUsage | null;\n}\n\nexport interface CreateTurnInput<TTurnCustom extends object = Record<string, never>> {\n turn: TurnRecordWithoutSnapshot<TTurnCustom>;\n /** Threads absent on the previous turn (turn 1: the root thread; else empty). */\n new_threads: NewThreadInit[];\n /**\n * All new messages, for new and carried-forward threads alike — collected by\n * draining orchestrator.send() BEFORE this call. Draining without persisting is\n * safe: send() mutates only in-memory threads, and if createTurn fails the whole\n * in-memory state (orchestrator, threads, resolver) is discarded — so either this\n * turn commits atomically with its appends, or nothing was persisted at all.\n */\n new_context_appends: TurnContextAppend[];\n /**\n * Complete post-send capability maps: exactly one entry per turn thread.\n * Authoritative for this turn; the store does not carry capability rows forward.\n */\n capability_states: ThreadCapabilityStateInit[];\n /** Set only if session.title is NULL (first write wins). */\n update_session_title_if_not_exist: string | null;\n}\n\nexport interface FreezeAndGetTurnInput {\n session_id: string;\n turn_id: string;\n /** Written onto the turn row when the freeze actually cancels a running turn. */\n reason: CancellationReason;\n /** Caller-built cancelled turn.done event; store persists atomically with state flip when it cancels. */\n turn_done_event: PersistedTurnEvent;\n}\n\nexport interface GetTurnInput {\n session_id: string;\n turn_id: string;\n}\n\nexport interface ListTurnsInput {\n session_id: string;\n limit: number;\n page_token: string | undefined;\n}\n\nexport interface UpdateTurnStateInput {\n session_id: string;\n turn_id: string;\n state: TerminalTurnState;\n /** Caller-built turn.done; written atomically with the state flip in the same tx. */\n turn_done_event: PersistedTurnEvent;\n}\n\nexport interface AppendToEventsInput {\n session_id: string;\n turn_id: string;\n events: PersistedTurnEvent[];\n}\n\nexport interface AddThreadsInput {\n session_id: string;\n turn_id: string;\n threads: AgentThreadSnapshot[];\n}\n\nexport interface RemoveThreadsInput {\n session_id: string;\n turn_id: string;\n thread_ids: string[];\n}\n\nexport interface AppendToThreadContextInput {\n session_id: string;\n turn_id: string;\n thread_id: string;\n context: ContextMessage[];\n current_context_usage: CurrentContextUsage | null;\n completion: SubAgentCompletionMarker | null;\n}\n\nexport interface OverwriteThreadContextInput {\n session_id: string;\n turn_id: string;\n event: ThreadOverwriteContextEvent;\n}\n\nexport interface PatchMCPServersInput {\n session_id: string;\n turn_id: string;\n mcp_servers: MCPServerInitInfo[];\n}\n\nexport interface PatchSandboxInfoInput {\n session_id: string;\n turn_id: string;\n sandbox_info: SandboxInfo;\n}\n\nexport interface PatchThreadCapabilityStateInput {\n session_id: string;\n turn_id: string;\n thread_id: string;\n key: string;\n state: JsonValue;\n}\n\nexport interface ListTurnEventsInput {\n session_id: string;\n turn_id: string;\n limit: number;\n page_token: string | undefined;\n order: 'asc' | 'desc' | undefined;\n}\n\nexport interface ListSessionEventsInput {\n session_id: string;\n limit: number;\n page_token: string | undefined;\n last_turn_id: string | undefined;\n}\n\n/**\n * Session/turn persistence contract. Pure durability: no streaming, SSE, or\n * subscription members. Callers authorize the parent session before using\n * turn-scoped operations, which rely on globally unique session_id values.\n * Capability maps are initialized atomically by createTurn and subsequently\n * updated through patchThreadCapabilityState. Agent binding is session-scoped\n * as a discriminated `agent` (ref | value); named agents are not hydrated on read.\n */\nexport interface ISessionStore<\n TSessionCustom extends object = Record<string, never>,\n TTurnCustom extends object = Record<string, never>,\n> {\n /**\n * Persists a discriminated `agent` (ref | value). SQL backends may flatten to columns.\n * `session_id` is globally unique across tenants.\n * Persists caller-supplied `created_by_subject` (immutable after create).\n * Sets `last_activity_timestamp_ms` (= now) on create.\n */\n createSession(input: CreateSessionInput<TSessionCustom>): Promise<void>;\n\n /** Permanently removes a session and all related data; missing sessions are a no-op. */\n deleteSession(input: DeleteSessionInput): Promise<void>;\n\n /**\n * Returns the session as stored (ref agents are not hydrated to a value).\n * Does **not** bump `last_activity_timestamp_ms` (read path).\n */\n getSession(input: GetSessionInput): Promise<SessionRecord<TSessionCustom> | undefined>;\n\n /**\n * Lookup by tenant-scoped `external_id`. Missing or null external ids are not found.\n * Does **not** bump `last_activity_timestamp_ms` (read path).\n */\n getSessionByExternalId(input: GetSessionByExternalIdInput): Promise<SessionRecord<TSessionCustom> | undefined>;\n\n /**\n * PATCH semantics — update only the provided fields:\n * - agent: replace inline binding (inline sessions only; reference → invariant error).\n * - title: set/replace the session title.\n * - metadata: full replace of the caller-owned string map when set.\n * Bumps `last_activity_timestamp_ms` (= now) in the same update.\n */\n updateSession(input: UpdateSessionInput<TSessionCustom>): Promise<void>;\n\n /**\n * Paginated list of the tenant's sessions ordered by `updated_at`\n * (`order` defaults to `desc`, `session_id` tie-break). `page_token` is a\n * keyset cursor on `(updated_at, session_id)`. `start_timestamp` /\n * `end_timestamp` are inclusive instant bounds on `created_at`. Optional\n * `agent_id` filters ref-bound sessions. `created_by_or_agent_ids` matches\n * rows created by its subject OR bound to one of its agent ids. Does **not**\n * bump `last_activity_timestamp_ms` (read path).\n */\n listSessions(\n input: ListSessionsInput,\n ): Promise<{ data: SessionRecord<TSessionCustom>[]; pagination: TokenPagination }>;\n\n /**\n * Creates the turn AND advances `session.last_turn_id`. Context merging is the\n * store's responsibility: `new_context_appends` and `new_threads` are applied\n * on top of the previous turn's snapshot when `turn.previous_turn_id` is set.\n * `capability_states` is the complete authoritative map for every resulting\n * thread and is persisted directly without store-side carry-forward.\n *\n * Atomicity (store contract): insert turn + set `last_turn_id` (+ session\n * turn-list append if the backend has one) MUST be one atomic unit per\n * session. Concurrent createTurn on the same session must not drop a turn\n * row or leave `last_turn_id` pointing at a turn that was never created.\n * The implementation supplies the mechanism (session lock, row lock/tx, …).\n *\n * Also bumps `session.last_activity_timestamp_ms` and increments\n * `session.metrics.total_turns` in that same atomic unit.\n *\n * Fork semantics for `turn.previous_turn_id`:\n * - `null` — new root turn (no parent); always allowed.\n * - string — fork/chain from that turn when it exists. Tip-equality is NOT\n * required; concurrent forks from the same tip both succeed. Unknown id is\n * allowed (relaxed) and treated as no inheritance. If that turn exists and\n * is still `running`, reject with {@link PreviousTurnRunningError} —\n * callers must {@link freezeAndGetTurn} first.\n * `last_turn_id` always advances to the new turn in the same atomic unit.\n *\n * `update_session_title_if_not_exist`: when set AND `session.title` is\n * unset/null, set it in the same atomic unit; an existing title is NEVER\n * overwritten (first write wins). The caller derives the value; the store\n * only conditionally sets it.\n */\n createTurn(input: CreateTurnInput<TTurnCustom>): Promise<void>;\n\n /**\n * Cancel if still running (persist `turn_done` and fold cost/duration into\n * `session.metrics`); already-terminal turns are a read. Missing → {@link TurnNotFoundError}.\n */\n freezeAndGetTurn(input: FreezeAndGetTurnInput): Promise<TurnRecord<TTurnCustom>>;\n\n /** Returns the turn record, or undefined if not found in this session. */\n getTurn(input: GetTurnInput): Promise<TurnRecord<TTurnCustom> | undefined>;\n\n /** Paginated list of turn rows (no snapshot); use getTurn for assembled snapshot. */\n listTurns(\n input: ListTurnsInput,\n ): Promise<{ data: TurnRecordWithoutSnapshot<TTurnCustom>[]; pagination: TokenPagination }>;\n\n /**\n * First terminal write wins (`running` → done/cancelled/error); otherwise 409.\n * Winning write also folds cost/duration into `session.metrics`. Missing → 404.\n * Must use the same lock/CAS as other turn mutations.\n */\n updateTurnState(input: UpdateTurnStateInput): Promise<void>;\n\n /**\n * Durable event log for the turn. MUST include lifecycle rows: a\n * `TurnCreatedEvent` at the start of the stream and a terminal `TurnDoneEvent`\n * (or its cancelled/error shape) at the end. Every turn written through this\n * API persists lifecycle in the stream — implementations never synthesize\n * missing created/done rows on read. Every event MUST carry `id` and\n * `created_at`; `id` is a monotonic ULID and is the primary within-turn sort\n * key. `created_at` records event creation time but is not the order key.\n */\n appendToEvents(input: AppendToEventsInput): Promise<void>;\n\n /** Adds thread snapshots to the turn (sub-agent spawns). */\n addThreads(input: AddThreadsInput): Promise<void>;\n\n /** Removes threads from the turn by id. */\n removeThreads(input: RemoveThreadsInput): Promise<void>;\n\n /** Appends messages to a thread's context; optionally updates usage / completion marker. */\n appendToThreadContext(input: AppendToThreadContextInput): Promise<void>;\n\n /** Replaces a thread's context wholesale (context-overwrite event). */\n overwriteThreadContext(input: OverwriteThreadContextInput): Promise<void>;\n\n /** Patches the turn snapshot's MCP server init info (by source id). */\n patchMCPServers(input: PatchMCPServersInput): Promise<void>;\n\n /** Patches the turn snapshot's sandbox info (id for cross-turn reattach). */\n patchSandboxInfo(input: PatchSandboxInfoInput): Promise<void>;\n\n /** Generic capability KV — store has zero Plan/feature knowledge. */\n patchThreadCapabilityState(input: PatchThreadCapabilityStateInput): Promise<void>;\n\n /** Paginated read ordered lexically by monotonic `event.id` (asc default). */\n listTurnEvents(input: ListTurnEventsInput): Promise<{\n data: PersistedTurnEvent[];\n pagination: TokenPagination;\n }>;\n\n /**\n * Paginated session-wide feed of persisted events across turns, including\n * turn.created / turn.done lifecycle rows. Reads only — never synthesizes.\n *\n * Includes events from **running** turns (the HTTP layer may 409 separately;\n * that is not store semantics). Ordering: newest turn first, then monotonic\n * `event.id` descending within each turn. `last_turn_id` anchors the window\n * to that turn plus its ancestors; when omitted, the session's current\n * `last_turn_id` selects the active branch. Continuation page tokens encode\n * that anchor and take precedence over `last_turn_id`, so later session\n * activity cannot move an in-progress pagination walk to another branch.\n * `ancestor_ids` may include only the previous N ancestors and need not reach\n * the root — implementations must spill through older turns' own\n * `ancestor_ids` when needed. Sibling branches from forks are excluded.\n */\n listSessionEvents(input: ListSessionEventsInput): Promise<{\n data: SessionEventItem[];\n pagination: TokenPagination;\n }>;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
@@ -2,7 +2,7 @@ import type { SessionRecord } from '../models/SessionRecord';
2
2
  import type { TurnRecord } from '../models/TurnRecord';
3
3
  import type { PersistedTurnEvent, SessionEventItem } from '../schemas/events';
4
4
  import type { TokenPagination } from '../schemas/pagination';
5
- import type { AddThreadsInput, AppendToEventsInput, AppendToThreadContextInput, CreateSessionInput, CreateTurnInput, DeleteSessionInput, FreezeAndGetTurnInput, GetSessionInput, GetTurnInput, ISessionStore, ListSessionEventsInput, ListSessionsInput, ListTurnEventsInput, ListTurnsInput, OverwriteThreadContextInput, PatchMCPServersInput, PatchSandboxInfoInput, PatchThreadCapabilityStateInput, RemoveThreadsInput, TurnRecordWithoutSnapshot, UpdateSessionInput, UpdateTurnStateInput } from './ISessionStore';
5
+ import type { AddThreadsInput, AppendToEventsInput, AppendToThreadContextInput, CreateSessionInput, CreateTurnInput, DeleteSessionInput, FreezeAndGetTurnInput, GetSessionByExternalIdInput, GetSessionInput, GetTurnInput, ISessionStore, ListSessionEventsInput, ListSessionsInput, ListTurnEventsInput, ListTurnsInput, OverwriteThreadContextInput, PatchMCPServersInput, PatchSandboxInfoInput, PatchThreadCapabilityStateInput, RemoveThreadsInput, TurnRecordWithoutSnapshot, UpdateSessionInput, UpdateTurnStateInput } from './ISessionStore';
6
6
  /**
7
7
  * Reference ISessionStore implementation. Proves the contract without SSE/subscription
8
8
  * hooks. Deep-copies on read/write boundaries so callers cannot mutate stored state.
@@ -14,6 +14,7 @@ export declare class InMemorySessionStore<TSessionCustom extends object = Record
14
14
  createSession(input: CreateSessionInput<TSessionCustom>): Promise<void>;
15
15
  deleteSession(input: DeleteSessionInput): Promise<void>;
16
16
  getSession(input: GetSessionInput): Promise<SessionRecord<TSessionCustom> | undefined>;
17
+ getSessionByExternalId(input: GetSessionByExternalIdInput): Promise<SessionRecord<TSessionCustom> | undefined>;
17
18
  updateSession(input: UpdateSessionInput<TSessionCustom>): Promise<void>;
18
19
  listSessions(input: ListSessionsInput): Promise<{
19
20
  data: SessionRecord<TSessionCustom>[];
@@ -28,6 +29,8 @@ export declare class InMemorySessionStore<TSessionCustom extends object = Record
28
29
  }>;
29
30
  updateTurnState(input: UpdateTurnStateInput): Promise<void>;
30
31
  appendToEvents(input: AppendToEventsInput): Promise<void>;
32
+ /** Cost from turn metrics; duration is completed_at − created_at, floored at 0. */
33
+ private addTerminalSessionMetrics;
31
34
  private requireTurn;
32
35
  private requireRunningTurn;
33
36
  addThreads(input: AddThreadsInput): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"InMemorySessionStore.d.ts","sourceRoot":"","sources":["../../../src/agent-session/store/InMemorySessionStore.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,KAAK,EAAE,UAAU,EAAgB,MAAM,sBAAsB,CAAC;AACrE,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC9E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAG7D,OAAO,KAAK,EACV,eAAe,EACf,mBAAmB,EACnB,0BAA0B,EAC1B,kBAAkB,EAClB,eAAe,EACf,kBAAkB,EAClB,qBAAqB,EACrB,eAAe,EACf,YAAY,EACZ,aAAa,EACb,sBAAsB,EACtB,iBAAiB,EACjB,mBAAmB,EACnB,cAAc,EAEd,2BAA2B,EAC3B,oBAAoB,EACpB,qBAAqB,EACrB,+BAA+B,EAC/B,kBAAkB,EAElB,yBAAyB,EACzB,kBAAkB,EAClB,oBAAoB,EACrB,MAAM,iBAAiB,CAAC;AA6HzB;;;GAGG;AACH,qBAAa,oBAAoB,CAC/B,cAAc,SAAS,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,EACrD,WAAW,SAAS,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAClD,YAAW,aAAa,CAAC,cAAc,EAAE,WAAW,CAAC;IACrD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoD;IAC7E,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA8C;IACpE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoC;IAErD,aAAa,CAAC,KAAK,EAAE,kBAAkB,CAAC,cAAc,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB5E;IAEK,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAY5D;IAEK,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,aAAa,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC,CAG3F;IAEK,aAAa,CAAC,KAAK,EAAE,kBAAkB,CAAC,cAAc,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAmB5E;IAEK,YAAY,CAChB,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAAC;QAAE,IAAI,EAAE,aAAa,CAAC,cAAc,CAAC,EAAE,CAAC;QAAC,UAAU,EAAE,eAAe,CAAA;KAAE,CAAC,CAoDjF;IAEK,UAAU,CAAC,KAAK,EAAE,eAAe,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAmDnE;IAEK,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAmBrF;IAEK,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,GAAG,SAAS,CAAC,CAG/E;IAEK,SAAS,CACb,KAAK,EAAE,cAAc,GACpB,OAAO,CAAC;QAAE,IAAI,EAAE,yBAAyB,CAAC,WAAW,CAAC,EAAE,CAAC;QAAC,UAAU,EAAE,eAAe,CAAA;KAAE,CAAC,CAe1F;IAEK,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAahE;IAEK,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAS9D;IAED,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,kBAAkB;IAQpB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAOtD;IAEK,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAU5D;IAEK,qBAAqB,CAAC,KAAK,EAAE,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAe5E;IAEK,sBAAsB,CAAC,KAAK,EAAE,2BAA2B,GAAG,OAAO,CAAC,IAAI,CAAC,CAW9E;IAEK,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAQhE;IAEK,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAKlE;IAEK,0BAA0B,CAAC,KAAK,EAAE,+BAA+B,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBtF;IAEK,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC;QACxD,IAAI,EAAE,kBAAkB,EAAE,CAAC;QAC3B,UAAU,EAAE,eAAe,CAAC;KAC7B,CAAC,CAWD;IAEK,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC;QAC9D,IAAI,EAAE,gBAAgB,EAAE,CAAC;QACzB,UAAU,EAAE,eAAe,CAAC;KAC7B,CAAC,CAkCD;IAED;;;;OAIG;IACH,OAAO,CAAC,oBAAoB;CAqB7B"}
1
+ {"version":3,"file":"InMemorySessionStore.d.ts","sourceRoot":"","sources":["../../../src/agent-session/store/InMemorySessionStore.ts"],"names":[],"mappings":"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"}
@@ -116,17 +116,32 @@ var InMemorySessionStore = class {
116
116
  if (this.sessions.has(key)) {
117
117
  throw new import_SessionStoreErrors.SessionAlreadyExistsError(input.session_id);
118
118
  }
119
+ const externalId = input.external_id;
120
+ if (externalId !== null) {
121
+ for (const stored of this.sessions.values()) {
122
+ if (stored.record.tenant_id === input.tenant_id && stored.record.external_id === externalId) {
123
+ throw new import_SessionStoreErrors.SessionExternalIdConflictError(externalId);
124
+ }
125
+ }
126
+ }
119
127
  const now = /* @__PURE__ */ new Date();
120
128
  const record = {
121
129
  tenant_id: input.tenant_id,
122
130
  session_id: input.session_id,
123
- created_by: input.created_by,
131
+ created_by_subject: input.created_by_subject,
124
132
  agent: deepCopy(input.agent),
125
133
  title: null,
126
134
  last_turn_id: null,
135
+ external_id: externalId,
127
136
  created_at: now,
128
137
  updated_at: now,
129
138
  last_activity_timestamp_ms: Date.now(),
139
+ metrics: {
140
+ total_cost_in_usd: 0,
141
+ total_duration_ms: 0,
142
+ total_turns: 0
143
+ },
144
+ metadata: deepCopy(input.metadata),
130
145
  custom: input.custom !== null ? deepCopy(input.custom) : null
131
146
  };
132
147
  this.sessions.set(key, { record, turnIds: [] });
@@ -149,6 +164,14 @@ var InMemorySessionStore = class {
149
164
  const stored = this.sessions.get(sessionKey(input.session_id));
150
165
  return stored?.record.tenant_id === input.tenant_id ? deepCopy(stored.record) : void 0;
151
166
  }
167
+ async getSessionByExternalId(input) {
168
+ for (const stored of this.sessions.values()) {
169
+ if (stored.record.tenant_id === input.tenant_id && stored.record.external_id === input.external_id) {
170
+ return deepCopy(stored.record);
171
+ }
172
+ }
173
+ return void 0;
174
+ }
152
175
  async updateSession(input) {
153
176
  const key = sessionKey(input.session_id);
154
177
  const stored = this.sessions.get(key);
@@ -164,6 +187,9 @@ var InMemorySessionStore = class {
164
187
  if (input.title !== void 0) {
165
188
  stored.record.title = input.title;
166
189
  }
190
+ if (input.metadata !== void 0) {
191
+ stored.record.metadata = deepCopy(input.metadata);
192
+ }
167
193
  const now = Date.now();
168
194
  stored.record.updated_at = new Date(now);
169
195
  stored.record.last_activity_timestamp_ms = now;
@@ -171,6 +197,7 @@ var InMemorySessionStore = class {
171
197
  }
172
198
  async listSessions(input) {
173
199
  const records = [];
200
+ const createdByOrAgentIds = input.created_by_or_agent_ids;
174
201
  for (const stored of this.sessions.values()) {
175
202
  if (stored.record.tenant_id !== input.tenant_id) {
176
203
  continue;
@@ -178,8 +205,12 @@ var InMemorySessionStore = class {
178
205
  if (input.agent_id !== void 0 && (stored.record.agent.type !== "reference" || stored.record.agent.id !== input.agent_id)) {
179
206
  continue;
180
207
  }
181
- if (input.created_by !== void 0 && stored.record.created_by !== input.created_by) {
182
- continue;
208
+ if (createdByOrAgentIds !== void 0) {
209
+ const creatorMatches = stored.record.created_by_subject.subject_id === createdByOrAgentIds.created_by_subject_id;
210
+ const agentMatches = stored.record.agent.type === "reference" && createdByOrAgentIds.agent_ids.includes(stored.record.agent.id);
211
+ if (!creatorMatches && !agentMatches) {
212
+ continue;
213
+ }
183
214
  }
184
215
  const createdAt = stored.record.created_at.getTime();
185
216
  if (input.start_timestamp !== void 0 && createdAt < input.start_timestamp.getTime()) {
@@ -250,6 +281,7 @@ var InMemorySessionStore = class {
250
281
  this.events.set(tKey, []);
251
282
  stored.turnIds.push(input.turn.turn_id);
252
283
  stored.record.last_turn_id = input.turn.turn_id;
284
+ stored.record.metrics.total_turns += 1;
253
285
  stored.record.last_activity_timestamp_ms = Date.now();
254
286
  stored.record.updated_at = /* @__PURE__ */ new Date();
255
287
  if (input.update_session_title_if_not_exist !== null && stored.record.title === null) {
@@ -271,6 +303,7 @@ var InMemorySessionStore = class {
271
303
  if (list) {
272
304
  list.push(deepCopy(input.turn_done_event));
273
305
  }
306
+ this.addTerminalSessionMetrics(input.session_id, turn.created_at, cancelledState);
274
307
  }
275
308
  return deepCopy(turn);
276
309
  }
@@ -306,6 +339,7 @@ var InMemorySessionStore = class {
306
339
  if (list) {
307
340
  list.push(deepCopy(input.turn_done_event));
308
341
  }
342
+ this.addTerminalSessionMetrics(input.session_id, turn.created_at, input.state);
309
343
  }
310
344
  async appendToEvents(input) {
311
345
  this.requireRunningTurn(input.session_id, input.turn_id);
@@ -317,6 +351,16 @@ var InMemorySessionStore = class {
317
351
  list.push(...deepCopy(input.events));
318
352
  return;
319
353
  }
354
+ /** Cost from turn metrics; duration is completed_at − created_at, floored at 0. */
355
+ addTerminalSessionMetrics(sessionId, created_at, state) {
356
+ const stored = this.sessions.get(sessionKey(sessionId));
357
+ if (!stored) {
358
+ throw new import_SessionStoreErrors.SessionNotFoundError(sessionId);
359
+ }
360
+ const elapsed_ms = Date.parse(state.completed_at) - created_at.getTime();
361
+ stored.record.metrics.total_cost_in_usd += state.metrics?.total_cost_in_usd ?? 0;
362
+ stored.record.metrics.total_duration_ms += elapsed_ms > 0 ? Math.trunc(elapsed_ms) : 0;
363
+ }
320
364
  requireTurn(sessionId, turnId) {
321
365
  const turn = this.turns.get(turnKey({ session_id: sessionId, turn_id: turnId }));
322
366
  if (!turn) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/agent-session/store/InMemorySessionStore.ts"],"sourcesContent":["import type { AgentThreadSnapshot } from '../../core/runtime/AgentThread.types';\nimport { getEmptyCurrentContextUsage } from '../../core/runtime/contextUsage';\nimport type { SessionRecord } from '../models/SessionRecord';\nimport type { TurnRecord, TurnSnapshot } from '../models/TurnRecord';\nimport type { PersistedTurnEvent, SessionEventItem } from '../schemas/events';\nimport type { TokenPagination } from '../schemas/pagination';\nimport type { TerminalTurnState } from '../schemas/turn';\nimport { assertCreateTurnThreadDelta } from './assertCreateTurnThreadDelta';\nimport type {\n AddThreadsInput,\n AppendToEventsInput,\n AppendToThreadContextInput,\n CreateSessionInput,\n CreateTurnInput,\n DeleteSessionInput,\n FreezeAndGetTurnInput,\n GetSessionInput,\n GetTurnInput,\n ISessionStore,\n ListSessionEventsInput,\n ListSessionsInput,\n ListTurnEventsInput,\n ListTurnsInput,\n NewThreadInit,\n OverwriteThreadContextInput,\n PatchMCPServersInput,\n PatchSandboxInfoInput,\n PatchThreadCapabilityStateInput,\n RemoveThreadsInput,\n TurnContextAppend,\n TurnRecordWithoutSnapshot,\n UpdateSessionInput,\n UpdateTurnStateInput,\n} from './ISessionStore';\nimport { decodeOffsetPageToken, encodeOffsetPageToken } from './OffsetPageToken';\nimport {\n decodeSessionEventPageToken,\n paginateSessionEventRows,\n type SessionEventPageCursor,\n} from './SessionEventPageToken';\nimport { decodeSessionListPageToken, paginateSessionListRows } from './SessionListPageToken';\nimport {\n PreviousTurnRunningError,\n SessionAlreadyExistsError,\n SessionNotFoundError,\n SessionStoreInvariantError,\n TurnAlreadyExistsError,\n TurnNotFoundError,\n TurnNotRunningError,\n} from './SessionStoreErrors';\n\n/* eslint-disable @typescript-eslint/require-await -- in-memory store is synchronous; methods stay async so thrown SessionStore*Error reject as Promises for ISessionStore callers */\n\ntype StoredEvent = PersistedTurnEvent;\n\ninterface StoredSession<TSessionCustom extends object> {\n record: SessionRecord<TSessionCustom>;\n turnIds: string[];\n}\n\nfunction deepCopy<T>(value: T): T {\n return structuredClone(value);\n}\n\nfunction sessionKey(sessionId: string): string {\n return sessionId;\n}\n\nfunction turnKey({ session_id, turn_id }: { session_id: string; turn_id: string }): string {\n return `${session_id}:${turn_id}`;\n}\n\n/** Lexicographic session_id order — shared by listSessions sort and keyset filter. */\nfunction compareSessionId(a: string, b: string): number {\n if (a < b) {\n return -1;\n }\n if (a > b) {\n return 1;\n }\n return 0;\n}\n\nfunction newThreadSnapshot(thread: NewThreadInit): AgentThreadSnapshot {\n return {\n thread_id: thread.thread_id,\n parent: thread.parent ?? null,\n agent_info: thread.agent_info ?? null,\n capability_state: null,\n context: [],\n current_context_usage: getEmptyCurrentContextUsage(),\n completion: null,\n };\n}\n\nfunction applyContextAppends(threads: Record<string, AgentThreadSnapshot>, appends: TurnContextAppend[]): void {\n for (const append of appends) {\n const thread = threads[append.thread_id];\n if (!thread) {\n throw new SessionStoreInvariantError(`new_context_appends references unknown thread ${append.thread_id}`);\n }\n thread.context.push(...deepCopy(append.context));\n if (append.current_context_usage !== null) {\n thread.current_context_usage = deepCopy(append.current_context_usage);\n }\n }\n}\n\nfunction buildSnapshotFromDelta(input: {\n previousSnapshot: TurnSnapshot | undefined;\n new_threads: NewThreadInit[];\n new_context_appends: TurnContextAppend[];\n capability_states: CreateTurnInput['capability_states'];\n}): TurnSnapshot {\n const threads: Record<string, AgentThreadSnapshot> = input.previousSnapshot\n ? deepCopy(input.previousSnapshot.threads)\n : {};\n\n assertCreateTurnThreadDelta({\n previousThreadIds: new Set(Object.keys(threads)),\n new_threads: input.new_threads,\n new_context_appends: input.new_context_appends,\n capability_states: input.capability_states,\n });\n\n for (const nt of input.new_threads) {\n threads[nt.thread_id] = newThreadSnapshot(nt);\n }\n\n applyContextAppends(threads, input.new_context_appends);\n for (const capability of input.capability_states) {\n const thread = threads[capability.thread_id];\n if (thread === undefined) {\n throw new SessionStoreInvariantError(`capability_states references unknown thread ${capability.thread_id}`);\n }\n thread.capability_state = deepCopy(capability.capability_state);\n }\n\n return {\n threads,\n mcp_servers: input.previousSnapshot ? deepCopy(input.previousSnapshot.mcp_servers) : null,\n sandbox_info: input.previousSnapshot ? deepCopy(input.previousSnapshot.sandbox_info) : null,\n };\n}\n\nfunction paginate<T>(items: T[], limit: number, pageToken?: string): { data: T[]; pagination: TokenPagination } {\n const offset = decodeOffsetPageToken(pageToken);\n const slice = items.slice(offset, offset + limit);\n return {\n data: slice,\n pagination: {\n limit,\n ...(offset + limit < items.length ? { next_page_token: encodeOffsetPageToken(offset + limit) } : {}),\n ...(offset > 0 ? { previous_page_token: encodeOffsetPageToken(Math.max(0, offset - limit)) } : {}),\n },\n };\n}\n\n/**\n * Reference ISessionStore implementation. Proves the contract without SSE/subscription\n * hooks. Deep-copies on read/write boundaries so callers cannot mutate stored state.\n */\nexport class InMemorySessionStore<\n TSessionCustom extends object = Record<string, never>,\n TTurnCustom extends object = Record<string, never>,\n> implements ISessionStore<TSessionCustom, TTurnCustom> {\n private readonly sessions = new Map<string, StoredSession<TSessionCustom>>();\n private readonly turns = new Map<string, TurnRecord<TTurnCustom>>();\n private readonly events = new Map<string, StoredEvent[]>();\n\n async createSession(input: CreateSessionInput<TSessionCustom>): Promise<void> {\n const key = sessionKey(input.session_id);\n if (this.sessions.has(key)) {\n throw new SessionAlreadyExistsError(input.session_id);\n }\n const now = new Date();\n const record: SessionRecord<TSessionCustom> = {\n tenant_id: input.tenant_id,\n session_id: input.session_id,\n created_by: input.created_by,\n agent: deepCopy(input.agent),\n title: null,\n last_turn_id: null,\n created_at: now,\n updated_at: now,\n last_activity_timestamp_ms: Date.now(),\n custom: input.custom !== null ? deepCopy(input.custom) : null,\n };\n this.sessions.set(key, { record, turnIds: [] });\n return;\n }\n\n async deleteSession(input: DeleteSessionInput): Promise<void> {\n const sKey = sessionKey(input.session_id);\n const stored = this.sessions.get(sKey);\n if (stored?.record.tenant_id !== input.tenant_id) {\n return;\n }\n for (const turnId of stored.turnIds) {\n const tKey = turnKey({ session_id: input.session_id, turn_id: turnId });\n this.turns.delete(tKey);\n this.events.delete(tKey);\n }\n this.sessions.delete(sKey);\n }\n\n async getSession(input: GetSessionInput): Promise<SessionRecord<TSessionCustom> | undefined> {\n const stored = this.sessions.get(sessionKey(input.session_id));\n return stored?.record.tenant_id === input.tenant_id ? deepCopy(stored.record) : undefined;\n }\n\n async updateSession(input: UpdateSessionInput<TSessionCustom>): Promise<void> {\n const key = sessionKey(input.session_id);\n const stored = this.sessions.get(key);\n if (stored?.record.tenant_id !== input.tenant_id) {\n throw new SessionNotFoundError(input.session_id);\n }\n if (input.agent !== undefined) {\n if (stored.record.agent.type === 'reference') {\n throw new SessionStoreInvariantError(`Session ${input.session_id} is named; agent cannot be updated`);\n }\n stored.record.agent = deepCopy(input.agent);\n }\n if (input.title !== undefined) {\n stored.record.title = input.title;\n }\n const now = Date.now();\n stored.record.updated_at = new Date(now);\n stored.record.last_activity_timestamp_ms = now;\n return;\n }\n\n async listSessions(\n input: ListSessionsInput,\n ): Promise<{ data: SessionRecord<TSessionCustom>[]; pagination: TokenPagination }> {\n const records: SessionRecord<TSessionCustom>[] = [];\n for (const stored of this.sessions.values()) {\n if (stored.record.tenant_id !== input.tenant_id) {\n continue;\n }\n if (\n input.agent_id !== undefined &&\n (stored.record.agent.type !== 'reference' || stored.record.agent.id !== input.agent_id)\n ) {\n continue;\n }\n if (input.created_by !== undefined && stored.record.created_by !== input.created_by) {\n continue;\n }\n const createdAt = stored.record.created_at.getTime();\n if (input.start_timestamp !== undefined && createdAt < input.start_timestamp.getTime()) {\n continue;\n }\n if (input.end_timestamp !== undefined && createdAt > input.end_timestamp.getTime()) {\n continue;\n }\n records.push(stored.record);\n }\n records.sort((a, b) => {\n const aTime = a.updated_at.getTime();\n const bTime = b.updated_at.getTime();\n if (aTime !== bTime) {\n return input.order === 'asc' ? aTime - bTime : bTime - aTime;\n }\n // Same lexicographic order as the keyset predicate below (and Postgres/SQLite).\n return input.order === 'asc'\n ? compareSessionId(a.session_id, b.session_id)\n : compareSessionId(b.session_id, a.session_id);\n });\n\n let filtered = records;\n const cursor = decodeSessionListPageToken(input.page_token);\n if (cursor) {\n const cursorTime = new Date(cursor.updated_at).getTime();\n filtered = records.filter(record => {\n const t = record.updated_at.getTime();\n const idCmp = compareSessionId(record.session_id, cursor.session_id);\n if (input.order === 'asc') {\n return t > cursorTime || (t === cursorTime && idCmp > 0);\n }\n return t < cursorTime || (t === cursorTime && idCmp < 0);\n });\n }\n\n const page = paginateSessionListRows(filtered, input.limit, row => row.updated_at.toISOString());\n return { data: deepCopy(page.data), pagination: page.pagination };\n }\n\n async createTurn(input: CreateTurnInput<TTurnCustom>): Promise<void> {\n // Atomicity is free here: this body is fully synchronous, so Node's\n // run-to-completion guarantees it. Real backends must still use their own\n // locking/transactions to satisfy the ISessionStore createTurn contract.\n const sKey = sessionKey(input.turn.session_id);\n const stored = this.sessions.get(sKey);\n if (!stored) {\n throw new SessionNotFoundError(input.turn.session_id);\n }\n\n const previousTurnId = input.turn.previous_turn_id;\n let previousSnapshot: TurnSnapshot | undefined;\n if (previousTurnId !== null) {\n const prevKey = turnKey({ session_id: input.turn.session_id, turn_id: previousTurnId });\n const prev = this.turns.get(prevKey);\n // Unknown previous_turn_id is allowed (relaxed): treat as no inheritance.\n // A still-running previous must be frozen first.\n if (prev !== undefined) {\n if (prev.state.status === 'running') {\n throw new PreviousTurnRunningError(previousTurnId);\n }\n previousSnapshot = prev.snapshot;\n }\n }\n\n const tKey = turnKey(input.turn);\n if (this.turns.has(tKey)) {\n throw new TurnAlreadyExistsError(input.turn.turn_id);\n }\n\n const snapshot = buildSnapshotFromDelta({\n previousSnapshot,\n new_threads: input.new_threads,\n new_context_appends: input.new_context_appends,\n capability_states: input.capability_states,\n });\n\n const turnRecord: TurnRecord<TTurnCustom> = {\n ...deepCopy(input.turn),\n snapshot,\n };\n\n this.turns.set(tKey, turnRecord);\n this.events.set(tKey, []);\n stored.turnIds.push(input.turn.turn_id);\n stored.record.last_turn_id = input.turn.turn_id;\n stored.record.last_activity_timestamp_ms = Date.now();\n stored.record.updated_at = new Date();\n if (input.update_session_title_if_not_exist !== null && stored.record.title === null) {\n stored.record.title = input.update_session_title_if_not_exist;\n }\n }\n\n async freezeAndGetTurn(input: FreezeAndGetTurnInput): Promise<TurnRecord<TTurnCustom>> {\n const tKey = turnKey(input);\n const turn = this.requireTurn(input.session_id, input.turn_id);\n\n if (turn.state.status === 'running') {\n const cancelledState: TerminalTurnState = {\n status: 'cancelled',\n reason: input.reason,\n completed_at: new Date().toISOString(),\n };\n turn.state = cancelledState;\n turn.updated_at = new Date();\n const list = this.events.get(tKey);\n if (list) {\n list.push(deepCopy(input.turn_done_event));\n }\n }\n\n return deepCopy(turn);\n }\n\n async getTurn(input: GetTurnInput): Promise<TurnRecord<TTurnCustom> | undefined> {\n const turn = this.turns.get(turnKey(input));\n return turn ? deepCopy(turn) : undefined;\n }\n\n async listTurns(\n input: ListTurnsInput,\n ): Promise<{ data: TurnRecordWithoutSnapshot<TTurnCustom>[]; pagination: TokenPagination }> {\n const stored = this.sessions.get(sessionKey(input.session_id));\n if (!stored) {\n throw new SessionNotFoundError(input.session_id);\n }\n const records = stored.turnIds.map(id => {\n const turn = this.turns.get(turnKey({ session_id: input.session_id, turn_id: id }));\n if (!turn) {\n throw new TurnNotFoundError(id);\n }\n const { snapshot, ...row } = deepCopy(turn);\n void snapshot;\n return row;\n });\n return paginate(records, input.limit, input.page_token);\n }\n\n async updateTurnState(input: UpdateTurnStateInput): Promise<void> {\n // Same as createTurn: synchronous body ⇒ atomic under run-to-completion.\n const tKey = turnKey(input);\n const turn = this.requireTurn(input.session_id, input.turn_id);\n if (turn.state.status !== 'running') {\n throw new TurnNotRunningError(input.turn_id, turn.state);\n }\n turn.state = deepCopy(input.state);\n turn.updated_at = new Date();\n const list = this.events.get(tKey);\n if (list) {\n list.push(deepCopy(input.turn_done_event));\n }\n }\n\n async appendToEvents(input: AppendToEventsInput): Promise<void> {\n this.requireRunningTurn(input.session_id, input.turn_id);\n const tKey = turnKey(input);\n const list = this.events.get(tKey);\n if (!list) {\n throw new TurnNotFoundError(input.turn_id);\n }\n list.push(...deepCopy(input.events));\n return;\n }\n\n private requireTurn(sessionId: string, turnId: string): TurnRecord<TTurnCustom> {\n const turn = this.turns.get(turnKey({ session_id: sessionId, turn_id: turnId }));\n if (!turn) {\n throw new TurnNotFoundError(turnId);\n }\n return turn;\n }\n\n private requireRunningTurn(sessionId: string, turnId: string): TurnRecord<TTurnCustom> {\n const turn = this.requireTurn(sessionId, turnId);\n if (turn.state.status !== 'running') {\n throw new TurnNotRunningError(turnId, turn.state);\n }\n return turn;\n }\n\n async addThreads(input: AddThreadsInput): Promise<void> {\n const turn = this.requireRunningTurn(input.session_id, input.turn_id);\n for (const thread of input.threads) {\n turn.snapshot.threads[thread.thread_id] = deepCopy(thread);\n }\n turn.updated_at = new Date();\n return;\n }\n\n async removeThreads(input: RemoveThreadsInput): Promise<void> {\n if (input.thread_ids.length === 0) {\n return;\n }\n const turn = this.requireRunningTurn(input.session_id, input.turn_id);\n for (const id of input.thread_ids) {\n Reflect.deleteProperty(turn.snapshot.threads, id);\n }\n turn.updated_at = new Date();\n return;\n }\n\n async appendToThreadContext(input: AppendToThreadContextInput): Promise<void> {\n const turn = this.requireRunningTurn(input.session_id, input.turn_id);\n const thread = turn.snapshot.threads[input.thread_id];\n if (!thread) {\n throw new SessionStoreInvariantError(`Thread not found: ${input.thread_id}`);\n }\n thread.context.push(...deepCopy(input.context));\n if (input.current_context_usage !== null) {\n thread.current_context_usage = deepCopy(input.current_context_usage);\n }\n if (input.completion !== null) {\n thread.completion = deepCopy(input.completion);\n }\n turn.updated_at = new Date();\n return;\n }\n\n async overwriteThreadContext(input: OverwriteThreadContextInput): Promise<void> {\n const turn = this.requireRunningTurn(input.session_id, input.turn_id);\n const threadId = input.event.thread_id;\n const thread = turn.snapshot.threads[threadId];\n if (!thread) {\n throw new SessionStoreInvariantError(`Thread not found: ${threadId}`);\n }\n thread.context = deepCopy(input.event.context);\n thread.current_context_usage = deepCopy(input.event.current_context_usage);\n turn.updated_at = new Date();\n return;\n }\n\n async patchMCPServers(input: PatchMCPServersInput): Promise<void> {\n const turn = this.requireRunningTurn(input.session_id, input.turn_id);\n turn.snapshot.mcp_servers ??= {};\n for (const server of input.mcp_servers) {\n turn.snapshot.mcp_servers[server.id] = deepCopy(server);\n }\n turn.updated_at = new Date();\n return;\n }\n\n async patchSandboxInfo(input: PatchSandboxInfoInput): Promise<void> {\n const turn = this.requireRunningTurn(input.session_id, input.turn_id);\n turn.snapshot.sandbox_info = deepCopy(input.sandbox_info);\n turn.updated_at = new Date();\n return;\n }\n\n async patchThreadCapabilityState(input: PatchThreadCapabilityStateInput): Promise<void> {\n const turn = this.requireRunningTurn(input.session_id, input.turn_id);\n const thread = turn.snapshot.threads[input.thread_id];\n if (!thread) {\n throw new SessionStoreInvariantError(`Thread not found: ${input.thread_id}`);\n }\n thread.capability_state ??= {};\n if (input.state === null) {\n Reflect.deleteProperty(thread.capability_state, input.key);\n if (Object.keys(thread.capability_state).length === 0) {\n thread.capability_state = null;\n }\n } else {\n thread.capability_state[input.key] = deepCopy(input.state);\n }\n turn.updated_at = new Date();\n return;\n }\n\n async listTurnEvents(input: ListTurnEventsInput): Promise<{\n data: PersistedTurnEvent[];\n pagination: TokenPagination;\n }> {\n this.requireTurn(input.session_id, input.turn_id);\n const list = this.events.get(turnKey(input));\n if (!list) {\n throw new TurnNotFoundError(input.turn_id);\n }\n const ordered = [...list].sort((a, b) =>\n input.order === 'desc' ? b.id.localeCompare(a.id) : a.id.localeCompare(b.id),\n );\n const page = paginate(ordered, input.limit, input.page_token);\n return { data: deepCopy(page.data), pagination: page.pagination };\n }\n\n async listSessionEvents(input: ListSessionEventsInput): Promise<{\n data: SessionEventItem[];\n pagination: TokenPagination;\n }> {\n const stored = this.sessions.get(sessionKey(input.session_id));\n if (!stored) {\n throw new SessionNotFoundError(input.session_id);\n }\n\n const decodedCursor = input.page_token === undefined ? undefined : decodeSessionEventPageToken(input.page_token);\n const lastTurnId = decodedCursor?.last_turn_id ?? input.last_turn_id ?? stored.record.last_turn_id;\n if (lastTurnId === null) {\n return { data: [], pagination: { limit: input.limit } };\n }\n const cursor: SessionEventPageCursor = {\n last_turn_id: lastTurnId,\n offset: decodedCursor?.offset ?? 0,\n };\n\n const anchor = this.turns.get(turnKey({ session_id: input.session_id, turn_id: cursor.last_turn_id }));\n if (!anchor) {\n throw new TurnNotFoundError(cursor.last_turn_id);\n }\n const turnIds = this.resolveAncestorChain(input.session_id, anchor);\n const flattened: SessionEventItem[] = [];\n for (const turnId of [...turnIds].reverse()) {\n const evts = this.events.get(turnKey({ session_id: input.session_id, turn_id: turnId })) ?? [];\n for (const event of [...evts].sort((a, b) => b.id.localeCompare(a.id))) {\n flattened.push({ turn_id: turnId, event });\n }\n }\n const page = paginateSessionEventRows(\n flattened.slice(cursor.offset, cursor.offset + input.limit + 1),\n input.limit,\n cursor,\n );\n return { data: deepCopy(page.data), pagination: page.pagination };\n }\n\n /**\n * Full chain (oldest first, anchor last). `ancestor_ids` may be only the\n * previous N ancestors, so spill through the oldest ancestor's own window\n * until a root or gap. A missing turn or repeated id ends the walk.\n */\n private resolveAncestorChain(sessionId: string, anchor: TurnRecord<TTurnCustom>): string[] {\n const chain = [...anchor.ancestor_ids, anchor.turn_id];\n const seen = new Set(chain);\n let oldestId = chain[0];\n while (oldestId && oldestId !== anchor.turn_id) {\n const oldest = this.turns.get(turnKey({ session_id: sessionId, turn_id: oldestId }));\n if (!oldest) {\n break;\n }\n const older = oldest.ancestor_ids.filter(id => !seen.has(id));\n if (older.length === 0) {\n break;\n }\n chain.unshift(...older);\n for (const id of older) {\n seen.add(id);\n }\n oldestId = older[0];\n }\n return chain;\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/agent-session/store/InMemorySessionStore.ts"],"sourcesContent":["import type { AgentThreadSnapshot } from '../../core/runtime/AgentThread.types';\nimport { getEmptyCurrentContextUsage } from '../../core/runtime/contextUsage';\nimport type { SessionRecord } from '../models/SessionRecord';\nimport type { TurnRecord, TurnSnapshot } from '../models/TurnRecord';\nimport type { PersistedTurnEvent, SessionEventItem } from '../schemas/events';\nimport type { TokenPagination } from '../schemas/pagination';\nimport type { TerminalTurnState } from '../schemas/turn';\nimport { assertCreateTurnThreadDelta } from './assertCreateTurnThreadDelta';\nimport type {\n AddThreadsInput,\n AppendToEventsInput,\n AppendToThreadContextInput,\n CreateSessionInput,\n CreateTurnInput,\n DeleteSessionInput,\n FreezeAndGetTurnInput,\n GetSessionByExternalIdInput,\n GetSessionInput,\n GetTurnInput,\n ISessionStore,\n ListSessionEventsInput,\n ListSessionsInput,\n ListTurnEventsInput,\n ListTurnsInput,\n NewThreadInit,\n OverwriteThreadContextInput,\n PatchMCPServersInput,\n PatchSandboxInfoInput,\n PatchThreadCapabilityStateInput,\n RemoveThreadsInput,\n TurnContextAppend,\n TurnRecordWithoutSnapshot,\n UpdateSessionInput,\n UpdateTurnStateInput,\n} from './ISessionStore';\nimport { decodeOffsetPageToken, encodeOffsetPageToken } from './OffsetPageToken';\nimport {\n decodeSessionEventPageToken,\n paginateSessionEventRows,\n type SessionEventPageCursor,\n} from './SessionEventPageToken';\nimport { decodeSessionListPageToken, paginateSessionListRows } from './SessionListPageToken';\nimport {\n PreviousTurnRunningError,\n SessionAlreadyExistsError,\n SessionExternalIdConflictError,\n SessionNotFoundError,\n SessionStoreInvariantError,\n TurnAlreadyExistsError,\n TurnNotFoundError,\n TurnNotRunningError,\n} from './SessionStoreErrors';\n\n/* eslint-disable @typescript-eslint/require-await -- in-memory store is synchronous; methods stay async so thrown SessionStore*Error reject as Promises for ISessionStore callers */\n\ntype StoredEvent = PersistedTurnEvent;\n\ninterface StoredSession<TSessionCustom extends object> {\n record: SessionRecord<TSessionCustom>;\n turnIds: string[];\n}\n\nfunction deepCopy<T>(value: T): T {\n return structuredClone(value);\n}\n\nfunction sessionKey(sessionId: string): string {\n return sessionId;\n}\n\nfunction turnKey({ session_id, turn_id }: { session_id: string; turn_id: string }): string {\n return `${session_id}:${turn_id}`;\n}\n\n/** Lexicographic session_id order — shared by listSessions sort and keyset filter. */\nfunction compareSessionId(a: string, b: string): number {\n if (a < b) {\n return -1;\n }\n if (a > b) {\n return 1;\n }\n return 0;\n}\n\nfunction newThreadSnapshot(thread: NewThreadInit): AgentThreadSnapshot {\n return {\n thread_id: thread.thread_id,\n parent: thread.parent ?? null,\n agent_info: thread.agent_info ?? null,\n capability_state: null,\n context: [],\n current_context_usage: getEmptyCurrentContextUsage(),\n completion: null,\n };\n}\n\nfunction applyContextAppends(threads: Record<string, AgentThreadSnapshot>, appends: TurnContextAppend[]): void {\n for (const append of appends) {\n const thread = threads[append.thread_id];\n if (!thread) {\n throw new SessionStoreInvariantError(`new_context_appends references unknown thread ${append.thread_id}`);\n }\n thread.context.push(...deepCopy(append.context));\n if (append.current_context_usage !== null) {\n thread.current_context_usage = deepCopy(append.current_context_usage);\n }\n }\n}\n\nfunction buildSnapshotFromDelta(input: {\n previousSnapshot: TurnSnapshot | undefined;\n new_threads: NewThreadInit[];\n new_context_appends: TurnContextAppend[];\n capability_states: CreateTurnInput['capability_states'];\n}): TurnSnapshot {\n const threads: Record<string, AgentThreadSnapshot> = input.previousSnapshot\n ? deepCopy(input.previousSnapshot.threads)\n : {};\n\n assertCreateTurnThreadDelta({\n previousThreadIds: new Set(Object.keys(threads)),\n new_threads: input.new_threads,\n new_context_appends: input.new_context_appends,\n capability_states: input.capability_states,\n });\n\n for (const nt of input.new_threads) {\n threads[nt.thread_id] = newThreadSnapshot(nt);\n }\n\n applyContextAppends(threads, input.new_context_appends);\n for (const capability of input.capability_states) {\n const thread = threads[capability.thread_id];\n if (thread === undefined) {\n throw new SessionStoreInvariantError(`capability_states references unknown thread ${capability.thread_id}`);\n }\n thread.capability_state = deepCopy(capability.capability_state);\n }\n\n return {\n threads,\n mcp_servers: input.previousSnapshot ? deepCopy(input.previousSnapshot.mcp_servers) : null,\n sandbox_info: input.previousSnapshot ? deepCopy(input.previousSnapshot.sandbox_info) : null,\n };\n}\n\nfunction paginate<T>(items: T[], limit: number, pageToken?: string): { data: T[]; pagination: TokenPagination } {\n const offset = decodeOffsetPageToken(pageToken);\n const slice = items.slice(offset, offset + limit);\n return {\n data: slice,\n pagination: {\n limit,\n ...(offset + limit < items.length ? { next_page_token: encodeOffsetPageToken(offset + limit) } : {}),\n ...(offset > 0 ? { previous_page_token: encodeOffsetPageToken(Math.max(0, offset - limit)) } : {}),\n },\n };\n}\n\n/**\n * Reference ISessionStore implementation. Proves the contract without SSE/subscription\n * hooks. Deep-copies on read/write boundaries so callers cannot mutate stored state.\n */\nexport class InMemorySessionStore<\n TSessionCustom extends object = Record<string, never>,\n TTurnCustom extends object = Record<string, never>,\n> implements ISessionStore<TSessionCustom, TTurnCustom> {\n private readonly sessions = new Map<string, StoredSession<TSessionCustom>>();\n private readonly turns = new Map<string, TurnRecord<TTurnCustom>>();\n private readonly events = new Map<string, StoredEvent[]>();\n\n async createSession(input: CreateSessionInput<TSessionCustom>): Promise<void> {\n const key = sessionKey(input.session_id);\n if (this.sessions.has(key)) {\n throw new SessionAlreadyExistsError(input.session_id);\n }\n const externalId = input.external_id;\n if (externalId !== null) {\n for (const stored of this.sessions.values()) {\n if (stored.record.tenant_id === input.tenant_id && stored.record.external_id === externalId) {\n throw new SessionExternalIdConflictError(externalId);\n }\n }\n }\n const now = new Date();\n const record: SessionRecord<TSessionCustom> = {\n tenant_id: input.tenant_id,\n session_id: input.session_id,\n created_by_subject: input.created_by_subject,\n agent: deepCopy(input.agent),\n title: null,\n last_turn_id: null,\n external_id: externalId,\n created_at: now,\n updated_at: now,\n last_activity_timestamp_ms: Date.now(),\n metrics: {\n total_cost_in_usd: 0,\n total_duration_ms: 0,\n total_turns: 0,\n },\n metadata: deepCopy(input.metadata),\n custom: input.custom !== null ? deepCopy(input.custom) : null,\n };\n this.sessions.set(key, { record, turnIds: [] });\n return;\n }\n\n async deleteSession(input: DeleteSessionInput): Promise<void> {\n const sKey = sessionKey(input.session_id);\n const stored = this.sessions.get(sKey);\n if (stored?.record.tenant_id !== input.tenant_id) {\n return;\n }\n for (const turnId of stored.turnIds) {\n const tKey = turnKey({ session_id: input.session_id, turn_id: turnId });\n this.turns.delete(tKey);\n this.events.delete(tKey);\n }\n this.sessions.delete(sKey);\n }\n\n async getSession(input: GetSessionInput): Promise<SessionRecord<TSessionCustom> | undefined> {\n const stored = this.sessions.get(sessionKey(input.session_id));\n return stored?.record.tenant_id === input.tenant_id ? deepCopy(stored.record) : undefined;\n }\n\n async getSessionByExternalId(input: GetSessionByExternalIdInput): Promise<SessionRecord<TSessionCustom> | undefined> {\n for (const stored of this.sessions.values()) {\n if (stored.record.tenant_id === input.tenant_id && stored.record.external_id === input.external_id) {\n return deepCopy(stored.record);\n }\n }\n return undefined;\n }\n\n async updateSession(input: UpdateSessionInput<TSessionCustom>): Promise<void> {\n const key = sessionKey(input.session_id);\n const stored = this.sessions.get(key);\n if (stored?.record.tenant_id !== input.tenant_id) {\n throw new SessionNotFoundError(input.session_id);\n }\n if (input.agent !== undefined) {\n if (stored.record.agent.type === 'reference') {\n throw new SessionStoreInvariantError(`Session ${input.session_id} is named; agent cannot be updated`);\n }\n stored.record.agent = deepCopy(input.agent);\n }\n if (input.title !== undefined) {\n stored.record.title = input.title;\n }\n if (input.metadata !== undefined) {\n stored.record.metadata = deepCopy(input.metadata);\n }\n const now = Date.now();\n stored.record.updated_at = new Date(now);\n stored.record.last_activity_timestamp_ms = now;\n return;\n }\n\n async listSessions(\n input: ListSessionsInput,\n ): Promise<{ data: SessionRecord<TSessionCustom>[]; pagination: TokenPagination }> {\n const records: SessionRecord<TSessionCustom>[] = [];\n const createdByOrAgentIds = input.created_by_or_agent_ids;\n for (const stored of this.sessions.values()) {\n if (stored.record.tenant_id !== input.tenant_id) {\n continue;\n }\n if (\n input.agent_id !== undefined &&\n (stored.record.agent.type !== 'reference' || stored.record.agent.id !== input.agent_id)\n ) {\n continue;\n }\n if (createdByOrAgentIds !== undefined) {\n const creatorMatches =\n stored.record.created_by_subject.subject_id === createdByOrAgentIds.created_by_subject_id;\n const agentMatches =\n stored.record.agent.type === 'reference' && createdByOrAgentIds.agent_ids.includes(stored.record.agent.id);\n if (!creatorMatches && !agentMatches) {\n continue;\n }\n }\n const createdAt = stored.record.created_at.getTime();\n if (input.start_timestamp !== undefined && createdAt < input.start_timestamp.getTime()) {\n continue;\n }\n if (input.end_timestamp !== undefined && createdAt > input.end_timestamp.getTime()) {\n continue;\n }\n records.push(stored.record);\n }\n records.sort((a, b) => {\n const aTime = a.updated_at.getTime();\n const bTime = b.updated_at.getTime();\n if (aTime !== bTime) {\n return input.order === 'asc' ? aTime - bTime : bTime - aTime;\n }\n // Same lexicographic order as the keyset predicate below (and Postgres/SQLite).\n return input.order === 'asc'\n ? compareSessionId(a.session_id, b.session_id)\n : compareSessionId(b.session_id, a.session_id);\n });\n\n let filtered = records;\n const cursor = decodeSessionListPageToken(input.page_token);\n if (cursor) {\n const cursorTime = new Date(cursor.updated_at).getTime();\n filtered = records.filter(record => {\n const t = record.updated_at.getTime();\n const idCmp = compareSessionId(record.session_id, cursor.session_id);\n if (input.order === 'asc') {\n return t > cursorTime || (t === cursorTime && idCmp > 0);\n }\n return t < cursorTime || (t === cursorTime && idCmp < 0);\n });\n }\n\n const page = paginateSessionListRows(filtered, input.limit, row => row.updated_at.toISOString());\n return { data: deepCopy(page.data), pagination: page.pagination };\n }\n\n async createTurn(input: CreateTurnInput<TTurnCustom>): Promise<void> {\n // Atomicity is free here: this body is fully synchronous, so Node's\n // run-to-completion guarantees it. Real backends must still use their own\n // locking/transactions to satisfy the ISessionStore createTurn contract.\n const sKey = sessionKey(input.turn.session_id);\n const stored = this.sessions.get(sKey);\n if (!stored) {\n throw new SessionNotFoundError(input.turn.session_id);\n }\n\n const previousTurnId = input.turn.previous_turn_id;\n let previousSnapshot: TurnSnapshot | undefined;\n if (previousTurnId !== null) {\n const prevKey = turnKey({ session_id: input.turn.session_id, turn_id: previousTurnId });\n const prev = this.turns.get(prevKey);\n // Unknown previous_turn_id is allowed (relaxed): treat as no inheritance.\n // A still-running previous must be frozen first.\n if (prev !== undefined) {\n if (prev.state.status === 'running') {\n throw new PreviousTurnRunningError(previousTurnId);\n }\n previousSnapshot = prev.snapshot;\n }\n }\n\n const tKey = turnKey(input.turn);\n if (this.turns.has(tKey)) {\n throw new TurnAlreadyExistsError(input.turn.turn_id);\n }\n\n const snapshot = buildSnapshotFromDelta({\n previousSnapshot,\n new_threads: input.new_threads,\n new_context_appends: input.new_context_appends,\n capability_states: input.capability_states,\n });\n\n const turnRecord: TurnRecord<TTurnCustom> = {\n ...deepCopy(input.turn),\n snapshot,\n };\n\n this.turns.set(tKey, turnRecord);\n this.events.set(tKey, []);\n stored.turnIds.push(input.turn.turn_id);\n stored.record.last_turn_id = input.turn.turn_id;\n stored.record.metrics.total_turns += 1;\n stored.record.last_activity_timestamp_ms = Date.now();\n stored.record.updated_at = new Date();\n if (input.update_session_title_if_not_exist !== null && stored.record.title === null) {\n stored.record.title = input.update_session_title_if_not_exist;\n }\n }\n\n async freezeAndGetTurn(input: FreezeAndGetTurnInput): Promise<TurnRecord<TTurnCustom>> {\n const tKey = turnKey(input);\n const turn = this.requireTurn(input.session_id, input.turn_id);\n\n if (turn.state.status === 'running') {\n const cancelledState: TerminalTurnState = {\n status: 'cancelled',\n reason: input.reason,\n completed_at: new Date().toISOString(),\n };\n turn.state = cancelledState;\n turn.updated_at = new Date();\n const list = this.events.get(tKey);\n if (list) {\n list.push(deepCopy(input.turn_done_event));\n }\n this.addTerminalSessionMetrics(input.session_id, turn.created_at, cancelledState);\n }\n\n return deepCopy(turn);\n }\n\n async getTurn(input: GetTurnInput): Promise<TurnRecord<TTurnCustom> | undefined> {\n const turn = this.turns.get(turnKey(input));\n return turn ? deepCopy(turn) : undefined;\n }\n\n async listTurns(\n input: ListTurnsInput,\n ): Promise<{ data: TurnRecordWithoutSnapshot<TTurnCustom>[]; pagination: TokenPagination }> {\n const stored = this.sessions.get(sessionKey(input.session_id));\n if (!stored) {\n throw new SessionNotFoundError(input.session_id);\n }\n const records = stored.turnIds.map(id => {\n const turn = this.turns.get(turnKey({ session_id: input.session_id, turn_id: id }));\n if (!turn) {\n throw new TurnNotFoundError(id);\n }\n const { snapshot, ...row } = deepCopy(turn);\n void snapshot;\n return row;\n });\n return paginate(records, input.limit, input.page_token);\n }\n\n async updateTurnState(input: UpdateTurnStateInput): Promise<void> {\n // Same as createTurn: synchronous body ⇒ atomic under run-to-completion.\n const tKey = turnKey(input);\n const turn = this.requireTurn(input.session_id, input.turn_id);\n if (turn.state.status !== 'running') {\n throw new TurnNotRunningError(input.turn_id, turn.state);\n }\n turn.state = deepCopy(input.state);\n turn.updated_at = new Date();\n const list = this.events.get(tKey);\n if (list) {\n list.push(deepCopy(input.turn_done_event));\n }\n this.addTerminalSessionMetrics(input.session_id, turn.created_at, input.state);\n }\n\n async appendToEvents(input: AppendToEventsInput): Promise<void> {\n this.requireRunningTurn(input.session_id, input.turn_id);\n const tKey = turnKey(input);\n const list = this.events.get(tKey);\n if (!list) {\n throw new TurnNotFoundError(input.turn_id);\n }\n list.push(...deepCopy(input.events));\n return;\n }\n\n /** Cost from turn metrics; duration is completed_at − created_at, floored at 0. */\n private addTerminalSessionMetrics(sessionId: string, created_at: Date, state: TerminalTurnState): void {\n const stored = this.sessions.get(sessionKey(sessionId));\n if (!stored) {\n throw new SessionNotFoundError(sessionId);\n }\n const elapsed_ms = Date.parse(state.completed_at) - created_at.getTime();\n stored.record.metrics.total_cost_in_usd += state.metrics?.total_cost_in_usd ?? 0;\n stored.record.metrics.total_duration_ms += elapsed_ms > 0 ? Math.trunc(elapsed_ms) : 0;\n }\n\n private requireTurn(sessionId: string, turnId: string): TurnRecord<TTurnCustom> {\n const turn = this.turns.get(turnKey({ session_id: sessionId, turn_id: turnId }));\n if (!turn) {\n throw new TurnNotFoundError(turnId);\n }\n return turn;\n }\n\n private requireRunningTurn(sessionId: string, turnId: string): TurnRecord<TTurnCustom> {\n const turn = this.requireTurn(sessionId, turnId);\n if (turn.state.status !== 'running') {\n throw new TurnNotRunningError(turnId, turn.state);\n }\n return turn;\n }\n\n async addThreads(input: AddThreadsInput): Promise<void> {\n const turn = this.requireRunningTurn(input.session_id, input.turn_id);\n for (const thread of input.threads) {\n turn.snapshot.threads[thread.thread_id] = deepCopy(thread);\n }\n turn.updated_at = new Date();\n return;\n }\n\n async removeThreads(input: RemoveThreadsInput): Promise<void> {\n if (input.thread_ids.length === 0) {\n return;\n }\n const turn = this.requireRunningTurn(input.session_id, input.turn_id);\n for (const id of input.thread_ids) {\n Reflect.deleteProperty(turn.snapshot.threads, id);\n }\n turn.updated_at = new Date();\n return;\n }\n\n async appendToThreadContext(input: AppendToThreadContextInput): Promise<void> {\n const turn = this.requireRunningTurn(input.session_id, input.turn_id);\n const thread = turn.snapshot.threads[input.thread_id];\n if (!thread) {\n throw new SessionStoreInvariantError(`Thread not found: ${input.thread_id}`);\n }\n thread.context.push(...deepCopy(input.context));\n if (input.current_context_usage !== null) {\n thread.current_context_usage = deepCopy(input.current_context_usage);\n }\n if (input.completion !== null) {\n thread.completion = deepCopy(input.completion);\n }\n turn.updated_at = new Date();\n return;\n }\n\n async overwriteThreadContext(input: OverwriteThreadContextInput): Promise<void> {\n const turn = this.requireRunningTurn(input.session_id, input.turn_id);\n const threadId = input.event.thread_id;\n const thread = turn.snapshot.threads[threadId];\n if (!thread) {\n throw new SessionStoreInvariantError(`Thread not found: ${threadId}`);\n }\n thread.context = deepCopy(input.event.context);\n thread.current_context_usage = deepCopy(input.event.current_context_usage);\n turn.updated_at = new Date();\n return;\n }\n\n async patchMCPServers(input: PatchMCPServersInput): Promise<void> {\n const turn = this.requireRunningTurn(input.session_id, input.turn_id);\n turn.snapshot.mcp_servers ??= {};\n for (const server of input.mcp_servers) {\n turn.snapshot.mcp_servers[server.id] = deepCopy(server);\n }\n turn.updated_at = new Date();\n return;\n }\n\n async patchSandboxInfo(input: PatchSandboxInfoInput): Promise<void> {\n const turn = this.requireRunningTurn(input.session_id, input.turn_id);\n turn.snapshot.sandbox_info = deepCopy(input.sandbox_info);\n turn.updated_at = new Date();\n return;\n }\n\n async patchThreadCapabilityState(input: PatchThreadCapabilityStateInput): Promise<void> {\n const turn = this.requireRunningTurn(input.session_id, input.turn_id);\n const thread = turn.snapshot.threads[input.thread_id];\n if (!thread) {\n throw new SessionStoreInvariantError(`Thread not found: ${input.thread_id}`);\n }\n thread.capability_state ??= {};\n if (input.state === null) {\n Reflect.deleteProperty(thread.capability_state, input.key);\n if (Object.keys(thread.capability_state).length === 0) {\n thread.capability_state = null;\n }\n } else {\n thread.capability_state[input.key] = deepCopy(input.state);\n }\n turn.updated_at = new Date();\n return;\n }\n\n async listTurnEvents(input: ListTurnEventsInput): Promise<{\n data: PersistedTurnEvent[];\n pagination: TokenPagination;\n }> {\n this.requireTurn(input.session_id, input.turn_id);\n const list = this.events.get(turnKey(input));\n if (!list) {\n throw new TurnNotFoundError(input.turn_id);\n }\n const ordered = [...list].sort((a, b) =>\n input.order === 'desc' ? b.id.localeCompare(a.id) : a.id.localeCompare(b.id),\n );\n const page = paginate(ordered, input.limit, input.page_token);\n return { data: deepCopy(page.data), pagination: page.pagination };\n }\n\n async listSessionEvents(input: ListSessionEventsInput): Promise<{\n data: SessionEventItem[];\n pagination: TokenPagination;\n }> {\n const stored = this.sessions.get(sessionKey(input.session_id));\n if (!stored) {\n throw new SessionNotFoundError(input.session_id);\n }\n\n const decodedCursor = input.page_token === undefined ? undefined : decodeSessionEventPageToken(input.page_token);\n const lastTurnId = decodedCursor?.last_turn_id ?? input.last_turn_id ?? stored.record.last_turn_id;\n if (lastTurnId === null) {\n return { data: [], pagination: { limit: input.limit } };\n }\n const cursor: SessionEventPageCursor = {\n last_turn_id: lastTurnId,\n offset: decodedCursor?.offset ?? 0,\n };\n\n const anchor = this.turns.get(turnKey({ session_id: input.session_id, turn_id: cursor.last_turn_id }));\n if (!anchor) {\n throw new TurnNotFoundError(cursor.last_turn_id);\n }\n const turnIds = this.resolveAncestorChain(input.session_id, anchor);\n const flattened: SessionEventItem[] = [];\n for (const turnId of [...turnIds].reverse()) {\n const evts = this.events.get(turnKey({ session_id: input.session_id, turn_id: turnId })) ?? [];\n for (const event of [...evts].sort((a, b) => b.id.localeCompare(a.id))) {\n flattened.push({ turn_id: turnId, event });\n }\n }\n const page = paginateSessionEventRows(\n flattened.slice(cursor.offset, cursor.offset + input.limit + 1),\n input.limit,\n cursor,\n );\n return { data: deepCopy(page.data), pagination: page.pagination };\n }\n\n /**\n * Full chain (oldest first, anchor last). `ancestor_ids` may be only the\n * previous N ancestors, so spill through the oldest ancestor's own window\n * until a root or gap. A missing turn or repeated id ends the walk.\n */\n private resolveAncestorChain(sessionId: string, anchor: TurnRecord<TTurnCustom>): string[] {\n const chain = [...anchor.ancestor_ids, anchor.turn_id];\n const seen = new Set(chain);\n let oldestId = chain[0];\n while (oldestId && oldestId !== anchor.turn_id) {\n const oldest = this.turns.get(turnKey({ session_id: sessionId, turn_id: oldestId }));\n if (!oldest) {\n break;\n }\n const older = oldest.ancestor_ids.filter(id => !seen.has(id));\n if (older.length === 0) {\n break;\n }\n chain.unshift(...older);\n for (const id of older) {\n seen.add(id);\n }\n oldestId = older[0];\n }\n return chain;\n }\n}\n"],"mappings":"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","names":[]}
@@ -10,6 +10,7 @@ import { decodeSessionListPageToken, paginateSessionListRows } from "./SessionLi
10
10
  import {
11
11
  PreviousTurnRunningError,
12
12
  SessionAlreadyExistsError,
13
+ SessionExternalIdConflictError,
13
14
  SessionNotFoundError,
14
15
  SessionStoreInvariantError,
15
16
  TurnAlreadyExistsError,
@@ -103,17 +104,32 @@ var InMemorySessionStore = class {
103
104
  if (this.sessions.has(key)) {
104
105
  throw new SessionAlreadyExistsError(input.session_id);
105
106
  }
107
+ const externalId = input.external_id;
108
+ if (externalId !== null) {
109
+ for (const stored of this.sessions.values()) {
110
+ if (stored.record.tenant_id === input.tenant_id && stored.record.external_id === externalId) {
111
+ throw new SessionExternalIdConflictError(externalId);
112
+ }
113
+ }
114
+ }
106
115
  const now = /* @__PURE__ */ new Date();
107
116
  const record = {
108
117
  tenant_id: input.tenant_id,
109
118
  session_id: input.session_id,
110
- created_by: input.created_by,
119
+ created_by_subject: input.created_by_subject,
111
120
  agent: deepCopy(input.agent),
112
121
  title: null,
113
122
  last_turn_id: null,
123
+ external_id: externalId,
114
124
  created_at: now,
115
125
  updated_at: now,
116
126
  last_activity_timestamp_ms: Date.now(),
127
+ metrics: {
128
+ total_cost_in_usd: 0,
129
+ total_duration_ms: 0,
130
+ total_turns: 0
131
+ },
132
+ metadata: deepCopy(input.metadata),
117
133
  custom: input.custom !== null ? deepCopy(input.custom) : null
118
134
  };
119
135
  this.sessions.set(key, { record, turnIds: [] });
@@ -136,6 +152,14 @@ var InMemorySessionStore = class {
136
152
  const stored = this.sessions.get(sessionKey(input.session_id));
137
153
  return stored?.record.tenant_id === input.tenant_id ? deepCopy(stored.record) : void 0;
138
154
  }
155
+ async getSessionByExternalId(input) {
156
+ for (const stored of this.sessions.values()) {
157
+ if (stored.record.tenant_id === input.tenant_id && stored.record.external_id === input.external_id) {
158
+ return deepCopy(stored.record);
159
+ }
160
+ }
161
+ return void 0;
162
+ }
139
163
  async updateSession(input) {
140
164
  const key = sessionKey(input.session_id);
141
165
  const stored = this.sessions.get(key);
@@ -151,6 +175,9 @@ var InMemorySessionStore = class {
151
175
  if (input.title !== void 0) {
152
176
  stored.record.title = input.title;
153
177
  }
178
+ if (input.metadata !== void 0) {
179
+ stored.record.metadata = deepCopy(input.metadata);
180
+ }
154
181
  const now = Date.now();
155
182
  stored.record.updated_at = new Date(now);
156
183
  stored.record.last_activity_timestamp_ms = now;
@@ -158,6 +185,7 @@ var InMemorySessionStore = class {
158
185
  }
159
186
  async listSessions(input) {
160
187
  const records = [];
188
+ const createdByOrAgentIds = input.created_by_or_agent_ids;
161
189
  for (const stored of this.sessions.values()) {
162
190
  if (stored.record.tenant_id !== input.tenant_id) {
163
191
  continue;
@@ -165,8 +193,12 @@ var InMemorySessionStore = class {
165
193
  if (input.agent_id !== void 0 && (stored.record.agent.type !== "reference" || stored.record.agent.id !== input.agent_id)) {
166
194
  continue;
167
195
  }
168
- if (input.created_by !== void 0 && stored.record.created_by !== input.created_by) {
169
- continue;
196
+ if (createdByOrAgentIds !== void 0) {
197
+ const creatorMatches = stored.record.created_by_subject.subject_id === createdByOrAgentIds.created_by_subject_id;
198
+ const agentMatches = stored.record.agent.type === "reference" && createdByOrAgentIds.agent_ids.includes(stored.record.agent.id);
199
+ if (!creatorMatches && !agentMatches) {
200
+ continue;
201
+ }
170
202
  }
171
203
  const createdAt = stored.record.created_at.getTime();
172
204
  if (input.start_timestamp !== void 0 && createdAt < input.start_timestamp.getTime()) {
@@ -237,6 +269,7 @@ var InMemorySessionStore = class {
237
269
  this.events.set(tKey, []);
238
270
  stored.turnIds.push(input.turn.turn_id);
239
271
  stored.record.last_turn_id = input.turn.turn_id;
272
+ stored.record.metrics.total_turns += 1;
240
273
  stored.record.last_activity_timestamp_ms = Date.now();
241
274
  stored.record.updated_at = /* @__PURE__ */ new Date();
242
275
  if (input.update_session_title_if_not_exist !== null && stored.record.title === null) {
@@ -258,6 +291,7 @@ var InMemorySessionStore = class {
258
291
  if (list) {
259
292
  list.push(deepCopy(input.turn_done_event));
260
293
  }
294
+ this.addTerminalSessionMetrics(input.session_id, turn.created_at, cancelledState);
261
295
  }
262
296
  return deepCopy(turn);
263
297
  }
@@ -293,6 +327,7 @@ var InMemorySessionStore = class {
293
327
  if (list) {
294
328
  list.push(deepCopy(input.turn_done_event));
295
329
  }
330
+ this.addTerminalSessionMetrics(input.session_id, turn.created_at, input.state);
296
331
  }
297
332
  async appendToEvents(input) {
298
333
  this.requireRunningTurn(input.session_id, input.turn_id);
@@ -304,6 +339,16 @@ var InMemorySessionStore = class {
304
339
  list.push(...deepCopy(input.events));
305
340
  return;
306
341
  }
342
+ /** Cost from turn metrics; duration is completed_at − created_at, floored at 0. */
343
+ addTerminalSessionMetrics(sessionId, created_at, state) {
344
+ const stored = this.sessions.get(sessionKey(sessionId));
345
+ if (!stored) {
346
+ throw new SessionNotFoundError(sessionId);
347
+ }
348
+ const elapsed_ms = Date.parse(state.completed_at) - created_at.getTime();
349
+ stored.record.metrics.total_cost_in_usd += state.metrics?.total_cost_in_usd ?? 0;
350
+ stored.record.metrics.total_duration_ms += elapsed_ms > 0 ? Math.trunc(elapsed_ms) : 0;
351
+ }
307
352
  requireTurn(sessionId, turnId) {
308
353
  const turn = this.turns.get(turnKey({ session_id: sessionId, turn_id: turnId }));
309
354
  if (!turn) {