@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
64 lines • 3.16 kB
JavaScript
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import { compareCodePoints } from "../utils/sort.js";
// Past this many, what came back is not a secret's shape, and reciting
// a host's names back to the agent is neither a useful hint nor ours to
// print.
export const MAX_LISTED_FIELDS = 12;
// Sources whose fields are the host's shape rather than a secret's,
// and are never named back however few of them there are: a hardened
// container starts from `env -i` plus a handful of credentials, so a
// count threshold alone would recite exactly the environment worth
// hiding.
export const OPAQUE_FIELD_SOURCES = new Set(['env']);
/**
* How a refusal names the fields the secret did carry.
*
* Its own module because both planes word this refusal -- the config
* plane's `resolveSources` and the env plane's `fillEnv`, from
* different packages -- and `errors.ts` is for the package's exception
* types. Returns what follows "has" in the message: the labels for a
* secret of ordinary size, a bare count for the process environment or
* for anything big enough to be one.
*/
export function fieldSummary(fields, source) {
const names = Object.keys(fields);
if (OPAQUE_FIELD_SOURCES.has(source) || names.length > MAX_LISTED_FIELDS) {
return `${String(names.length)} fields`;
}
return `{${names.sort(compareCodePoints).join(', ')}}`;
}
/**
* How a refusal names what a config schema rejected.
*
* The field path and the issue code per issue, and nothing else -- not
* zod's rendered message, which a custom refinement is free to build
* out of the input it refused. Every config this plane parses is one a
* fetched credential may have just landed in: a source's own, a mount's,
* an account CLI's. An unrecognized key carries no path, so its own
* names stand in; they are what the deployment wrote in the block, not
* anything fetched. Mirrors Python's `error_summary`, which has the
* harder job: pydantic's rendering quotes the input outright.
*/
export function errorSummary(error) {
return error.issues.map(issueDetail).join('; ');
}
function issueDetail(issue) {
const path = issue.path.map(String).join('.');
// A model-level refinement (one credential of two, one drive target of
// four) carries no path; python's `error_summary` reports it as `config`.
const where = path !== '' ? path : issue.code === 'unrecognized_keys' ? issue.keys.join(', ') : 'config';
return `${where}: ${issue.code}`;
}
//# sourceMappingURL=summary.js.map