@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.
- package/dist/agent-session/SessionHandle.d.ts +1 -0
- package/dist/agent-session/SessionHandle.d.ts.map +1 -1
- package/dist/agent-session/SessionHandle.js +3 -0
- package/dist/agent-session/SessionHandle.js.map +1 -1
- package/dist/agent-session/SessionHandle.mjs +3 -0
- package/dist/agent-session/SessionHandle.mjs.map +1 -1
- package/dist/agent-session/Sessions.d.ts +17 -6
- package/dist/agent-session/Sessions.d.ts.map +1 -1
- package/dist/agent-session/Sessions.js +45 -1
- package/dist/agent-session/Sessions.js.map +1 -1
- package/dist/agent-session/Sessions.mjs +45 -1
- package/dist/agent-session/Sessions.mjs.map +1 -1
- package/dist/agent-session/index.d.ts +6 -4
- package/dist/agent-session/index.d.ts.map +1 -1
- package/dist/agent-session/index.js +9 -0
- package/dist/agent-session/index.js.map +1 -1
- package/dist/agent-session/index.mjs +7 -1
- package/dist/agent-session/index.mjs.map +1 -1
- package/dist/agent-session/models/SessionRecord.d.ts +10 -2
- package/dist/agent-session/models/SessionRecord.d.ts.map +1 -1
- package/dist/agent-session/models/SessionRecord.js.map +1 -1
- package/dist/agent-session/schemas/session.d.ts +19 -1
- package/dist/agent-session/schemas/session.d.ts.map +1 -1
- package/dist/agent-session/schemas/session.js +23 -3
- package/dist/agent-session/schemas/session.js.map +1 -1
- package/dist/agent-session/schemas/session.mjs +21 -3
- package/dist/agent-session/schemas/session.mjs.map +1 -1
- package/dist/agent-session/schemas/subject.d.ts +9 -0
- package/dist/agent-session/schemas/subject.d.ts.map +1 -0
- package/dist/agent-session/schemas/subject.js +36 -0
- package/dist/agent-session/schemas/subject.js.map +1 -0
- package/dist/agent-session/schemas/subject.mjs +11 -0
- package/dist/agent-session/schemas/subject.mjs.map +1 -0
- package/dist/agent-session/store/ISessionStore.d.ts +29 -20
- package/dist/agent-session/store/ISessionStore.d.ts.map +1 -1
- package/dist/agent-session/store/ISessionStore.js.map +1 -1
- package/dist/agent-session/store/InMemorySessionStore.d.ts +4 -1
- package/dist/agent-session/store/InMemorySessionStore.d.ts.map +1 -1
- package/dist/agent-session/store/InMemorySessionStore.js +47 -3
- package/dist/agent-session/store/InMemorySessionStore.js.map +1 -1
- package/dist/agent-session/store/InMemorySessionStore.mjs +48 -3
- package/dist/agent-session/store/InMemorySessionStore.mjs.map +1 -1
- package/dist/agent-session/store/SessionStoreErrors.d.ts +4 -0
- package/dist/agent-session/store/SessionStoreErrors.d.ts.map +1 -1
- package/dist/agent-session/store/SessionStoreErrors.js +10 -0
- package/dist/agent-session/store/SessionStoreErrors.js.map +1 -1
- package/dist/agent-session/store/SessionStoreErrors.mjs +9 -0
- package/dist/agent-session/store/SessionStoreErrors.mjs.map +1 -1
- package/dist/core/llm/VercelAILLM.d.ts +1 -1
- package/dist/core/llm/VercelAILLM.d.ts.map +1 -1
- package/dist/core/llm/VercelAILLM.js +4 -2
- package/dist/core/llm/VercelAILLM.js.map +1 -1
- package/dist/core/llm/VercelAILLM.mjs +4 -2
- package/dist/core/llm/VercelAILLM.mjs.map +1 -1
- package/dist/core/mcp/remoteMcpClient.js +1 -1
- package/dist/core/mcp/remoteMcpClient.js.map +1 -1
- package/dist/core/mcp/remoteMcpClient.mjs +1 -1
- package/dist/core/mcp/remoteMcpClient.mjs.map +1 -1
- 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;
|
|
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
|
-
|
|
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 (
|
|
182
|
-
|
|
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
|
-
|
|
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 (
|
|
169
|
-
|
|
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) {
|