Files
rea/src/application/environment/environmentState.ts
T
Edgar Arakelyan 2e6b38cef0 feat(environment): render more read tools from the state and describe responses (#5352)
* feat(environment): render more read tools from the state and describe responses

Keep main's environment serving and fold in coverage for non-entity reads,
response summaries, and MCP tool annotations.

Squashed rebase of #5352 onto main.

* fix(environment): keep rendered documents and quoted samples inside their budgets

A recorded body of exponential numbers can render past 256 KiB and turn a
working read into a state size error, so that document stays a recorded replay.
Description samples are measured after JSON quoting, and the replay-fallback
catalog check uses a tool count the longer response descriptions still separate.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* test(environment): pin the replay card this branch serves

The replay-only card test pinned main's 11,131-byte card. This branch's descriptions name each tool's recorded responses, so the card it serves is 12,818 bytes; the pin and its name follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: morluto <morluto@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 07:08:44 +08:00

251 lines
8.4 KiB
TypeScript

import type { AnalysisViewResult } from "../../domain/analysisView/analysisView.js";
import {
changesState,
type StateDocument,
type StateEntity,
type StateType,
type StateView,
} from "../../domain/analysisView/environmentStateRules.js";
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
import type {
AnalysisInputIssue,
AnalysisResourceConstraintError,
} from "../../domain/analysisErrorCore.js";
import { projectAnalysisError } from "../../domain/analysisErrorProjection.js";
import { err, ok, type Result } from "../../domain/result.js";
import { inspectAnalysisViewValidated } from "../analysisView/AnalysisViewService.js";
import {
ENVIRONMENT_SERVE_OPERATION,
inputError,
parseEvidenceFile,
parseViewEvidence,
type EnvironmentManifestTool,
type EnvironmentSource,
} from "./environmentSource.js";
import {
environmentToolDefinitions,
type EnvironmentDataPlane,
type EnvironmentToolDefinition,
} from "./environmentTools.js";
type EnvironmentStateView = Extract<
AnalysisViewResult,
{ kind: "environment-state" }
>;
/** The initial state an environment-state view extracted for the served manifest, authenticated and cross-checked. */
export interface EnvironmentState {
readonly evidenceId: string;
readonly types: readonly StateType[];
/** Render plans keyed by tool name. */
readonly views: ReadonlyMap<string, StateView>;
readonly entities: readonly StateEntity[];
readonly documents: readonly StateDocument[];
}
/** Serving from the state is read-only: tools whose requests may change state are not served. */
const servedFromState = (tool: EnvironmentManifestTool): boolean =>
!changesState(tool.method, tool.path_template);
/** The tools an environment serves and the data plane its card declares. */
export interface ServedCatalog {
readonly tools: readonly EnvironmentToolDefinition[];
readonly dataPlane: EnvironmentDataPlane;
}
/** The catalog an environment serves: every manifest tool for replay, the read tools and the data plane from a state. */
export const servedCatalog = (
source: EnvironmentSource,
fromState: boolean,
name: string,
operation: string = ENVIRONMENT_SERVE_OPERATION,
): Result<ServedCatalog, AnalysisResourceConstraintError> => {
const dataPlane: EnvironmentDataPlane = fromState ? "state" : "none";
const catalogSource: EnvironmentSource = fromState
? {
...source,
manifest: {
...source.manifest,
tools: source.manifest.tools.filter(servedFromState),
},
}
: source;
const tools = environmentToolDefinitions(catalogSource, name, {
operation,
dataPlane,
});
return tools.ok ? ok({ tools: tools.value, dataPlane }) : tools;
};
const refusal = (
operation: string,
issue: AnalysisInputIssue,
userMessage: string,
): AnalysisError =>
inputError(
operation,
[{ ...issue, path: ["state", "normalized_result", ...issue.path] }],
userMessage,
);
/** Why the state does not belong to the served manifest and capture, if it does not. */
const stateMismatch = (
operation: string,
view: EnvironmentStateView,
source: EnvironmentSource,
): AnalysisError | undefined => {
if (view.parent_evidence_id !== source.evidenceId)
return refusal(
operation,
{
path: ["parent_evidence_id"],
reason: "invalid_value",
expected: source.evidenceId,
message: `The state cites parent Evidence ${view.parent_evidence_id}; the evidence file is ${source.evidenceId}.`,
},
"The state was extracted from a different capture than the supplied evidence file.",
);
const rules = source.manifest.view.template_rules;
if (
view.view.template_rules.length !== rules.length ||
view.view.template_rules.some((rule, index) => rule !== rules[index])
)
return refusal(
operation,
{
path: ["view", "template_rules"],
reason: "invalid_value",
expected: [...rules],
message:
"The state must be extracted with the manifest's template_rules, in the same order, so its tool names are the manifest's.",
},
"The state was extracted with different template rules than the manifest.",
);
const tools = new Set(source.manifest.tools.map((tool) => tool.name));
const index = view.views.findIndex((entry) => !tools.has(entry.tool));
return index < 0
? undefined
: refusal(
operation,
{
path: ["views", index, "tool"],
reason: "invalid_value",
message: "Every state view must name a tool of the served manifest.",
},
"The state names a tool the manifest does not have.",
);
};
/**
* Load the environment-state Evidence for an already loaded manifest and
* capture, refusing a state extracted from another capture or with other
* template rules; refusals name the command that loaded it.
*/
export const loadEnvironmentState = (
stateValue: unknown,
source: EnvironmentSource,
operation: string = ENVIRONMENT_SERVE_OPERATION,
): Result<EnvironmentState, AnalysisError> => {
const parsed = parseViewEvidence(
operation,
"state",
stateValue,
"environment-state",
);
if (!parsed.ok) return parsed;
const { view, evidenceId } = parsed.value;
const mismatch = stateMismatch(operation, view, source);
if (mismatch !== undefined) return err(mismatch);
return ok({
evidenceId,
types: view.types,
views: new Map(view.views.map((entry) => [entry.tool, entry])),
entities: view.entities,
documents: view.documents,
});
};
/** Derive the state the capture observed with the manifest's rules, projected as retained Evidence, and check it as a supplied state. */
export const deriveEnvironmentState = (
source: EnvironmentSource,
captureValue: unknown,
operation: string = ENVIRONMENT_SERVE_OPERATION,
): Result<EnvironmentState, AnalysisError> => {
const capture = parseEvidenceFile(operation, "evidence", captureValue);
if (!capture.ok) return capture;
const captureId = capture.value.evidence_id;
const projected = inspectAnalysisViewValidated(
{
source: { kind: "retained-evidence", evidence_id: captureId },
view: {
kind: "environment-state",
template_rules: [...source.manifest.view.template_rules],
},
},
(evidenceId) => (evidenceId === captureId ? capture.value : undefined),
);
return projected.ok
? loadEnvironmentState(projected.value, source, operation)
: projected;
};
/** What a command reports about the state: the state Evidence it serves, or why it serves replay. */
export type StateServingReport =
| { readonly served: true; readonly evidence_id: string }
| { readonly served: false; readonly reason: "replay-only" }
| {
readonly served: false;
readonly reason: "unavailable";
readonly code: string;
readonly message: string;
};
/** The state an environment serves, if any, and how its command reports it. */
export interface ServedState {
readonly state: EnvironmentState | undefined;
readonly report: StateServingReport;
}
/** What preparing the state yields: served or unavailable, never replay-only, which only the serve command chooses. */
export interface PreparedState extends ServedState {
readonly report: Exclude<
StateServingReport,
{ readonly reason: "replay-only" }
>;
}
const unavailable = (error: AnalysisError): PreparedState => {
const { code, message } = projectAnalysisError(error);
return {
state: undefined,
report: { served: false, reason: "unavailable", code, message },
};
};
/** Serve the derived state when its catalog fits under the served name; otherwise serve replay and report why. */
export const prepareServedState = (
source: EnvironmentSource,
captureValue: unknown,
options: {
readonly name?: string | undefined;
readonly operation?: string;
} = {},
): PreparedState => {
const operation = options.operation ?? ENVIRONMENT_SERVE_OPERATION;
// The state catalog depends only on the manifest and name, so checking it first spares deriving a state that cannot be served.
const catalog = servedCatalog(
source,
true,
options.name ?? source.manifest.name,
operation,
);
if (!catalog.ok) return unavailable(catalog.error);
const derived = deriveEnvironmentState(source, captureValue, operation);
return derived.ok
? {
state: derived.value,
report: { served: true, evidence_id: derived.value.evidenceId },
}
: unavailable(derived.error);
};