@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
81 lines • 3.63 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";
import { SecretsError } from "./errors.js";
// One registry, populated by registerSecrets. Python's core registry
// hardwires its builtin table beside this; here the builtins live in
// the node package (they read process state and load SDKs core cannot
// name), registered on import the way compression codecs are.
const REGISTERED = new Map();
/**
* Register a secrets source under a name.
*
* Host-side only, like `registerCli`: the embedding program calls it,
* never a line the agent types. A source is one config model plus one
* async function; there is no Provider class. Registering an existing
* name replaces it, builtins included -- the host owns both sides of
* this registry, so shadowing `env` is a deployment decision, not an
* escalation.
*/
export function registerSecrets(name, configModel, fetch, requirePeer) {
REGISTERED.set(name, {
configModel,
fetch,
...(requirePeer !== undefined ? { requirePeer } : {}),
});
}
/** Every name `sourceFor` can resolve. */
export function knownSources() {
return [...REGISTERED.keys()].sort(compareCodePoints);
}
/**
* Resolve a source name to its config model and fetch function.
*
* Throws SecretsError when `name` is not registered. The node package
* registers the builtin sources (`env`, `dotenv`, `aws-sm`) on import;
* a browser workspace resolves only what its embedder registered.
*/
export function sourceFor(name) {
const entry = REGISTERED.get(name);
if (entry === undefined) {
throw new SecretsError(`unknown secrets source '${name}'; known: [${knownSources().join(', ')}]`);
}
entry.requirePeer?.();
return entry;
}
/**
* Fetch one secret from a named source.
*
* The whole call path: resolve the source, take its config, run its
* fetch. Pure and module-level -- there is no resolver class, and no
* cache: fetched values live only on session vars.
*
* `source` names a declared instance first and a source second, so a
* deployment with one account of a platform can leave the `secrets:`
* block out entirely and still spell `from: aws-sm`. An undeclared
* name builds its config from ambient defaults, which is what every
* source did before the block existed.
*/
export async function fetchSecret(source, ref, sources) {
// Own properties only, which is what Python's dict lookup does: a
// plain object answers `sources['constructor']` with a prototype
// member, and an undeclared source named after one would reach
// `.fetch` on it instead of falling through to the registry.
const entry = sources !== undefined && Object.hasOwn(sources, source) ? sources[source] : undefined;
if (entry !== undefined)
return entry.fetch(entry.config, ref);
const { configModel, fetch } = sourceFor(source);
return fetch(configModel.parse({}), ref);
}
//# sourceMappingURL=registry.js.map