mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-26 09:12:13 +08:00
Compare commits
1 Commits
v2026.6.10
...
refactor/s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
478b599e9b |
@@ -177,6 +177,18 @@
|
||||
"@openclaw/media-generation-core/*": [
|
||||
"../dist/plugin-sdk/packages/media-generation-core/src/*.d.ts"
|
||||
],
|
||||
"@openclaw/secrets-core": [
|
||||
"../dist/plugin-sdk/packages/secrets-core/src/index.d.ts"
|
||||
],
|
||||
"@openclaw/secrets-core/secret-input": [
|
||||
"../dist/plugin-sdk/packages/secrets-core/src/secret-input.d.ts"
|
||||
],
|
||||
"@openclaw/secrets-core/secret-ref": [
|
||||
"../dist/plugin-sdk/packages/secrets-core/src/secret-ref.d.ts"
|
||||
],
|
||||
"@openclaw/secrets-core/*": [
|
||||
"../dist/plugin-sdk/packages/secrets-core/src/*.d.ts"
|
||||
],
|
||||
"@openclaw/terminal-core": [
|
||||
"../dist/plugin-sdk/packages/terminal-core/src/index.d.ts"
|
||||
],
|
||||
|
||||
@@ -186,6 +186,18 @@
|
||||
"@openclaw/media-generation-core/*": [
|
||||
"../../dist/plugin-sdk/packages/media-generation-core/src/*.d.ts"
|
||||
],
|
||||
"@openclaw/secrets-core": [
|
||||
"../../dist/plugin-sdk/packages/secrets-core/src/index.d.ts"
|
||||
],
|
||||
"@openclaw/secrets-core/secret-input": [
|
||||
"../../dist/plugin-sdk/packages/secrets-core/src/secret-input.d.ts"
|
||||
],
|
||||
"@openclaw/secrets-core/secret-ref": [
|
||||
"../../dist/plugin-sdk/packages/secrets-core/src/secret-ref.d.ts"
|
||||
],
|
||||
"@openclaw/secrets-core/*": [
|
||||
"../../dist/plugin-sdk/packages/secrets-core/src/*.d.ts"
|
||||
],
|
||||
"@openclaw/terminal-core": [
|
||||
"../../dist/plugin-sdk/packages/terminal-core/src/index.d.ts"
|
||||
],
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"../../packages/markdown-core/src/**/*.ts",
|
||||
"../../packages/media-generation-core/src/**/*.ts",
|
||||
"../../packages/model-catalog-core/src/**/*.ts",
|
||||
"../../packages/secrets-core/src/**/*.ts",
|
||||
"../../packages/terminal-core/src/**/*.ts",
|
||||
"../../src/plugin-sdk/**/*.ts",
|
||||
"../../src/video-generation/dashscope-compatible.ts",
|
||||
|
||||
3
packages/secrets-core/dist/index.d.mts
vendored
Normal file
3
packages/secrets-core/dist/index.d.mts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import { DEFAULT_SECRET_PROVIDER_ALIAS, ENV_SECRET_REF_ID_RE, LEGACY_DOUBLE_UNDERSCORE_ENV_MARKER_PREFIX, LEGACY_SECRETREF_ENV_MARKER_PREFIX, SecretDefaults, SecretInput, SecretRef, SecretRefSource, coerceSecretRef, isSecretRef, isValidEnvSecretRefId, parseEnvTemplateSecretRef, parseLegacySecretRefEnvMarker, resolveSecretInputRef } from "./secret-ref.mjs";
|
||||
import { SecretInputStringResolution, SecretInputStringResolutionMode, assertSecretInputResolved, hasConfiguredSecretInput, normalizeOptionalSecretInput, normalizeResolvedSecretInputString, normalizeSecretInput, normalizeSecretInputString, resolveSecretInputString } from "./secret-input.mjs";
|
||||
export { DEFAULT_SECRET_PROVIDER_ALIAS, ENV_SECRET_REF_ID_RE, LEGACY_DOUBLE_UNDERSCORE_ENV_MARKER_PREFIX, LEGACY_SECRETREF_ENV_MARKER_PREFIX, SecretDefaults, SecretInput, SecretInputStringResolution, SecretInputStringResolutionMode, SecretRef, SecretRefSource, assertSecretInputResolved, coerceSecretRef, hasConfiguredSecretInput, isSecretRef, isValidEnvSecretRefId, normalizeOptionalSecretInput, normalizeResolvedSecretInputString, normalizeSecretInput, normalizeSecretInputString, parseEnvTemplateSecretRef, parseLegacySecretRefEnvMarker, resolveSecretInputRef, resolveSecretInputString };
|
||||
3
packages/secrets-core/dist/index.mjs
vendored
Normal file
3
packages/secrets-core/dist/index.mjs
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import { DEFAULT_SECRET_PROVIDER_ALIAS, ENV_SECRET_REF_ID_RE, LEGACY_DOUBLE_UNDERSCORE_ENV_MARKER_PREFIX, LEGACY_SECRETREF_ENV_MARKER_PREFIX, coerceSecretRef, isSecretRef, isValidEnvSecretRefId, parseEnvTemplateSecretRef, parseLegacySecretRefEnvMarker, resolveSecretInputRef } from "./secret-ref.mjs";
|
||||
import { assertSecretInputResolved, hasConfiguredSecretInput, normalizeOptionalSecretInput, normalizeResolvedSecretInputString, normalizeSecretInput, normalizeSecretInputString, resolveSecretInputString } from "./secret-input.mjs";
|
||||
export { DEFAULT_SECRET_PROVIDER_ALIAS, ENV_SECRET_REF_ID_RE, LEGACY_DOUBLE_UNDERSCORE_ENV_MARKER_PREFIX, LEGACY_SECRETREF_ENV_MARKER_PREFIX, assertSecretInputResolved, coerceSecretRef, hasConfiguredSecretInput, isSecretRef, isValidEnvSecretRefId, normalizeOptionalSecretInput, normalizeResolvedSecretInputString, normalizeSecretInput, normalizeSecretInputString, parseEnvTemplateSecretRef, parseLegacySecretRefEnvMarker, resolveSecretInputRef, resolveSecretInputString };
|
||||
49
packages/secrets-core/dist/secret-input.d.mts
vendored
Normal file
49
packages/secrets-core/dist/secret-input.d.mts
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
import { SecretDefaults, SecretRef } from "./secret-ref.mjs";
|
||||
|
||||
//#region packages/secrets-core/src/secret-input.d.ts
|
||||
type SecretInputStringResolutionMode = "strict" | "inspect";
|
||||
type SecretInputStringResolution = {
|
||||
status: "available";
|
||||
value: string;
|
||||
ref: null;
|
||||
} | {
|
||||
status: "configured_unavailable";
|
||||
value: undefined;
|
||||
ref: SecretRef;
|
||||
} | {
|
||||
status: "missing";
|
||||
value: undefined;
|
||||
ref: null;
|
||||
};
|
||||
/**
|
||||
* Normalize copy/pasted credentials for HTTP/auth use.
|
||||
*
|
||||
* Line breaks embedded in API keys are common paste artifacts, and non-Latin1
|
||||
* rich-text characters can crash header construction before auth fails.
|
||||
* Preserve ordinary internal spaces for values such as `Bearer <token>`.
|
||||
*/
|
||||
declare function normalizeSecretInput(value: unknown): string;
|
||||
declare function normalizeOptionalSecretInput(value: unknown): string | undefined;
|
||||
declare function normalizeSecretInputString(value: unknown): string | undefined;
|
||||
declare function hasConfiguredSecretInput(value: unknown, defaults?: SecretDefaults): boolean;
|
||||
declare function assertSecretInputResolved(params: {
|
||||
value: unknown;
|
||||
refValue?: unknown;
|
||||
defaults?: SecretDefaults;
|
||||
path: string;
|
||||
}): void;
|
||||
declare function resolveSecretInputString(params: {
|
||||
value: unknown;
|
||||
refValue?: unknown;
|
||||
defaults?: SecretDefaults;
|
||||
path: string;
|
||||
mode?: SecretInputStringResolutionMode;
|
||||
}): SecretInputStringResolution;
|
||||
declare function normalizeResolvedSecretInputString(params: {
|
||||
value: unknown;
|
||||
refValue?: unknown;
|
||||
defaults?: SecretDefaults;
|
||||
path: string;
|
||||
}): string | undefined;
|
||||
//#endregion
|
||||
export { SecretInputStringResolution, SecretInputStringResolutionMode, assertSecretInputResolved, hasConfiguredSecretInput, normalizeOptionalSecretInput, normalizeResolvedSecretInputString, normalizeSecretInput, normalizeSecretInputString, resolveSecretInputString };
|
||||
86
packages/secrets-core/dist/secret-input.mjs
vendored
Normal file
86
packages/secrets-core/dist/secret-input.mjs
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
import { coerceSecretRef, resolveSecretInputRef } from "./secret-ref.mjs";
|
||||
//#region packages/secrets-core/src/secret-input.ts
|
||||
/**
|
||||
* Normalize copy/pasted credentials for HTTP/auth use.
|
||||
*
|
||||
* Line breaks embedded in API keys are common paste artifacts, and non-Latin1
|
||||
* rich-text characters can crash header construction before auth fails.
|
||||
* Preserve ordinary internal spaces for values such as `Bearer <token>`.
|
||||
*/
|
||||
function normalizeSecretInput(value) {
|
||||
if (typeof value !== "string") return "";
|
||||
const collapsed = value.replace(/[\r\n\u2028\u2029]+/g, "");
|
||||
let latin1Only = "";
|
||||
for (const char of collapsed) {
|
||||
const codePoint = char.codePointAt(0);
|
||||
if (typeof codePoint === "number" && codePoint <= 255) latin1Only += char;
|
||||
}
|
||||
return latin1Only.trim();
|
||||
}
|
||||
function normalizeOptionalSecretInput(value) {
|
||||
const normalized = normalizeSecretInput(value);
|
||||
return normalized ? normalized : void 0;
|
||||
}
|
||||
function normalizeSecretInputString(value) {
|
||||
if (typeof value !== "string") return;
|
||||
const trimmed = value.trim();
|
||||
return trimmed.length > 0 ? trimmed : void 0;
|
||||
}
|
||||
function hasConfiguredSecretInput(value, defaults) {
|
||||
if (normalizeSecretInputString(value)) return true;
|
||||
return coerceSecretRef(value, defaults) !== null;
|
||||
}
|
||||
function formatSecretRefLabel(ref) {
|
||||
return `${ref.source}:${ref.provider}:${ref.id}`;
|
||||
}
|
||||
function createUnresolvedSecretInputError(params) {
|
||||
return /* @__PURE__ */ new Error(`${params.path}: unresolved SecretRef "${formatSecretRefLabel(params.ref)}". Resolve this command against an active gateway runtime snapshot before reading it.`);
|
||||
}
|
||||
function assertSecretInputResolved(params) {
|
||||
const { ref } = resolveSecretInputRef({
|
||||
value: params.value,
|
||||
refValue: params.refValue,
|
||||
defaults: params.defaults
|
||||
});
|
||||
if (!ref) return;
|
||||
throw createUnresolvedSecretInputError({
|
||||
path: params.path,
|
||||
ref
|
||||
});
|
||||
}
|
||||
function resolveSecretInputString(params) {
|
||||
const normalized = normalizeSecretInputString(params.value);
|
||||
if (normalized) return {
|
||||
status: "available",
|
||||
value: normalized,
|
||||
ref: null
|
||||
};
|
||||
const { ref } = resolveSecretInputRef({
|
||||
value: params.value,
|
||||
refValue: params.refValue,
|
||||
defaults: params.defaults
|
||||
});
|
||||
if (!ref) return {
|
||||
status: "missing",
|
||||
value: void 0,
|
||||
ref: null
|
||||
};
|
||||
if ((params.mode ?? "strict") === "strict") throw createUnresolvedSecretInputError({
|
||||
path: params.path,
|
||||
ref
|
||||
});
|
||||
return {
|
||||
status: "configured_unavailable",
|
||||
value: void 0,
|
||||
ref
|
||||
};
|
||||
}
|
||||
function normalizeResolvedSecretInputString(params) {
|
||||
const resolved = resolveSecretInputString({
|
||||
...params,
|
||||
mode: "strict"
|
||||
});
|
||||
if (resolved.status === "available") return resolved.value;
|
||||
}
|
||||
//#endregion
|
||||
export { assertSecretInputResolved, hasConfiguredSecretInput, normalizeOptionalSecretInput, normalizeResolvedSecretInputString, normalizeSecretInput, normalizeSecretInputString, resolveSecretInputString };
|
||||
40
packages/secrets-core/dist/secret-ref.d.mts
vendored
Normal file
40
packages/secrets-core/dist/secret-ref.d.mts
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
//#region packages/secrets-core/src/secret-ref.d.ts
|
||||
type SecretRefSource = "env" | "file" | "exec";
|
||||
/**
|
||||
* Stable identifier for a secret in a configured source.
|
||||
* Examples:
|
||||
* - env source: provider "default", id "OPENAI_API_KEY"
|
||||
* - file source: provider "mounted-json", id "/providers/openai/apiKey"
|
||||
* - exec source: provider "vault", id "openai/api-key"
|
||||
*/
|
||||
type SecretRef = {
|
||||
source: SecretRefSource;
|
||||
provider: string;
|
||||
id: string;
|
||||
};
|
||||
type SecretInput = string | SecretRef;
|
||||
type SecretDefaults = {
|
||||
env?: string;
|
||||
file?: string;
|
||||
exec?: string;
|
||||
};
|
||||
declare const DEFAULT_SECRET_PROVIDER_ALIAS = "default";
|
||||
declare const ENV_SECRET_REF_ID_RE: RegExp;
|
||||
declare const LEGACY_SECRETREF_ENV_MARKER_PREFIX = "secretref-env:";
|
||||
declare const LEGACY_DOUBLE_UNDERSCORE_ENV_MARKER_PREFIX = "__env__:";
|
||||
declare function isValidEnvSecretRefId(value: string): boolean;
|
||||
declare function isSecretRef(value: unknown): value is SecretRef;
|
||||
declare function parseEnvTemplateSecretRef(value: unknown, provider?: string): SecretRef | null;
|
||||
declare function parseLegacySecretRefEnvMarker(value: unknown, provider?: string): SecretRef | null;
|
||||
declare function coerceSecretRef(value: unknown, defaults?: SecretDefaults): SecretRef | null;
|
||||
declare function resolveSecretInputRef(params: {
|
||||
value: unknown;
|
||||
refValue?: unknown;
|
||||
defaults?: SecretDefaults;
|
||||
}): {
|
||||
explicitRef: SecretRef | null;
|
||||
inlineRef: SecretRef | null;
|
||||
ref: SecretRef | null;
|
||||
};
|
||||
//#endregion
|
||||
export { DEFAULT_SECRET_PROVIDER_ALIAS, ENV_SECRET_REF_ID_RE, LEGACY_DOUBLE_UNDERSCORE_ENV_MARKER_PREFIX, LEGACY_SECRETREF_ENV_MARKER_PREFIX, SecretDefaults, SecretInput, SecretRef, SecretRefSource, coerceSecretRef, isSecretRef, isValidEnvSecretRefId, parseEnvTemplateSecretRef, parseLegacySecretRefEnvMarker, resolveSecretInputRef };
|
||||
84
packages/secrets-core/dist/secret-ref.mjs
vendored
Normal file
84
packages/secrets-core/dist/secret-ref.mjs
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
//#region packages/secrets-core/src/secret-ref.ts
|
||||
const DEFAULT_SECRET_PROVIDER_ALIAS = "default";
|
||||
const ENV_SECRET_REF_ID_RE = /^[A-Z][A-Z0-9_]{0,127}$/;
|
||||
const LEGACY_SECRETREF_ENV_MARKER_PREFIX = "secretref-env:";
|
||||
const LEGACY_DOUBLE_UNDERSCORE_ENV_MARKER_PREFIX = "__env__:";
|
||||
const ENV_SECRET_TEMPLATE_RE = /^\$\{([A-Z][A-Z0-9_]{0,127})\}$/;
|
||||
const ENV_SECRET_SHORTHAND_RE = /^\$([A-Z][A-Z0-9_]{0,127})$/;
|
||||
const SECRET_REF_SOURCES = new Set([
|
||||
"env",
|
||||
"file",
|
||||
"exec"
|
||||
]);
|
||||
function isRecord(value) {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
function hasSecretRefSource(value) {
|
||||
return typeof value === "string" && SECRET_REF_SOURCES.has(value);
|
||||
}
|
||||
function hasNonEmptyString(value) {
|
||||
return typeof value === "string" && value.trim().length > 0;
|
||||
}
|
||||
function isValidEnvSecretRefId(value) {
|
||||
return ENV_SECRET_REF_ID_RE.test(value);
|
||||
}
|
||||
function isSecretRef(value) {
|
||||
if (!isRecord(value)) return false;
|
||||
if (Object.keys(value).length !== 3) return false;
|
||||
return hasSecretRefSource(value.source) && hasNonEmptyString(value.provider) && hasNonEmptyString(value.id);
|
||||
}
|
||||
function isLegacySecretRefWithoutProvider(value) {
|
||||
if (!isRecord(value)) return false;
|
||||
return hasSecretRefSource(value.source) && hasNonEmptyString(value.id) && value.provider === void 0;
|
||||
}
|
||||
function parseEnvTemplateSecretRef(value, provider = DEFAULT_SECRET_PROVIDER_ALIAS) {
|
||||
if (typeof value !== "string") return null;
|
||||
const trimmed = value.trim();
|
||||
const match = ENV_SECRET_TEMPLATE_RE.exec(trimmed) ?? ENV_SECRET_SHORTHAND_RE.exec(trimmed);
|
||||
if (!match) return null;
|
||||
return {
|
||||
source: "env",
|
||||
provider: provider.trim() || "default",
|
||||
id: match[1]
|
||||
};
|
||||
}
|
||||
function parseLegacySecretRefEnvMarker(value, provider = DEFAULT_SECRET_PROVIDER_ALIAS) {
|
||||
if (typeof value !== "string") return null;
|
||||
const trimmed = value.trim();
|
||||
const prefix = trimmed.startsWith("secretref-env:") ? LEGACY_SECRETREF_ENV_MARKER_PREFIX : trimmed.startsWith("__env__:") ? LEGACY_DOUBLE_UNDERSCORE_ENV_MARKER_PREFIX : void 0;
|
||||
if (!prefix) return null;
|
||||
const id = trimmed.slice(prefix.length);
|
||||
if (!ENV_SECRET_REF_ID_RE.test(id)) return null;
|
||||
return {
|
||||
source: "env",
|
||||
provider: provider.trim() || "default",
|
||||
id
|
||||
};
|
||||
}
|
||||
function coerceSecretRef(value, defaults) {
|
||||
if (isSecretRef(value)) return value;
|
||||
const legacyEnvMarker = parseLegacySecretRefEnvMarker(value, defaults?.env);
|
||||
if (legacyEnvMarker) return legacyEnvMarker;
|
||||
if (isLegacySecretRefWithoutProvider(value)) {
|
||||
const provider = value.source === "env" ? defaults?.env ?? "default" : value.source === "file" ? defaults?.file ?? "default" : defaults?.exec ?? "default";
|
||||
return {
|
||||
source: value.source,
|
||||
provider,
|
||||
id: value.id
|
||||
};
|
||||
}
|
||||
const envTemplate = parseEnvTemplateSecretRef(value, defaults?.env);
|
||||
if (envTemplate) return envTemplate;
|
||||
return null;
|
||||
}
|
||||
function resolveSecretInputRef(params) {
|
||||
const explicitRef = coerceSecretRef(params.refValue, params.defaults);
|
||||
const inlineRef = explicitRef ? null : coerceSecretRef(params.value, params.defaults);
|
||||
return {
|
||||
explicitRef,
|
||||
inlineRef,
|
||||
ref: explicitRef ?? inlineRef
|
||||
};
|
||||
}
|
||||
//#endregion
|
||||
export { DEFAULT_SECRET_PROVIDER_ALIAS, ENV_SECRET_REF_ID_RE, LEGACY_DOUBLE_UNDERSCORE_ENV_MARKER_PREFIX, LEGACY_SECRETREF_ENV_MARKER_PREFIX, coerceSecretRef, isSecretRef, isValidEnvSecretRefId, parseEnvTemplateSecretRef, parseLegacySecretRefEnvMarker, resolveSecretInputRef };
|
||||
31
packages/secrets-core/package.json
Normal file
31
packages/secrets-core/package.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "@openclaw/secrets-core",
|
||||
"version": "0.0.0-private",
|
||||
"private": true,
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"type": "module",
|
||||
"main": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.mts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.mts",
|
||||
"import": "./dist/index.mjs",
|
||||
"default": "./dist/index.mjs"
|
||||
},
|
||||
"./secret-input": {
|
||||
"types": "./dist/secret-input.d.mts",
|
||||
"import": "./dist/secret-input.mjs",
|
||||
"default": "./dist/secret-input.mjs"
|
||||
},
|
||||
"./secret-ref": {
|
||||
"types": "./dist/secret-ref.d.mts",
|
||||
"import": "./dist/secret-ref.mjs",
|
||||
"default": "./dist/secret-ref.mjs"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsdown src/index.ts src/secret-input.ts src/secret-ref.ts --no-config --platform node --format esm --dts --out-dir dist --clean"
|
||||
}
|
||||
}
|
||||
2
packages/secrets-core/src/index.ts
Normal file
2
packages/secrets-core/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./secret-input.js";
|
||||
export * from "./secret-ref.js";
|
||||
@@ -1,9 +1,43 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
normalizeOptionalSecretInput,
|
||||
normalizeResolvedSecretInputString,
|
||||
normalizeSecretInput,
|
||||
parseLegacySecretRefEnvMarker,
|
||||
resolveSecretInputString,
|
||||
} from "./types.secrets.js";
|
||||
} from "./index.js";
|
||||
|
||||
describe("normalizeSecretInput", () => {
|
||||
it("returns empty string for non-string values", () => {
|
||||
expect(normalizeSecretInput(undefined)).toBe("");
|
||||
expect(normalizeSecretInput(null)).toBe("");
|
||||
expect(normalizeSecretInput(123)).toBe("");
|
||||
expect(normalizeSecretInput({})).toBe("");
|
||||
});
|
||||
|
||||
it("strips embedded line breaks and surrounding whitespace", () => {
|
||||
expect(normalizeSecretInput(" sk-\r\nabc\n123 ")).toBe("sk-abc123");
|
||||
});
|
||||
|
||||
it("drops non-Latin1 code points that can break HTTP ByteString headers", () => {
|
||||
expect(normalizeSecretInput("key-\u0417\u2502-token")).toBe("key--token");
|
||||
});
|
||||
|
||||
it("preserves Latin-1 characters and internal spaces", () => {
|
||||
expect(normalizeSecretInput(" café token ")).toBe("café token");
|
||||
});
|
||||
});
|
||||
|
||||
describe("normalizeOptionalSecretInput", () => {
|
||||
it("returns undefined when normalized value is empty", () => {
|
||||
expect(normalizeOptionalSecretInput(" \r\n ")).toBeUndefined();
|
||||
expect(normalizeOptionalSecretInput("\u0417\u2502")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns normalized value when non-empty", () => {
|
||||
expect(normalizeOptionalSecretInput(" key-\u0417 ")).toBe("key-");
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveSecretInputString", () => {
|
||||
it("returns available for non-empty string values", () => {
|
||||
134
packages/secrets-core/src/secret-input.ts
Normal file
134
packages/secrets-core/src/secret-input.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import {
|
||||
coerceSecretRef,
|
||||
resolveSecretInputRef,
|
||||
type SecretDefaults,
|
||||
type SecretRef,
|
||||
} from "./secret-ref.js";
|
||||
|
||||
export type SecretInputStringResolutionMode = "strict" | "inspect";
|
||||
export type SecretInputStringResolution =
|
||||
| { status: "available"; value: string; ref: null }
|
||||
| { status: "configured_unavailable"; value: undefined; ref: SecretRef }
|
||||
| { status: "missing"; value: undefined; ref: null };
|
||||
|
||||
/**
|
||||
* Normalize copy/pasted credentials for HTTP/auth use.
|
||||
*
|
||||
* Line breaks embedded in API keys are common paste artifacts, and non-Latin1
|
||||
* rich-text characters can crash header construction before auth fails.
|
||||
* Preserve ordinary internal spaces for values such as `Bearer <token>`.
|
||||
*/
|
||||
export function normalizeSecretInput(value: unknown): string {
|
||||
if (typeof value !== "string") {
|
||||
return "";
|
||||
}
|
||||
const collapsed = value.replace(/[\r\n\u2028\u2029]+/g, "");
|
||||
let latin1Only = "";
|
||||
for (const char of collapsed) {
|
||||
const codePoint = char.codePointAt(0);
|
||||
if (typeof codePoint === "number" && codePoint <= 0xff) {
|
||||
latin1Only += char;
|
||||
}
|
||||
}
|
||||
return latin1Only.trim();
|
||||
}
|
||||
|
||||
export function normalizeOptionalSecretInput(value: unknown): string | undefined {
|
||||
const normalized = normalizeSecretInput(value);
|
||||
return normalized ? normalized : undefined;
|
||||
}
|
||||
|
||||
export function normalizeSecretInputString(value: unknown): string | undefined {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
return trimmed.length > 0 ? trimmed : undefined;
|
||||
}
|
||||
|
||||
export function hasConfiguredSecretInput(value: unknown, defaults?: SecretDefaults): boolean {
|
||||
if (normalizeSecretInputString(value)) {
|
||||
return true;
|
||||
}
|
||||
return coerceSecretRef(value, defaults) !== null;
|
||||
}
|
||||
|
||||
function formatSecretRefLabel(ref: SecretRef): string {
|
||||
return `${ref.source}:${ref.provider}:${ref.id}`;
|
||||
}
|
||||
|
||||
function createUnresolvedSecretInputError(params: { path: string; ref: SecretRef }): Error {
|
||||
return new Error(
|
||||
`${params.path}: unresolved SecretRef "${formatSecretRefLabel(params.ref)}". Resolve this command against an active gateway runtime snapshot before reading it.`,
|
||||
);
|
||||
}
|
||||
|
||||
export function assertSecretInputResolved(params: {
|
||||
value: unknown;
|
||||
refValue?: unknown;
|
||||
defaults?: SecretDefaults;
|
||||
path: string;
|
||||
}): void {
|
||||
const { ref } = resolveSecretInputRef({
|
||||
value: params.value,
|
||||
refValue: params.refValue,
|
||||
defaults: params.defaults,
|
||||
});
|
||||
if (!ref) {
|
||||
return;
|
||||
}
|
||||
throw createUnresolvedSecretInputError({ path: params.path, ref });
|
||||
}
|
||||
|
||||
export function resolveSecretInputString(params: {
|
||||
value: unknown;
|
||||
refValue?: unknown;
|
||||
defaults?: SecretDefaults;
|
||||
path: string;
|
||||
mode?: SecretInputStringResolutionMode;
|
||||
}): SecretInputStringResolution {
|
||||
const normalized = normalizeSecretInputString(params.value);
|
||||
if (normalized) {
|
||||
return {
|
||||
status: "available",
|
||||
value: normalized,
|
||||
ref: null,
|
||||
};
|
||||
}
|
||||
const { ref } = resolveSecretInputRef({
|
||||
value: params.value,
|
||||
refValue: params.refValue,
|
||||
defaults: params.defaults,
|
||||
});
|
||||
if (!ref) {
|
||||
return {
|
||||
status: "missing",
|
||||
value: undefined,
|
||||
ref: null,
|
||||
};
|
||||
}
|
||||
if ((params.mode ?? "strict") === "strict") {
|
||||
throw createUnresolvedSecretInputError({ path: params.path, ref });
|
||||
}
|
||||
return {
|
||||
status: "configured_unavailable",
|
||||
value: undefined,
|
||||
ref,
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeResolvedSecretInputString(params: {
|
||||
value: unknown;
|
||||
refValue?: unknown;
|
||||
defaults?: SecretDefaults;
|
||||
path: string;
|
||||
}): string | undefined {
|
||||
const resolved = resolveSecretInputString({
|
||||
...params,
|
||||
mode: "strict",
|
||||
});
|
||||
if (resolved.status === "available") {
|
||||
return resolved.value;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { parseEnvTemplateSecretRef } from "./types.secrets.js";
|
||||
import { parseEnvTemplateSecretRef } from "./secret-ref.js";
|
||||
|
||||
describe("parseEnvTemplateSecretRef", () => {
|
||||
it("parses ${VAR} template syntax", () => {
|
||||
163
packages/secrets-core/src/secret-ref.ts
Normal file
163
packages/secrets-core/src/secret-ref.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
export type SecretRefSource = "env" | "file" | "exec"; // pragma: allowlist secret
|
||||
|
||||
/**
|
||||
* Stable identifier for a secret in a configured source.
|
||||
* Examples:
|
||||
* - env source: provider "default", id "OPENAI_API_KEY"
|
||||
* - file source: provider "mounted-json", id "/providers/openai/apiKey"
|
||||
* - exec source: provider "vault", id "openai/api-key"
|
||||
*/
|
||||
export type SecretRef = {
|
||||
source: SecretRefSource;
|
||||
provider: string;
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type SecretInput = string | SecretRef;
|
||||
export type SecretDefaults = {
|
||||
env?: string;
|
||||
file?: string;
|
||||
exec?: string;
|
||||
};
|
||||
|
||||
export const DEFAULT_SECRET_PROVIDER_ALIAS = "default"; // pragma: allowlist secret
|
||||
export const ENV_SECRET_REF_ID_RE = /^[A-Z][A-Z0-9_]{0,127}$/;
|
||||
export const LEGACY_SECRETREF_ENV_MARKER_PREFIX = "secretref-env:"; // pragma: allowlist secret
|
||||
export const LEGACY_DOUBLE_UNDERSCORE_ENV_MARKER_PREFIX = "__env__:"; // pragma: allowlist secret
|
||||
|
||||
const ENV_SECRET_TEMPLATE_RE = /^\$\{([A-Z][A-Z0-9_]{0,127})\}$/;
|
||||
const ENV_SECRET_SHORTHAND_RE = /^\$([A-Z][A-Z0-9_]{0,127})$/;
|
||||
const SECRET_REF_SOURCES = new Set<SecretRefSource>(["env", "file", "exec"]);
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function hasSecretRefSource(value: unknown): value is SecretRefSource {
|
||||
return typeof value === "string" && SECRET_REF_SOURCES.has(value as SecretRefSource);
|
||||
}
|
||||
|
||||
function hasNonEmptyString(value: unknown): value is string {
|
||||
return typeof value === "string" && value.trim().length > 0;
|
||||
}
|
||||
|
||||
export function isValidEnvSecretRefId(value: string): boolean {
|
||||
return ENV_SECRET_REF_ID_RE.test(value);
|
||||
}
|
||||
|
||||
export function isSecretRef(value: unknown): value is SecretRef {
|
||||
if (!isRecord(value)) {
|
||||
return false;
|
||||
}
|
||||
if (Object.keys(value).length !== 3) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
hasSecretRefSource(value.source) &&
|
||||
hasNonEmptyString(value.provider) &&
|
||||
hasNonEmptyString(value.id)
|
||||
);
|
||||
}
|
||||
|
||||
function isLegacySecretRefWithoutProvider(
|
||||
value: unknown,
|
||||
): value is { source: SecretRefSource; id: string } {
|
||||
if (!isRecord(value)) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
hasSecretRefSource(value.source) && hasNonEmptyString(value.id) && value.provider === undefined
|
||||
);
|
||||
}
|
||||
|
||||
export function parseEnvTemplateSecretRef(
|
||||
value: unknown,
|
||||
provider = DEFAULT_SECRET_PROVIDER_ALIAS,
|
||||
): SecretRef | null {
|
||||
if (typeof value !== "string") {
|
||||
return null;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
const match = ENV_SECRET_TEMPLATE_RE.exec(trimmed) ?? ENV_SECRET_SHORTHAND_RE.exec(trimmed);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
source: "env",
|
||||
provider: provider.trim() || DEFAULT_SECRET_PROVIDER_ALIAS,
|
||||
id: match[1],
|
||||
};
|
||||
}
|
||||
|
||||
export function parseLegacySecretRefEnvMarker(
|
||||
value: unknown,
|
||||
provider = DEFAULT_SECRET_PROVIDER_ALIAS,
|
||||
): SecretRef | null {
|
||||
if (typeof value !== "string") {
|
||||
return null;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
const prefix = trimmed.startsWith(LEGACY_SECRETREF_ENV_MARKER_PREFIX)
|
||||
? LEGACY_SECRETREF_ENV_MARKER_PREFIX
|
||||
: trimmed.startsWith(LEGACY_DOUBLE_UNDERSCORE_ENV_MARKER_PREFIX)
|
||||
? LEGACY_DOUBLE_UNDERSCORE_ENV_MARKER_PREFIX
|
||||
: undefined;
|
||||
if (!prefix) {
|
||||
return null;
|
||||
}
|
||||
const id = trimmed.slice(prefix.length);
|
||||
if (!ENV_SECRET_REF_ID_RE.test(id)) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
source: "env",
|
||||
provider: provider.trim() || DEFAULT_SECRET_PROVIDER_ALIAS,
|
||||
id,
|
||||
};
|
||||
}
|
||||
|
||||
export function coerceSecretRef(value: unknown, defaults?: SecretDefaults): SecretRef | null {
|
||||
if (isSecretRef(value)) {
|
||||
return value;
|
||||
}
|
||||
const legacyEnvMarker = parseLegacySecretRefEnvMarker(value, defaults?.env);
|
||||
if (legacyEnvMarker) {
|
||||
return legacyEnvMarker;
|
||||
}
|
||||
if (isLegacySecretRefWithoutProvider(value)) {
|
||||
const provider =
|
||||
value.source === "env"
|
||||
? (defaults?.env ?? DEFAULT_SECRET_PROVIDER_ALIAS)
|
||||
: value.source === "file"
|
||||
? (defaults?.file ?? DEFAULT_SECRET_PROVIDER_ALIAS)
|
||||
: (defaults?.exec ?? DEFAULT_SECRET_PROVIDER_ALIAS);
|
||||
return {
|
||||
source: value.source,
|
||||
provider,
|
||||
id: value.id,
|
||||
};
|
||||
}
|
||||
const envTemplate = parseEnvTemplateSecretRef(value, defaults?.env);
|
||||
if (envTemplate) {
|
||||
return envTemplate;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function resolveSecretInputRef(params: {
|
||||
value: unknown;
|
||||
refValue?: unknown;
|
||||
defaults?: SecretDefaults;
|
||||
}): {
|
||||
explicitRef: SecretRef | null;
|
||||
inlineRef: SecretRef | null;
|
||||
ref: SecretRef | null;
|
||||
} {
|
||||
const explicitRef = coerceSecretRef(params.refValue, params.defaults);
|
||||
const inlineRef = explicitRef ? null : coerceSecretRef(params.value, params.defaults);
|
||||
return {
|
||||
explicitRef,
|
||||
inlineRef,
|
||||
ref: explicitRef ?? inlineRef,
|
||||
};
|
||||
}
|
||||
2
packages/web-content-core/dist/index.mjs
vendored
2
packages/web-content-core/dist/index.mjs
vendored
@@ -1,2 +1,2 @@
|
||||
import { hasWebProviderEntryCredential, providerRequiresCredential, readWebProviderEnvValue, resolveWebProviderConfig, resolveWebProviderDefinition } from "./provider-runtime-shared.mjs";
|
||||
import { a as resolveWebProviderDefinition, i as resolveWebProviderConfig, n as providerRequiresCredential, r as readWebProviderEnvValue, t as hasWebProviderEntryCredential } from "./provider-runtime-shared-D4ll_JBj.mjs";
|
||||
export { hasWebProviderEntryCredential, providerRequiresCredential, readWebProviderEnvValue, resolveWebProviderConfig, resolveWebProviderDefinition };
|
||||
|
||||
175
packages/web-content-core/dist/provider-runtime-shared-D4ll_JBj.mjs
vendored
Normal file
175
packages/web-content-core/dist/provider-runtime-shared-D4ll_JBj.mjs
vendored
Normal file
@@ -0,0 +1,175 @@
|
||||
//#region packages/secrets-core/src/secret-ref.ts
|
||||
const DEFAULT_SECRET_PROVIDER_ALIAS = "default";
|
||||
const ENV_SECRET_REF_ID_RE = /^[A-Z][A-Z0-9_]{0,127}$/;
|
||||
const LEGACY_SECRETREF_ENV_MARKER_PREFIX = "secretref-env:";
|
||||
const LEGACY_DOUBLE_UNDERSCORE_ENV_MARKER_PREFIX = "__env__:";
|
||||
const ENV_SECRET_TEMPLATE_RE = /^\$\{([A-Z][A-Z0-9_]{0,127})\}$/;
|
||||
const ENV_SECRET_SHORTHAND_RE = /^\$([A-Z][A-Z0-9_]{0,127})$/;
|
||||
const SECRET_REF_SOURCES = new Set([
|
||||
"env",
|
||||
"file",
|
||||
"exec"
|
||||
]);
|
||||
function isRecord(value) {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
function hasSecretRefSource(value) {
|
||||
return typeof value === "string" && SECRET_REF_SOURCES.has(value);
|
||||
}
|
||||
function hasNonEmptyString(value) {
|
||||
return typeof value === "string" && value.trim().length > 0;
|
||||
}
|
||||
function isSecretRef(value) {
|
||||
if (!isRecord(value)) return false;
|
||||
if (Object.keys(value).length !== 3) return false;
|
||||
return hasSecretRefSource(value.source) && hasNonEmptyString(value.provider) && hasNonEmptyString(value.id);
|
||||
}
|
||||
function isLegacySecretRefWithoutProvider(value) {
|
||||
if (!isRecord(value)) return false;
|
||||
return hasSecretRefSource(value.source) && hasNonEmptyString(value.id) && value.provider === void 0;
|
||||
}
|
||||
function parseEnvTemplateSecretRef(value, provider = DEFAULT_SECRET_PROVIDER_ALIAS) {
|
||||
if (typeof value !== "string") return null;
|
||||
const trimmed = value.trim();
|
||||
const match = ENV_SECRET_TEMPLATE_RE.exec(trimmed) ?? ENV_SECRET_SHORTHAND_RE.exec(trimmed);
|
||||
if (!match) return null;
|
||||
return {
|
||||
source: "env",
|
||||
provider: provider.trim() || "default",
|
||||
id: match[1]
|
||||
};
|
||||
}
|
||||
function parseLegacySecretRefEnvMarker(value, provider = DEFAULT_SECRET_PROVIDER_ALIAS) {
|
||||
if (typeof value !== "string") return null;
|
||||
const trimmed = value.trim();
|
||||
const prefix = trimmed.startsWith("secretref-env:") ? LEGACY_SECRETREF_ENV_MARKER_PREFIX : trimmed.startsWith("__env__:") ? LEGACY_DOUBLE_UNDERSCORE_ENV_MARKER_PREFIX : void 0;
|
||||
if (!prefix) return null;
|
||||
const id = trimmed.slice(prefix.length);
|
||||
if (!ENV_SECRET_REF_ID_RE.test(id)) return null;
|
||||
return {
|
||||
source: "env",
|
||||
provider: provider.trim() || "default",
|
||||
id
|
||||
};
|
||||
}
|
||||
function coerceSecretRef(value, defaults) {
|
||||
if (isSecretRef(value)) return value;
|
||||
const legacyEnvMarker = parseLegacySecretRefEnvMarker(value, defaults?.env);
|
||||
if (legacyEnvMarker) return legacyEnvMarker;
|
||||
if (isLegacySecretRefWithoutProvider(value)) {
|
||||
const provider = value.source === "env" ? defaults?.env ?? "default" : value.source === "file" ? defaults?.file ?? "default" : defaults?.exec ?? "default";
|
||||
return {
|
||||
source: value.source,
|
||||
provider,
|
||||
id: value.id
|
||||
};
|
||||
}
|
||||
const envTemplate = parseEnvTemplateSecretRef(value, defaults?.env);
|
||||
if (envTemplate) return envTemplate;
|
||||
return null;
|
||||
}
|
||||
//#endregion
|
||||
//#region packages/secrets-core/src/secret-input.ts
|
||||
/**
|
||||
* Normalize copy/pasted credentials for HTTP/auth use.
|
||||
*
|
||||
* Line breaks embedded in API keys are common paste artifacts, and non-Latin1
|
||||
* rich-text characters can crash header construction before auth fails.
|
||||
* Preserve ordinary internal spaces for values such as `Bearer <token>`.
|
||||
*/
|
||||
function normalizeSecretInput(value) {
|
||||
if (typeof value !== "string") return "";
|
||||
const collapsed = value.replace(/[\r\n\u2028\u2029]+/g, "");
|
||||
let latin1Only = "";
|
||||
for (const char of collapsed) {
|
||||
const codePoint = char.codePointAt(0);
|
||||
if (typeof codePoint === "number" && codePoint <= 255) latin1Only += char;
|
||||
}
|
||||
return latin1Only.trim();
|
||||
}
|
||||
function normalizeSecretInputString(value) {
|
||||
if (typeof value !== "string") return;
|
||||
const trimmed = value.trim();
|
||||
return trimmed.length > 0 ? trimmed : void 0;
|
||||
}
|
||||
//#endregion
|
||||
//#region packages/web-content-core/src/provider-runtime-shared.ts
|
||||
function resolveWebProviderConfig(cfg, kind) {
|
||||
const webConfig = cfg?.tools?.web;
|
||||
if (!webConfig || typeof webConfig !== "object") return;
|
||||
const toolConfig = webConfig[kind];
|
||||
if (!toolConfig || typeof toolConfig !== "object") return;
|
||||
return toolConfig;
|
||||
}
|
||||
function readWebProviderEnvValue(envVars, processEnv = process.env) {
|
||||
for (const envVar of envVars) {
|
||||
const value = normalizeSecretInput(processEnv[envVar]);
|
||||
if (value) return value;
|
||||
}
|
||||
}
|
||||
function providerRequiresCredential(provider) {
|
||||
return provider.requiresCredential !== false;
|
||||
}
|
||||
function hasWebProviderEntryCredential(params) {
|
||||
if (!providerRequiresCredential(params.provider)) return true;
|
||||
const rawValue = params.resolveRawValue({
|
||||
provider: params.provider,
|
||||
config: params.config,
|
||||
toolConfig: params.toolConfig
|
||||
});
|
||||
const configuredRef = coerceSecretRef(rawValue);
|
||||
if (configuredRef && configuredRef.source !== "env") return true;
|
||||
if (normalizeSecretInput(normalizeSecretInputString(rawValue))) return true;
|
||||
if (params.provider.authProviderId && params.resolveProviderAuthValue?.(params.provider.authProviderId)) return true;
|
||||
if (params.resolveEnvValue({
|
||||
provider: params.provider,
|
||||
configuredEnvVarId: configuredRef?.source === "env" ? configuredRef.id : void 0
|
||||
})) return true;
|
||||
const fallbackRawValue = params.resolveFallbackRawValue?.({
|
||||
provider: params.provider,
|
||||
config: params.config,
|
||||
toolConfig: params.toolConfig
|
||||
});
|
||||
const fallbackRef = coerceSecretRef(fallbackRawValue);
|
||||
if (fallbackRef && fallbackRef.source !== "env") return true;
|
||||
if (normalizeSecretInput(normalizeSecretInputString(fallbackRawValue))) return true;
|
||||
return Boolean(fallbackRef?.source === "env" ? params.resolveEnvValue({
|
||||
provider: params.provider,
|
||||
configuredEnvVarId: fallbackRef.id
|
||||
}) : void 0);
|
||||
}
|
||||
function resolveWebProviderDefinition(params) {
|
||||
if (!params.resolveEnabled({
|
||||
toolConfig: params.toolConfig,
|
||||
sandboxed: params.sandboxed
|
||||
})) return null;
|
||||
const providers = params.providers.filter(Boolean);
|
||||
if (providers.length === 0) return null;
|
||||
const autoProviderId = params.resolveAutoProviderId({
|
||||
config: params.config,
|
||||
toolConfig: params.toolConfig,
|
||||
providers
|
||||
});
|
||||
const providerId = params.providerId ?? params.runtimeMetadata?.selectedProvider ?? autoProviderId;
|
||||
if (!providerId) return null;
|
||||
const provider = providers.find((entry) => entry.id === providerId) ?? providers.find((entry) => entry.id === params.resolveFallbackProviderId?.({
|
||||
config: params.config,
|
||||
toolConfig: params.toolConfig,
|
||||
providers,
|
||||
providerId
|
||||
}));
|
||||
if (!provider) return null;
|
||||
const definition = params.createTool({
|
||||
provider,
|
||||
config: params.config,
|
||||
toolConfig: params.toolConfig,
|
||||
runtimeMetadata: params.runtimeMetadata
|
||||
});
|
||||
if (!definition) return null;
|
||||
return {
|
||||
provider,
|
||||
definition
|
||||
};
|
||||
}
|
||||
//#endregion
|
||||
export { resolveWebProviderDefinition as a, resolveWebProviderConfig as i, providerRequiresCredential as n, readWebProviderEnvValue as r, hasWebProviderEntryCredential as t };
|
||||
@@ -1,136 +1,2 @@
|
||||
//#region packages/web-content-core/src/provider-runtime-shared.ts
|
||||
const DEFAULT_SECRET_PROVIDER_ALIAS = "default";
|
||||
const ENV_SECRET_REF_ID_RE = /^[A-Z][A-Z0-9_]{0,127}$/;
|
||||
const LEGACY_SECRETREF_ENV_MARKER_PREFIX = "secretref-env:";
|
||||
const LEGACY_DOUBLE_UNDERSCORE_ENV_MARKER_PREFIX = "__env__:";
|
||||
const ENV_SECRET_TEMPLATE_RE = /^\$\{([A-Z][A-Z0-9_]{0,127})\}$/;
|
||||
const ENV_SECRET_SHORTHAND_RE = /^\$([A-Z][A-Z0-9_]{0,127})$/;
|
||||
function isRecord(value) {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
function normalizeSecretInputString(value) {
|
||||
if (typeof value !== "string") return;
|
||||
const trimmed = value.trim();
|
||||
return trimmed.length > 0 ? trimmed : void 0;
|
||||
}
|
||||
function normalizeSecretInput(value) {
|
||||
if (typeof value !== "string") return "";
|
||||
const collapsed = value.replace(/[\r\n\u2028\u2029]+/g, "");
|
||||
let latin1Only = "";
|
||||
for (const char of collapsed) {
|
||||
const codePoint = char.codePointAt(0);
|
||||
if (typeof codePoint === "number" && codePoint <= 255) latin1Only += char;
|
||||
}
|
||||
return latin1Only.trim();
|
||||
}
|
||||
function isSecretRef(value) {
|
||||
if (!isRecord(value)) return false;
|
||||
if (Object.keys(value).length !== 3) return false;
|
||||
return (value.source === "env" || value.source === "file" || value.source === "exec") && typeof value.provider === "string" && value.provider.trim().length > 0 && typeof value.id === "string" && value.id.trim().length > 0;
|
||||
}
|
||||
function coerceSecretRef(value) {
|
||||
if (isSecretRef(value)) return value;
|
||||
if (typeof value === "string") {
|
||||
const trimmed = value.trim();
|
||||
const legacyPrefix = trimmed.startsWith(LEGACY_SECRETREF_ENV_MARKER_PREFIX) ? LEGACY_SECRETREF_ENV_MARKER_PREFIX : trimmed.startsWith(LEGACY_DOUBLE_UNDERSCORE_ENV_MARKER_PREFIX) ? LEGACY_DOUBLE_UNDERSCORE_ENV_MARKER_PREFIX : void 0;
|
||||
if (legacyPrefix) {
|
||||
const id = trimmed.slice(legacyPrefix.length);
|
||||
return ENV_SECRET_REF_ID_RE.test(id) ? {
|
||||
source: "env",
|
||||
provider: DEFAULT_SECRET_PROVIDER_ALIAS,
|
||||
id
|
||||
} : null;
|
||||
}
|
||||
const match = ENV_SECRET_TEMPLATE_RE.exec(trimmed) ?? ENV_SECRET_SHORTHAND_RE.exec(trimmed);
|
||||
return match ? {
|
||||
source: "env",
|
||||
provider: DEFAULT_SECRET_PROVIDER_ALIAS,
|
||||
id: match[1]
|
||||
} : null;
|
||||
}
|
||||
if (isRecord(value) && (value.source === "env" || value.source === "file" || value.source === "exec") && typeof value.id === "string" && value.id.trim().length > 0 && value.provider === void 0) return {
|
||||
source: value.source,
|
||||
provider: DEFAULT_SECRET_PROVIDER_ALIAS,
|
||||
id: value.id
|
||||
};
|
||||
return null;
|
||||
}
|
||||
function resolveWebProviderConfig(cfg, kind) {
|
||||
const webConfig = cfg?.tools?.web;
|
||||
if (!webConfig || typeof webConfig !== "object") return;
|
||||
const toolConfig = webConfig[kind];
|
||||
if (!toolConfig || typeof toolConfig !== "object") return;
|
||||
return toolConfig;
|
||||
}
|
||||
function readWebProviderEnvValue(envVars, processEnv = process.env) {
|
||||
for (const envVar of envVars) {
|
||||
const value = normalizeSecretInput(processEnv[envVar]);
|
||||
if (value) return value;
|
||||
}
|
||||
}
|
||||
function providerRequiresCredential(provider) {
|
||||
return provider.requiresCredential !== false;
|
||||
}
|
||||
function hasWebProviderEntryCredential(params) {
|
||||
if (!providerRequiresCredential(params.provider)) return true;
|
||||
const rawValue = params.resolveRawValue({
|
||||
provider: params.provider,
|
||||
config: params.config,
|
||||
toolConfig: params.toolConfig
|
||||
});
|
||||
const configuredRef = coerceSecretRef(rawValue);
|
||||
if (configuredRef && configuredRef.source !== "env") return true;
|
||||
if (normalizeSecretInput(normalizeSecretInputString(rawValue))) return true;
|
||||
if (params.provider.authProviderId && params.resolveProviderAuthValue?.(params.provider.authProviderId)) return true;
|
||||
if (params.resolveEnvValue({
|
||||
provider: params.provider,
|
||||
configuredEnvVarId: configuredRef?.source === "env" ? configuredRef.id : void 0
|
||||
})) return true;
|
||||
const fallbackRawValue = params.resolveFallbackRawValue?.({
|
||||
provider: params.provider,
|
||||
config: params.config,
|
||||
toolConfig: params.toolConfig
|
||||
});
|
||||
const fallbackRef = coerceSecretRef(fallbackRawValue);
|
||||
if (fallbackRef && fallbackRef.source !== "env") return true;
|
||||
if (normalizeSecretInput(normalizeSecretInputString(fallbackRawValue))) return true;
|
||||
return Boolean(fallbackRef?.source === "env" ? params.resolveEnvValue({
|
||||
provider: params.provider,
|
||||
configuredEnvVarId: fallbackRef.id
|
||||
}) : void 0);
|
||||
}
|
||||
function resolveWebProviderDefinition(params) {
|
||||
if (!params.resolveEnabled({
|
||||
toolConfig: params.toolConfig,
|
||||
sandboxed: params.sandboxed
|
||||
})) return null;
|
||||
const providers = params.providers.filter(Boolean);
|
||||
if (providers.length === 0) return null;
|
||||
const autoProviderId = params.resolveAutoProviderId({
|
||||
config: params.config,
|
||||
toolConfig: params.toolConfig,
|
||||
providers
|
||||
});
|
||||
const providerId = params.providerId ?? params.runtimeMetadata?.selectedProvider ?? autoProviderId;
|
||||
if (!providerId) return null;
|
||||
const provider = providers.find((entry) => entry.id === providerId) ?? providers.find((entry) => entry.id === params.resolveFallbackProviderId?.({
|
||||
config: params.config,
|
||||
toolConfig: params.toolConfig,
|
||||
providers,
|
||||
providerId
|
||||
}));
|
||||
if (!provider) return null;
|
||||
const definition = params.createTool({
|
||||
provider,
|
||||
config: params.config,
|
||||
toolConfig: params.toolConfig,
|
||||
runtimeMetadata: params.runtimeMetadata
|
||||
});
|
||||
if (!definition) return null;
|
||||
return {
|
||||
provider,
|
||||
definition
|
||||
};
|
||||
}
|
||||
//#endregion
|
||||
import { a as resolveWebProviderDefinition, i as resolveWebProviderConfig, n as providerRequiresCredential, r as readWebProviderEnvValue, t as hasWebProviderEntryCredential } from "./provider-runtime-shared-D4ll_JBj.mjs";
|
||||
export { hasWebProviderEntryCredential, providerRequiresCredential, readWebProviderEnvValue, resolveWebProviderConfig, resolveWebProviderDefinition };
|
||||
|
||||
@@ -22,5 +22,8 @@
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsdown src/index.ts src/provider-runtime-shared.ts --no-config --platform node --format esm --dts --out-dir dist --clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@openclaw/secrets-core": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
import {
|
||||
coerceSecretRef,
|
||||
normalizeSecretInput,
|
||||
normalizeSecretInputString,
|
||||
} from "@openclaw/secrets-core";
|
||||
|
||||
export type WebProviderConfigSource = {
|
||||
tools?: {
|
||||
web?: {
|
||||
@@ -7,21 +13,6 @@ export type WebProviderConfigSource = {
|
||||
};
|
||||
};
|
||||
|
||||
type SecretRefSource = "env" | "file" | "exec";
|
||||
|
||||
type SecretRef = {
|
||||
source: SecretRefSource;
|
||||
provider: string;
|
||||
id: string;
|
||||
};
|
||||
|
||||
const DEFAULT_SECRET_PROVIDER_ALIAS = "default";
|
||||
const ENV_SECRET_REF_ID_RE = /^[A-Z][A-Z0-9_]{0,127}$/;
|
||||
const LEGACY_SECRETREF_ENV_MARKER_PREFIX = "secretref-env:";
|
||||
const LEGACY_DOUBLE_UNDERSCORE_ENV_MARKER_PREFIX = "__env__:";
|
||||
const ENV_SECRET_TEMPLATE_RE = /^\$\{([A-Z][A-Z0-9_]{0,127})\}$/;
|
||||
const ENV_SECRET_SHORTHAND_RE = /^\$([A-Z][A-Z0-9_]{0,127})$/;
|
||||
|
||||
type RuntimeWebProviderMetadata = {
|
||||
providerConfigured?: string;
|
||||
selectedProvider?: string;
|
||||
@@ -35,85 +26,6 @@ type ProviderWithCredential = {
|
||||
|
||||
type WebContentProcessEnv = Record<string, string | undefined>;
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function normalizeSecretInputString(value: unknown): string | undefined {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
return trimmed.length > 0 ? trimmed : undefined;
|
||||
}
|
||||
|
||||
function normalizeSecretInput(value: unknown): string {
|
||||
if (typeof value !== "string") {
|
||||
return "";
|
||||
}
|
||||
const collapsed = value.replace(/[\r\n\u2028\u2029]+/g, "");
|
||||
let latin1Only = "";
|
||||
for (const char of collapsed) {
|
||||
const codePoint = char.codePointAt(0);
|
||||
if (typeof codePoint === "number" && codePoint <= 0xff) {
|
||||
latin1Only += char;
|
||||
}
|
||||
}
|
||||
return latin1Only.trim();
|
||||
}
|
||||
|
||||
function isSecretRef(value: unknown): value is SecretRef {
|
||||
if (!isRecord(value)) {
|
||||
return false;
|
||||
}
|
||||
if (Object.keys(value).length !== 3) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
(value.source === "env" || value.source === "file" || value.source === "exec") &&
|
||||
typeof value.provider === "string" &&
|
||||
value.provider.trim().length > 0 &&
|
||||
typeof value.id === "string" &&
|
||||
value.id.trim().length > 0
|
||||
);
|
||||
}
|
||||
|
||||
function coerceSecretRef(value: unknown): SecretRef | null {
|
||||
if (isSecretRef(value)) {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
const trimmed = value.trim();
|
||||
const legacyPrefix = trimmed.startsWith(LEGACY_SECRETREF_ENV_MARKER_PREFIX)
|
||||
? LEGACY_SECRETREF_ENV_MARKER_PREFIX
|
||||
: trimmed.startsWith(LEGACY_DOUBLE_UNDERSCORE_ENV_MARKER_PREFIX)
|
||||
? LEGACY_DOUBLE_UNDERSCORE_ENV_MARKER_PREFIX
|
||||
: undefined;
|
||||
if (legacyPrefix) {
|
||||
const id = trimmed.slice(legacyPrefix.length);
|
||||
return ENV_SECRET_REF_ID_RE.test(id)
|
||||
? { source: "env", provider: DEFAULT_SECRET_PROVIDER_ALIAS, id }
|
||||
: null;
|
||||
}
|
||||
const match = ENV_SECRET_TEMPLATE_RE.exec(trimmed) ?? ENV_SECRET_SHORTHAND_RE.exec(trimmed);
|
||||
return match ? { source: "env", provider: DEFAULT_SECRET_PROVIDER_ALIAS, id: match[1] } : null;
|
||||
}
|
||||
if (
|
||||
isRecord(value) &&
|
||||
(value.source === "env" || value.source === "file" || value.source === "exec") &&
|
||||
typeof value.id === "string" &&
|
||||
value.id.trim().length > 0 &&
|
||||
value.provider === undefined
|
||||
) {
|
||||
return {
|
||||
source: value.source,
|
||||
provider: DEFAULT_SECRET_PROVIDER_ALIAS,
|
||||
id: value.id,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function resolveWebProviderConfig(
|
||||
cfg: WebProviderConfigSource | undefined,
|
||||
kind: "search" | "fetch",
|
||||
|
||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@@ -1838,6 +1838,8 @@ importers:
|
||||
specifier: workspace:*
|
||||
version: link:../gateway-client
|
||||
|
||||
packages/secrets-core: {}
|
||||
|
||||
packages/speech-core:
|
||||
dependencies:
|
||||
openclaw:
|
||||
@@ -1853,10 +1855,14 @@ importers:
|
||||
specifier: 5.6.2
|
||||
version: 5.6.2
|
||||
|
||||
packages/web-content-core: {}
|
||||
|
||||
packages/tool-call-repair: {}
|
||||
|
||||
packages/web-content-core:
|
||||
dependencies:
|
||||
'@openclaw/secrets-core':
|
||||
specifier: workspace:*
|
||||
version: link:../secrets-core
|
||||
|
||||
ui:
|
||||
dependencies:
|
||||
'@create-markdown/preview':
|
||||
|
||||
@@ -51,6 +51,7 @@ export const BUILD_ALL_STEPS = [
|
||||
"packages/media-understanding-common/package.json",
|
||||
"packages/terminal-core/package.json",
|
||||
"packages/model-catalog-core/package.json",
|
||||
"packages/secrets-core/package.json",
|
||||
"packages/web-content-core/package.json",
|
||||
"packages/memory-host-sdk/package.json",
|
||||
"tsconfig.json",
|
||||
@@ -59,6 +60,7 @@ export const BUILD_ALL_STEPS = [
|
||||
"packages/llm-core/src",
|
||||
"packages/markdown-core/src",
|
||||
"packages/model-catalog-core/src",
|
||||
"packages/secrets-core/src",
|
||||
"packages/memory-host-sdk/src",
|
||||
"packages/media-generation-core/src",
|
||||
"packages/media-understanding-common/src",
|
||||
|
||||
@@ -116,6 +116,14 @@ export const EXTENSION_PACKAGE_BOUNDARY_BASE_PATHS = {
|
||||
"@openclaw/media-generation-core/*": [
|
||||
"../dist/plugin-sdk/packages/media-generation-core/src/*.d.ts",
|
||||
],
|
||||
"@openclaw/secrets-core": ["../dist/plugin-sdk/packages/secrets-core/src/index.d.ts"],
|
||||
"@openclaw/secrets-core/secret-input": [
|
||||
"../dist/plugin-sdk/packages/secrets-core/src/secret-input.d.ts",
|
||||
],
|
||||
"@openclaw/secrets-core/secret-ref": [
|
||||
"../dist/plugin-sdk/packages/secrets-core/src/secret-ref.d.ts",
|
||||
],
|
||||
"@openclaw/secrets-core/*": ["../dist/plugin-sdk/packages/secrets-core/src/*.d.ts"],
|
||||
"@openclaw/terminal-core": ["../dist/plugin-sdk/packages/terminal-core/src/index.d.ts"],
|
||||
"@openclaw/terminal-core/ansi": ["../dist/plugin-sdk/packages/terminal-core/src/ansi.d.ts"],
|
||||
"@openclaw/terminal-core/decorative-emoji": [
|
||||
|
||||
@@ -9,6 +9,7 @@ const TSDOWN_PACKAGE_NAMES = [
|
||||
"media-understanding-common",
|
||||
"model-catalog-core",
|
||||
"net-policy",
|
||||
"secrets-core",
|
||||
"speech-core",
|
||||
"terminal-core",
|
||||
];
|
||||
|
||||
@@ -18,6 +18,7 @@ const PLUGIN_SDK_TYPE_INPUTS = [
|
||||
"packages/llm-core/src",
|
||||
"packages/markdown-core/src",
|
||||
"packages/model-catalog-core/src",
|
||||
"packages/secrets-core/src",
|
||||
"packages/memory-host-sdk/src",
|
||||
"packages/media-generation-core/src",
|
||||
"packages/media-understanding-common/src",
|
||||
@@ -76,6 +77,9 @@ const ROOT_DTS_REQUIRED_OUTPUTS = [
|
||||
"dist/plugin-sdk/packages/model-catalog-core/src/provider-id.d.ts",
|
||||
"dist/plugin-sdk/packages/model-catalog-core/src/provider-model-id-normalization.d.ts",
|
||||
"dist/plugin-sdk/packages/model-catalog-core/src/provider-model-id-normalize.d.ts",
|
||||
"dist/plugin-sdk/packages/secrets-core/src/index.d.ts",
|
||||
"dist/plugin-sdk/packages/secrets-core/src/secret-input.d.ts",
|
||||
"dist/plugin-sdk/packages/secrets-core/src/secret-ref.d.ts",
|
||||
"dist/plugin-sdk/error-runtime.d.ts",
|
||||
"dist/plugin-sdk/plugin-entry.d.ts",
|
||||
"dist/plugin-sdk/provider-auth.d.ts",
|
||||
@@ -105,6 +109,9 @@ const PACKAGE_DTS_REQUIRED_OUTPUTS = [
|
||||
"packages/plugin-sdk/dist/packages/model-catalog-core/src/provider-id.d.ts",
|
||||
"packages/plugin-sdk/dist/packages/model-catalog-core/src/provider-model-id-normalization.d.ts",
|
||||
"packages/plugin-sdk/dist/packages/model-catalog-core/src/provider-model-id-normalize.d.ts",
|
||||
"packages/plugin-sdk/dist/packages/secrets-core/src/index.d.ts",
|
||||
"packages/plugin-sdk/dist/packages/secrets-core/src/secret-input.d.ts",
|
||||
"packages/plugin-sdk/dist/packages/secrets-core/src/secret-ref.d.ts",
|
||||
"packages/plugin-sdk/dist/packages/terminal-core/src/ansi.d.ts",
|
||||
"packages/plugin-sdk/dist/packages/terminal-core/src/decorative-emoji.d.ts",
|
||||
"packages/plugin-sdk/dist/packages/terminal-core/src/health-style.d.ts",
|
||||
|
||||
@@ -1,258 +1,28 @@
|
||||
import { isRecord } from "../utils.js";
|
||||
|
||||
export type SecretRefSource = "env" | "file" | "exec"; // pragma: allowlist secret
|
||||
|
||||
/**
|
||||
* Stable identifier for a secret in a configured source.
|
||||
* Examples:
|
||||
* - env source: provider "default", id "OPENAI_API_KEY"
|
||||
* - file source: provider "mounted-json", id "/providers/openai/apiKey"
|
||||
* - exec source: provider "vault", id "openai/api-key"
|
||||
*/
|
||||
export type SecretRef = {
|
||||
source: SecretRefSource;
|
||||
provider: string;
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type SecretInput = string | SecretRef;
|
||||
export const DEFAULT_SECRET_PROVIDER_ALIAS = "default"; // pragma: allowlist secret
|
||||
export const ENV_SECRET_REF_ID_RE = /^[A-Z][A-Z0-9_]{0,127}$/;
|
||||
export const LEGACY_SECRETREF_ENV_MARKER_PREFIX = "secretref-env:"; // pragma: allowlist secret
|
||||
export const LEGACY_DOUBLE_UNDERSCORE_ENV_MARKER_PREFIX = "__env__:"; // pragma: allowlist secret
|
||||
const ENV_SECRET_TEMPLATE_RE = /^\$\{([A-Z][A-Z0-9_]{0,127})\}$/;
|
||||
const ENV_SECRET_SHORTHAND_RE = /^\$([A-Z][A-Z0-9_]{0,127})$/;
|
||||
export type SecretInputStringResolutionMode = "strict" | "inspect";
|
||||
export type SecretInputStringResolution =
|
||||
| { status: "available"; value: string; ref: null }
|
||||
| { status: "configured_unavailable"; value: undefined; ref: SecretRef }
|
||||
| { status: "missing"; value: undefined; ref: null };
|
||||
type SecretDefaults = {
|
||||
env?: string;
|
||||
file?: string;
|
||||
exec?: string;
|
||||
};
|
||||
|
||||
export function isValidEnvSecretRefId(value: string): boolean {
|
||||
return ENV_SECRET_REF_ID_RE.test(value);
|
||||
}
|
||||
|
||||
export function isSecretRef(value: unknown): value is SecretRef {
|
||||
if (!isRecord(value)) {
|
||||
return false;
|
||||
}
|
||||
if (Object.keys(value).length !== 3) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
(value.source === "env" || value.source === "file" || value.source === "exec") &&
|
||||
typeof value.provider === "string" &&
|
||||
value.provider.trim().length > 0 &&
|
||||
typeof value.id === "string" &&
|
||||
value.id.trim().length > 0
|
||||
);
|
||||
}
|
||||
|
||||
function isLegacySecretRefWithoutProvider(
|
||||
value: unknown,
|
||||
): value is { source: SecretRefSource; id: string } {
|
||||
if (!isRecord(value)) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
(value.source === "env" || value.source === "file" || value.source === "exec") &&
|
||||
typeof value.id === "string" &&
|
||||
value.id.trim().length > 0 &&
|
||||
value.provider === undefined
|
||||
);
|
||||
}
|
||||
|
||||
export function parseEnvTemplateSecretRef(
|
||||
value: unknown,
|
||||
provider = DEFAULT_SECRET_PROVIDER_ALIAS,
|
||||
): SecretRef | null {
|
||||
if (typeof value !== "string") {
|
||||
return null;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
const match = ENV_SECRET_TEMPLATE_RE.exec(trimmed) ?? ENV_SECRET_SHORTHAND_RE.exec(trimmed);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
source: "env",
|
||||
provider: provider.trim() || DEFAULT_SECRET_PROVIDER_ALIAS,
|
||||
id: match[1],
|
||||
};
|
||||
}
|
||||
|
||||
export function parseLegacySecretRefEnvMarker(
|
||||
value: unknown,
|
||||
provider = DEFAULT_SECRET_PROVIDER_ALIAS,
|
||||
): SecretRef | null {
|
||||
if (typeof value !== "string") {
|
||||
return null;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
const prefix = trimmed.startsWith(LEGACY_SECRETREF_ENV_MARKER_PREFIX)
|
||||
? LEGACY_SECRETREF_ENV_MARKER_PREFIX
|
||||
: trimmed.startsWith(LEGACY_DOUBLE_UNDERSCORE_ENV_MARKER_PREFIX)
|
||||
? LEGACY_DOUBLE_UNDERSCORE_ENV_MARKER_PREFIX
|
||||
: undefined;
|
||||
if (!prefix) {
|
||||
return null;
|
||||
}
|
||||
const id = trimmed.slice(prefix.length);
|
||||
if (!ENV_SECRET_REF_ID_RE.test(id)) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
source: "env",
|
||||
provider: provider.trim() || DEFAULT_SECRET_PROVIDER_ALIAS,
|
||||
id,
|
||||
};
|
||||
}
|
||||
|
||||
export function coerceSecretRef(value: unknown, defaults?: SecretDefaults): SecretRef | null {
|
||||
if (isSecretRef(value)) {
|
||||
return value;
|
||||
}
|
||||
const legacyEnvMarker = parseLegacySecretRefEnvMarker(value, defaults?.env);
|
||||
if (legacyEnvMarker) {
|
||||
return legacyEnvMarker;
|
||||
}
|
||||
if (isLegacySecretRefWithoutProvider(value)) {
|
||||
const provider =
|
||||
value.source === "env"
|
||||
? (defaults?.env ?? DEFAULT_SECRET_PROVIDER_ALIAS)
|
||||
: value.source === "file"
|
||||
? (defaults?.file ?? DEFAULT_SECRET_PROVIDER_ALIAS)
|
||||
: (defaults?.exec ?? DEFAULT_SECRET_PROVIDER_ALIAS);
|
||||
return {
|
||||
source: value.source,
|
||||
provider,
|
||||
id: value.id,
|
||||
};
|
||||
}
|
||||
const envTemplate = parseEnvTemplateSecretRef(value, defaults?.env);
|
||||
if (envTemplate) {
|
||||
return envTemplate;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function hasConfiguredSecretInput(value: unknown, defaults?: SecretDefaults): boolean {
|
||||
if (normalizeSecretInputString(value)) {
|
||||
return true;
|
||||
}
|
||||
return coerceSecretRef(value, defaults) !== null;
|
||||
}
|
||||
|
||||
export function normalizeSecretInputString(value: unknown): string | undefined {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
return trimmed.length > 0 ? trimmed : undefined;
|
||||
}
|
||||
|
||||
function formatSecretRefLabel(ref: SecretRef): string {
|
||||
return `${ref.source}:${ref.provider}:${ref.id}`;
|
||||
}
|
||||
|
||||
function createUnresolvedSecretInputError(params: { path: string; ref: SecretRef }): Error {
|
||||
return new Error(
|
||||
`${params.path}: unresolved SecretRef "${formatSecretRefLabel(params.ref)}". Resolve this command against an active gateway runtime snapshot before reading it.`,
|
||||
);
|
||||
}
|
||||
|
||||
export function assertSecretInputResolved(params: {
|
||||
value: unknown;
|
||||
refValue?: unknown;
|
||||
defaults?: SecretDefaults;
|
||||
path: string;
|
||||
}): void {
|
||||
const { ref } = resolveSecretInputRef({
|
||||
value: params.value,
|
||||
refValue: params.refValue,
|
||||
defaults: params.defaults,
|
||||
});
|
||||
if (!ref) {
|
||||
return;
|
||||
}
|
||||
throw createUnresolvedSecretInputError({ path: params.path, ref });
|
||||
}
|
||||
|
||||
export function resolveSecretInputString(params: {
|
||||
value: unknown;
|
||||
refValue?: unknown;
|
||||
defaults?: SecretDefaults;
|
||||
path: string;
|
||||
mode?: SecretInputStringResolutionMode;
|
||||
}): SecretInputStringResolution {
|
||||
const normalized = normalizeSecretInputString(params.value);
|
||||
if (normalized) {
|
||||
return {
|
||||
status: "available",
|
||||
value: normalized,
|
||||
ref: null,
|
||||
};
|
||||
}
|
||||
const { ref } = resolveSecretInputRef({
|
||||
value: params.value,
|
||||
refValue: params.refValue,
|
||||
defaults: params.defaults,
|
||||
});
|
||||
if (!ref) {
|
||||
return {
|
||||
status: "missing",
|
||||
value: undefined,
|
||||
ref: null,
|
||||
};
|
||||
}
|
||||
if ((params.mode ?? "strict") === "strict") {
|
||||
throw createUnresolvedSecretInputError({ path: params.path, ref });
|
||||
}
|
||||
return {
|
||||
status: "configured_unavailable",
|
||||
value: undefined,
|
||||
ref,
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeResolvedSecretInputString(params: {
|
||||
value: unknown;
|
||||
refValue?: unknown;
|
||||
defaults?: SecretDefaults;
|
||||
path: string;
|
||||
}): string | undefined {
|
||||
const resolved = resolveSecretInputString({
|
||||
...params,
|
||||
mode: "strict",
|
||||
});
|
||||
if (resolved.status === "available") {
|
||||
return resolved.value;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function resolveSecretInputRef(params: {
|
||||
value: unknown;
|
||||
refValue?: unknown;
|
||||
defaults?: SecretDefaults;
|
||||
}): {
|
||||
explicitRef: SecretRef | null;
|
||||
inlineRef: SecretRef | null;
|
||||
ref: SecretRef | null;
|
||||
} {
|
||||
const explicitRef = coerceSecretRef(params.refValue, params.defaults);
|
||||
const inlineRef = explicitRef ? null : coerceSecretRef(params.value, params.defaults);
|
||||
return {
|
||||
explicitRef,
|
||||
inlineRef,
|
||||
ref: explicitRef ?? inlineRef,
|
||||
};
|
||||
}
|
||||
export {
|
||||
DEFAULT_SECRET_PROVIDER_ALIAS,
|
||||
ENV_SECRET_REF_ID_RE,
|
||||
LEGACY_DOUBLE_UNDERSCORE_ENV_MARKER_PREFIX,
|
||||
LEGACY_SECRETREF_ENV_MARKER_PREFIX,
|
||||
assertSecretInputResolved,
|
||||
coerceSecretRef,
|
||||
hasConfiguredSecretInput,
|
||||
isSecretRef,
|
||||
isValidEnvSecretRefId,
|
||||
normalizeResolvedSecretInputString,
|
||||
normalizeSecretInput,
|
||||
normalizeSecretInputString,
|
||||
parseEnvTemplateSecretRef,
|
||||
parseLegacySecretRefEnvMarker,
|
||||
resolveSecretInputRef,
|
||||
resolveSecretInputString,
|
||||
} from "../../packages/secrets-core/src/index.js";
|
||||
export type {
|
||||
SecretInput,
|
||||
SecretInputStringResolution,
|
||||
SecretInputStringResolutionMode,
|
||||
SecretRef,
|
||||
SecretRefSource,
|
||||
} from "../../packages/secrets-core/src/index.js";
|
||||
|
||||
export type EnvSecretProviderConfig = {
|
||||
source: "env";
|
||||
|
||||
@@ -197,6 +197,7 @@ describe("opt-in extension package boundaries", () => {
|
||||
"../../packages/markdown-core/src/**/*.ts",
|
||||
"../../packages/media-generation-core/src/**/*.ts",
|
||||
"../../packages/model-catalog-core/src/**/*.ts",
|
||||
"../../packages/secrets-core/src/**/*.ts",
|
||||
"../../packages/terminal-core/src/**/*.ts",
|
||||
"../../src/plugin-sdk/**/*.ts",
|
||||
"../../src/video-generation/dashscope-compatible.ts",
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { normalizeOptionalSecretInput, normalizeSecretInput } from "./normalize-secret-input.js";
|
||||
|
||||
describe("normalizeSecretInput", () => {
|
||||
it("returns empty string for non-string values", () => {
|
||||
expect(normalizeSecretInput(undefined)).toBe("");
|
||||
expect(normalizeSecretInput(null)).toBe("");
|
||||
expect(normalizeSecretInput(123)).toBe("");
|
||||
expect(normalizeSecretInput({})).toBe("");
|
||||
});
|
||||
|
||||
it("strips embedded line breaks and surrounding whitespace", () => {
|
||||
expect(normalizeSecretInput(" sk-\r\nabc\n123 ")).toBe("sk-abc123");
|
||||
});
|
||||
|
||||
it("drops non-Latin1 code points that can break HTTP ByteString headers", () => {
|
||||
// U+0417 (Cyrillic З) and U+2502 (box drawing │) are > 255.
|
||||
expect(normalizeSecretInput("key-\u0417\u2502-token")).toBe("key--token");
|
||||
});
|
||||
|
||||
it("preserves Latin-1 characters and internal spaces", () => {
|
||||
expect(normalizeSecretInput(" café token ")).toBe("café token");
|
||||
});
|
||||
});
|
||||
|
||||
describe("normalizeOptionalSecretInput", () => {
|
||||
it("returns undefined when normalized value is empty", () => {
|
||||
expect(normalizeOptionalSecretInput(" \r\n ")).toBeUndefined();
|
||||
expect(normalizeOptionalSecretInput("\u0417\u2502")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns normalized value when non-empty", () => {
|
||||
expect(normalizeOptionalSecretInput(" key-\u0417 ")).toBe("key-");
|
||||
});
|
||||
});
|
||||
@@ -1,34 +1,4 @@
|
||||
/**
|
||||
* Secret normalization for copy/pasted credentials.
|
||||
*
|
||||
* Common footgun: line breaks (especially `\r`) embedded in API keys/tokens.
|
||||
* We strip line breaks anywhere, then trim whitespace at the ends.
|
||||
*
|
||||
* Another frequent source of runtime failures is rich-text/Unicode artifacts
|
||||
* (smart punctuation, box-drawing chars, etc.) pasted into API keys. These can
|
||||
* break HTTP header construction (`ByteString` violations). Drop non-Latin1
|
||||
* code points so malformed keys fail as auth errors instead of crashing request
|
||||
* setup.
|
||||
*
|
||||
* Intentionally does NOT remove ordinary spaces inside the string to avoid
|
||||
* silently altering "Bearer <token>" style values.
|
||||
*/
|
||||
export function normalizeSecretInput(value: unknown): string {
|
||||
if (typeof value !== "string") {
|
||||
return "";
|
||||
}
|
||||
const collapsed = value.replace(/[\r\n\u2028\u2029]+/g, "");
|
||||
let latin1Only = "";
|
||||
for (const char of collapsed) {
|
||||
const codePoint = char.codePointAt(0);
|
||||
if (typeof codePoint === "number" && codePoint <= 0xff) {
|
||||
latin1Only += char;
|
||||
}
|
||||
}
|
||||
return latin1Only.trim();
|
||||
}
|
||||
|
||||
export function normalizeOptionalSecretInput(value: unknown): string | undefined {
|
||||
const normalized = normalizeSecretInput(value);
|
||||
return normalized ? normalized : undefined;
|
||||
}
|
||||
export {
|
||||
normalizeOptionalSecretInput,
|
||||
normalizeSecretInput,
|
||||
} from "../../packages/secrets-core/src/secret-input.js";
|
||||
|
||||
@@ -331,6 +331,18 @@ export const sharedVitestConfig = {
|
||||
find: "@openclaw/net-policy",
|
||||
replacement: path.join(repoRoot, "packages", "net-policy", "src", "index.ts"),
|
||||
},
|
||||
{
|
||||
find: "@openclaw/secrets-core/secret-input",
|
||||
replacement: path.join(repoRoot, "packages", "secrets-core", "src", "secret-input.ts"),
|
||||
},
|
||||
{
|
||||
find: "@openclaw/secrets-core/secret-ref",
|
||||
replacement: path.join(repoRoot, "packages", "secrets-core", "src", "secret-ref.ts"),
|
||||
},
|
||||
{
|
||||
find: "@openclaw/secrets-core",
|
||||
replacement: path.join(repoRoot, "packages", "secrets-core", "src", "index.ts"),
|
||||
},
|
||||
...sourcePluginSdkSubpaths.map((subpath) => ({
|
||||
find: `openclaw/plugin-sdk/${subpath}`,
|
||||
replacement: path.join(repoRoot, "src", "plugin-sdk", `${subpath}.ts`),
|
||||
|
||||
@@ -149,6 +149,10 @@
|
||||
],
|
||||
"@openclaw/net-policy/url-userinfo": ["./packages/net-policy/src/url-userinfo.ts"],
|
||||
"@openclaw/net-policy/*": ["./packages/net-policy/src/*"],
|
||||
"@openclaw/secrets-core": ["./packages/secrets-core/src/index.ts"],
|
||||
"@openclaw/secrets-core/secret-input": ["./packages/secrets-core/src/secret-input.ts"],
|
||||
"@openclaw/secrets-core/secret-ref": ["./packages/secrets-core/src/secret-ref.ts"],
|
||||
"@openclaw/secrets-core/*": ["./packages/secrets-core/src/*"],
|
||||
"@openclaw/web-content-core": ["./packages/web-content-core/src/index.ts"],
|
||||
"@openclaw/web-content-core/provider-runtime-shared": [
|
||||
"./packages/web-content-core/src/provider-runtime-shared.ts"
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"packages/media-generation-core/src/**/*.ts",
|
||||
"packages/model-catalog-core/src/**/*.ts",
|
||||
"packages/memory-host-sdk/src/**/*.ts",
|
||||
"packages/secrets-core/src/**/*.ts",
|
||||
"packages/terminal-core/src/**/*.ts",
|
||||
"src/video-generation/dashscope-compatible.ts",
|
||||
"src/video-generation/types.ts",
|
||||
|
||||
@@ -455,6 +455,14 @@ function buildWebContentCoreDistEntries(): Record<string, string> {
|
||||
};
|
||||
}
|
||||
|
||||
function buildSecretsCoreDistEntries(): Record<string, string> {
|
||||
return {
|
||||
index: "packages/secrets-core/src/index.ts",
|
||||
"secret-input": "packages/secrets-core/src/secret-input.ts",
|
||||
"secret-ref": "packages/secrets-core/src/secret-ref.ts",
|
||||
};
|
||||
}
|
||||
|
||||
function buildSpeechCoreDistEntries(): Record<string, string> {
|
||||
return {
|
||||
api: "packages/speech-core/api.ts",
|
||||
@@ -654,6 +662,12 @@ export default defineConfig([
|
||||
neverBundle: shouldExternalizeTerminalCoreDependency,
|
||||
},
|
||||
}),
|
||||
nodeWorkspacePackageBuildConfig({
|
||||
clean: true,
|
||||
dts: RUN_NODE_SKIP_DTS_BUILD ? false : undefined,
|
||||
entry: buildSecretsCoreDistEntries(),
|
||||
outDir: tsdownPackageOutputRoot("secrets-core"),
|
||||
}),
|
||||
nodeWorkspacePackageBuildConfig({
|
||||
clean: true,
|
||||
dts: RUN_NODE_SKIP_DTS_BUILD ? false : undefined,
|
||||
|
||||
Reference in New Issue
Block a user