@arnilo/prism 0.2.3 → 0.2.5
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/CHANGELOG.md +10 -0
- package/README.md +3 -3
- package/dist/agent-session/create-agent.d.ts +6 -0
- package/dist/agent-session/create-agent.js +13 -0
- package/dist/agent-session/event-subscriber.d.ts +17 -0
- package/dist/agent-session/event-subscriber.js +68 -0
- package/dist/agent-session/helpers.d.ts +44 -0
- package/dist/agent-session/helpers.js +194 -0
- package/dist/agent-session/session.d.ts +101 -0
- package/dist/agent-session/session.js +1568 -0
- package/dist/agent-session.d.ts +6 -105
- package/dist/agent-session.js +6 -1833
- package/dist/contracts-core/agent.d.ts +257 -0
- package/dist/contracts-core/agent.js +5 -0
- package/dist/contracts-core/compaction.d.ts +72 -0
- package/dist/contracts-core/compaction.js +2 -0
- package/dist/contracts-core/content.d.ts +116 -0
- package/dist/contracts-core/content.js +2 -0
- package/dist/contracts-core/extensions.d.ts +163 -0
- package/dist/contracts-core/extensions.js +2 -0
- package/dist/contracts-core/loop.d.ts +98 -0
- package/dist/contracts-core/loop.js +2 -0
- package/dist/contracts-core/persistence.d.ts +366 -0
- package/dist/contracts-core/persistence.js +9 -0
- package/dist/contracts-core/provider.d.ts +97 -0
- package/dist/contracts-core/provider.js +2 -0
- package/dist/contracts-core/resources.d.ts +44 -0
- package/dist/contracts-core/resources.js +7 -0
- package/dist/contracts-core/run-limits.d.ts +81 -0
- package/dist/contracts-core/run-limits.js +2 -0
- package/dist/contracts-core/session.d.ts +187 -0
- package/dist/contracts-core/session.js +131 -0
- package/dist/contracts-core.d.ts +13 -1425
- package/dist/contracts-core.js +10 -138
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/docs/0.1.0-readiness.md +15 -13
- package/docs/index.md +2 -2
- package/docs/migration.md +8 -0
- package/docs/release-and-install.md +59 -8
- package/package.json +2 -2
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/** Contracts-core run-limits family (0.2.5 plan 025 Task 1 split).
|
|
2
|
+
* Moved verbatim from contracts-core.ts; public surface unchanged behind the barrel. */
|
|
3
|
+
import type { ProviderTurnResult, ToolResult } from "../contracts-protocol.js";
|
|
4
|
+
import type { Message, ToolCallContent } from "./content.js";
|
|
5
|
+
export interface RunLimits {
|
|
6
|
+
readonly maxTurns?: number;
|
|
7
|
+
readonly maxProviderAttempts?: number;
|
|
8
|
+
readonly maxToolRounds?: number;
|
|
9
|
+
readonly maxToolCalls?: number;
|
|
10
|
+
readonly maxWallTimeMs?: number;
|
|
11
|
+
readonly maxRequestBytes?: number;
|
|
12
|
+
readonly maxResponseBytes?: number;
|
|
13
|
+
readonly maxInputTokens?: number;
|
|
14
|
+
readonly maxOutputTokens?: number;
|
|
15
|
+
readonly maxTotalTokens?: number;
|
|
16
|
+
readonly maxCost?: {
|
|
17
|
+
readonly amount: number;
|
|
18
|
+
readonly currency: string;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export type RunLimitName = keyof Required<RunLimits>;
|
|
22
|
+
export interface RunLimitCounters {
|
|
23
|
+
readonly turns: number;
|
|
24
|
+
readonly providerAttempts: number;
|
|
25
|
+
readonly toolRounds: number;
|
|
26
|
+
readonly toolCalls: number;
|
|
27
|
+
readonly wallTimeMs: number;
|
|
28
|
+
readonly requestBytes: number;
|
|
29
|
+
readonly responseBytes: number;
|
|
30
|
+
readonly inputTokens: number;
|
|
31
|
+
readonly outputTokens: number;
|
|
32
|
+
readonly totalTokens: number;
|
|
33
|
+
readonly cost: number;
|
|
34
|
+
}
|
|
35
|
+
export interface RunLimitBreach {
|
|
36
|
+
readonly limit: RunLimitName;
|
|
37
|
+
readonly maximum: number;
|
|
38
|
+
readonly observed: number;
|
|
39
|
+
readonly currency?: string;
|
|
40
|
+
}
|
|
41
|
+
export type GuardrailStage = "input" | "output" | "tool_input" | "tool_output";
|
|
42
|
+
export type GuardrailAction = "allow" | "block" | "tripwire" | "interrupt";
|
|
43
|
+
export type GuardrailValue<S extends GuardrailStage> = S extends "input" ? readonly Message[] : S extends "output" ? ProviderTurnResult : S extends "tool_input" ? ToolCallContent : ToolResult;
|
|
44
|
+
export interface GuardrailContext<S extends GuardrailStage> {
|
|
45
|
+
readonly stage: S;
|
|
46
|
+
readonly value: GuardrailValue<S>;
|
|
47
|
+
readonly sessionId: string;
|
|
48
|
+
readonly runId: string;
|
|
49
|
+
readonly toolCallId?: string;
|
|
50
|
+
readonly toolName?: string;
|
|
51
|
+
readonly metadata: Readonly<Record<string, unknown>>;
|
|
52
|
+
readonly signal: AbortSignal;
|
|
53
|
+
}
|
|
54
|
+
export interface GuardrailDecision {
|
|
55
|
+
readonly action: GuardrailAction;
|
|
56
|
+
readonly reason?: string;
|
|
57
|
+
/** Public data only; Prism JSON-normalizes, bounds, and redacts it before emission. */
|
|
58
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
59
|
+
}
|
|
60
|
+
export interface Guardrail<S extends GuardrailStage = GuardrailStage> {
|
|
61
|
+
readonly name: string;
|
|
62
|
+
readonly stage: S;
|
|
63
|
+
/** Host-authored stable identity for durable definitions; unused by ordinary runs. */
|
|
64
|
+
readonly revision?: string;
|
|
65
|
+
evaluate(context: GuardrailContext<S>): GuardrailDecision | Promise<GuardrailDecision>;
|
|
66
|
+
}
|
|
67
|
+
export interface GuardrailRecord {
|
|
68
|
+
readonly guardrail: string;
|
|
69
|
+
readonly stage: GuardrailStage;
|
|
70
|
+
readonly action: GuardrailAction;
|
|
71
|
+
readonly reason?: string;
|
|
72
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
73
|
+
}
|
|
74
|
+
export interface Guardrails {
|
|
75
|
+
readonly input?: readonly Guardrail<"input">[];
|
|
76
|
+
readonly output?: readonly Guardrail<"output">[];
|
|
77
|
+
readonly toolInput?: readonly Guardrail<"tool_input">[];
|
|
78
|
+
readonly toolOutput?: readonly Guardrail<"tool_output">[];
|
|
79
|
+
/** Defaults to sequential; at most 16 stage evaluations run at once. */
|
|
80
|
+
readonly maxConcurrency?: number;
|
|
81
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/** Contracts-core session family (0.2.5 plan 025 Task 1 split).
|
|
2
|
+
* Moved verbatim from contracts-core.ts; public surface unchanged behind the barrel. */
|
|
3
|
+
import type { AgentEvent } from "../contracts-protocol.js";
|
|
4
|
+
import type { OwnershipScope, PersistencePage, PersistenceQuery } from "./persistence.js";
|
|
5
|
+
import type { Message, ModelConfig } from "./content.js";
|
|
6
|
+
export type SessionEntryKind = "message" | "event" | "summary" | "metadata" | "model_change" | "label" | "custom" | "compaction";
|
|
7
|
+
export declare const SESSION_ENTRY_KINDS: readonly SessionEntryKind[];
|
|
8
|
+
export declare const SESSION_ENTRY_SCHEMA_VERSION = 1;
|
|
9
|
+
export declare function isSessionEntryKind(value: unknown): value is SessionEntryKind;
|
|
10
|
+
export interface SessionEntry {
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly parentId?: string;
|
|
13
|
+
readonly sessionId: string;
|
|
14
|
+
readonly timestamp: string;
|
|
15
|
+
readonly kind: SessionEntryKind;
|
|
16
|
+
readonly schemaVersion?: 1;
|
|
17
|
+
readonly runId?: string;
|
|
18
|
+
readonly message?: Message;
|
|
19
|
+
readonly event?: AgentEvent;
|
|
20
|
+
readonly model?: ModelConfig;
|
|
21
|
+
readonly previousModel?: ModelConfig;
|
|
22
|
+
readonly label?: string;
|
|
23
|
+
readonly summary?: string;
|
|
24
|
+
readonly data?: unknown;
|
|
25
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
26
|
+
}
|
|
27
|
+
export interface SessionStore {
|
|
28
|
+
append(entry: SessionEntry, options?: SessionAppendOptions): Promise<void>;
|
|
29
|
+
list(sessionId: string): Promise<readonly SessionEntry[]>;
|
|
30
|
+
get?(id: string): Promise<SessionEntry | undefined>;
|
|
31
|
+
/** DB-friendly branch read: return one branch's ancestor chain as a page so adapters
|
|
32
|
+
* avoid `list(sessionId)` (full-session scan) + in-memory rebuild. Optional — the
|
|
33
|
+
* built-in memory/JSONL stores omit it and the runtime falls back to `list()`. */
|
|
34
|
+
readBranchPath?(query: SessionBranchRead): Promise<PersistencePage<SessionEntry>>;
|
|
35
|
+
/**
|
|
36
|
+
* Optional bounded session search. Prefer implementing this **or** returning a companion
|
|
37
|
+
* `SessionIndex` from the adapter factory — hosts must not need both. Call
|
|
38
|
+
* `resolveSessionSearchQuery` before scan/query. Memory defaults to capped linear
|
|
39
|
+
* search (`sessionSearchMode: "unsupported"` throws). JSONL throws unsupported.
|
|
40
|
+
*/
|
|
41
|
+
searchSessions?(query: SessionSearchQuery): Promise<PersistencePage<SessionSearchHit>>;
|
|
42
|
+
}
|
|
43
|
+
/** Host-written `SessionRecord.metadata` / session metadata key for workspace filtering. */
|
|
44
|
+
export declare const SESSION_SEARCH_WORKSPACE_METADATA_KEY: "workspaceRoot";
|
|
45
|
+
export declare const DEFAULT_SESSION_SEARCH_LIMIT = 20;
|
|
46
|
+
export declare const HARD_MAX_SESSION_SEARCH_LIMIT = 100;
|
|
47
|
+
export declare const DEFAULT_MAX_SESSION_SEARCH_QUERY_BYTES: number;
|
|
48
|
+
export declare const HARD_MAX_SESSION_SEARCH_QUERY_BYTES: number;
|
|
49
|
+
export declare const DEFAULT_MAX_SESSION_SEARCH_SNIPPET_BYTES = 512;
|
|
50
|
+
export declare const HARD_MAX_SESSION_SEARCH_SNIPPET_BYTES: number;
|
|
51
|
+
export declare const DEFAULT_MAX_SESSION_SEARCH_CURSOR_BYTES: number;
|
|
52
|
+
export declare const HARD_MAX_SESSION_SEARCH_CURSOR_BYTES: number;
|
|
53
|
+
export declare const DEFAULT_MAX_SESSION_SEARCH_LINEAR_SESSIONS = 1000;
|
|
54
|
+
export declare const HARD_MAX_SESSION_SEARCH_LINEAR_SESSIONS = 5000;
|
|
55
|
+
export declare const DEFAULT_MAX_SESSION_SEARCH_LINEAR_ENTRIES = 10000;
|
|
56
|
+
export declare const HARD_MAX_SESSION_SEARCH_LINEAR_ENTRIES = 50000;
|
|
57
|
+
export declare const DEFAULT_MAX_SESSION_SEARCH_LINEAR_BYTES: number;
|
|
58
|
+
export declare const HARD_MAX_SESSION_SEARCH_LINEAR_BYTES: number;
|
|
59
|
+
export declare const DEFAULT_MAX_SESSION_SEARCH_FTS_CANDIDATES = 1000;
|
|
60
|
+
export declare const HARD_MAX_SESSION_SEARCH_FTS_CANDIDATES = 5000;
|
|
61
|
+
/** Bounded session search filters. Workspace matches host-written `metadata.workspaceRoot`. */
|
|
62
|
+
export interface SessionSearchQuery extends PersistenceQuery, OwnershipScope {
|
|
63
|
+
readonly workspaceRoot?: string;
|
|
64
|
+
/** Optional full-text / message+summary query (adapter-defined matching). */
|
|
65
|
+
readonly query?: string;
|
|
66
|
+
readonly provider?: string;
|
|
67
|
+
readonly model?: string;
|
|
68
|
+
readonly label?: string;
|
|
69
|
+
readonly summary?: string;
|
|
70
|
+
readonly fromUpdatedAt?: string;
|
|
71
|
+
readonly toUpdatedAt?: string;
|
|
72
|
+
readonly signal?: AbortSignal;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Safe search hit for resume/checkout. Never includes credentials or raw full transcripts.
|
|
76
|
+
* `leafId` is the branch tip for `session.checkout` when known.
|
|
77
|
+
*/
|
|
78
|
+
export interface SessionSearchHit {
|
|
79
|
+
readonly sessionId: string;
|
|
80
|
+
readonly leafId?: string;
|
|
81
|
+
readonly updatedAt?: string;
|
|
82
|
+
readonly label?: string;
|
|
83
|
+
readonly summary?: string;
|
|
84
|
+
readonly snippet?: string;
|
|
85
|
+
/** Safe display fields only (e.g. workspaceRoot); never credentials. */
|
|
86
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
87
|
+
}
|
|
88
|
+
/** Narrow search seam; adapters may implement this instead of `SessionStore.searchSessions`. */
|
|
89
|
+
export interface SessionIndex {
|
|
90
|
+
search(query: SessionSearchQuery): Promise<PersistencePage<SessionSearchHit>>;
|
|
91
|
+
}
|
|
92
|
+
/** Validated search query with finite `limit` / `order` filled in. */
|
|
93
|
+
export interface ResolvedSessionSearchQuery extends SessionSearchQuery {
|
|
94
|
+
readonly limit: number;
|
|
95
|
+
readonly order: "asc" | "desc";
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* O(1) validation before any scan/query. Applies default page limit; rejects NaN,
|
|
99
|
+
* non-positive limits, oversize query/cursor/filter strings, and invalid order.
|
|
100
|
+
*/
|
|
101
|
+
export declare function resolveSessionSearchQuery(query: SessionSearchQuery): ResolvedSessionSearchQuery;
|
|
102
|
+
export declare const SESSION_SEARCH_UNSUPPORTED_CODE: "session_search_unsupported";
|
|
103
|
+
/** Thrown when a store opts out of `searchSessions` (memory `unsupported`, JSONL). */
|
|
104
|
+
export declare class SessionSearchUnsupportedError extends Error {
|
|
105
|
+
readonly code: "session_search_unsupported";
|
|
106
|
+
constructor(message?: string);
|
|
107
|
+
}
|
|
108
|
+
export declare function isSessionSearchUnsupported(error: unknown): error is SessionSearchUnsupportedError;
|
|
109
|
+
/** Query for a single branch's ancestor chain (DB-friendly: one recursive/ancestor query
|
|
110
|
+
* instead of a full-session scan). Honored by `SessionStore.readBranchPath` and the pure
|
|
111
|
+
* branch helpers' reader overload. `leafId` is optional (omit for the latest leaf). */
|
|
112
|
+
export interface SessionBranchRead {
|
|
113
|
+
readonly sessionId: string;
|
|
114
|
+
readonly leafId?: string;
|
|
115
|
+
readonly cursor?: string;
|
|
116
|
+
readonly limit?: number;
|
|
117
|
+
}
|
|
118
|
+
/** Database-neutral callable returning one branch's ancestor chain as a page. Implementations
|
|
119
|
+
* issue a single recursive CTE / ancestor walk; the pure helpers follow `nextCursor` to
|
|
120
|
+
* completion. Returns redacted `SessionEntry` values only (stores already persist redacted
|
|
121
|
+
* entries; the runtime redacts before append). */
|
|
122
|
+
export type BranchReader = (query: SessionBranchRead) => Promise<PersistencePage<SessionEntry>>;
|
|
123
|
+
/**
|
|
124
|
+
* Options for `SessionStore.append`. Stores that honor them reject dangling
|
|
125
|
+
* `expectedParentId` values and deduplicate exact retries by `idempotencyKey` +
|
|
126
|
+
* parent. Production stores may add stricter branch-tip CAS and report
|
|
127
|
+
* `currentLeafId` in `SessionAppendConflictError`. `idempotencyKey` is an opaque
|
|
128
|
+
* host string; stores redact it like metadata when persisted. Carries no
|
|
129
|
+
* credentials, credential resolvers, provider instances, or unredacted secrets.
|
|
130
|
+
*/
|
|
131
|
+
export interface SessionAppendOptions {
|
|
132
|
+
/** Parent entry the new entry should attach to. Must exist when provided. */
|
|
133
|
+
readonly expectedParentId?: string;
|
|
134
|
+
/** Opaque host idempotency key; exact retries for one parent deduplicate. */
|
|
135
|
+
readonly idempotencyKey?: string;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Durable pointer to a branch tip. One session may own many handles (one per
|
|
139
|
+
* leaf). `BranchRecord.leafEntryId` is the persistence-side equivalent.
|
|
140
|
+
*/
|
|
141
|
+
export interface SessionBranchHandle {
|
|
142
|
+
readonly sessionId: string;
|
|
143
|
+
readonly leafId: string;
|
|
144
|
+
}
|
|
145
|
+
/** Stable error code carried by `SessionAppendConflictError`. */
|
|
146
|
+
export declare const SESSION_APPEND_CONFLICT_CODE: "session_append_conflict";
|
|
147
|
+
/** CAS conflict code for `appendSession` metadata writes. Stable and message-independent. */
|
|
148
|
+
export declare const SESSION_METADATA_CONFLICT_CODE: "metadata_conflict";
|
|
149
|
+
/** Conflict details carried by `SessionMetadataConflictError`. Versions only; never metadata content. */
|
|
150
|
+
export interface SessionMetadataConflict {
|
|
151
|
+
readonly code: typeof SESSION_METADATA_CONFLICT_CODE;
|
|
152
|
+
readonly id: string;
|
|
153
|
+
readonly expectedVersion: number;
|
|
154
|
+
readonly currentVersion: number;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Thrown when `appendSession` is called with an `expectedVersion` CAS guard and the
|
|
158
|
+
* stored session's version no longer matches (concurrent create/branch/archive, or a
|
|
159
|
+
* delete raced the write). Recognize via the stable `code` or `isSessionMetadataConflict`.
|
|
160
|
+
*/
|
|
161
|
+
export declare class SessionMetadataConflictError extends Error {
|
|
162
|
+
readonly conflict: SessionMetadataConflict;
|
|
163
|
+
readonly code: "metadata_conflict";
|
|
164
|
+
constructor(conflict: SessionMetadataConflict);
|
|
165
|
+
}
|
|
166
|
+
/** Type guard keyed off the stable `code` (works across bundles; not message text). */
|
|
167
|
+
export declare function isSessionMetadataConflict(error: unknown): error is SessionMetadataConflictError;
|
|
168
|
+
/** Conflict details carried by `SessionAppendConflictError`. Carries no secrets. */
|
|
169
|
+
export interface SessionAppendConflict {
|
|
170
|
+
readonly code: typeof SESSION_APPEND_CONFLICT_CODE;
|
|
171
|
+
readonly expectedParentId?: string;
|
|
172
|
+
readonly currentLeafId?: string;
|
|
173
|
+
readonly idempotencyDuplicate?: boolean;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Thrown when `SessionStore.append` rejects an entry under `SessionAppendOptions`
|
|
177
|
+
* (dangling/stale `expectedParentId`, stricter adapter CAS failure, or duplicate
|
|
178
|
+
* idempotency key for the same parent). Recognize via the stable `code` and
|
|
179
|
+
* `isSessionAppendConflict`, not message text.
|
|
180
|
+
*/
|
|
181
|
+
export declare class SessionAppendConflictError extends Error {
|
|
182
|
+
readonly conflict: SessionAppendConflict;
|
|
183
|
+
readonly code: "session_append_conflict";
|
|
184
|
+
constructor(conflict: SessionAppendConflict);
|
|
185
|
+
}
|
|
186
|
+
/** Type guard keyed off the stable `code` (works across bundles; not message text). */
|
|
187
|
+
export declare function isSessionAppendConflict(error: unknown): error is SessionAppendConflictError;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
export const SESSION_ENTRY_KINDS = [
|
|
2
|
+
"message",
|
|
3
|
+
"event",
|
|
4
|
+
"summary",
|
|
5
|
+
"metadata",
|
|
6
|
+
"model_change",
|
|
7
|
+
"label",
|
|
8
|
+
"custom",
|
|
9
|
+
"compaction",
|
|
10
|
+
];
|
|
11
|
+
const SESSION_ENTRY_KIND_SET = new Set(SESSION_ENTRY_KINDS);
|
|
12
|
+
export const SESSION_ENTRY_SCHEMA_VERSION = 1;
|
|
13
|
+
export function isSessionEntryKind(value) {
|
|
14
|
+
return typeof value === "string" && SESSION_ENTRY_KIND_SET.has(value);
|
|
15
|
+
}
|
|
16
|
+
/** Host-written `SessionRecord.metadata` / session metadata key for workspace filtering. */
|
|
17
|
+
export const SESSION_SEARCH_WORKSPACE_METADATA_KEY = "workspaceRoot";
|
|
18
|
+
export const DEFAULT_SESSION_SEARCH_LIMIT = 20;
|
|
19
|
+
export const HARD_MAX_SESSION_SEARCH_LIMIT = 100;
|
|
20
|
+
export const DEFAULT_MAX_SESSION_SEARCH_QUERY_BYTES = 4 * 1024;
|
|
21
|
+
export const HARD_MAX_SESSION_SEARCH_QUERY_BYTES = 16 * 1024;
|
|
22
|
+
export const DEFAULT_MAX_SESSION_SEARCH_SNIPPET_BYTES = 512;
|
|
23
|
+
export const HARD_MAX_SESSION_SEARCH_SNIPPET_BYTES = 4 * 1024;
|
|
24
|
+
export const DEFAULT_MAX_SESSION_SEARCH_CURSOR_BYTES = 1 * 1024;
|
|
25
|
+
export const HARD_MAX_SESSION_SEARCH_CURSOR_BYTES = 4 * 1024;
|
|
26
|
+
export const DEFAULT_MAX_SESSION_SEARCH_LINEAR_SESSIONS = 1_000;
|
|
27
|
+
export const HARD_MAX_SESSION_SEARCH_LINEAR_SESSIONS = 5_000;
|
|
28
|
+
export const DEFAULT_MAX_SESSION_SEARCH_LINEAR_ENTRIES = 10_000;
|
|
29
|
+
export const HARD_MAX_SESSION_SEARCH_LINEAR_ENTRIES = 50_000;
|
|
30
|
+
export const DEFAULT_MAX_SESSION_SEARCH_LINEAR_BYTES = 8 * 1024 * 1024;
|
|
31
|
+
export const HARD_MAX_SESSION_SEARCH_LINEAR_BYTES = 64 * 1024 * 1024;
|
|
32
|
+
export const DEFAULT_MAX_SESSION_SEARCH_FTS_CANDIDATES = 1_000;
|
|
33
|
+
export const HARD_MAX_SESSION_SEARCH_FTS_CANDIDATES = 5_000;
|
|
34
|
+
/**
|
|
35
|
+
* O(1) validation before any scan/query. Applies default page limit; rejects NaN,
|
|
36
|
+
* non-positive limits, oversize query/cursor/filter strings, and invalid order.
|
|
37
|
+
*/
|
|
38
|
+
export function resolveSessionSearchQuery(query) {
|
|
39
|
+
const limit = query.limit === undefined ? DEFAULT_SESSION_SEARCH_LIMIT : query.limit;
|
|
40
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > HARD_MAX_SESSION_SEARCH_LIMIT) {
|
|
41
|
+
throw new TypeError(`SessionSearchQuery.limit must be a safe integer from 1 to ${HARD_MAX_SESSION_SEARCH_LIMIT}`);
|
|
42
|
+
}
|
|
43
|
+
const order = query.order ?? "desc";
|
|
44
|
+
if (order !== "asc" && order !== "desc") {
|
|
45
|
+
throw new TypeError('SessionSearchQuery.order must be "asc" or "desc"');
|
|
46
|
+
}
|
|
47
|
+
assertSearchStringBytes(query.query, "query", HARD_MAX_SESSION_SEARCH_QUERY_BYTES);
|
|
48
|
+
assertSearchStringBytes(query.cursor, "cursor", HARD_MAX_SESSION_SEARCH_CURSOR_BYTES);
|
|
49
|
+
assertSearchStringBytes(query.workspaceRoot, "workspaceRoot", HARD_MAX_SESSION_SEARCH_QUERY_BYTES);
|
|
50
|
+
assertSearchStringBytes(query.provider, "provider", HARD_MAX_SESSION_SEARCH_QUERY_BYTES);
|
|
51
|
+
assertSearchStringBytes(query.model, "model", HARD_MAX_SESSION_SEARCH_QUERY_BYTES);
|
|
52
|
+
assertSearchStringBytes(query.label, "label", HARD_MAX_SESSION_SEARCH_QUERY_BYTES);
|
|
53
|
+
assertSearchStringBytes(query.summary, "summary", HARD_MAX_SESSION_SEARCH_QUERY_BYTES);
|
|
54
|
+
assertSearchStringBytes(query.tenantId, "tenantId", HARD_MAX_SESSION_SEARCH_QUERY_BYTES);
|
|
55
|
+
assertSearchStringBytes(query.accountId, "accountId", HARD_MAX_SESSION_SEARCH_QUERY_BYTES);
|
|
56
|
+
assertSearchStringBytes(query.userId, "userId", HARD_MAX_SESSION_SEARCH_QUERY_BYTES);
|
|
57
|
+
assertSearchStringBytes(query.fromUpdatedAt, "fromUpdatedAt", HARD_MAX_SESSION_SEARCH_QUERY_BYTES);
|
|
58
|
+
assertSearchStringBytes(query.toUpdatedAt, "toUpdatedAt", HARD_MAX_SESSION_SEARCH_QUERY_BYTES);
|
|
59
|
+
return { ...query, limit, order };
|
|
60
|
+
}
|
|
61
|
+
function assertSearchStringBytes(value, name, hardMax) {
|
|
62
|
+
if (value === undefined)
|
|
63
|
+
return;
|
|
64
|
+
if (typeof value !== "string") {
|
|
65
|
+
throw new TypeError(`SessionSearchQuery.${name} must be a string`);
|
|
66
|
+
}
|
|
67
|
+
// ponytail: UTF-8 byte length via TextEncoder; upgrade only if a non-Unicode host appears.
|
|
68
|
+
const bytes = new TextEncoder().encode(value).byteLength;
|
|
69
|
+
if (bytes > hardMax) {
|
|
70
|
+
throw new TypeError(`SessionSearchQuery.${name} exceeds ${hardMax} bytes`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export const SESSION_SEARCH_UNSUPPORTED_CODE = "session_search_unsupported";
|
|
74
|
+
/** Thrown when a store opts out of `searchSessions` (memory `unsupported`, JSONL). */
|
|
75
|
+
export class SessionSearchUnsupportedError extends Error {
|
|
76
|
+
code = SESSION_SEARCH_UNSUPPORTED_CODE;
|
|
77
|
+
constructor(message = "session search is unsupported by this store") {
|
|
78
|
+
super(message);
|
|
79
|
+
this.name = "SessionSearchUnsupportedError";
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export function isSessionSearchUnsupported(error) {
|
|
83
|
+
return error instanceof Error && error.code === SESSION_SEARCH_UNSUPPORTED_CODE;
|
|
84
|
+
}
|
|
85
|
+
/** Stable error code carried by `SessionAppendConflictError`. */
|
|
86
|
+
export const SESSION_APPEND_CONFLICT_CODE = "session_append_conflict";
|
|
87
|
+
/** CAS conflict code for `appendSession` metadata writes. Stable and message-independent. */
|
|
88
|
+
export const SESSION_METADATA_CONFLICT_CODE = "metadata_conflict";
|
|
89
|
+
/**
|
|
90
|
+
* Thrown when `appendSession` is called with an `expectedVersion` CAS guard and the
|
|
91
|
+
* stored session's version no longer matches (concurrent create/branch/archive, or a
|
|
92
|
+
* delete raced the write). Recognize via the stable `code` or `isSessionMetadataConflict`.
|
|
93
|
+
*/
|
|
94
|
+
export class SessionMetadataConflictError extends Error {
|
|
95
|
+
conflict;
|
|
96
|
+
code = SESSION_METADATA_CONFLICT_CODE;
|
|
97
|
+
constructor(conflict) {
|
|
98
|
+
super(`session metadata conflict: expected version ${conflict.expectedVersion}, current ${conflict.currentVersion}`);
|
|
99
|
+
this.conflict = conflict;
|
|
100
|
+
this.name = "SessionMetadataConflictError";
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/** Type guard keyed off the stable `code` (works across bundles; not message text). */
|
|
104
|
+
export function isSessionMetadataConflict(error) {
|
|
105
|
+
return error instanceof Error && error.code === SESSION_METADATA_CONFLICT_CODE;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Thrown when `SessionStore.append` rejects an entry under `SessionAppendOptions`
|
|
109
|
+
* (dangling/stale `expectedParentId`, stricter adapter CAS failure, or duplicate
|
|
110
|
+
* idempotency key for the same parent). Recognize via the stable `code` and
|
|
111
|
+
* `isSessionAppendConflict`, not message text.
|
|
112
|
+
*/
|
|
113
|
+
export class SessionAppendConflictError extends Error {
|
|
114
|
+
conflict;
|
|
115
|
+
code = SESSION_APPEND_CONFLICT_CODE;
|
|
116
|
+
constructor(conflict) {
|
|
117
|
+
const detail = conflict.idempotencyDuplicate
|
|
118
|
+
? `idempotency key already used`
|
|
119
|
+
: conflict.currentLeafId !== undefined
|
|
120
|
+
? `expected parent ${conflict.expectedParentId ?? "<none>"} does not match current leaf ${conflict.currentLeafId}`
|
|
121
|
+
: `expected parent ${conflict.expectedParentId ?? "<none>"} is unavailable`;
|
|
122
|
+
super(`session append conflict: ${detail}`);
|
|
123
|
+
this.conflict = conflict;
|
|
124
|
+
this.name = "SessionAppendConflictError";
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/** Type guard keyed off the stable `code` (works across bundles; not message text). */
|
|
128
|
+
export function isSessionAppendConflict(error) {
|
|
129
|
+
return error instanceof Error && error.code === SESSION_APPEND_CONFLICT_CODE;
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=session.js.map
|