@restormel/testing-keys-adapter 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) Allotment Technology Ltd
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,6 @@
1
+ import type { KeysModelAdapterOptions, ModelResolutionResult } from "./types.js";
2
+ /**
3
+ * Resolve a single logical model ref for test execution. Keys first; optional OpenAI env fallback.
4
+ */
5
+ export declare function resolveModel(logicalRef: string, options?: KeysModelAdapterOptions): Promise<ModelResolutionResult>;
6
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAoB,qBAAqB,EAAiB,MAAM,YAAY,CAAC;AAuClH;;GAEG;AACH,wBAAsB,YAAY,CAChC,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,qBAAqB,CAAC,CAmFhC"}
@@ -0,0 +1,87 @@
1
+ import { createHttpKeysTransport } from "./transport-http.js";
2
+ import { readSecretFromEnv } from "./materialize.js";
3
+ const REF_PREFIX = "ref:restormel-keys:";
4
+ function isValidLogicalRef(ref) {
5
+ if (typeof ref !== "string" || ref.length === 0 || ref.length > 512)
6
+ return false;
7
+ if (ref.startsWith(REF_PREFIX) && ref.length > REF_PREFIX.length)
8
+ return true;
9
+ return false;
10
+ }
11
+ function fail(code, message, cause) {
12
+ return { ok: false, error: { code, message, cause } };
13
+ }
14
+ function buildResolved(logicalRef, provider, model, modelId, apiKey, source, providerBaseUrl) {
15
+ return {
16
+ meta: {
17
+ logicalRef,
18
+ provider,
19
+ model,
20
+ resolutionSource: source,
21
+ invocationCount: 0,
22
+ },
23
+ modelId,
24
+ providerBaseUrl,
25
+ credentials: { apiKey },
26
+ };
27
+ }
28
+ /**
29
+ * Resolve a single logical model ref for test execution. Keys first; optional OpenAI env fallback.
30
+ */
31
+ export async function resolveModel(logicalRef, options = {}) {
32
+ const warnings = [];
33
+ if (!isValidLogicalRef(logicalRef)) {
34
+ return fail("invalid_ref", `Expected logical ref "${REF_PREFIX}…", got ${JSON.stringify(logicalRef)}`);
35
+ }
36
+ const transport = options.transport ??
37
+ (options.keysApiBaseUrl
38
+ ? createHttpKeysTransport({
39
+ baseUrl: options.keysApiBaseUrl,
40
+ getKeysApiToken: options.keysApiTokenEnvVar
41
+ ? () => process.env[options.keysApiTokenEnvVar]
42
+ : undefined,
43
+ })
44
+ : undefined);
45
+ if (transport) {
46
+ const tr = await transport.resolve(logicalRef);
47
+ if (tr.ok) {
48
+ const mat = readSecretFromEnv(tr.secretEnvVar);
49
+ if (!mat.ok) {
50
+ return fail("keys_missing_secret_binding", `Keys resolved model but ${mat.message}`);
51
+ }
52
+ return {
53
+ ok: true,
54
+ warnings,
55
+ model: buildResolved(logicalRef, tr.provider, tr.model, tr.model, mat.apiKey, "keys", tr.baseUrl),
56
+ };
57
+ }
58
+ warnings.push(`Keys resolution failed (${tr.code}): ${tr.message}. ${options.openAiEnvFallback?.enabled ? "Attempting documented env fallback." : "No fallback configured."}`);
59
+ }
60
+ else {
61
+ warnings.push("Keys transport not configured (no transport and no keysApiBaseUrl). BYOK should flow through Restormel / Keys when available.");
62
+ }
63
+ const fb = options.openAiEnvFallback;
64
+ if (!fb?.enabled) {
65
+ if (!transport) {
66
+ return fail("keys_not_configured", "Configure Keys (keysApiBaseUrl or transport) or enable openAiEnvFallback for the documented non-Keys escape hatch.");
67
+ }
68
+ return fail("fallback_disabled", "Keys resolution failed and OpenAI env fallback is disabled.");
69
+ }
70
+ if (fb.forLogicalRef !== undefined && fb.forLogicalRef !== logicalRef) {
71
+ return fail("fallback_disabled", `Env fallback is restricted to ref ${fb.forLogicalRef}; got ${logicalRef}`);
72
+ }
73
+ const apiKeyVar = fb.apiKeyEnvVar ?? "OPENAI_API_KEY";
74
+ const key = process.env[apiKeyVar];
75
+ if (key === undefined || key === "") {
76
+ return fail("fallback_missing_env", `Fallback enabled but ${apiKeyVar} is unset or empty.`);
77
+ }
78
+ const modelId = fb.defaultModel ?? "gpt-4o-mini";
79
+ const baseUrl = fb.baseUrl;
80
+ warnings.push(`RESTORMEL_TESTING_KEYS_FALLBACK: using ${apiKeyVar} for OpenAI-compatible calls. Prefer Restormel / Keys for BYOK and auditability.`);
81
+ return {
82
+ ok: true,
83
+ warnings,
84
+ model: buildResolved(logicalRef, "openai", modelId, modelId, key, "env_fallback", baseUrl),
85
+ };
86
+ }
87
+ //# sourceMappingURL=adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { KeysModelAdapterOptions } from "./types.js";
2
+ /**
3
+ * Build Keys adapter options from documented environment variables (CLI, GitHub Action, local).
4
+ *
5
+ * | Variable | Purpose |
6
+ * |----------|---------|
7
+ * | `RESTORMEL_KEYS_API_BASE_URL` | Keys HTTP API origin (e.g. `https://keys.example.com`). Enables POST `/v1/testing/resolve-model`. |
8
+ * | `RESTORMEL_KEYS_API_TOKEN_ENV` | Optional. Name of env var holding the **Keys API** bearer token (default `RESTORMEL_KEYS_API_TOKEN`). Values are never logged. |
9
+ * | `RESTORMEL_TESTING_OPENAI_FALLBACK` | Set to `1` to allow documented OpenAI-compatible env fallback when Keys is unset or resolution fails (`OPENAI_API_KEY`). |
10
+ */
11
+ export declare function keysAdapterOptionsFromProcessEnv(): KeysModelAdapterOptions | undefined;
12
+ //# sourceMappingURL=env-from-process.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-from-process.d.ts","sourceRoot":"","sources":["../src/env-from-process.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAE1D;;;;;;;;GAQG;AACH,wBAAgB,gCAAgC,IAAI,uBAAuB,GAAG,SAAS,CAmBtF"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Build Keys adapter options from documented environment variables (CLI, GitHub Action, local).
3
+ *
4
+ * | Variable | Purpose |
5
+ * |----------|---------|
6
+ * | `RESTORMEL_KEYS_API_BASE_URL` | Keys HTTP API origin (e.g. `https://keys.example.com`). Enables POST `/v1/testing/resolve-model`. |
7
+ * | `RESTORMEL_KEYS_API_TOKEN_ENV` | Optional. Name of env var holding the **Keys API** bearer token (default `RESTORMEL_KEYS_API_TOKEN`). Values are never logged. |
8
+ * | `RESTORMEL_TESTING_OPENAI_FALLBACK` | Set to `1` to allow documented OpenAI-compatible env fallback when Keys is unset or resolution fails (`OPENAI_API_KEY`). |
9
+ */
10
+ export function keysAdapterOptionsFromProcessEnv() {
11
+ const baseUrl = process.env.RESTORMEL_KEYS_API_BASE_URL?.trim();
12
+ const tokenEnvName = process.env.RESTORMEL_KEYS_API_TOKEN_ENV?.trim() || "RESTORMEL_KEYS_API_TOKEN";
13
+ const fallbackOn = process.env.RESTORMEL_TESTING_OPENAI_FALLBACK?.trim() === "1";
14
+ const opts = {};
15
+ if (baseUrl) {
16
+ opts.keysApiBaseUrl = baseUrl;
17
+ opts.keysApiTokenEnvVar = tokenEnvName;
18
+ }
19
+ if (fallbackOn) {
20
+ opts.openAiEnvFallback = { enabled: true };
21
+ }
22
+ if (!opts.keysApiBaseUrl && !opts.openAiEnvFallback) {
23
+ return undefined;
24
+ }
25
+ return opts;
26
+ }
27
+ //# sourceMappingURL=env-from-process.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-from-process.js","sourceRoot":"","sources":["../src/env-from-process.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,MAAM,UAAU,gCAAgC;IAC9C,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,2BAA2B,EAAE,IAAI,EAAE,CAAC;IAChE,MAAM,YAAY,GAChB,OAAO,CAAC,GAAG,CAAC,4BAA4B,EAAE,IAAI,EAAE,IAAI,0BAA0B,CAAC;IACjF,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,iCAAiC,EAAE,IAAI,EAAE,KAAK,GAAG,CAAC;IAEjF,MAAM,IAAI,GAA4B,EAAE,CAAC;IACzC,IAAI,OAAO,EAAE,CAAC;QACZ,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC;QAC9B,IAAI,CAAC,kBAAkB,GAAG,YAAY,CAAC;IACzC,CAAC;IACD,IAAI,UAAU,EAAE,CAAC;QACf,IAAI,CAAC,iBAAiB,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC7C,CAAC;IAED,IAAI,CAAC,IAAI,CAAC,cAAc,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACpD,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @restormel/testing-keys-adapter — thin Restormel / Keys resolution seam for Testing (MVP).
3
+ */
4
+ export declare const testingKeysAdapterPackage: "@restormel/testing-keys-adapter";
5
+ export { keysAdapterOptionsFromProcessEnv } from "./env-from-process.js";
6
+ export { resolveModel } from "./adapter.js";
7
+ export { readSecretFromEnv } from "./materialize.js";
8
+ export { createHttpKeysTransport } from "./transport-http.js";
9
+ export { createStubKeysTransport } from "./transport-stub.js";
10
+ export type { StubResolutionEntry } from "./transport-stub.js";
11
+ export type { KeysAdapterError, KeysAdapterErrorCode, KeysHttpResolveResponseBody, KeysModelAdapterOptions, KeysResolutionTransport, KeysTransportResolution, ModelResolutionResult, OpenAiEnvFallbackOptions, ResolvedModel, } from "./types.js";
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,yBAAyB,EAAG,iCAA0C,CAAC;AAEpF,OAAO,EAAE,gCAAgC,EAAE,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,YAAY,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,YAAY,EACV,gBAAgB,EAChB,oBAAoB,EACpB,2BAA2B,EAC3B,uBAAuB,EACvB,uBAAuB,EACvB,uBAAuB,EACvB,qBAAqB,EACrB,wBAAwB,EACxB,aAAa,GACd,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @restormel/testing-keys-adapter — thin Restormel / Keys resolution seam for Testing (MVP).
3
+ */
4
+ export const testingKeysAdapterPackage = "@restormel/testing-keys-adapter";
5
+ export { keysAdapterOptionsFromProcessEnv } from "./env-from-process.js";
6
+ export { resolveModel } from "./adapter.js";
7
+ export { readSecretFromEnv } from "./materialize.js";
8
+ export { createHttpKeysTransport } from "./transport-http.js";
9
+ export { createStubKeysTransport } from "./transport-stub.js";
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,iCAA0C,CAAC;AAEpF,OAAO,EAAE,gCAAgC,EAAE,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Read BYOK material from the environment. Variable name only — never the secret value in errors beyond "missing".
3
+ */
4
+ export declare function readSecretFromEnv(secretEnvVar: string): {
5
+ ok: true;
6
+ apiKey: string;
7
+ } | {
8
+ ok: false;
9
+ message: string;
10
+ };
11
+ //# sourceMappingURL=materialize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"materialize.d.ts","sourceRoot":"","sources":["../src/materialize.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAUrH"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Read BYOK material from the environment. Variable name only — never the secret value in errors beyond "missing".
3
+ */
4
+ export function readSecretFromEnv(secretEnvVar) {
5
+ const name = secretEnvVar.startsWith("env:") ? secretEnvVar.slice("env:".length) : secretEnvVar;
6
+ if (!/^[A-Z][A-Z0-9_]*$/.test(name)) {
7
+ return { ok: false, message: `Invalid secret env var name: ${secretEnvVar}` };
8
+ }
9
+ const apiKey = process.env[name];
10
+ if (apiKey === undefined || apiKey === "") {
11
+ return { ok: false, message: `Environment variable ${name} is unset or empty` };
12
+ }
13
+ return { ok: true, apiKey };
14
+ }
15
+ //# sourceMappingURL=materialize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"materialize.js","sourceRoot":"","sources":["../src/materialize.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,YAAoB;IACpD,MAAM,IAAI,GAAG,YAAY,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC;IAChG,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,gCAAgC,YAAY,EAAE,EAAE,CAAC;IAChF,CAAC;IACD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACjC,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,EAAE,EAAE,CAAC;QAC1C,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,wBAAwB,IAAI,oBAAoB,EAAE,CAAC;IAClF,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAC9B,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { KeysResolutionTransport } from "./types.js";
2
+ /**
3
+ * Calls Keys over HTTP. Path is versioned under this repo’s adapter until Keys publishes a canonical route.
4
+ */
5
+ export declare function createHttpKeysTransport(options: {
6
+ baseUrl: string;
7
+ /** Return bearer token for Keys API (not the provider key). */
8
+ getKeysApiToken?: () => string | undefined;
9
+ }): KeysResolutionTransport;
10
+ //# sourceMappingURL=transport-http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport-http.d.ts","sourceRoot":"","sources":["../src/transport-http.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA+B,uBAAuB,EAA2B,MAAM,YAAY,CAAC;AAehH;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,eAAe,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;CAC5C,GAAG,uBAAuB,CAsD1B"}
@@ -0,0 +1,72 @@
1
+ function isResolveBody(x) {
2
+ if (x === null || typeof x !== "object")
3
+ return false;
4
+ const o = x;
5
+ return (typeof o.provider === "string" &&
6
+ typeof o.model === "string" &&
7
+ typeof o.secretEnvVar === "string" &&
8
+ o.provider.length > 0 &&
9
+ o.model.length > 0 &&
10
+ o.secretEnvVar.length > 0);
11
+ }
12
+ /**
13
+ * Calls Keys over HTTP. Path is versioned under this repo’s adapter until Keys publishes a canonical route.
14
+ */
15
+ export function createHttpKeysTransport(options) {
16
+ const root = options.baseUrl.replace(/\/?$/, "");
17
+ return {
18
+ async resolve(logicalRef) {
19
+ const url = `${root}/v1/testing/resolve-model`;
20
+ const headers = {
21
+ "content-type": "application/json",
22
+ accept: "application/json",
23
+ };
24
+ const token = options.getKeysApiToken?.();
25
+ if (token) {
26
+ headers.authorization = `Bearer ${token}`;
27
+ }
28
+ let res;
29
+ try {
30
+ res = await fetch(url, {
31
+ method: "POST",
32
+ headers,
33
+ body: JSON.stringify({ logicalRef }),
34
+ });
35
+ }
36
+ catch (e) {
37
+ return {
38
+ ok: false,
39
+ code: "network",
40
+ message: `Keys HTTP request failed: ${e instanceof Error ? e.message : String(e)}`,
41
+ cause: e,
42
+ };
43
+ }
44
+ if (!res.ok) {
45
+ const text = await res.text().catch(() => "");
46
+ return {
47
+ ok: false,
48
+ code: `http_${res.status}`,
49
+ message: `Keys returned ${res.status}${text ? `: ${text.slice(0, 200)}` : ""}`,
50
+ };
51
+ }
52
+ let body;
53
+ try {
54
+ body = await res.json();
55
+ }
56
+ catch (e) {
57
+ return { ok: false, code: "invalid_json", message: "Keys response was not JSON", cause: e };
58
+ }
59
+ if (!isResolveBody(body)) {
60
+ return { ok: false, code: "invalid_body", message: "Keys response missing provider, model, or secretEnvVar" };
61
+ }
62
+ return {
63
+ ok: true,
64
+ provider: body.provider,
65
+ model: body.model,
66
+ secretEnvVar: body.secretEnvVar,
67
+ baseUrl: typeof body.baseUrl === "string" ? body.baseUrl : undefined,
68
+ };
69
+ },
70
+ };
71
+ }
72
+ //# sourceMappingURL=transport-http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport-http.js","sourceRoot":"","sources":["../src/transport-http.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { KeysResolutionTransport } from "./types.js";
2
+ export interface StubResolutionEntry {
3
+ provider: string;
4
+ model: string;
5
+ /** Name of process.env var holding the provider API key. */
6
+ secretEnvVar: string;
7
+ baseUrl?: string;
8
+ }
9
+ /** In-memory map for tests and offline development; not a substitute for real Keys in production. */
10
+ export declare function createStubKeysTransport(map: Record<string, StubResolutionEntry>): KeysResolutionTransport;
11
+ //# sourceMappingURL=transport-stub.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport-stub.d.ts","sourceRoot":"","sources":["../src/transport-stub.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAA2B,MAAM,YAAY,CAAC;AAEnF,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qGAAqG;AACrG,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,GAAG,uBAAuB,CAoBzG"}
@@ -0,0 +1,23 @@
1
+ /** In-memory map for tests and offline development; not a substitute for real Keys in production. */
2
+ export function createStubKeysTransport(map) {
3
+ return {
4
+ async resolve(logicalRef) {
5
+ const hit = map[logicalRef];
6
+ if (!hit) {
7
+ return {
8
+ ok: false,
9
+ code: "unknown_ref",
10
+ message: `Stub Keys transport: no entry for logical ref "${logicalRef}"`,
11
+ };
12
+ }
13
+ return {
14
+ ok: true,
15
+ provider: hit.provider,
16
+ model: hit.model,
17
+ secretEnvVar: hit.secretEnvVar,
18
+ baseUrl: hit.baseUrl,
19
+ };
20
+ },
21
+ };
22
+ }
23
+ //# sourceMappingURL=transport-stub.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport-stub.js","sourceRoot":"","sources":["../src/transport-stub.ts"],"names":[],"mappings":"AAUA,qGAAqG;AACrG,MAAM,UAAU,uBAAuB,CAAC,GAAwC;IAC9E,OAAO;QACL,KAAK,CAAC,OAAO,CAAC,UAAkB;YAC9B,MAAM,GAAG,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC;YAC5B,IAAI,CAAC,GAAG,EAAE,CAAC;gBACT,OAAO;oBACL,EAAE,EAAE,KAAK;oBACT,IAAI,EAAE,aAAa;oBACnB,OAAO,EAAE,kDAAkD,UAAU,GAAG;iBACzE,CAAC;YACJ,CAAC;YACD,OAAO;gBACL,EAAE,EAAE,IAAI;gBACR,QAAQ,EAAE,GAAG,CAAC,QAAQ;gBACtB,KAAK,EAAE,GAAG,CAAC,KAAK;gBAChB,YAAY,EAAE,GAAG,CAAC,YAAY;gBAC9B,OAAO,EAAE,GAAG,CAAC,OAAO;aACrB,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,83 @@
1
+ import type { KeysModelMeta } from "@restormel/testing-core";
2
+ /**
3
+ * Documented HTTP contract (placeholder until Keys publishes a stable public API).
4
+ * Keys should respond with 200 and this JSON shape. Adapter does not implement Keys-side logic.
5
+ */
6
+ export interface KeysHttpResolveResponseBody {
7
+ provider: string;
8
+ model: string;
9
+ /** Environment variable name holding the provider API key (BYOK), not the secret value. */
10
+ secretEnvVar: string;
11
+ /** Optional OpenAI-compatible API base URL. */
12
+ baseUrl?: string;
13
+ }
14
+ export interface ResolvedModel {
15
+ /** Safe to log and attach to RunRecord.keysModelMeta. */
16
+ meta: KeysModelMeta;
17
+ /** Provider API model id for requests. */
18
+ modelId: string;
19
+ /** OpenAI-compatible base URL if applicable. */
20
+ providerBaseUrl?: string;
21
+ /**
22
+ * In-memory credentials only. Never JSON.stringify, log, or write to artefacts.
23
+ */
24
+ credentials: {
25
+ apiKey: string;
26
+ };
27
+ }
28
+ export type KeysAdapterErrorCode = "invalid_ref" | "keys_not_configured" | "keys_unreachable" | "keys_rejected" | "keys_missing_secret_binding" | "fallback_disabled" | "fallback_missing_env";
29
+ export interface KeysAdapterError {
30
+ code: KeysAdapterErrorCode;
31
+ message: string;
32
+ cause?: unknown;
33
+ }
34
+ export type ModelResolutionResult = {
35
+ ok: true;
36
+ model: ResolvedModel;
37
+ warnings: string[];
38
+ } | {
39
+ ok: false;
40
+ error: KeysAdapterError;
41
+ };
42
+ /** Swappable transport: HTTP to Keys, in-memory stub, or future SDK. */
43
+ export interface KeysResolutionTransport {
44
+ resolve(logicalRef: string): Promise<KeysTransportResolution>;
45
+ }
46
+ export type KeysTransportResolution = {
47
+ ok: true;
48
+ provider: string;
49
+ model: string;
50
+ secretEnvVar: string;
51
+ baseUrl?: string;
52
+ } | {
53
+ ok: false;
54
+ code: string;
55
+ message: string;
56
+ cause?: unknown;
57
+ };
58
+ /**
59
+ * When Keys cannot resolve, optionally use a single OpenAI-compatible env key (documented escape hatch).
60
+ */
61
+ export interface OpenAiEnvFallbackOptions {
62
+ enabled: boolean;
63
+ /** Env var for the API key (default OPENAI_API_KEY). */
64
+ apiKeyEnvVar?: string;
65
+ /** Model id when using fallback (default gpt-4o-mini). */
66
+ defaultModel?: string;
67
+ /** Optional base URL (default official API). */
68
+ baseUrl?: string;
69
+ /** Logical ref this fallback applies to; if omitted, applies to any ref when Keys fails. */
70
+ forLogicalRef?: string;
71
+ }
72
+ export interface KeysModelAdapterOptions {
73
+ /**
74
+ * Try transport first (Keys). If unset and no fallback, resolution fails unless you inject a transport via factory.
75
+ */
76
+ transport?: KeysResolutionTransport;
77
+ /** POST JSON to `${baseUrl}/v1/testing/resolve-model` when transport not passed explicitly. */
78
+ keysApiBaseUrl?: string;
79
+ /** Env var holding bearer token for the Keys HTTP API (optional). */
80
+ keysApiTokenEnvVar?: string;
81
+ openAiEnvFallback?: OpenAiEnvFallbackOptions;
82
+ }
83
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAE7D;;;GAGG;AACH,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,2FAA2F;IAC3F,YAAY,EAAE,MAAM,CAAC;IACrB,+CAA+C;IAC/C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,yDAAyD;IACzD,IAAI,EAAE,aAAa,CAAC;IACpB,0CAA0C;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;OAEG;IACH,WAAW,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CACjC;AAED,MAAM,MAAM,oBAAoB,GAC5B,aAAa,GACb,qBAAqB,GACrB,kBAAkB,GAClB,eAAe,GACf,6BAA6B,GAC7B,mBAAmB,GACnB,sBAAsB,CAAC;AAE3B,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,oBAAoB,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,MAAM,qBAAqB,GAC7B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,aAAa,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,GACtD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAE3C,wEAAwE;AACxE,MAAM,WAAW,uBAAuB;IACtC,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;CAC/D;AAED,MAAM,MAAM,uBAAuB,GAC/B;IACE,EAAE,EAAE,IAAI,CAAC;IACT,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GACD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAElE;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,wDAAwD;IACxD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4FAA4F;IAC5F,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,uBAAuB;IACtC;;OAEG;IACH,SAAS,CAAC,EAAE,uBAAuB,CAAC;IACpC,+FAA+F;IAC/F,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qEAAqE;IACrE,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,iBAAiB,CAAC,EAAE,wBAAwB,CAAC;CAC9C"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@restormel/testing-keys-adapter",
3
+ "version": "0.1.0",
4
+ "description": "Restormel / Keys resolution and BYOK execution seam (scaffold).",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/Allotment-Technology-Ltd/restormel-testing.git",
9
+ "directory": "packages/keys-adapter"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/Allotment-Technology-Ltd/restormel-testing/issues"
13
+ },
14
+ "homepage": "https://github.com/Allotment-Technology-Ltd/restormel-testing/tree/main/packages/keys-adapter#readme",
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "type": "module",
19
+ "main": "./dist/index.js",
20
+ "types": "./dist/index.d.ts",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.ts",
24
+ "import": "./dist/index.js"
25
+ }
26
+ },
27
+ "files": [
28
+ "dist"
29
+ ],
30
+ "engines": {
31
+ "node": ">=20"
32
+ },
33
+ "dependencies": {
34
+ "@restormel/testing-core": "0.1.0"
35
+ },
36
+ "devDependencies": {
37
+ "typescript": "^5.7.0"
38
+ },
39
+ "scripts": {
40
+ "build": "tsc -b tsconfig.json",
41
+ "typecheck": "tsc -b tsconfig.json"
42
+ }
43
+ }