@arnilo/prism 0.0.14 → 0.0.16
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 +22 -2
- package/README.md +5 -4
- package/dist/agent-definitions.js +2 -3
- package/dist/agent-loops.d.ts +4 -0
- package/dist/agent-loops.js +28 -10
- package/dist/agent-run-lifecycle.d.ts +1 -2
- package/dist/agent-run-lifecycle.js +1 -1
- package/dist/agent-run-state.js +16 -3
- package/dist/agents.d.ts +1 -1
- package/dist/agents.js +121 -51
- package/dist/cache-helpers.js +18 -9
- package/dist/checkpoints.js +5 -9
- package/dist/cli-init.js +3 -7
- package/dist/cli-runner.d.ts +1 -1
- package/dist/cli-runner.js +74 -13
- package/dist/compaction.js +5 -4
- package/dist/config.js +7 -4
- package/dist/content.js +26 -24
- package/dist/context-budget.js +12 -8
- package/dist/contracts.d.ts +79 -3
- package/dist/contracts.js +4 -1
- package/dist/contribution-parsing.js +6 -2
- package/dist/conversations.js +2 -1
- package/dist/credentials.d.ts +1 -1
- package/dist/credentials.js +3 -1
- package/dist/event-multiplexer.js +1 -3
- package/dist/feedback.js +11 -9
- package/dist/guardrails.d.ts +1 -1
- package/dist/guardrails.js +17 -14
- package/dist/identity.js +10 -2
- package/dist/index.d.ts +83 -84
- package/dist/index.js +42 -42
- package/dist/input.d.ts +2 -2
- package/dist/input.js +40 -24
- package/dist/instruction-injection.d.ts +1 -1
- package/dist/node/agent-definitions.js +16 -8
- package/dist/node/contribution-discovery.d.ts +1 -2
- package/dist/node/contribution-discovery.js +3 -3
- package/dist/node/session-store-jsonl.js +10 -7
- package/dist/node/settings.d.ts +1 -1
- package/dist/node/settings.js +1 -1
- package/dist/node/system-project-prompts.js +2 -4
- package/dist/node/trust.js +1 -1
- package/dist/persistence-lifecycle.js +1 -3
- package/dist/provider-events.d.ts +1 -0
- package/dist/provider-events.js +6 -1
- package/dist/provider-request-policy.js +3 -4
- package/dist/providers/media.d.ts +1 -1
- package/dist/providers/openai-compatible.js +3 -4
- package/dist/providers/openai-primitives.js +7 -7
- package/dist/redaction.d.ts +1 -0
- package/dist/redaction.js +5 -2
- package/dist/resources.d.ts +2 -2
- package/dist/resources.js +2 -2
- package/dist/rpc.js +42 -9
- package/dist/run-ledger.js +13 -4
- package/dist/run-limits.js +49 -10
- package/dist/secure-agent.js +1 -1
- package/dist/security.js +7 -2
- package/dist/session-stores.d.ts +1 -1
- package/dist/session-stores.js +13 -13
- package/dist/structured-output.js +2 -2
- package/dist/system-prompts.js +7 -2
- package/dist/testing/compaction-conformance.js +5 -1
- package/dist/testing/extension-conformance.js +15 -3
- package/dist/testing/feedback.d.ts +1 -3
- package/dist/testing/feedback.js +1 -1
- package/dist/testing/persistence-schema.js +206 -37
- package/dist/testing/provider-conformance.js +3 -3
- package/dist/testing/run-ledger-conformance.js +1 -1
- package/dist/testing/session-store-conformance.js +1 -1
- package/dist/testing/tool-conformance.js +30 -5
- package/dist/thinking.js +4 -1
- package/dist/tools.d.ts +2 -2
- package/dist/tools.js +24 -5
- package/docs/0.1.0-readiness.md +139 -0
- package/docs/host-security.md +4 -1
- package/docs/index.md +14 -11
- package/docs/migration.md +58 -1
- package/docs/multimodal-content.md +8 -5
- package/docs/performance.md +67 -0
- package/docs/provider-caching.md +8 -0
- package/docs/provider-conformance.md +29 -5
- package/docs/provider-packages.md +22 -1
- package/docs/providers/ai-sdk.md +23 -7
- package/docs/providers/openai.md +22 -3
- package/docs/public-contracts.md +1 -1
- package/docs/rag.md +41 -12
- package/docs/release-and-install.md +153 -16
- package/docs/resource-loading.md +3 -0
- package/docs/working-and-semantic-memory.md +22 -4
- package/package.json +14 -6
- package/docs/review-coverage-2026-07-14.md +0 -260
- package/docs/review-coverage-2026-07-15.md +0 -193
- package/docs/review-coverage-2026-07-17-provider-validation.md +0 -192
- package/docs/review-coverage-2026-07-19-phase-3.md +0 -174
- package/docs/review-coverage-2026-07-20-phase-4.md +0 -175
- package/docs/review-coverage-2026-07-21-phase-5.md +0 -172
- package/docs/review-coverage-2026-07-22-phase-6.md +0 -209
- package/docs/review-coverage-2026-07-22-phase-7.md +0 -173
- package/docs/review-coverage-2026-07-23-phase-8.md +0 -245
- package/docs/review-coverage-2026-07-25-phase-9.md +0 -256
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.0.16] - 2026-07-26
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Phase 11 simplification/readiness: new public export `resolveRedactor` from `@arnilo/prism` (single survivor of four private copies across evals/memory/rag/workflows) and a new internal `@arnilo/prism-session-store-codecs` package (shared SQLite/Postgres row codecs, not enrolled in any profile family), bringing the exact graph to **44 publishable manifests**.
|
|
7
|
+
- Offline pre-publish release gates: `npm run release:gate` (API-surface `.d.ts` diff vs `scripts/compat-baseline/`, tarball deny-list, exact version ranges), wired into `npm run sdk:ready`.
|
|
8
|
+
- Performance budgets in `scripts/budgets.json`, enforced by `scripts/budget-gate.test.mjs` (in `npm test`) and `scripts/benchmark-0.0.16.mjs`.
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- Dropped historical `docs/review-coverage-*.md` from the root tarball (11 files, ~283 KB): packed size 659,478 → ≈575,680 bytes, 281 → 270 files.
|
|
12
|
+
- All six profiles (`prism-all`, `prism-base`, `prism-code`, `prism-compaction`, `prism-providers`, `prism-sdk`) retained on adoption evidence; zero retirements. No runtime behavior changes.
|
|
13
|
+
|
|
14
|
+
## [0.0.15] - 2026-07-26
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- Phase 10 provider, memory, and RAG parity: OpenAI hosted-tool attribution, bounded Responses continuation and Realtime seam; exact AI SDK V4 mapping; bounded RAG source lifecycle, document adapters, reranking, citation provenance, content trust, and ingestion status; memory export/rebuild with production-store conformance.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Versioned all **43** publishable manifests, exact internal ranges, and lockfile entries to `0.0.15`; no package was added.
|
|
23
|
+
- Added network-free Phase 10 evidence: `scripts/benchmark-0.0.15.mjs`.
|
|
24
|
+
|
|
3
25
|
## [0.0.14] - 2026-07-26
|
|
4
26
|
|
|
5
27
|
### Added
|
|
@@ -28,8 +50,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
28
50
|
|
|
29
51
|
All notable changes to this project will be documented in this file.
|
|
30
52
|
|
|
31
|
-
## [Unreleased]
|
|
32
|
-
|
|
33
53
|
## [0.0.12] - 2026-07-22
|
|
34
54
|
|
|
35
55
|
### Added
|
package/README.md
CHANGED
|
@@ -34,10 +34,11 @@ packages. Prism defines contracts, not apps.
|
|
|
34
34
|
- **Config, settings, security**: layered config merge, settings providers,
|
|
35
35
|
credential resolvers, trust/permission policies, and secret redaction.
|
|
36
36
|
- **CLI/RPC/server**: `prism --mode print|json|rpc`, `prism init`, optional framework-free authorized Web agent/workflow routes, and explicit MCP server exposure.
|
|
37
|
-
- **
|
|
38
|
-
|
|
39
|
-
and
|
|
40
|
-
|
|
37
|
+
- **Ecosystem parity (0.0.15)**: OpenAI hosted-tool attribution, bounded Responses
|
|
38
|
+
continuation/Realtime, exact AI SDK V4 mapping, bounded RAG lifecycle/reranking/trust,
|
|
39
|
+
and consent-bound memory export/rebuild; provider, RAG, and memory packages remain optional.
|
|
40
|
+
- **Co-work contracts (0.0.14)**: conversation/artifact review types, deny-by-default device
|
|
41
|
+
contracts, and OAuth refresh/revoke helpers; services stay in optional packages.
|
|
41
42
|
|
|
42
43
|
## Install
|
|
43
44
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createAgent } from "./agents.js";
|
|
2
|
-
import {
|
|
2
|
+
import { createSkillRegistry, resolveActiveSkills } from "./skills.js";
|
|
3
3
|
import { createToolRegistry } from "./tools.js";
|
|
4
4
|
/** Resolve an {@link AgentDefinition} into a runnable {@link Agent}.
|
|
5
5
|
*
|
|
@@ -96,8 +96,7 @@ function hasList(value) {
|
|
|
96
96
|
return typeof value === "object" && value !== null && "list" in value && typeof value.list === "function";
|
|
97
97
|
}
|
|
98
98
|
function resolveSkills(names, tools, context) {
|
|
99
|
-
const registry = context.skillsRegistry ??
|
|
100
|
-
(context.registries?.skills ? createSkillRegistry(context.registries.skills.list()) : undefined);
|
|
99
|
+
const registry = context.skillsRegistry ?? (context.registries?.skills ? createSkillRegistry(context.registries.skills.list()) : undefined);
|
|
101
100
|
if (!names && !context.activateAllCapabilities)
|
|
102
101
|
return undefined;
|
|
103
102
|
if (!registry) {
|
package/dist/agent-loops.d.ts
CHANGED
|
@@ -14,6 +14,10 @@ export declare function resolveToolConcurrency(options: {
|
|
|
14
14
|
}, config: {
|
|
15
15
|
loop?: AgentLoopStrategy | AgentLoopOptions;
|
|
16
16
|
}): number;
|
|
17
|
+
/** Calls the host must dispatch. Provider-hosted calls (`authority: "provider-hosted"`)
|
|
18
|
+
* were already executed server-side; the assistant response text carries their effect, so
|
|
19
|
+
* the host neither dispatches them nor appends a `tool_result`. */
|
|
20
|
+
export declare function dispatchableToolCalls(calls: readonly ToolCallContent[]): readonly ToolCallContent[];
|
|
17
21
|
/** Dispatch tool calls with bounded concurrency; append transcript rows in call order. */
|
|
18
22
|
export declare function dispatchToolCallsInOrder(calls: readonly ToolCallContent[], ctx: LoopContext): Promise<void>;
|
|
19
23
|
export declare function resolveLoop(options: {
|
package/dist/agent-loops.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { inputMessages } from "./input.js";
|
|
2
1
|
import { createId } from "./ids.js";
|
|
2
|
+
import { inputMessages } from "./input.js";
|
|
3
3
|
import { artifactStructuredOutputRequest, withoutStructuredOutput } from "./structured-output.js";
|
|
4
4
|
function throwIfAborted(signal) {
|
|
5
5
|
if (signal.aborted)
|
|
@@ -8,7 +8,10 @@ function throwIfAborted(signal) {
|
|
|
8
8
|
function toolResultMessage(result) {
|
|
9
9
|
return {
|
|
10
10
|
role: "tool",
|
|
11
|
-
content: [
|
|
11
|
+
content: [
|
|
12
|
+
{ type: "tool_result", toolCallId: result.toolCallId, name: result.name, result: result.value, error: result.error },
|
|
13
|
+
...(result.content ?? []),
|
|
14
|
+
],
|
|
12
15
|
metadata: result.metadata,
|
|
13
16
|
};
|
|
14
17
|
}
|
|
@@ -39,7 +42,8 @@ export const singleShotLoop = {
|
|
|
39
42
|
ctx.emit({ type: "message_finished", sessionId: ctx.sessionId, runId: ctx.runId, message });
|
|
40
43
|
}
|
|
41
44
|
ctx.emit({ type: "turn_finished", sessionId: ctx.sessionId, runId: ctx.runId, turn });
|
|
42
|
-
|
|
45
|
+
const dispatchable = dispatchableToolCalls(calls);
|
|
46
|
+
if (dispatchable.length === 0 || toolRounds >= ctx.maxToolRounds) {
|
|
43
47
|
// Soft-interrupt / late steer: keep same run going when queue still has text.
|
|
44
48
|
if (await ctx.applyPendingSteers?.()) {
|
|
45
49
|
nextInput = [];
|
|
@@ -48,7 +52,7 @@ export const singleShotLoop = {
|
|
|
48
52
|
break;
|
|
49
53
|
}
|
|
50
54
|
toolRounds += 1;
|
|
51
|
-
await dispatchToolCallsInOrder(
|
|
55
|
+
await dispatchToolCallsInOrder(dispatchable, ctx);
|
|
52
56
|
nextInput = [];
|
|
53
57
|
}
|
|
54
58
|
return usage;
|
|
@@ -113,13 +117,23 @@ export function generateValidateReviseLoop(opts) {
|
|
|
113
117
|
}
|
|
114
118
|
ctx.emit({ type: "turn_finished", sessionId: ctx.sessionId, runId: ctx.runId, turn });
|
|
115
119
|
if (opts.toolCalls === "bounded" && calls.length > 0) {
|
|
120
|
+
const dispatchable = dispatchableToolCalls(calls);
|
|
121
|
+
if (dispatchable.length === 0) {
|
|
122
|
+
// Only provider-hosted calls; no host tool to run. Continue without charging a round.
|
|
123
|
+
nextInput = [];
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
116
126
|
if (toolRounds >= ctx.maxToolRounds) {
|
|
117
|
-
const result = {
|
|
127
|
+
const result = {
|
|
128
|
+
ok: false,
|
|
129
|
+
errors: [{ message: "maximum tool rounds exceeded" }],
|
|
130
|
+
metadata: { reason: "tool_round_limit" },
|
|
131
|
+
};
|
|
118
132
|
ctx.emit({ type: "artifact_failed", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt: attempts + 1, result });
|
|
119
133
|
return usage;
|
|
120
134
|
}
|
|
121
135
|
toolRounds += 1;
|
|
122
|
-
await dispatchToolCallsInOrder(
|
|
136
|
+
await dispatchToolCallsInOrder(dispatchable, { ...ctx, toolConcurrency: 1 });
|
|
123
137
|
nextInput = [];
|
|
124
138
|
continue;
|
|
125
139
|
}
|
|
@@ -159,7 +173,7 @@ export function generateValidateReviseLoop(opts) {
|
|
|
159
173
|
: undefined;
|
|
160
174
|
const attempt = ++attempts;
|
|
161
175
|
ctx.emit({ type: "artifact_validation_started", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt });
|
|
162
|
-
const result = parseFailure ?? await opts.validator(parsed.value, artifactCtx);
|
|
176
|
+
const result = parseFailure ?? (await opts.validator(parsed.value, artifactCtx));
|
|
163
177
|
ctx.emit({ type: "artifact_validation_finished", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt, result });
|
|
164
178
|
if (result.ok) {
|
|
165
179
|
ctx.emit({ type: "artifact_finished", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt, result });
|
|
@@ -195,14 +209,18 @@ export function resolveToolConcurrency(options, config) {
|
|
|
195
209
|
return 1;
|
|
196
210
|
return Math.floor(value);
|
|
197
211
|
}
|
|
212
|
+
/** Calls the host must dispatch. Provider-hosted calls (`authority: "provider-hosted"`)
|
|
213
|
+
* were already executed server-side; the assistant response text carries their effect, so
|
|
214
|
+
* the host neither dispatches them nor appends a `tool_result`. */
|
|
215
|
+
export function dispatchableToolCalls(calls) {
|
|
216
|
+
return calls.filter((call) => call.authority !== "provider-hosted");
|
|
217
|
+
}
|
|
198
218
|
/** Dispatch tool calls with bounded concurrency; append transcript rows in call order. */
|
|
199
219
|
export async function dispatchToolCallsInOrder(calls, ctx) {
|
|
200
220
|
if (calls.length === 0)
|
|
201
221
|
return;
|
|
202
222
|
ctx.chargeToolRound?.(calls);
|
|
203
|
-
const concurrency = calls.some((call) => ctx.isToolCallExclusive?.(call))
|
|
204
|
-
? 1
|
|
205
|
-
: Math.max(1, Math.min(ctx.toolConcurrency, calls.length));
|
|
223
|
+
const concurrency = calls.some((call) => ctx.isToolCallExclusive?.(call)) ? 1 : Math.max(1, Math.min(ctx.toolConcurrency, calls.length));
|
|
206
224
|
if (concurrency === 1) {
|
|
207
225
|
for (const call of calls) {
|
|
208
226
|
const result = await ctx.dispatchToolCall(call);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type { Agent, AgentEvent, AgentRunRef, AgentRunResult, AgentRunResume, AgentRunStatusResult, OwnershipScope, SubscribeOptions } from "./contracts.js";
|
|
2
|
-
import type { CheckpointStore } from "./contracts.js";
|
|
1
|
+
import type { Agent, AgentEvent, AgentRunRef, AgentRunResult, AgentRunResume, AgentRunStatusResult, CheckpointStore, OwnershipScope, SubscribeOptions } from "./contracts.js";
|
|
3
2
|
export interface AgentRunLifecycleAgent {
|
|
4
3
|
readonly agent: Agent;
|
|
5
4
|
/** Current host-authored revision; it must match the stored revision. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { AgentRunStateError } from "./contracts.js";
|
|
2
1
|
import { loadAgentRunState, publicState } from "./agent-run-state.js";
|
|
3
2
|
import { resumeAgentRun, resumeAgentRunStream } from "./agents.js";
|
|
3
|
+
import { AgentRunStateError } from "./contracts.js";
|
|
4
4
|
function assertAgentId(actual, expected) {
|
|
5
5
|
if (expected !== undefined && actual !== expected)
|
|
6
6
|
throw new AgentRunStateError("Agent run capability mismatch");
|
package/dist/agent-run-state.js
CHANGED
|
@@ -21,7 +21,9 @@ export function agentFingerprint(agent, revision) {
|
|
|
21
21
|
model: config.model,
|
|
22
22
|
tools: tools.map((tool) => ({ name: tool.name, parameters: tool.parameters, exclusive: tool.exclusive })),
|
|
23
23
|
guardrails: guardrails.map((guardrail) => ({ name: guardrail.name, stage: guardrail.stage, revision: guardrail.revision })),
|
|
24
|
-
loop: typeof config.loop === "object" && config.loop && "strategy" in config.loop
|
|
24
|
+
loop: typeof config.loop === "object" && config.loop && "strategy" in config.loop
|
|
25
|
+
? config.loop.strategy
|
|
26
|
+
: (config.loop?.name ?? "single-shot"),
|
|
25
27
|
});
|
|
26
28
|
return createHash("sha256").update(value).digest("hex");
|
|
27
29
|
}
|
|
@@ -43,7 +45,10 @@ export async function loadAgentRunState(checkpoints, ref, ownership) {
|
|
|
43
45
|
const record = await checkpoints.loadCheckpoint({ namespace: AGENT_RUN_STATE_NAMESPACE, key: ref.runId, ...ownership });
|
|
44
46
|
if (!record)
|
|
45
47
|
throw new AgentRunStateError(`No durable agent run ${ref.runId}`);
|
|
46
|
-
if (ref.sessionId &&
|
|
48
|
+
if (ref.sessionId &&
|
|
49
|
+
record.value &&
|
|
50
|
+
typeof record.value === "object" &&
|
|
51
|
+
record.value.sessionId !== ref.sessionId) {
|
|
47
52
|
throw new AgentRunStateError("Agent run session mismatch");
|
|
48
53
|
}
|
|
49
54
|
return { record, state: parseAgentRunState(record.value, record.version) };
|
|
@@ -95,7 +100,15 @@ export function parseAgentRunState(value, version) {
|
|
|
95
100
|
const state = value;
|
|
96
101
|
if (state.schemaVersion !== AGENT_RUN_STATE_SCHEMA_VERSION)
|
|
97
102
|
throw new AgentRunStateError(`Unsupported agent run state schemaVersion ${String(state.schemaVersion)}`);
|
|
98
|
-
if (!state.agentId ||
|
|
103
|
+
if (!state.agentId ||
|
|
104
|
+
!state.definitionRevision ||
|
|
105
|
+
!state.fingerprint ||
|
|
106
|
+
!state.runId ||
|
|
107
|
+
!state.sessionId ||
|
|
108
|
+
!state.model ||
|
|
109
|
+
!state.status ||
|
|
110
|
+
!state.counters ||
|
|
111
|
+
!state.deadlineAt) {
|
|
99
112
|
throw new AgentRunStateError("Malformed agent run state");
|
|
100
113
|
}
|
|
101
114
|
return boundState({ ...state, version }, DEFAULT_MAX_AGENT_RUN_STATE_BYTES);
|
package/dist/agents.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Agent, AgentConfig, AgentEvent, AgentRunResult, AgentRunResume, AgentRunResumeOptions, AgentRunResumeStreamOptions,
|
|
1
|
+
import type { Agent, AgentConfig, AgentEvent, AgentRunRef, AgentRunResult, AgentRunResume, AgentRunResumeOptions, AgentRunResumeStreamOptions, AgentSession, AgentSessionConfig } from "./contracts.js";
|
|
2
2
|
export declare function createAgent(config: AgentConfig): Agent;
|
|
3
3
|
export declare function createAgentSession(config: AgentSessionConfig & {
|
|
4
4
|
readonly agent: Agent;
|
package/dist/agents.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import { AgentRunError, AgentRunStateError, DEFAULT_MAX_PENDING_STEER_BYTES, DEFAULT_MAX_PENDING_STEERS, } from "./contracts.js";
|
|
2
1
|
import { resolveLoop, resolveToolConcurrency } from "./agent-loops.js";
|
|
3
|
-
import {
|
|
4
|
-
import { assertGuardrailsAllowed, GuardrailError, runGuardrails } from "./guardrails.js";
|
|
5
|
-
import { createProviderTurnMetadata, readProviderHttpStatus } from "./observability.js";
|
|
2
|
+
import { agentFingerprint, initialAgentRunState, loadAgentRunState, publicState, saveAgentRunState, validateRunStateOptions, } from "./agent-run-state.js";
|
|
6
3
|
import { createDefaultCompactionStrategy, isCompactionEntryData } from "./compaction.js";
|
|
4
|
+
import { AgentRunError, AgentRunStateError, DEFAULT_MAX_PENDING_STEER_BYTES, DEFAULT_MAX_PENDING_STEERS } from "./contracts.js";
|
|
5
|
+
import { assertGuardrailsAllowed, GuardrailError, runGuardrails } from "./guardrails.js";
|
|
6
|
+
import { identityTelemetryAttributes, ownershipFromIdentity, resolveRunIdentity } from "./identity.js";
|
|
7
|
+
import { createId } from "./ids.js";
|
|
7
8
|
import { assembleProviderInput } from "./input.js";
|
|
9
|
+
import { createProviderTurnMetadata, readProviderHttpStatus } from "./observability.js";
|
|
8
10
|
import { providerToolCallDeltaContent, reconstructToolCallDeltas } from "./provider-events.js";
|
|
9
11
|
import { createProviderRequestPolicyChain, normalizeProviderRequestPolicyResult } from "./provider-request-policy.js";
|
|
10
|
-
import {
|
|
11
|
-
import { errorToErrorInfo, redactAgentEvent, redactProviderRequest, redactRunLedgerRecord, redactSecrets, redactSessionEntry } from "./redaction.js";
|
|
12
|
-
import { composeSystemPrompt, mergeSystemPromptConfig } from "./system-prompts.js";
|
|
12
|
+
import { errorToErrorInfo, redactAgentEvent, redactProviderRequest, redactRunLedgerRecord, redactSecrets, redactSessionEntry, } from "./redaction.js";
|
|
13
13
|
import { createDefaultRetryPolicy, waitForRetry } from "./retry.js";
|
|
14
|
-
import { createMemorySessionStore, createSessionEntry, getSessionBranchEntries, rebuildSessionContext } from "./session-stores.js";
|
|
15
14
|
import { isFlushableRunLedger } from "./run-ledger.js";
|
|
16
|
-
import { createToolRegistry, dispatchToolCall } from "./tools.js";
|
|
17
15
|
import { RunLimitError, RunLimitTracker, resolveRunLimits } from "./run-limits.js";
|
|
18
|
-
import {
|
|
16
|
+
import { createMemorySessionStore, createSessionEntry, getSessionBranchEntries, rebuildSessionContext, } from "./session-stores.js";
|
|
19
17
|
import { resolveActiveSkills } from "./skills.js";
|
|
20
|
-
import {
|
|
18
|
+
import { assertStructuredOutputRequestSupported, resolveRunProviderOptions } from "./structured-output.js";
|
|
19
|
+
import { composeSystemPrompt, mergeSystemPromptConfig } from "./system-prompts.js";
|
|
20
|
+
import { createToolRegistry, dispatchToolCall } from "./tools.js";
|
|
21
21
|
export function createAgent(config) {
|
|
22
22
|
return {
|
|
23
23
|
config,
|
|
@@ -39,7 +39,9 @@ export async function* resumeAgentRunStream(agent, ref, resume, options) {
|
|
|
39
39
|
const prepared = await prepareAgentRunResume(agent, ref, resume, options, options.signal);
|
|
40
40
|
const subscription = prepared.session.subscribe(options);
|
|
41
41
|
let settled = false;
|
|
42
|
-
const runPromise = executePreparedAgentRunResume(prepared, options.signal).finally(() => {
|
|
42
|
+
const runPromise = executePreparedAgentRunResume(prepared, options.signal).finally(() => {
|
|
43
|
+
settled = true;
|
|
44
|
+
});
|
|
43
45
|
try {
|
|
44
46
|
for await (const event of subscription) {
|
|
45
47
|
if ("runId" in event && event.runId !== ref.runId)
|
|
@@ -58,7 +60,9 @@ export async function* resumeAgentRunStream(agent, ref, resume, options) {
|
|
|
58
60
|
async function prepareAgentRunResume(agent, ref, resume, options, signal) {
|
|
59
61
|
throwIfAbortedSignal(signal);
|
|
60
62
|
const { record, state } = await loadAgentRunState(options.checkpoints, ref, options.ownership);
|
|
61
|
-
if (state.definitionRevision !== options.definitionRevision ||
|
|
63
|
+
if (state.definitionRevision !== options.definitionRevision ||
|
|
64
|
+
state.agentId !== (agent.config.id ?? agent.config.name) ||
|
|
65
|
+
state.fingerprint !== agentFingerprint(agent, options.definitionRevision)) {
|
|
62
66
|
throw new AgentRunStateError("Agent definition revision or fingerprint mismatch on resume");
|
|
63
67
|
}
|
|
64
68
|
if (record.version !== resume.expectedVersion || state.status !== "suspended") {
|
|
@@ -211,7 +215,11 @@ class RuntimeAgentSession {
|
|
|
211
215
|
}
|
|
212
216
|
}
|
|
213
217
|
async resumeDurable(state, runState, ownership, signal) {
|
|
214
|
-
return this.runInternal(state.input ?? [], { runState, ownership, signal }, state.runId, {
|
|
218
|
+
return this.runInternal(state.input ?? [], { runState, ownership, signal }, state.runId, {
|
|
219
|
+
options: runState,
|
|
220
|
+
state,
|
|
221
|
+
version: state.version,
|
|
222
|
+
});
|
|
215
223
|
}
|
|
216
224
|
async recordDurableDenial(runId, interruption, version, ownership) {
|
|
217
225
|
this.activeLedger = this.agent.config.runLedger;
|
|
@@ -229,7 +237,11 @@ class RuntimeAgentSession {
|
|
|
229
237
|
}
|
|
230
238
|
}
|
|
231
239
|
async runInternal(input, options, runId, resumed) {
|
|
232
|
-
if (this.agent.config.secure &&
|
|
240
|
+
if (this.agent.config.secure &&
|
|
241
|
+
(options.redactor !== undefined ||
|
|
242
|
+
options.ownership !== undefined ||
|
|
243
|
+
options.validate !== undefined ||
|
|
244
|
+
options.runState !== undefined)) {
|
|
233
245
|
throw new AgentRunStateError("Secure agent defaults cannot be replaced per run");
|
|
234
246
|
}
|
|
235
247
|
const requestedLimits = options.maxToolRounds === undefined
|
|
@@ -316,7 +328,14 @@ class RuntimeAgentSession {
|
|
|
316
328
|
const { registry, tools } = activeTools(this.agent.config.tools);
|
|
317
329
|
const activeSkills = this.resolveRunSkills(options, tools);
|
|
318
330
|
if (options.model && JSON.stringify(options.model) !== JSON.stringify(this.agent.config.model)) {
|
|
319
|
-
await this.appendEntry(createSessionEntry({
|
|
331
|
+
await this.appendEntry(createSessionEntry({
|
|
332
|
+
sessionId: this.id,
|
|
333
|
+
parentId: this.currentLeafId,
|
|
334
|
+
runId,
|
|
335
|
+
kind: "model_change",
|
|
336
|
+
previousModel: this.agent.config.model,
|
|
337
|
+
model: options.model,
|
|
338
|
+
}));
|
|
320
339
|
}
|
|
321
340
|
const inputMessages = inputToMessages(input).map((message) => this.redact(message));
|
|
322
341
|
const inputGuardrails = await runGuardrails({
|
|
@@ -340,7 +359,9 @@ class RuntimeAgentSession {
|
|
|
340
359
|
await this.appendMessage(message, runId);
|
|
341
360
|
await this.autoCompact(runId, options, controller.signal, inputMessages);
|
|
342
361
|
const maxToolRounds = resolvedLimits.maxToolRounds;
|
|
343
|
-
const systemInstructions = composeSystemPrompt(mergeSystemPromptConfig(this.agent.config.systemPrompt, options.systemPrompt), {
|
|
362
|
+
const systemInstructions = composeSystemPrompt(mergeSystemPromptConfig(this.agent.config.systemPrompt, options.systemPrompt), {
|
|
363
|
+
base: this.agent.config.instructions,
|
|
364
|
+
});
|
|
344
365
|
const contextProviders = [
|
|
345
366
|
...(this.agent.config.context ?? []),
|
|
346
367
|
// ponytail: skill context after host context; no per-skill token budget yet.
|
|
@@ -429,7 +450,7 @@ class RuntimeAgentSession {
|
|
|
429
450
|
limits.charge("maxTurns");
|
|
430
451
|
assembledTurn = false;
|
|
431
452
|
const policyResult = await this.applyProviderRequestPolicies(request, runId, options, metadata, controller.signal);
|
|
432
|
-
const middlewareRequest = await this.agent.config.middleware?.run("provider_request", policyResult.request) ?? policyResult.request;
|
|
453
|
+
const middlewareRequest = (await this.agent.config.middleware?.run("provider_request", policyResult.request)) ?? policyResult.request;
|
|
433
454
|
try {
|
|
434
455
|
return await this.generateWithRetry(this.redactProviderRequest(middlewareRequest), runId, options, controller.signal, policyResult.secrets, this.activeLoopTurn, recordProviderUsage);
|
|
435
456
|
}
|
|
@@ -468,12 +489,22 @@ class RuntimeAgentSession {
|
|
|
468
489
|
return;
|
|
469
490
|
const pending = durable.state?.pending;
|
|
470
491
|
if (pending?.call.id === mediatedCall.id && pending.status === "ready") {
|
|
471
|
-
await this.persistDurable({
|
|
492
|
+
await this.persistDurable({
|
|
493
|
+
...durable.state,
|
|
494
|
+
status: "running",
|
|
495
|
+
pending: { ...pending, status: "dispatched" },
|
|
496
|
+
interruption: undefined,
|
|
497
|
+
});
|
|
472
498
|
return;
|
|
473
499
|
}
|
|
474
500
|
if (!durable.options.interruptBeforeTool)
|
|
475
501
|
return;
|
|
476
|
-
const interruption = {
|
|
502
|
+
const interruption = {
|
|
503
|
+
kind: "tool_approval",
|
|
504
|
+
reason: "Tool side effect requires approval",
|
|
505
|
+
toolCallId: mediatedCall.id,
|
|
506
|
+
toolName: mediatedCall.name,
|
|
507
|
+
};
|
|
477
508
|
throw new AgentRunSuspended(await this.suspendDurable({
|
|
478
509
|
runId,
|
|
479
510
|
model,
|
|
@@ -508,13 +539,19 @@ class RuntimeAgentSession {
|
|
|
508
539
|
const result = await ctx.dispatchToolCall(resumed.state.pending.call);
|
|
509
540
|
await ctx.appendMessage({
|
|
510
541
|
role: "tool",
|
|
511
|
-
content: [
|
|
542
|
+
content: [
|
|
543
|
+
{ type: "tool_result", toolCallId: result.toolCallId, name: result.name, result: result.value, error: result.error },
|
|
544
|
+
...(result.content ?? []),
|
|
545
|
+
],
|
|
512
546
|
metadata: result.metadata,
|
|
513
547
|
});
|
|
514
548
|
}
|
|
515
549
|
const loopUsage = await loop.run(ctx);
|
|
516
550
|
if (loop.name === "generate-validate-revise" && !artifactFinished) {
|
|
517
|
-
throw Object.assign(new Error(artifactFailedInfo?.message ?? "artifact loop ended without a validated artifact"), {
|
|
551
|
+
throw Object.assign(new Error(artifactFailedInfo?.message ?? "artifact loop ended without a validated artifact"), {
|
|
552
|
+
name: "ArtifactFailed",
|
|
553
|
+
code: artifactFailedInfo?.code ?? "artifact_failed",
|
|
554
|
+
});
|
|
518
555
|
}
|
|
519
556
|
usage = runUsage.value() ?? loopUsage;
|
|
520
557
|
if (usage && this.activeLedger) {
|
|
@@ -661,21 +698,22 @@ class RuntimeAgentSession {
|
|
|
661
698
|
const durable = this.activeDurable;
|
|
662
699
|
if (!durable)
|
|
663
700
|
throw new AgentRunStateError("Durable interruption is not configured");
|
|
664
|
-
const state = durable.state ??
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
701
|
+
const state = durable.state ??
|
|
702
|
+
initialAgentRunState({
|
|
703
|
+
agent: this.agent,
|
|
704
|
+
options: durable.options,
|
|
705
|
+
runId: input.runId,
|
|
706
|
+
sessionId: this.id,
|
|
707
|
+
leafId: this.currentLeafId,
|
|
708
|
+
model: input.model,
|
|
709
|
+
counters: input.limits.snapshot(),
|
|
710
|
+
deadlineAt: input.limits.deadlineAt,
|
|
711
|
+
status: "suspended",
|
|
712
|
+
interruption: input.interruption,
|
|
713
|
+
messages: input.messages,
|
|
714
|
+
pending: input.pending,
|
|
715
|
+
interruptBeforeTool: durable.options.interruptBeforeTool,
|
|
716
|
+
});
|
|
679
717
|
return this.persistDurable({
|
|
680
718
|
...state,
|
|
681
719
|
leafId: this.currentLeafId,
|
|
@@ -723,7 +761,13 @@ class RuntimeAgentSession {
|
|
|
723
761
|
await this.rebuildHistory();
|
|
724
762
|
}
|
|
725
763
|
fork(options = {}) {
|
|
726
|
-
return createAgentSession({
|
|
764
|
+
return createAgentSession({
|
|
765
|
+
agent: this.agent,
|
|
766
|
+
id: this.id,
|
|
767
|
+
store: this.store,
|
|
768
|
+
leafId: options.leafId ?? this.currentLeafId,
|
|
769
|
+
metadata: this.metadata,
|
|
770
|
+
});
|
|
727
771
|
}
|
|
728
772
|
async clone(options = {}) {
|
|
729
773
|
const id = options.id ?? randomId("session");
|
|
@@ -739,7 +783,13 @@ class RuntimeAgentSession {
|
|
|
739
783
|
const { id: _oldId, parentId: _oldParentId, sessionId: _oldSessionId, ...rest } = entry;
|
|
740
784
|
await this.store.append({ ...rest, id: nextId, parentId: entry.parentId ? remap.get(entry.parentId) : undefined, sessionId: id });
|
|
741
785
|
}
|
|
742
|
-
return createAgentSession({
|
|
786
|
+
return createAgentSession({
|
|
787
|
+
agent: this.agent,
|
|
788
|
+
id,
|
|
789
|
+
store: this.store,
|
|
790
|
+
leafId: branch.length ? remap.get(branch[branch.length - 1].id) : undefined,
|
|
791
|
+
metadata: this.metadata,
|
|
792
|
+
});
|
|
743
793
|
}
|
|
744
794
|
branchReader() {
|
|
745
795
|
// ponytail: prefer the store's readBranchPath (one ancestor-chain query) when present so a
|
|
@@ -753,9 +803,7 @@ class RuntimeAgentSession {
|
|
|
753
803
|
// the resolver entirely; otherwise `RunOptions.providerSource` overrides
|
|
754
804
|
// `AgentConfig.providerSource` for this run. A miss on every source fails
|
|
755
805
|
// closed with `Unknown provider: ${model.provider}` before any provider turn.
|
|
756
|
-
const provider = this.agent.config.provider ??
|
|
757
|
-
options.providerSource?.(model) ??
|
|
758
|
-
this.agent.config.providerSource?.(model);
|
|
806
|
+
const provider = this.agent.config.provider ?? options.providerSource?.(model) ?? this.agent.config.providerSource?.(model);
|
|
759
807
|
if (!provider)
|
|
760
808
|
throw new Error(`Unknown provider: ${model.provider}`);
|
|
761
809
|
this.activeProvider = provider;
|
|
@@ -828,7 +876,10 @@ class RuntimeAgentSession {
|
|
|
828
876
|
throw errorFromInfo(info);
|
|
829
877
|
const context = { sessionId: this.id, runId, attempt, error: info, metadata: retry?.metadata, signal };
|
|
830
878
|
let decision = await policy.decide(context);
|
|
831
|
-
const payload = await this.agent.config.middleware?.run("retry", { context, decision }) ?? {
|
|
879
|
+
const payload = (await this.agent.config.middleware?.run("retry", { context, decision })) ?? {
|
|
880
|
+
context,
|
|
881
|
+
decision,
|
|
882
|
+
};
|
|
832
883
|
decision = payload.decision;
|
|
833
884
|
if (!decision.retry)
|
|
834
885
|
throw errorFromInfo(info);
|
|
@@ -1029,16 +1080,35 @@ class RuntimeAgentSession {
|
|
|
1029
1080
|
throwIfAbortedSignal(signal);
|
|
1030
1081
|
const entries = await this.entries();
|
|
1031
1082
|
const secrets = options.secrets ?? [];
|
|
1032
|
-
const strategy = options.strategy ??
|
|
1033
|
-
|
|
1083
|
+
const strategy = options.strategy ??
|
|
1084
|
+
createDefaultCompactionStrategy({ keepRecentEntries: options.keepRecentEntries, maxSummaryChars: options.maxSummaryChars, secrets });
|
|
1085
|
+
const context = {
|
|
1086
|
+
sessionId: this.id,
|
|
1087
|
+
entries,
|
|
1088
|
+
keepRecentEntries: options.keepRecentEntries,
|
|
1089
|
+
trigger,
|
|
1090
|
+
secrets,
|
|
1091
|
+
metadata: options.metadata,
|
|
1092
|
+
signal,
|
|
1093
|
+
};
|
|
1034
1094
|
this.emit({ type: "compaction_started", sessionId: this.id, runId });
|
|
1035
1095
|
let result = await strategy.compact(context);
|
|
1036
1096
|
result = { ...result, summary: redactSecrets(result.summary, secrets) };
|
|
1037
|
-
const payload = await this.agent.config.middleware?.run("compaction", { context, result }) ?? {
|
|
1097
|
+
const payload = (await this.agent.config.middleware?.run("compaction", { context, result })) ?? {
|
|
1098
|
+
context,
|
|
1099
|
+
result,
|
|
1100
|
+
};
|
|
1038
1101
|
result = { ...payload.result, summary: redactSecrets(payload.result.summary, secrets) };
|
|
1039
1102
|
const source = result.entries?.find((entry) => entry.kind === "compaction");
|
|
1040
1103
|
const data = isCompactionEntryData(source?.data) ? source.data : undefined;
|
|
1041
|
-
const entry = createSessionEntry({
|
|
1104
|
+
const entry = createSessionEntry({
|
|
1105
|
+
sessionId: this.id,
|
|
1106
|
+
parentId: this.currentLeafId,
|
|
1107
|
+
runId,
|
|
1108
|
+
kind: "compaction",
|
|
1109
|
+
summary: result.summary,
|
|
1110
|
+
data,
|
|
1111
|
+
});
|
|
1042
1112
|
await this.appendEntry(entry);
|
|
1043
1113
|
const finalResult = { ...result, entries: [entry] };
|
|
1044
1114
|
this.emit({ type: "compaction_finished", sessionId: this.id, runId, summary: finalResult.summary });
|
|
@@ -1181,8 +1251,8 @@ class SteerSoftInterrupt extends Error {
|
|
|
1181
1251
|
}
|
|
1182
1252
|
}
|
|
1183
1253
|
function isSteerSoftInterrupt(error) {
|
|
1184
|
-
return error instanceof SteerSoftInterrupt
|
|
1185
|
-
|
|
1254
|
+
return (error instanceof SteerSoftInterrupt ||
|
|
1255
|
+
(typeof error === "object" && error !== null && error.code === STEER_SOFT_INTERRUPT_CODE));
|
|
1186
1256
|
}
|
|
1187
1257
|
function finalAssistantMessage(history) {
|
|
1188
1258
|
for (let index = history.length - 1; index >= 0; index -= 1) {
|
|
@@ -1295,8 +1365,8 @@ function createUsageAccumulator() {
|
|
|
1295
1365
|
if (value !== undefined)
|
|
1296
1366
|
sums.set(key, (sums.get(key) ?? 0) + value);
|
|
1297
1367
|
}
|
|
1298
|
-
const total = usage.totalTokens
|
|
1299
|
-
|
|
1368
|
+
const total = usage.totalTokens ??
|
|
1369
|
+
(usage.inputTokens !== undefined || usage.outputTokens !== undefined
|
|
1300
1370
|
? (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0)
|
|
1301
1371
|
: undefined);
|
|
1302
1372
|
if (total !== undefined)
|
package/dist/cache-helpers.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export function sanitizeCacheKey(value, maxLength) {
|
|
2
|
-
const key = value
|
|
2
|
+
const key = value
|
|
3
|
+
?.replace(/[^A-Za-z0-9_.:-]+/g, "-")
|
|
4
|
+
.replace(/^-+|-+$/g, "")
|
|
5
|
+
.slice(0, Math.max(0, maxLength));
|
|
3
6
|
return key || undefined;
|
|
4
7
|
}
|
|
5
8
|
export function mapCacheRetention(retention, model) {
|
|
@@ -37,7 +40,7 @@ export function cacheSavings(usage, model) {
|
|
|
37
40
|
const cacheRead = model.cost?.cacheRead;
|
|
38
41
|
if (read === undefined || input === undefined || cacheRead === undefined)
|
|
39
42
|
return undefined;
|
|
40
|
-
return read * Math.max(0, input - cacheRead) / costUnitDivisor(model.cost?.unit);
|
|
43
|
+
return (read * Math.max(0, input - cacheRead)) / costUnitDivisor(model.cost?.unit);
|
|
41
44
|
}
|
|
42
45
|
export function cacheUsageReport(usage, model) {
|
|
43
46
|
if (!usage)
|
|
@@ -48,17 +51,23 @@ export function cacheUsageReport(usage, model) {
|
|
|
48
51
|
cacheWriteTokens: usage.cacheWriteTokens ?? 0,
|
|
49
52
|
hitRate: cacheHitRate(usage),
|
|
50
53
|
estimatedSavings,
|
|
51
|
-
currency: estimatedSavings === undefined ? usage.currency : model?.cost?.currency ?? usage.currency,
|
|
54
|
+
currency: estimatedSavings === undefined ? usage.currency : (model?.cost?.currency ?? usage.currency),
|
|
52
55
|
};
|
|
53
56
|
}
|
|
54
57
|
function resolveBreakpoint(messages, breakpoint) {
|
|
55
58
|
switch (breakpoint.location) {
|
|
56
|
-
case "system_prompt":
|
|
57
|
-
|
|
58
|
-
case "
|
|
59
|
-
|
|
60
|
-
case "
|
|
61
|
-
|
|
59
|
+
case "system_prompt":
|
|
60
|
+
return messages.findIndex((message) => message.role === "system");
|
|
61
|
+
case "tools":
|
|
62
|
+
return messages.findIndex((message) => message.role === "tool" || message.content.some((block) => block.type === "tool_call" || block.type === "tool_result"));
|
|
63
|
+
case "stable_context":
|
|
64
|
+
return messages.findIndex((message) => message.role !== "system");
|
|
65
|
+
case "last_stable_message":
|
|
66
|
+
return messages.length > 1 ? messages.length - 2 : messages.length - 1;
|
|
67
|
+
case "last_user_message":
|
|
68
|
+
return findLastIndex(messages, (message) => message.role === "user");
|
|
69
|
+
case "message_id":
|
|
70
|
+
return messages.findIndex((message) => message.id === breakpoint.messageId);
|
|
62
71
|
}
|
|
63
72
|
}
|
|
64
73
|
function findLastIndex(items, predicate) {
|
package/dist/checkpoints.js
CHANGED
|
@@ -61,9 +61,7 @@ export function createMemoryCheckpointStore(options = {}) {
|
|
|
61
61
|
throwIfAborted(query.signal);
|
|
62
62
|
const offset = decodeCursor(query.cursor);
|
|
63
63
|
const limit = Math.min(Math.max(1, query.limit ?? 100), maxPageSize);
|
|
64
|
-
const categories = query.category === undefined
|
|
65
|
-
? undefined
|
|
66
|
-
: new Set(Array.isArray(query.category) ? query.category : [query.category]);
|
|
64
|
+
const categories = query.category === undefined ? undefined : new Set(Array.isArray(query.category) ? query.category : [query.category]);
|
|
67
65
|
const items = [...records.values()]
|
|
68
66
|
.filter((record) => query.namespace === undefined || record.namespace === query.namespace)
|
|
69
67
|
.filter((record) => query.keyPrefix === undefined || record.key.startsWith(query.keyPrefix))
|
|
@@ -104,14 +102,12 @@ function ownership(input) {
|
|
|
104
102
|
};
|
|
105
103
|
}
|
|
106
104
|
function ownershipMatches(expected, actual) {
|
|
107
|
-
return expected.tenantId === actual.tenantId
|
|
108
|
-
&& expected.accountId === actual.accountId
|
|
109
|
-
&& expected.userId === actual.userId;
|
|
105
|
+
return expected.tenantId === actual.tenantId && expected.accountId === actual.accountId && expected.userId === actual.userId;
|
|
110
106
|
}
|
|
111
107
|
function ownershipFilterMatches(expected, actual) {
|
|
112
|
-
return (expected.tenantId === undefined || expected.tenantId === actual.tenantId)
|
|
113
|
-
|
|
114
|
-
|
|
108
|
+
return ((expected.tenantId === undefined || expected.tenantId === actual.tenantId) &&
|
|
109
|
+
(expected.accountId === undefined || expected.accountId === actual.accountId) &&
|
|
110
|
+
(expected.userId === undefined || expected.userId === actual.userId));
|
|
115
111
|
}
|
|
116
112
|
function assertOwnership(expected, actual) {
|
|
117
113
|
if (!ownershipMatches(expected, actual)) {
|