@arnilo/prism 0.0.11 → 0.0.13
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 +24 -0
- package/dist/agent-run-lifecycle.d.ts +5 -1
- package/dist/agent-run-lifecycle.js +17 -1
- package/dist/agents.d.ts +3 -1
- package/dist/agents.js +98 -20
- package/dist/contracts.d.ts +29 -1
- package/dist/extensions.d.ts +11 -0
- package/dist/extensions.js +15 -0
- package/dist/identity.d.ts +92 -0
- package/dist/identity.js +257 -0
- package/dist/index.d.ts +8 -4
- package/dist/index.js +4 -2
- package/dist/persistence-lifecycle.d.ts +103 -0
- package/dist/persistence-lifecycle.js +204 -0
- package/dist/providers/openai-compatible.d.ts +5 -1
- package/dist/providers/openai-compatible.js +15 -6
- package/dist/secure-agent.js +7 -1
- package/dist/testing/persistence-schema.d.ts +2 -2
- package/dist/testing/persistence-schema.js +35 -2
- package/dist/tools.d.ts +2 -0
- package/dist/tools.js +6 -0
- package/docs/a2a.md +3 -0
- package/docs/ag-ui.md +123 -0
- package/docs/agent-events.md +2 -1
- package/docs/agent-identity.md +111 -0
- package/docs/agent-session-runtime.md +5 -1
- package/docs/coding-agent-tools.md +2 -0
- package/docs/compaction-and-retry.md +2 -1
- package/docs/compaction-llm.md +20 -1
- package/docs/credential-storage.md +5 -1
- package/docs/credentials-and-redaction.md +8 -0
- package/docs/database-persistence.md +18 -7
- package/docs/extensions.md +1 -0
- package/docs/guardrails.md +3 -0
- package/docs/host-security.md +7 -1
- package/docs/index.md +25 -14
- package/docs/mcp-tools.md +2 -0
- package/docs/migration.md +43 -0
- package/docs/model-routing.md +102 -0
- package/docs/observability.md +2 -0
- package/docs/performance.md +38 -0
- package/docs/policy-and-audit.md +127 -0
- package/docs/postgres-persistence.md +1 -1
- package/docs/provider-packages.md +17 -0
- package/docs/provider-request-policies.md +2 -0
- package/docs/providers/anthropic.md +3 -2
- package/docs/providers/azure.md +74 -0
- package/docs/providers/bedrock.md +72 -0
- package/docs/providers/google.md +4 -2
- package/docs/providers/openai-compatible.md +3 -1
- package/docs/providers/openai.md +2 -2
- package/docs/providers/openrouter.md +2 -0
- package/docs/providers/vertex.md +71 -0
- package/docs/public-contracts.md +5 -1
- package/docs/release-and-install.md +167 -17
- package/docs/review-coverage-2026-07-22-phase-7.md +173 -0
- package/docs/review-coverage-2026-07-23-phase-8.md +245 -0
- package/docs/runs-and-usage.md +3 -0
- package/docs/server.md +34 -4
- package/docs/sqlite-persistence.md +1 -1
- package/docs/supervisors.md +2 -0
- package/docs/work-connectors.md +28 -0
- package/docs/work-tools.md +114 -0
- package/package.json +5 -1
package/dist/identity.js
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
export const DEFAULT_IDENTITY_LIMITS = {
|
|
2
|
+
maxScopes: 64,
|
|
3
|
+
maxScopeBytes: 128,
|
|
4
|
+
maxMetadataBytes: 4 * 1024,
|
|
5
|
+
maxCredentialRefBytes: 256,
|
|
6
|
+
maxPrincipalIdBytes: 256,
|
|
7
|
+
};
|
|
8
|
+
export const HARD_IDENTITY_LIMITS = {
|
|
9
|
+
maxScopes: 256,
|
|
10
|
+
maxScopeBytes: 512,
|
|
11
|
+
maxMetadataBytes: 16 * 1024,
|
|
12
|
+
maxCredentialRefBytes: 2 * 1024,
|
|
13
|
+
maxPrincipalIdBytes: 2 * 1024,
|
|
14
|
+
};
|
|
15
|
+
export class IdentityError extends Error {
|
|
16
|
+
reason;
|
|
17
|
+
code = "ERR_PRISM_IDENTITY";
|
|
18
|
+
constructor(message, reason) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.reason = reason;
|
|
21
|
+
this.name = "IdentityError";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function resolveIdentityLimits(input = {}) {
|
|
25
|
+
return {
|
|
26
|
+
maxScopes: bound("maxScopes", input.maxScopes, DEFAULT_IDENTITY_LIMITS.maxScopes, HARD_IDENTITY_LIMITS.maxScopes),
|
|
27
|
+
maxScopeBytes: bound("maxScopeBytes", input.maxScopeBytes, DEFAULT_IDENTITY_LIMITS.maxScopeBytes, HARD_IDENTITY_LIMITS.maxScopeBytes),
|
|
28
|
+
maxMetadataBytes: bound("maxMetadataBytes", input.maxMetadataBytes, DEFAULT_IDENTITY_LIMITS.maxMetadataBytes, HARD_IDENTITY_LIMITS.maxMetadataBytes),
|
|
29
|
+
maxCredentialRefBytes: bound("maxCredentialRefBytes", input.maxCredentialRefBytes, DEFAULT_IDENTITY_LIMITS.maxCredentialRefBytes, HARD_IDENTITY_LIMITS.maxCredentialRefBytes),
|
|
30
|
+
maxPrincipalIdBytes: bound("maxPrincipalIdBytes", input.maxPrincipalIdBytes, DEFAULT_IDENTITY_LIMITS.maxPrincipalIdBytes, HARD_IDENTITY_LIMITS.maxPrincipalIdBytes),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/** Project verified identity onto ownership scope fields used by persistence/server. */
|
|
34
|
+
export function ownershipFromIdentity(identity) {
|
|
35
|
+
return {
|
|
36
|
+
tenantId: identity.tenantId,
|
|
37
|
+
...(identity.accountId !== undefined ? { accountId: identity.accountId } : {}),
|
|
38
|
+
...(identity.userId !== undefined ? { userId: identity.userId } : {}),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Fail closed on shape/limits/expiry/revocation/tenant mismatch.
|
|
43
|
+
* Sync and network-free — host {@link IdentityVerifier} owns remote checks.
|
|
44
|
+
*/
|
|
45
|
+
export function assertIdentityActive(identity, options = {}) {
|
|
46
|
+
const limits = resolveIdentityLimits(options.limits);
|
|
47
|
+
assertIdentityShape(identity, limits);
|
|
48
|
+
if (identity.verified !== true)
|
|
49
|
+
throw new IdentityError("Identity is not host-verified", "unverified");
|
|
50
|
+
if (options.expectedTenantId !== undefined && identity.tenantId !== options.expectedTenantId) {
|
|
51
|
+
throw new IdentityError("Identity tenant mismatch", "wrong_tenant");
|
|
52
|
+
}
|
|
53
|
+
const now = options.now ?? Date.now();
|
|
54
|
+
if (identity.revokedAt !== undefined) {
|
|
55
|
+
const revokedAt = Date.parse(identity.revokedAt);
|
|
56
|
+
if (!Number.isFinite(revokedAt))
|
|
57
|
+
throw new IdentityError("Identity revokedAt is invalid", "invalid_revoked_at");
|
|
58
|
+
if (revokedAt <= now)
|
|
59
|
+
throw new IdentityError("Identity has been revoked", "revoked");
|
|
60
|
+
}
|
|
61
|
+
if (identity.expiresAt !== undefined) {
|
|
62
|
+
const expiresAt = Date.parse(identity.expiresAt);
|
|
63
|
+
if (!Number.isFinite(expiresAt))
|
|
64
|
+
throw new IdentityError("Identity expiresAt is invalid", "invalid_expires_at");
|
|
65
|
+
if (expiresAt <= now)
|
|
66
|
+
throw new IdentityError("Identity has expired", "expired");
|
|
67
|
+
}
|
|
68
|
+
const issuedAt = Date.parse(identity.issuedAt);
|
|
69
|
+
if (!Number.isFinite(issuedAt))
|
|
70
|
+
throw new IdentityError("Identity issuedAt is invalid", "invalid_issued_at");
|
|
71
|
+
if (issuedAt > now + 60_000)
|
|
72
|
+
throw new IdentityError("Identity issuedAt is in the future", "invalid_issued_at");
|
|
73
|
+
}
|
|
74
|
+
/** Delegation may only narrow scopes; tenant and ownership ids stay immutable. */
|
|
75
|
+
export function narrowIdentity(parent, options) {
|
|
76
|
+
assertIdentityActive(parent, { limits: options.limits });
|
|
77
|
+
const limits = resolveIdentityLimits(options.limits);
|
|
78
|
+
if (options.scopes.length === 0)
|
|
79
|
+
throw new IdentityError("Delegated scopes must be non-empty", "empty_scopes");
|
|
80
|
+
const parentScopes = new Set(parent.scopes);
|
|
81
|
+
for (const scope of options.scopes) {
|
|
82
|
+
if (!parentScopes.has(scope))
|
|
83
|
+
throw new IdentityError(`Delegation widens scope: ${scope}`, "scope_widen");
|
|
84
|
+
}
|
|
85
|
+
if (options.expiresAt !== undefined) {
|
|
86
|
+
const childExp = Date.parse(options.expiresAt);
|
|
87
|
+
if (!Number.isFinite(childExp))
|
|
88
|
+
throw new IdentityError("Delegated expiresAt is invalid", "invalid_expires_at");
|
|
89
|
+
if (parent.expiresAt !== undefined) {
|
|
90
|
+
const parentExp = Date.parse(parent.expiresAt);
|
|
91
|
+
if (Number.isFinite(parentExp) && childExp > parentExp) {
|
|
92
|
+
throw new IdentityError("Delegation cannot extend expiry", "expiry_widen");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const child = {
|
|
97
|
+
tenantId: parent.tenantId,
|
|
98
|
+
...(parent.accountId !== undefined ? { accountId: parent.accountId } : {}),
|
|
99
|
+
...(parent.userId !== undefined ? { userId: parent.userId } : {}),
|
|
100
|
+
principal: options.principal ?? parent.principal,
|
|
101
|
+
...(parent.sponsor !== undefined ? { sponsor: parent.sponsor } : {}),
|
|
102
|
+
...(parent.owner !== undefined ? { owner: parent.owner } : {}),
|
|
103
|
+
delegatedFrom: parent.principal,
|
|
104
|
+
scopes: Object.freeze([...options.scopes]),
|
|
105
|
+
credentialRefs: Object.freeze([...(options.credentialRefs ?? parent.credentialRefs ?? [])]),
|
|
106
|
+
issuedAt: parent.issuedAt,
|
|
107
|
+
...(options.expiresAt !== undefined ? { expiresAt: options.expiresAt } : parent.expiresAt !== undefined ? { expiresAt: parent.expiresAt } : {}),
|
|
108
|
+
...(parent.revokedAt !== undefined ? { revokedAt: parent.revokedAt } : {}),
|
|
109
|
+
...(options.metadata !== undefined ? { metadata: options.metadata } : parent.metadata !== undefined ? { metadata: parent.metadata } : {}),
|
|
110
|
+
verified: true,
|
|
111
|
+
};
|
|
112
|
+
assertIdentityActive(child, { limits: options.limits });
|
|
113
|
+
assertPrincipalBounds(child.principal, limits);
|
|
114
|
+
return child;
|
|
115
|
+
}
|
|
116
|
+
/** Refuse ownership that widens or conflicts with a verified identity. */
|
|
117
|
+
export function assertIdentityMatchesOwnership(identity, ownership) {
|
|
118
|
+
assertIdentityActive(identity);
|
|
119
|
+
if (!ownership)
|
|
120
|
+
return;
|
|
121
|
+
if (ownership.tenantId !== undefined && ownership.tenantId !== identity.tenantId) {
|
|
122
|
+
throw new IdentityError("Ownership tenant conflicts with identity", "ownership_tenant");
|
|
123
|
+
}
|
|
124
|
+
if (ownership.accountId !== undefined) {
|
|
125
|
+
if (identity.accountId === undefined || ownership.accountId !== identity.accountId) {
|
|
126
|
+
throw new IdentityError("Ownership account conflicts with identity", "ownership_account");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (ownership.userId !== undefined) {
|
|
130
|
+
if (identity.userId === undefined || ownership.userId !== identity.userId) {
|
|
131
|
+
throw new IdentityError("Ownership user conflicts with identity", "ownership_user");
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Propagation check: child must share tenant, not widen scopes, and remain active.
|
|
137
|
+
* Used by supervisor/A2A/MCP/server when forwarding identity across a boundary.
|
|
138
|
+
*/
|
|
139
|
+
export function assertIdentityPropagation(parent, child, options = {}) {
|
|
140
|
+
assertIdentityActive(parent, options);
|
|
141
|
+
assertIdentityActive(child, options);
|
|
142
|
+
if (child.tenantId !== parent.tenantId)
|
|
143
|
+
throw new IdentityError("Propagated identity changed tenant", "tenant_widen");
|
|
144
|
+
if (parent.accountId !== undefined && child.accountId !== parent.accountId) {
|
|
145
|
+
throw new IdentityError("Propagated identity changed account", "account_widen");
|
|
146
|
+
}
|
|
147
|
+
if (parent.userId !== undefined && child.userId !== parent.userId) {
|
|
148
|
+
throw new IdentityError("Propagated identity changed user", "user_widen");
|
|
149
|
+
}
|
|
150
|
+
const parentScopes = new Set(parent.scopes);
|
|
151
|
+
for (const scope of child.scopes) {
|
|
152
|
+
if (!parentScopes.has(scope))
|
|
153
|
+
throw new IdentityError(`Propagated identity widens scope: ${scope}`, "scope_widen");
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/** Redacted identity refs safe for telemetry/ledger metadata (no secrets, no raw JWT). */
|
|
157
|
+
export function identityTelemetryAttributes(identity) {
|
|
158
|
+
assertIdentityActive(identity);
|
|
159
|
+
return {
|
|
160
|
+
"prism.identity.tenant_id": identity.tenantId,
|
|
161
|
+
"prism.identity.principal_kind": String(identity.principal.kind),
|
|
162
|
+
"prism.identity.principal_id": identity.principal.id,
|
|
163
|
+
"prism.identity.scope_count": String(identity.scopes.length),
|
|
164
|
+
...(identity.accountId !== undefined ? { "prism.identity.account_id": identity.accountId } : {}),
|
|
165
|
+
...(identity.userId !== undefined ? { "prism.identity.user_id": identity.userId } : {}),
|
|
166
|
+
...(identity.sponsor !== undefined ? { "prism.identity.sponsor_id": identity.sponsor.id } : {}),
|
|
167
|
+
...(identity.credentialRefs?.length ? { "prism.identity.credential_ref_count": String(identity.credentialRefs.length) } : {}),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/** Resolve run identity: explicit option wins; otherwise agent default. Assert when present. */
|
|
171
|
+
export function resolveRunIdentity(runIdentity, agentIdentity, ownership, options = {}) {
|
|
172
|
+
const identity = runIdentity ?? agentIdentity;
|
|
173
|
+
if (!identity)
|
|
174
|
+
return undefined;
|
|
175
|
+
assertIdentityActive(identity, options);
|
|
176
|
+
assertIdentityMatchesOwnership(identity, ownership ?? ownershipFromIdentity(identity));
|
|
177
|
+
return identity;
|
|
178
|
+
}
|
|
179
|
+
function assertIdentityShape(identity, limits) {
|
|
180
|
+
if (typeof identity.tenantId !== "string" || identity.tenantId.length === 0) {
|
|
181
|
+
throw new IdentityError("Identity tenantId is required", "missing_tenant");
|
|
182
|
+
}
|
|
183
|
+
if (byteLength(identity.tenantId) > limits.maxPrincipalIdBytes) {
|
|
184
|
+
throw new IdentityError("Identity tenantId exceeds byte limit", "tenant_too_large");
|
|
185
|
+
}
|
|
186
|
+
assertPrincipalBounds(identity.principal, limits);
|
|
187
|
+
if (identity.sponsor)
|
|
188
|
+
assertPrincipalBounds(identity.sponsor, limits);
|
|
189
|
+
if (identity.owner)
|
|
190
|
+
assertPrincipalBounds(identity.owner, limits);
|
|
191
|
+
if (identity.delegatedFrom)
|
|
192
|
+
assertPrincipalBounds(identity.delegatedFrom, limits);
|
|
193
|
+
if (!Array.isArray(identity.scopes) || identity.scopes.length === 0) {
|
|
194
|
+
throw new IdentityError("Identity scopes must be a non-empty array", "empty_scopes");
|
|
195
|
+
}
|
|
196
|
+
if (identity.scopes.length > limits.maxScopes) {
|
|
197
|
+
throw new IdentityError(`Identity exceeds maxScopes (${limits.maxScopes})`, "too_many_scopes");
|
|
198
|
+
}
|
|
199
|
+
for (const scope of identity.scopes) {
|
|
200
|
+
if (typeof scope !== "string" || scope.length === 0)
|
|
201
|
+
throw new IdentityError("Identity scope is invalid", "invalid_scope");
|
|
202
|
+
if (byteLength(scope) > limits.maxScopeBytes)
|
|
203
|
+
throw new IdentityError("Identity scope exceeds byte limit", "scope_too_large");
|
|
204
|
+
}
|
|
205
|
+
if (identity.credentialRefs) {
|
|
206
|
+
for (const ref of identity.credentialRefs) {
|
|
207
|
+
if (typeof ref !== "string" || ref.length === 0)
|
|
208
|
+
throw new IdentityError("Credential ref is invalid", "invalid_credential_ref");
|
|
209
|
+
if (byteLength(ref) > limits.maxCredentialRefBytes) {
|
|
210
|
+
throw new IdentityError("Credential ref exceeds byte limit", "credential_ref_too_large");
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (identity.metadata !== undefined) {
|
|
215
|
+
let encoded;
|
|
216
|
+
try {
|
|
217
|
+
encoded = JSON.stringify(identity.metadata);
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
throw new IdentityError("Identity metadata is not JSON-serializable", "invalid_metadata");
|
|
221
|
+
}
|
|
222
|
+
if (byteLength(encoded) > limits.maxMetadataBytes) {
|
|
223
|
+
throw new IdentityError("Identity metadata exceeds byte limit", "metadata_too_large");
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (identity.accountId !== undefined && byteLength(identity.accountId) > limits.maxPrincipalIdBytes) {
|
|
227
|
+
throw new IdentityError("Identity accountId exceeds byte limit", "account_too_large");
|
|
228
|
+
}
|
|
229
|
+
if (identity.userId !== undefined && byteLength(identity.userId) > limits.maxPrincipalIdBytes) {
|
|
230
|
+
throw new IdentityError("Identity userId exceeds byte limit", "user_too_large");
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function assertPrincipalBounds(principal, limits) {
|
|
234
|
+
if (typeof principal.id !== "string" || principal.id.length === 0) {
|
|
235
|
+
throw new IdentityError("Principal id is required", "missing_principal");
|
|
236
|
+
}
|
|
237
|
+
if (byteLength(principal.id) > limits.maxPrincipalIdBytes) {
|
|
238
|
+
throw new IdentityError("Principal id exceeds byte limit", "principal_too_large");
|
|
239
|
+
}
|
|
240
|
+
if (typeof principal.kind !== "string" || principal.kind.length === 0 || byteLength(principal.kind) > 64) {
|
|
241
|
+
throw new IdentityError("Principal kind is invalid", "invalid_principal_kind");
|
|
242
|
+
}
|
|
243
|
+
if (principal.displayName !== undefined && byteLength(principal.displayName) > limits.maxPrincipalIdBytes) {
|
|
244
|
+
throw new IdentityError("Principal displayName exceeds byte limit", "display_name_too_large");
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function bound(name, value, fallback, hard) {
|
|
248
|
+
const resolved = value ?? fallback;
|
|
249
|
+
if (!Number.isSafeInteger(resolved) || resolved < 1 || resolved > hard) {
|
|
250
|
+
throw new RangeError(`${name} must be a positive safe integer no greater than ${hard}`);
|
|
251
|
+
}
|
|
252
|
+
return resolved;
|
|
253
|
+
}
|
|
254
|
+
function byteLength(value) {
|
|
255
|
+
return Buffer.byteLength(value, "utf8");
|
|
256
|
+
}
|
|
257
|
+
//# sourceMappingURL=identity.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export type * from "./contracts.js";
|
|
2
2
|
export type { RunLimitCounters, RunLimitName, SecureAgentOptions } from "./contracts.js";
|
|
3
3
|
export { isSessionEntryKind, SESSION_APPEND_CONFLICT_CODE, SESSION_ENTRY_KINDS, SESSION_ENTRY_SCHEMA_VERSION, SessionAppendConflictError, isSessionAppendConflict, AgentRunError, AgentRunStateError, SESSION_SEARCH_WORKSPACE_METADATA_KEY, SESSION_SEARCH_UNSUPPORTED_CODE, SessionSearchUnsupportedError, isSessionSearchUnsupported, DEFAULT_SESSION_SEARCH_LIMIT, HARD_MAX_SESSION_SEARCH_LIMIT, DEFAULT_MAX_SESSION_SEARCH_QUERY_BYTES, HARD_MAX_SESSION_SEARCH_QUERY_BYTES, DEFAULT_MAX_SESSION_SEARCH_SNIPPET_BYTES, HARD_MAX_SESSION_SEARCH_SNIPPET_BYTES, DEFAULT_MAX_SESSION_SEARCH_CURSOR_BYTES, HARD_MAX_SESSION_SEARCH_CURSOR_BYTES, DEFAULT_MAX_SESSION_SEARCH_LINEAR_SESSIONS, HARD_MAX_SESSION_SEARCH_LINEAR_SESSIONS, DEFAULT_MAX_SESSION_SEARCH_LINEAR_ENTRIES, HARD_MAX_SESSION_SEARCH_LINEAR_ENTRIES, DEFAULT_MAX_SESSION_SEARCH_LINEAR_BYTES, HARD_MAX_SESSION_SEARCH_LINEAR_BYTES, DEFAULT_MAX_SESSION_SEARCH_FTS_CANDIDATES, HARD_MAX_SESSION_SEARCH_FTS_CANDIDATES, resolveSessionSearchQuery, DEFAULT_MAX_PENDING_STEERS, HARD_MAX_PENDING_STEERS, DEFAULT_MAX_PENDING_STEER_BYTES, HARD_MAX_PENDING_STEER_BYTES } from "./contracts.js";
|
|
4
|
-
export { createAgent, createAgentSession, resumeAgentRun } from "./agents.js";
|
|
4
|
+
export { createAgent, createAgentSession, resumeAgentRun, resumeAgentRunStream } from "./agents.js";
|
|
5
5
|
export { createBatchedRunLedger, isFlushableRunLedger, DEFAULT_LEDGER_BATCH_ENTRIES, HARD_LEDGER_BATCH_ENTRIES, DEFAULT_LEDGER_BATCH_BYTES, HARD_LEDGER_BATCH_BYTES, DEFAULT_LEDGER_BATCH_DELAY_MS, HARD_LEDGER_BATCH_DELAY_MS, } from "./run-ledger.js";
|
|
6
6
|
export type { BatchedRunLedgerOptions } from "./run-ledger.js";
|
|
7
7
|
export { createSecureAgent } from "./secure-agent.js";
|
|
@@ -11,9 +11,11 @@ export { CHECKPOINT_CONFLICT_CODE, CheckpointConflictError, createMemoryCheckpoi
|
|
|
11
11
|
export { AGENT_RUN_STATE_NAMESPACE, AGENT_RUN_STATE_SCHEMA_VERSION, DEFAULT_MAX_AGENT_RUN_STATE_BYTES, HARD_MAX_AGENT_RUN_STATE_BYTES, agentFingerprint, loadAgentRunState } from "./agent-run-state.js";
|
|
12
12
|
export type { StoredAgentRunState } from "./agent-run-state.js";
|
|
13
13
|
export { createAgentRunLifecycle } from "./agent-run-lifecycle.js";
|
|
14
|
-
export type { AgentRunLifecycle, AgentRunLifecycleAgent, AgentRunLifecycleOptions, AgentRunLifecycleRequest } from "./agent-run-lifecycle.js";
|
|
14
|
+
export type { AgentRunLifecycle, AgentRunLifecycleAgent, AgentRunLifecycleOptions, AgentRunLifecycleRequest, AgentRunLifecycleStreamRequest } from "./agent-run-lifecycle.js";
|
|
15
15
|
export type { MemoryCheckpointStoreOptions } from "./checkpoints.js";
|
|
16
16
|
export { LEASE_CONFLICT_CODE, LeaseConflictError, createMemoryLeaseStore } from "./leases.js";
|
|
17
|
+
export { createMemoryPersistenceLifecycle, isResourceHeld, PersistenceLifecycleError, DEFAULT_LIFECYCLE_PAGE_SIZE, HARD_LIFECYCLE_PAGE_SIZE, DEFAULT_MAX_HOLD_REASON_BYTES, HARD_MAX_HOLD_REASON_BYTES, } from "./persistence-lifecycle.js";
|
|
18
|
+
export type { PersistenceResourceKind, LegalHoldRecord, PutLegalHoldInput, ReleaseLegalHoldInput, LegalHoldQuery, ApplyRetentionInput, ApplyRetentionResult, LegalHoldExportItem, ExportUnderHoldInput, TenantQuota, SetTenantQuotaInput, ConsumeTenantQuotaInput, PersistenceLifecycleStore, } from "./persistence-lifecycle.js";
|
|
17
19
|
export { createEventMultiplexer } from "./event-multiplexer.js";
|
|
18
20
|
export type { EventMultiplexer, EventMultiplexerOptions, EventOverflowInfo, EventOverflowPolicy } from "./event-multiplexer.js";
|
|
19
21
|
export { applyCacheControl, cacheHitRate, cacheSavings, cacheUsageReport, mapCacheRetention, sanitizeCacheKey } from "./cache-helpers.js";
|
|
@@ -30,7 +32,7 @@ export { createContributionRegistries, createContributionRegistry, registerDisco
|
|
|
30
32
|
export type { ContributionRegistries, ContributionRegistriesOptions, ContributionRegistry, ContributionRegistryOptions } from "./contributions.js";
|
|
31
33
|
export { createChainedCredentialResolver, createEnvCredentialResolver, createExplicitCredentialResolver, createMemoryCredentialStore, refreshOAuthCredential, resolveCredentialValue } from "./credentials.js";
|
|
32
34
|
export { createExtensionEventBus, createExtensionKernel } from "./extensions.js";
|
|
33
|
-
export type { ExtensionErrorPolicy, ExtensionEventBus, ExtensionEventHandler, ExtensionKernel, ExtensionKernelOptions } from "./extensions.js";
|
|
35
|
+
export type { ExtensionErrorPolicy, ExtensionEventBus, ExtensionEventHandler, ExtensionKernel, ExtensionKernelOptions, ExtensionLoadPolicy } from "./extensions.js";
|
|
34
36
|
export type { CredentialRecord, CredentialValueSource, MemoryCredentialStore } from "./credentials.js";
|
|
35
37
|
export { createModelRegistry } from "./models.js";
|
|
36
38
|
export { authMethodKey, defineProviderPackage, systemPromptContributionKey } from "./provider-packages.js";
|
|
@@ -70,6 +72,8 @@ export { createSecretRedactor, errorToErrorInfo, redactAgentEvent, redactMessage
|
|
|
70
72
|
export type { SecretRedactor } from "./redaction.js";
|
|
71
73
|
export { assertPermission, assertTrusted, checkPermission, createStaticPermissionPolicy, createStaticTrustPolicy, denialToErrorInfo, isTrusted, PermissionDeniedError, TrustDeniedError } from "./security.js";
|
|
72
74
|
export type { PermissionDecision, PermissionPolicy, PermissionRequest, TrustDecision, TrustPolicy, TrustRequest } from "./security.js";
|
|
75
|
+
export { assertIdentityActive, assertIdentityMatchesOwnership, assertIdentityPropagation, DEFAULT_IDENTITY_LIMITS, HARD_IDENTITY_LIMITS, IdentityError, identityTelemetryAttributes, narrowIdentity, ownershipFromIdentity, resolveIdentityLimits, resolveRunIdentity, } from "./identity.js";
|
|
76
|
+
export type { AgentIdentity, AssertIdentityActiveOptions, IdentityLimits, IdentityVerifier, NarrowIdentityOptions, Principal, ResolvedIdentityLimits, } from "./identity.js";
|
|
73
77
|
export { applyExecutionDecision, assertExecutionAllowed, checkExecution, ExecutionDeniedError } from "./execution-policy.js";
|
|
74
78
|
export type { ExecutionAction, ExecutionDecision, ExecutionPolicy, ExecutionRisk } from "./execution-policy.js";
|
|
75
79
|
export { createSkillRegistry, resolveActiveSkills } from "./skills.js";
|
|
@@ -85,5 +89,5 @@ export type { DispatchToolCallOptions, ToolArgumentValidationError, ToolArgument
|
|
|
85
89
|
export type { DuplicateRegistrationOptions, DuplicateRegistrationPolicy } from "./registry-options.js";
|
|
86
90
|
export { dispatchToolCallsInOrder, generateValidateReviseLoop, isAgentLoopOptions, resolveLoop, resolveToolConcurrency, singleShotLoop } from "./agent-loops.js";
|
|
87
91
|
export declare const name = "prism";
|
|
88
|
-
export declare const version = "0.0.
|
|
92
|
+
export declare const version = "0.0.13";
|
|
89
93
|
export declare const description = "Agent harness for AI providers, agents, sessions, and tools.";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { isSessionEntryKind, SESSION_APPEND_CONFLICT_CODE, SESSION_ENTRY_KINDS, SESSION_ENTRY_SCHEMA_VERSION, SessionAppendConflictError, isSessionAppendConflict, AgentRunError, AgentRunStateError, SESSION_SEARCH_WORKSPACE_METADATA_KEY, SESSION_SEARCH_UNSUPPORTED_CODE, SessionSearchUnsupportedError, isSessionSearchUnsupported, DEFAULT_SESSION_SEARCH_LIMIT, HARD_MAX_SESSION_SEARCH_LIMIT, DEFAULT_MAX_SESSION_SEARCH_QUERY_BYTES, HARD_MAX_SESSION_SEARCH_QUERY_BYTES, DEFAULT_MAX_SESSION_SEARCH_SNIPPET_BYTES, HARD_MAX_SESSION_SEARCH_SNIPPET_BYTES, DEFAULT_MAX_SESSION_SEARCH_CURSOR_BYTES, HARD_MAX_SESSION_SEARCH_CURSOR_BYTES, DEFAULT_MAX_SESSION_SEARCH_LINEAR_SESSIONS, HARD_MAX_SESSION_SEARCH_LINEAR_SESSIONS, DEFAULT_MAX_SESSION_SEARCH_LINEAR_ENTRIES, HARD_MAX_SESSION_SEARCH_LINEAR_ENTRIES, DEFAULT_MAX_SESSION_SEARCH_LINEAR_BYTES, HARD_MAX_SESSION_SEARCH_LINEAR_BYTES, DEFAULT_MAX_SESSION_SEARCH_FTS_CANDIDATES, HARD_MAX_SESSION_SEARCH_FTS_CANDIDATES, resolveSessionSearchQuery, DEFAULT_MAX_PENDING_STEERS, HARD_MAX_PENDING_STEERS, DEFAULT_MAX_PENDING_STEER_BYTES, HARD_MAX_PENDING_STEER_BYTES } from "./contracts.js";
|
|
2
|
-
export { createAgent, createAgentSession, resumeAgentRun } from "./agents.js";
|
|
2
|
+
export { createAgent, createAgentSession, resumeAgentRun, resumeAgentRunStream } from "./agents.js";
|
|
3
3
|
export { createBatchedRunLedger, isFlushableRunLedger, DEFAULT_LEDGER_BATCH_ENTRIES, HARD_LEDGER_BATCH_ENTRIES, DEFAULT_LEDGER_BATCH_BYTES, HARD_LEDGER_BATCH_BYTES, DEFAULT_LEDGER_BATCH_DELAY_MS, HARD_LEDGER_BATCH_DELAY_MS, } from "./run-ledger.js";
|
|
4
4
|
export { createSecureAgent } from "./secure-agent.js";
|
|
5
5
|
export { createMemoryRunFeedbackStore, prepareRunFeedback, requireRunFeedbackOwnership, runFeedbackPageLimit, RunFeedbackError, } from "./feedback.js";
|
|
@@ -7,6 +7,7 @@ export { CHECKPOINT_CONFLICT_CODE, CheckpointConflictError, createMemoryCheckpoi
|
|
|
7
7
|
export { AGENT_RUN_STATE_NAMESPACE, AGENT_RUN_STATE_SCHEMA_VERSION, DEFAULT_MAX_AGENT_RUN_STATE_BYTES, HARD_MAX_AGENT_RUN_STATE_BYTES, agentFingerprint, loadAgentRunState } from "./agent-run-state.js";
|
|
8
8
|
export { createAgentRunLifecycle } from "./agent-run-lifecycle.js";
|
|
9
9
|
export { LEASE_CONFLICT_CODE, LeaseConflictError, createMemoryLeaseStore } from "./leases.js";
|
|
10
|
+
export { createMemoryPersistenceLifecycle, isResourceHeld, PersistenceLifecycleError, DEFAULT_LIFECYCLE_PAGE_SIZE, HARD_LIFECYCLE_PAGE_SIZE, DEFAULT_MAX_HOLD_REASON_BYTES, HARD_MAX_HOLD_REASON_BYTES, } from "./persistence-lifecycle.js";
|
|
10
11
|
export { createEventMultiplexer } from "./event-multiplexer.js";
|
|
11
12
|
export { applyCacheControl, cacheHitRate, cacheSavings, cacheUsageReport, mapCacheRetention, sanitizeCacheKey } from "./cache-helpers.js";
|
|
12
13
|
export { resolveAgentDefinition } from "./agent-definitions.js";
|
|
@@ -38,6 +39,7 @@ export { providerContentDelta, providerDone, providerError, providerTextDelta, p
|
|
|
38
39
|
export { createProviderRegistry, createProviderResolver } from "./providers.js";
|
|
39
40
|
export { createSecretRedactor, errorToErrorInfo, redactAgentEvent, redactMessage, redactProviderRequest, redactRunLedgerRecord, redactSecrets, redactSessionEntry } from "./redaction.js";
|
|
40
41
|
export { assertPermission, assertTrusted, checkPermission, createStaticPermissionPolicy, createStaticTrustPolicy, denialToErrorInfo, isTrusted, PermissionDeniedError, TrustDeniedError } from "./security.js";
|
|
42
|
+
export { assertIdentityActive, assertIdentityMatchesOwnership, assertIdentityPropagation, DEFAULT_IDENTITY_LIMITS, HARD_IDENTITY_LIMITS, IdentityError, identityTelemetryAttributes, narrowIdentity, ownershipFromIdentity, resolveIdentityLimits, resolveRunIdentity, } from "./identity.js";
|
|
41
43
|
export { applyExecutionDecision, assertExecutionAllowed, checkExecution, ExecutionDeniedError } from "./execution-policy.js";
|
|
42
44
|
export { createSkillRegistry, resolveActiveSkills } from "./skills.js";
|
|
43
45
|
export { resolveInstructionInjectors, runInstructionInjectors } from "./instruction-injection.js";
|
|
@@ -46,6 +48,6 @@ export { assertGuardrailsAllowed, GuardrailError, MAX_GUARDRAIL_CONCURRENCY, run
|
|
|
46
48
|
export { createRunLimitTracker, DEFAULT_RUN_LIMITS, HARD_MAX_RUN_COST, HARD_RUN_LIMITS, RunLimitError, RunLimitTracker, resolveRunLimits } from "./run-limits.js";
|
|
47
49
|
export { dispatchToolCallsInOrder, generateValidateReviseLoop, isAgentLoopOptions, resolveLoop, resolveToolConcurrency, singleShotLoop } from "./agent-loops.js";
|
|
48
50
|
export const name = "prism";
|
|
49
|
-
export const version = "0.0.
|
|
51
|
+
export const version = "0.0.13";
|
|
50
52
|
export const description = "Agent harness for AI providers, agents, sessions, and tools.";
|
|
51
53
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { OwnershipScope, PersistencePage, PersistenceQuery, RetentionPolicy } from "./contracts.js";
|
|
2
|
+
/** Resource classes covered by holds, quotas, and retention sweeps. */
|
|
3
|
+
export type PersistenceResourceKind = "session" | "entry" | "run" | "event" | "tool_call" | "usage" | "checkpoint" | "feedback" | "audit" | "work_artifact" | "connector_operation";
|
|
4
|
+
export declare const DEFAULT_LIFECYCLE_PAGE_SIZE = 100;
|
|
5
|
+
export declare const HARD_LIFECYCLE_PAGE_SIZE = 500;
|
|
6
|
+
export declare const DEFAULT_MAX_HOLD_REASON_BYTES = 1024;
|
|
7
|
+
export declare const HARD_MAX_HOLD_REASON_BYTES: number;
|
|
8
|
+
export declare class PersistenceLifecycleError extends Error {
|
|
9
|
+
readonly code: string;
|
|
10
|
+
constructor(message: string, code?: string);
|
|
11
|
+
}
|
|
12
|
+
export interface LegalHoldRecord extends OwnershipScope {
|
|
13
|
+
readonly id: string;
|
|
14
|
+
readonly resourceKind: PersistenceResourceKind;
|
|
15
|
+
readonly resourceId: string;
|
|
16
|
+
readonly reason: string;
|
|
17
|
+
readonly createdAt: string;
|
|
18
|
+
readonly createdBy?: string;
|
|
19
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
20
|
+
}
|
|
21
|
+
export interface PutLegalHoldInput extends OwnershipScope {
|
|
22
|
+
readonly resourceKind: PersistenceResourceKind;
|
|
23
|
+
readonly resourceId: string;
|
|
24
|
+
readonly reason: string;
|
|
25
|
+
readonly id?: string;
|
|
26
|
+
readonly createdBy?: string;
|
|
27
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
28
|
+
readonly signal?: AbortSignal;
|
|
29
|
+
}
|
|
30
|
+
export interface ReleaseLegalHoldInput extends OwnershipScope {
|
|
31
|
+
readonly id: string;
|
|
32
|
+
readonly signal?: AbortSignal;
|
|
33
|
+
}
|
|
34
|
+
export interface LegalHoldQuery extends PersistenceQuery, OwnershipScope {
|
|
35
|
+
readonly resourceKind?: PersistenceResourceKind;
|
|
36
|
+
readonly resourceId?: string;
|
|
37
|
+
readonly holdId?: string;
|
|
38
|
+
readonly signal?: AbortSignal;
|
|
39
|
+
}
|
|
40
|
+
export interface ApplyRetentionInput extends OwnershipScope {
|
|
41
|
+
readonly policy: RetentionPolicy;
|
|
42
|
+
/** When omitted, adapters discover expired/over-limit session ids for this ownership page. */
|
|
43
|
+
readonly candidates?: readonly string[];
|
|
44
|
+
readonly cursor?: string;
|
|
45
|
+
readonly limit?: number;
|
|
46
|
+
readonly signal?: AbortSignal;
|
|
47
|
+
}
|
|
48
|
+
export interface ApplyRetentionResult {
|
|
49
|
+
readonly deleted: readonly string[];
|
|
50
|
+
readonly skippedHeld: readonly string[];
|
|
51
|
+
readonly nextCursor?: string;
|
|
52
|
+
}
|
|
53
|
+
export interface LegalHoldExportItem {
|
|
54
|
+
readonly holdId: string;
|
|
55
|
+
readonly resourceKind: PersistenceResourceKind;
|
|
56
|
+
readonly resourceId: string;
|
|
57
|
+
readonly reason: string;
|
|
58
|
+
readonly createdAt: string;
|
|
59
|
+
readonly redacted: true;
|
|
60
|
+
}
|
|
61
|
+
export interface ExportUnderHoldInput extends OwnershipScope {
|
|
62
|
+
readonly holdId?: string;
|
|
63
|
+
readonly resourceKind?: PersistenceResourceKind;
|
|
64
|
+
readonly cursor?: string;
|
|
65
|
+
readonly limit?: number;
|
|
66
|
+
readonly signal?: AbortSignal;
|
|
67
|
+
}
|
|
68
|
+
export interface TenantQuota extends OwnershipScope {
|
|
69
|
+
readonly resourceKind: PersistenceResourceKind;
|
|
70
|
+
readonly limit: number;
|
|
71
|
+
readonly used: number;
|
|
72
|
+
readonly updatedAt: string;
|
|
73
|
+
}
|
|
74
|
+
export interface SetTenantQuotaInput extends OwnershipScope {
|
|
75
|
+
readonly resourceKind: PersistenceResourceKind;
|
|
76
|
+
readonly limit: number;
|
|
77
|
+
readonly signal?: AbortSignal;
|
|
78
|
+
}
|
|
79
|
+
export interface ConsumeTenantQuotaInput extends OwnershipScope {
|
|
80
|
+
readonly resourceKind: PersistenceResourceKind;
|
|
81
|
+
/** Units to consume. Default 1. */
|
|
82
|
+
readonly delta?: number;
|
|
83
|
+
readonly signal?: AbortSignal;
|
|
84
|
+
}
|
|
85
|
+
/** Optional persistence lifecycle capability (retention / hold / export / quota). */
|
|
86
|
+
export interface PersistenceLifecycleStore {
|
|
87
|
+
putLegalHold(input: PutLegalHoldInput): Promise<LegalHoldRecord>;
|
|
88
|
+
releaseLegalHold(input: ReleaseLegalHoldInput): Promise<boolean>;
|
|
89
|
+
listLegalHolds(query: LegalHoldQuery): Promise<PersistencePage<LegalHoldRecord>>;
|
|
90
|
+
/** Deletes candidates not under legal hold. Hold always wins over retention. */
|
|
91
|
+
applyRetention(input: ApplyRetentionInput): Promise<ApplyRetentionResult>;
|
|
92
|
+
exportUnderHold(input: ExportUnderHoldInput): Promise<PersistencePage<LegalHoldExportItem>>;
|
|
93
|
+
setTenantQuota(input: SetTenantQuotaInput): Promise<TenantQuota>;
|
|
94
|
+
getTenantQuota(input: OwnershipScope & {
|
|
95
|
+
readonly resourceKind: PersistenceResourceKind;
|
|
96
|
+
readonly signal?: AbortSignal;
|
|
97
|
+
}): Promise<TenantQuota | null>;
|
|
98
|
+
/** Fails closed when used + delta would exceed limit. */
|
|
99
|
+
consumeTenantQuota(input: ConsumeTenantQuotaInput): Promise<TenantQuota>;
|
|
100
|
+
}
|
|
101
|
+
export declare function createMemoryPersistenceLifecycle(): PersistenceLifecycleStore;
|
|
102
|
+
/** True when a session/resource id is under an active legal hold in the page of holds. */
|
|
103
|
+
export declare function isResourceHeld(holds: readonly Pick<LegalHoldRecord, "resourceKind" | "resourceId">[], resourceKind: PersistenceResourceKind, resourceId: string): boolean;
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
export const DEFAULT_LIFECYCLE_PAGE_SIZE = 100;
|
|
2
|
+
export const HARD_LIFECYCLE_PAGE_SIZE = 500;
|
|
3
|
+
export const DEFAULT_MAX_HOLD_REASON_BYTES = 1024;
|
|
4
|
+
export const HARD_MAX_HOLD_REASON_BYTES = 8 * 1024;
|
|
5
|
+
export class PersistenceLifecycleError extends Error {
|
|
6
|
+
code;
|
|
7
|
+
constructor(message, code = "ERR_PRISM_PERSISTENCE_LIFECYCLE") {
|
|
8
|
+
super(message);
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.name = "PersistenceLifecycleError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function createMemoryPersistenceLifecycle() {
|
|
14
|
+
const holds = new Map();
|
|
15
|
+
const quotas = new Map();
|
|
16
|
+
const deleted = new Set();
|
|
17
|
+
return {
|
|
18
|
+
async putLegalHold(input) {
|
|
19
|
+
throwIfAborted(input.signal);
|
|
20
|
+
assertOwnership(input);
|
|
21
|
+
const reason = assertReason(input.reason);
|
|
22
|
+
if (!input.resourceId || !input.resourceKind) {
|
|
23
|
+
throw new PersistenceLifecycleError("resourceKind and resourceId are required", "ERR_PRISM_LIFECYCLE_HOLD");
|
|
24
|
+
}
|
|
25
|
+
const id = input.id ?? crypto.randomUUID();
|
|
26
|
+
const record = {
|
|
27
|
+
id,
|
|
28
|
+
resourceKind: input.resourceKind,
|
|
29
|
+
resourceId: input.resourceId,
|
|
30
|
+
reason,
|
|
31
|
+
createdAt: new Date().toISOString(),
|
|
32
|
+
...(input.createdBy === undefined ? {} : { createdBy: input.createdBy }),
|
|
33
|
+
...(input.metadata === undefined ? {} : { metadata: input.metadata }),
|
|
34
|
+
...ownership(input),
|
|
35
|
+
};
|
|
36
|
+
holds.set(id, record);
|
|
37
|
+
return record;
|
|
38
|
+
},
|
|
39
|
+
async releaseLegalHold(input) {
|
|
40
|
+
throwIfAborted(input.signal);
|
|
41
|
+
assertOwnership(input);
|
|
42
|
+
const current = holds.get(input.id);
|
|
43
|
+
if (!current)
|
|
44
|
+
return false;
|
|
45
|
+
assertSameOwnership(input, current);
|
|
46
|
+
holds.delete(input.id);
|
|
47
|
+
return true;
|
|
48
|
+
},
|
|
49
|
+
async listLegalHolds(query) {
|
|
50
|
+
throwIfAborted(query.signal);
|
|
51
|
+
assertOwnership(query);
|
|
52
|
+
const limit = pageLimit(query.limit);
|
|
53
|
+
const items = [...holds.values()]
|
|
54
|
+
.filter((hold) => sameOwnership(query, hold))
|
|
55
|
+
.filter((hold) => query.holdId === undefined || hold.id === query.holdId)
|
|
56
|
+
.filter((hold) => query.resourceKind === undefined || hold.resourceKind === query.resourceKind)
|
|
57
|
+
.filter((hold) => query.resourceId === undefined || hold.resourceId === query.resourceId)
|
|
58
|
+
.sort((a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id));
|
|
59
|
+
const start = query.cursor ? items.findIndex((item) => item.id === query.cursor) + 1 : 0;
|
|
60
|
+
const slice = items.slice(Math.max(0, start), Math.max(0, start) + limit);
|
|
61
|
+
const next = start + limit < items.length ? slice.at(-1)?.id : undefined;
|
|
62
|
+
return { items: slice, ...(next === undefined ? {} : { nextCursor: next }) };
|
|
63
|
+
},
|
|
64
|
+
async applyRetention(input) {
|
|
65
|
+
throwIfAborted(input.signal);
|
|
66
|
+
assertOwnership(input);
|
|
67
|
+
const held = new Set([...holds.values()]
|
|
68
|
+
.filter((hold) => sameOwnership(input, hold) && hold.resourceKind === "session")
|
|
69
|
+
.map((hold) => hold.resourceId));
|
|
70
|
+
const candidates = input.candidates ?? [];
|
|
71
|
+
const deletedIds = [];
|
|
72
|
+
const skippedHeld = [];
|
|
73
|
+
for (const id of candidates) {
|
|
74
|
+
if (held.has(id)) {
|
|
75
|
+
skippedHeld.push(id);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
deleted.add(id);
|
|
79
|
+
deletedIds.push(id);
|
|
80
|
+
}
|
|
81
|
+
return { deleted: deletedIds, skippedHeld };
|
|
82
|
+
},
|
|
83
|
+
async exportUnderHold(input) {
|
|
84
|
+
throwIfAborted(input.signal);
|
|
85
|
+
assertOwnership(input);
|
|
86
|
+
const page = await this.listLegalHolds({
|
|
87
|
+
...ownership(input),
|
|
88
|
+
holdId: input.holdId,
|
|
89
|
+
resourceKind: input.resourceKind,
|
|
90
|
+
cursor: input.cursor,
|
|
91
|
+
limit: input.limit,
|
|
92
|
+
signal: input.signal,
|
|
93
|
+
});
|
|
94
|
+
return {
|
|
95
|
+
items: page.items.map((hold) => ({
|
|
96
|
+
holdId: hold.id,
|
|
97
|
+
resourceKind: hold.resourceKind,
|
|
98
|
+
resourceId: hold.resourceId,
|
|
99
|
+
reason: hold.reason,
|
|
100
|
+
createdAt: hold.createdAt,
|
|
101
|
+
redacted: true,
|
|
102
|
+
})),
|
|
103
|
+
...(page.nextCursor === undefined ? {} : { nextCursor: page.nextCursor }),
|
|
104
|
+
};
|
|
105
|
+
},
|
|
106
|
+
async setTenantQuota(input) {
|
|
107
|
+
throwIfAborted(input.signal);
|
|
108
|
+
assertOwnership(input);
|
|
109
|
+
if (!Number.isSafeInteger(input.limit) || input.limit < 0) {
|
|
110
|
+
throw new PersistenceLifecycleError("limit must be a non-negative safe integer", "ERR_PRISM_LIFECYCLE_QUOTA");
|
|
111
|
+
}
|
|
112
|
+
const key = quotaKey(input, input.resourceKind);
|
|
113
|
+
const previous = quotas.get(key);
|
|
114
|
+
const record = {
|
|
115
|
+
...ownership(input),
|
|
116
|
+
resourceKind: input.resourceKind,
|
|
117
|
+
limit: input.limit,
|
|
118
|
+
used: previous?.used ?? 0,
|
|
119
|
+
updatedAt: new Date().toISOString(),
|
|
120
|
+
};
|
|
121
|
+
if (record.used > record.limit) {
|
|
122
|
+
throw new PersistenceLifecycleError("quota already exceeded", "ERR_PRISM_LIFECYCLE_QUOTA");
|
|
123
|
+
}
|
|
124
|
+
quotas.set(key, record);
|
|
125
|
+
return record;
|
|
126
|
+
},
|
|
127
|
+
async getTenantQuota(input) {
|
|
128
|
+
throwIfAborted(input.signal);
|
|
129
|
+
assertOwnership(input);
|
|
130
|
+
return quotas.get(quotaKey(input, input.resourceKind)) ?? null;
|
|
131
|
+
},
|
|
132
|
+
async consumeTenantQuota(input) {
|
|
133
|
+
throwIfAborted(input.signal);
|
|
134
|
+
assertOwnership(input);
|
|
135
|
+
const delta = input.delta ?? 1;
|
|
136
|
+
if (!Number.isSafeInteger(delta) || delta < 1) {
|
|
137
|
+
throw new PersistenceLifecycleError("delta must be a positive safe integer", "ERR_PRISM_LIFECYCLE_QUOTA");
|
|
138
|
+
}
|
|
139
|
+
const key = quotaKey(input, input.resourceKind);
|
|
140
|
+
const current = quotas.get(key);
|
|
141
|
+
if (!current) {
|
|
142
|
+
throw new PersistenceLifecycleError("tenant quota not configured", "ERR_PRISM_LIFECYCLE_QUOTA");
|
|
143
|
+
}
|
|
144
|
+
if (current.used + delta > current.limit) {
|
|
145
|
+
throw new PersistenceLifecycleError(`tenant quota exhausted for ${input.resourceKind}`, "ERR_PRISM_LIFECYCLE_QUOTA_EXHAUSTED");
|
|
146
|
+
}
|
|
147
|
+
const next = {
|
|
148
|
+
...current,
|
|
149
|
+
used: current.used + delta,
|
|
150
|
+
updatedAt: new Date().toISOString(),
|
|
151
|
+
};
|
|
152
|
+
quotas.set(key, next);
|
|
153
|
+
return next;
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/** True when a session/resource id is under an active legal hold in the page of holds. */
|
|
158
|
+
export function isResourceHeld(holds, resourceKind, resourceId) {
|
|
159
|
+
return holds.some((hold) => hold.resourceKind === resourceKind && hold.resourceId === resourceId);
|
|
160
|
+
}
|
|
161
|
+
function ownership(input) {
|
|
162
|
+
return {
|
|
163
|
+
...(input.tenantId === undefined ? {} : { tenantId: input.tenantId }),
|
|
164
|
+
...(input.accountId === undefined ? {} : { accountId: input.accountId }),
|
|
165
|
+
...(input.userId === undefined ? {} : { userId: input.userId }),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function sameOwnership(a, b) {
|
|
169
|
+
return a.tenantId === b.tenantId && a.accountId === b.accountId && a.userId === b.userId;
|
|
170
|
+
}
|
|
171
|
+
function assertSameOwnership(expected, actual) {
|
|
172
|
+
if (!sameOwnership(expected, actual)) {
|
|
173
|
+
throw new PersistenceLifecycleError("ownership mismatch", "ERR_PRISM_LIFECYCLE_OWNERSHIP");
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function assertOwnership(input) {
|
|
177
|
+
if (![input.tenantId, input.accountId, input.userId].some((value) => typeof value === "string" && value.length > 0)) {
|
|
178
|
+
throw new PersistenceLifecycleError("ownership required", "ERR_PRISM_LIFECYCLE_OWNERSHIP");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function assertReason(reason) {
|
|
182
|
+
if (typeof reason !== "string" || !reason.trim()) {
|
|
183
|
+
throw new PersistenceLifecycleError("reason is required", "ERR_PRISM_LIFECYCLE_HOLD");
|
|
184
|
+
}
|
|
185
|
+
if (Buffer.byteLength(reason, "utf8") > HARD_MAX_HOLD_REASON_BYTES) {
|
|
186
|
+
throw new PersistenceLifecycleError("reason exceeds limit", "ERR_PRISM_LIFECYCLE_HOLD");
|
|
187
|
+
}
|
|
188
|
+
return reason;
|
|
189
|
+
}
|
|
190
|
+
function pageLimit(limit) {
|
|
191
|
+
const resolved = limit ?? DEFAULT_LIFECYCLE_PAGE_SIZE;
|
|
192
|
+
if (!Number.isSafeInteger(resolved) || resolved < 1 || resolved > HARD_LIFECYCLE_PAGE_SIZE) {
|
|
193
|
+
throw new PersistenceLifecycleError(`limit must be 1..${HARD_LIFECYCLE_PAGE_SIZE}`, "ERR_PRISM_LIFECYCLE_LIMITS");
|
|
194
|
+
}
|
|
195
|
+
return resolved;
|
|
196
|
+
}
|
|
197
|
+
function quotaKey(ownership, kind) {
|
|
198
|
+
return `${ownership.tenantId ?? ""}\0${ownership.accountId ?? ""}\0${ownership.userId ?? ""}\0${kind}`;
|
|
199
|
+
}
|
|
200
|
+
function throwIfAborted(signal) {
|
|
201
|
+
if (signal?.aborted)
|
|
202
|
+
throw signal.reason ?? new DOMException("Aborted", "AbortError");
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=persistence-lifecycle.js.map
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import type { AIProvider } from "../contracts.js";
|
|
1
|
+
import type { AIProvider, ProviderRequest } from "../contracts.js";
|
|
2
2
|
import { type CredentialValueSource } from "../credentials.js";
|
|
3
3
|
export interface OpenAICompatibleProviderOptions {
|
|
4
4
|
readonly id?: string;
|
|
5
5
|
readonly baseUrl: string;
|
|
6
6
|
readonly apiKey?: CredentialValueSource;
|
|
7
7
|
readonly fetch?: typeof fetch;
|
|
8
|
+
/** Override chat-completions URL (default `${baseUrl}/chat/completions`). */
|
|
9
|
+
readonly chatCompletionsUrl?: string | ((request: ProviderRequest) => string);
|
|
10
|
+
/** Default `bearer`. Azure resource keys use `api-key`; host-signed fetches may use `none`. */
|
|
11
|
+
readonly authStyle?: "bearer" | "api-key" | "none";
|
|
8
12
|
}
|
|
9
13
|
export declare function createOpenAICompatibleProvider(options: OpenAICompatibleProviderOptions): AIProvider;
|