@agentproto/providers-store 0.2.1 → 0.3.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/dist/index.d.ts CHANGED
@@ -32,6 +32,18 @@
32
32
  * through to here for free.
33
33
  */
34
34
  declare const PROVIDER_ENV_VARS: Readonly<Record<string, string>>;
35
+ /**
36
+ * Provider → ADDITIONAL env-var names its stored key also satisfies, beyond the
37
+ * canonical {@link PROVIDER_ENV_VARS} name. DERIVED from the catalog's
38
+ * {@link PROVIDER_KEY_ENV_ALIASES} (the single source of truth, same as
39
+ * `PROVIDER_ENV_VARS` derives from `PROVIDER_KEY_ENV`) so the alias facts live
40
+ * in one place. `injectProviderKeysIntoEnv` sets the canonical name AND every
41
+ * alias here, letting one `auth provider set` satisfy a consumer that reads a
42
+ * different env name (e.g. mastracode reads `GOOGLE_API_KEY`, not our canonical
43
+ * `GOOGLE_GENERATIVE_AI_API_KEY`). No non-catalog gateway provider has a known
44
+ * alias today; add them alongside the spread if one ever does.
45
+ */
46
+ declare const PROVIDER_ENV_ALIASES: Readonly<Record<string, readonly string[]>>;
35
47
  type KnownProvider = keyof typeof PROVIDER_ENV_VARS;
36
48
  interface ProviderEntry {
37
49
  /** The provider API key (or gateway key). */
@@ -49,6 +61,9 @@ declare function providersPath(): string;
49
61
  /** Resolve the env-var name for a provider (canonical map, else
50
62
  * `<PROVIDER>_API_KEY` upper-snake fallback so new providers still work). */
51
63
  declare function providerEnvVar(provider: string): string;
64
+ /** Additional env-var names a provider's stored key also satisfies, beyond
65
+ * {@link providerEnvVar}. Empty for providers with no verified alias. */
66
+ declare function providerEnvAliases(provider: string): readonly string[];
52
67
  declare function loadProviders(): Promise<ProvidersFile>;
53
68
  /**
54
69
  * Read a single provider's stored api key from `providers.json`, or undefined
@@ -67,9 +82,14 @@ declare function removeProviderKey(provider: string): Promise<boolean>;
67
82
  * Inject stored provider keys into a target env (default `process.env`).
68
83
  * **Explicit env always wins** — a var already set is never overwritten, so a
69
84
  * one-off `FOO_API_KEY=… serve` or a CI secret takes precedence over the
70
- * store. Returns the list of provider names actually injected (for a boot log
71
- * that names providers, never values).
85
+ * store. One provider key is written to its canonical env name AND every
86
+ * verified alias ({@link providerEnvAliases}) — so a consumer that reads a
87
+ * different env name for the same provider (e.g. mastracode reads
88
+ * `GOOGLE_API_KEY`, not our canonical `GOOGLE_GENERATIVE_AI_API_KEY`) is
89
+ * satisfied too. Returns the list of provider names actually injected (for a
90
+ * boot log that names providers, never values) — one entry per provider, not
91
+ * per env name.
72
92
  */
73
93
  declare function injectProviderKeysIntoEnv(env?: NodeJS.ProcessEnv): Promise<string[]>;
74
94
 
75
- export { type KnownProvider, PROVIDER_ENV_VARS, type ProviderEntry, type ProvidersFile, getProviderKey, injectProviderKeysIntoEnv, loadProviders, providerEnvVar, providersPath, removeProviderKey, setProviderKey };
95
+ export { type KnownProvider, PROVIDER_ENV_ALIASES, PROVIDER_ENV_VARS, type ProviderEntry, type ProvidersFile, getProviderKey, injectProviderKeysIntoEnv, loadProviders, providerEnvAliases, providerEnvVar, providersPath, removeProviderKey, setProviderKey };
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { readFile, mkdir, writeFile } from 'fs/promises';
2
2
  import { homedir } from 'os';
3
3
  import { resolve, join } from 'path';
4
- import { PROVIDER_KEY_ENV } from '@agentproto/model-catalog';
4
+ import { PROVIDER_KEY_ENV, PROVIDER_KEY_ENV_ALIASES } from '@agentproto/model-catalog';
5
5
 
6
6
  /**
7
7
  * @agentproto/providers-store v0.1.0
@@ -18,6 +18,9 @@ var PROVIDER_ENV_VARS = {
18
18
  // upstream model families behind one key.
19
19
  "vercel-ai-gateway": "AI_GATEWAY_API_KEY"
20
20
  };
21
+ var PROVIDER_ENV_ALIASES = {
22
+ ...PROVIDER_KEY_ENV_ALIASES
23
+ };
21
24
  function emptyFile() {
22
25
  return { version: 1, providers: {} };
23
26
  }
@@ -27,6 +30,9 @@ function providersPath() {
27
30
  function providerEnvVar(provider) {
28
31
  return PROVIDER_ENV_VARS[provider] ?? `${provider.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}_API_KEY`;
29
32
  }
33
+ function providerEnvAliases(provider) {
34
+ return PROVIDER_ENV_ALIASES[provider] ?? [];
35
+ }
30
36
  async function loadProviders() {
31
37
  try {
32
38
  const raw = await readFile(providersPath(), "utf8");
@@ -77,15 +83,20 @@ async function injectProviderKeysIntoEnv(env = process.env) {
77
83
  const injected = [];
78
84
  for (const [provider, entry] of Object.entries(file.providers)) {
79
85
  if (!entry?.apiKey) continue;
80
- const name = providerEnvVar(provider);
81
- if (env[name]) continue;
82
- env[name] = entry.apiKey;
86
+ const names = /* @__PURE__ */ new Set([providerEnvVar(provider), ...providerEnvAliases(provider)]);
87
+ let didInject = false;
88
+ for (const name of names) {
89
+ if (env[name]) continue;
90
+ env[name] = entry.apiKey;
91
+ didInject = true;
92
+ }
93
+ if (!didInject) continue;
83
94
  if (entry.baseUrl) env[`${provider.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}_BASE_URL`] = entry.baseUrl;
84
95
  injected.push(provider);
85
96
  }
86
97
  return injected;
87
98
  }
88
99
 
89
- export { PROVIDER_ENV_VARS, getProviderKey, injectProviderKeysIntoEnv, loadProviders, providerEnvVar, providersPath, removeProviderKey, setProviderKey };
100
+ export { PROVIDER_ENV_ALIASES, PROVIDER_ENV_VARS, getProviderKey, injectProviderKeysIntoEnv, loadProviders, providerEnvAliases, providerEnvVar, providersPath, removeProviderKey, setProviderKey };
90
101
  //# sourceMappingURL=index.mjs.map
91
102
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * `~/.agentproto/providers.json` — the provider API-key store (mode 0600).\n *\n * Distinct from `credentials.json` (host-binding OAuth tokens for\n * `serve --connect`) and from per-adapter `setup[]` tokens. This file holds\n * the LLM/model **provider** keys (anthropic, openrouter, openai, …) that the\n * model gateways in spawned agents read from the environment.\n *\n * Why a store at all: before this, the only way to give the daemon a provider\n * key was an ambient `export FOO_API_KEY=…` in whatever shell launched\n * `serve`. That's invisible, easy to forget, and per-shell. Storing the keys\n * here (0600, same-user-only) lets `serve` inject them at boot — set once,\n * works for every daemon. Explicit env still wins (see injectProviderKeysIntoEnv).\n *\n * Keys never leave this file except into the daemon's own process env; they\n * are never logged. A browser-loaded localhost page can't read a 0600 file —\n * the same defence the runtime.json bearer token relies on.\n */\n\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\"\nimport { homedir } from \"node:os\"\nimport { join, resolve } from \"node:path\"\nimport { PROVIDER_KEY_ENV } from \"@agentproto/model-catalog\"\n\n/**\n * Canonical provider → environment-variable name. The spawned model gateways\n * (Mastra in mastra-agent, hermes/opencode's routers) read the provider key\n * from these env names. Aligned with the adapter manifests' `models.env`\n * maps + the common SDK conventions.\n *\n * DERIVED from `@agentproto/model-catalog`'s {@link PROVIDER_KEY_ENV} (the\n * single source of truth, co-located with the provider enum — DECISION 1) so\n * the two maps that used to restate the same fact can't drift, plus the few\n * NON-catalog gateway providers this store also fronts (`groq`,\n * `vercel-ai-gateway`) which aren't LLM-catalog `provider` values but still\n * carry a key here. Adding a catalog provider forces adding its key env in the\n * catalog (the `Record<CatalogProvider, string>` is exhaustive), and it flows\n * through to here for free.\n */\nexport const PROVIDER_ENV_VARS: Readonly<Record<string, string>> = {\n ...PROVIDER_KEY_ENV,\n // ── Non-catalog gateway providers (not LLM-catalog `provider` enum values,\n // so absent from PROVIDER_KEY_ENV, but still keyed through this store) ──\n groq: \"GROQ_API_KEY\",\n xai: \"XAI_API_KEY\",\n // Vercel AI Gateway — a gateway provider, like openrouter, that fronts many\n // upstream model families behind one key.\n \"vercel-ai-gateway\": \"AI_GATEWAY_API_KEY\",\n}\n\nexport type KnownProvider = keyof typeof PROVIDER_ENV_VARS\n\nexport interface ProviderEntry {\n /** The provider API key (or gateway key). */\n apiKey: string\n /** Optional custom base URL (self-hosted / proxy). */\n baseUrl?: string\n /** Wall-clock ISO timestamp the key was last set. */\n updatedAt: string\n}\n\nexport interface ProvidersFile {\n version: 1\n providers: Record<string, ProviderEntry>\n}\n\n/** Fresh empty file each call — never share the `providers` object, or a\n * later `setProviderKey` mutation leaks into every other load in-process. */\nfunction emptyFile(): ProvidersFile {\n return { version: 1, providers: {} }\n}\n\nexport function providersPath(): string {\n return resolve(homedir(), \".agentproto\", \"providers.json\")\n}\n\n/** Resolve the env-var name for a provider (canonical map, else\n * `<PROVIDER>_API_KEY` upper-snake fallback so new providers still work). */\nexport function providerEnvVar(provider: string): string {\n return (\n PROVIDER_ENV_VARS[provider] ??\n `${provider.toUpperCase().replace(/[^A-Z0-9]+/g, \"_\")}_API_KEY`\n )\n}\n\nexport async function loadProviders(): Promise<ProvidersFile> {\n try {\n const raw = await readFile(providersPath(), \"utf8\")\n const parsed = JSON.parse(raw) as unknown\n if (\n !parsed ||\n typeof parsed !== \"object\" ||\n !(\"providers\" in parsed) ||\n typeof (parsed as Record<string, unknown>).providers !== \"object\" ||\n (parsed as Record<string, unknown>).providers === null\n ) {\n return emptyFile()\n }\n return {\n version: 1,\n providers: (parsed as ProvidersFile).providers,\n }\n } catch {\n return emptyFile() // ENOENT / malformed → empty\n }\n}\n\n/**\n * Read a single provider's stored api key from `providers.json`, or undefined\n * when the provider has no entry (or the file doesn't exist). The EXPLICIT,\n * verifiable credential source for the billing-auth resolver's api-key mode\n * (`agentproto auth provider set <provider> <key>` writes it) — distinct from\n * the ambient shell env, which the resolver never reads. Returns the raw key;\n * only a non-secret fingerprint of it is ever surfaced back to a caller.\n */\nexport async function getProviderKey(provider: string): Promise<string | undefined> {\n const file = await loadProviders()\n const entry = file.providers[provider]\n return entry?.apiKey || undefined\n}\n\nasync function writeProviders(file: ProvidersFile): Promise<void> {\n const dir = join(homedir(), \".agentproto\")\n await mkdir(dir, { recursive: true })\n // mode 0600 so other local users can't read the keys.\n await writeFile(providersPath(), JSON.stringify(file, null, 2) + \"\\n\", {\n encoding: \"utf8\",\n mode: 0o600,\n })\n}\n\n/** Set (or replace) a provider's key. Returns the env-var it maps to. */\nexport async function setProviderKey(\n provider: string,\n apiKey: string,\n baseUrl?: string,\n): Promise<string> {\n const file = await loadProviders()\n file.providers[provider] = {\n apiKey,\n ...(baseUrl ? { baseUrl } : {}),\n updatedAt: new Date().toISOString(),\n }\n await writeProviders(file)\n return providerEnvVar(provider)\n}\n\n/** Remove a provider's key. Returns true if it existed. */\nexport async function removeProviderKey(provider: string): Promise<boolean> {\n const file = await loadProviders()\n if (!(provider in file.providers)) return false\n delete file.providers[provider]\n await writeProviders(file)\n return true\n}\n\n/**\n * Inject stored provider keys into a target env (default `process.env`).\n * **Explicit env always wins** — a var already set is never overwritten, so a\n * one-off `FOO_API_KEY=… serve` or a CI secret takes precedence over the\n * store. Returns the list of provider names actually injected (for a boot log\n * that names providers, never values).\n */\nexport async function injectProviderKeysIntoEnv(\n env: NodeJS.ProcessEnv = process.env,\n): Promise<string[]> {\n const file = await loadProviders()\n const injected: string[] = []\n for (const [provider, entry] of Object.entries(file.providers)) {\n if (!entry?.apiKey) continue\n const name = providerEnvVar(provider)\n if (env[name]) continue // explicit env wins\n env[name] = entry.apiKey\n if (entry.baseUrl) env[`${provider.toUpperCase().replace(/[^A-Z0-9]+/g, \"_\")}_BASE_URL`] = entry.baseUrl\n injected.push(provider)\n }\n return injected\n}\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * `~/.agentproto/providers.json` — the provider API-key store (mode 0600).\n *\n * Distinct from `credentials.json` (host-binding OAuth tokens for\n * `serve --connect`) and from per-adapter `setup[]` tokens. This file holds\n * the LLM/model **provider** keys (anthropic, openrouter, openai, …) that the\n * model gateways in spawned agents read from the environment.\n *\n * Why a store at all: before this, the only way to give the daemon a provider\n * key was an ambient `export FOO_API_KEY=…` in whatever shell launched\n * `serve`. That's invisible, easy to forget, and per-shell. Storing the keys\n * here (0600, same-user-only) lets `serve` inject them at boot — set once,\n * works for every daemon. Explicit env still wins (see injectProviderKeysIntoEnv).\n *\n * Keys never leave this file except into the daemon's own process env; they\n * are never logged. A browser-loaded localhost page can't read a 0600 file —\n * the same defence the runtime.json bearer token relies on.\n */\n\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\"\nimport { homedir } from \"node:os\"\nimport { join, resolve } from \"node:path\"\nimport { PROVIDER_KEY_ENV, PROVIDER_KEY_ENV_ALIASES } from \"@agentproto/model-catalog\"\n\n/**\n * Canonical provider → environment-variable name. The spawned model gateways\n * (Mastra in mastra-agent, hermes/opencode's routers) read the provider key\n * from these env names. Aligned with the adapter manifests' `models.env`\n * maps + the common SDK conventions.\n *\n * DERIVED from `@agentproto/model-catalog`'s {@link PROVIDER_KEY_ENV} (the\n * single source of truth, co-located with the provider enum — DECISION 1) so\n * the two maps that used to restate the same fact can't drift, plus the few\n * NON-catalog gateway providers this store also fronts (`groq`,\n * `vercel-ai-gateway`) which aren't LLM-catalog `provider` values but still\n * carry a key here. Adding a catalog provider forces adding its key env in the\n * catalog (the `Record<CatalogProvider, string>` is exhaustive), and it flows\n * through to here for free.\n */\nexport const PROVIDER_ENV_VARS: Readonly<Record<string, string>> = {\n ...PROVIDER_KEY_ENV,\n // ── Non-catalog gateway providers (not LLM-catalog `provider` enum values,\n // so absent from PROVIDER_KEY_ENV, but still keyed through this store) ──\n groq: \"GROQ_API_KEY\",\n xai: \"XAI_API_KEY\",\n // Vercel AI Gateway — a gateway provider, like openrouter, that fronts many\n // upstream model families behind one key.\n \"vercel-ai-gateway\": \"AI_GATEWAY_API_KEY\",\n}\n\n/**\n * Provider → ADDITIONAL env-var names its stored key also satisfies, beyond the\n * canonical {@link PROVIDER_ENV_VARS} name. DERIVED from the catalog's\n * {@link PROVIDER_KEY_ENV_ALIASES} (the single source of truth, same as\n * `PROVIDER_ENV_VARS` derives from `PROVIDER_KEY_ENV`) so the alias facts live\n * in one place. `injectProviderKeysIntoEnv` sets the canonical name AND every\n * alias here, letting one `auth provider set` satisfy a consumer that reads a\n * different env name (e.g. mastracode reads `GOOGLE_API_KEY`, not our canonical\n * `GOOGLE_GENERATIVE_AI_API_KEY`). No non-catalog gateway provider has a known\n * alias today; add them alongside the spread if one ever does.\n */\nexport const PROVIDER_ENV_ALIASES: Readonly<Record<string, readonly string[]>> = {\n ...PROVIDER_KEY_ENV_ALIASES,\n}\n\nexport type KnownProvider = keyof typeof PROVIDER_ENV_VARS\n\nexport interface ProviderEntry {\n /** The provider API key (or gateway key). */\n apiKey: string\n /** Optional custom base URL (self-hosted / proxy). */\n baseUrl?: string\n /** Wall-clock ISO timestamp the key was last set. */\n updatedAt: string\n}\n\nexport interface ProvidersFile {\n version: 1\n providers: Record<string, ProviderEntry>\n}\n\n/** Fresh empty file each call — never share the `providers` object, or a\n * later `setProviderKey` mutation leaks into every other load in-process. */\nfunction emptyFile(): ProvidersFile {\n return { version: 1, providers: {} }\n}\n\nexport function providersPath(): string {\n return resolve(homedir(), \".agentproto\", \"providers.json\")\n}\n\n/** Resolve the env-var name for a provider (canonical map, else\n * `<PROVIDER>_API_KEY` upper-snake fallback so new providers still work). */\nexport function providerEnvVar(provider: string): string {\n return (\n PROVIDER_ENV_VARS[provider] ??\n `${provider.toUpperCase().replace(/[^A-Z0-9]+/g, \"_\")}_API_KEY`\n )\n}\n\n/** Additional env-var names a provider's stored key also satisfies, beyond\n * {@link providerEnvVar}. Empty for providers with no verified alias. */\nexport function providerEnvAliases(provider: string): readonly string[] {\n return PROVIDER_ENV_ALIASES[provider] ?? []\n}\n\nexport async function loadProviders(): Promise<ProvidersFile> {\n try {\n const raw = await readFile(providersPath(), \"utf8\")\n const parsed = JSON.parse(raw) as unknown\n if (\n !parsed ||\n typeof parsed !== \"object\" ||\n !(\"providers\" in parsed) ||\n typeof (parsed as Record<string, unknown>).providers !== \"object\" ||\n (parsed as Record<string, unknown>).providers === null\n ) {\n return emptyFile()\n }\n return {\n version: 1,\n providers: (parsed as ProvidersFile).providers,\n }\n } catch {\n return emptyFile() // ENOENT / malformed → empty\n }\n}\n\n/**\n * Read a single provider's stored api key from `providers.json`, or undefined\n * when the provider has no entry (or the file doesn't exist). The EXPLICIT,\n * verifiable credential source for the billing-auth resolver's api-key mode\n * (`agentproto auth provider set <provider> <key>` writes it) — distinct from\n * the ambient shell env, which the resolver never reads. Returns the raw key;\n * only a non-secret fingerprint of it is ever surfaced back to a caller.\n */\nexport async function getProviderKey(provider: string): Promise<string | undefined> {\n const file = await loadProviders()\n const entry = file.providers[provider]\n return entry?.apiKey || undefined\n}\n\nasync function writeProviders(file: ProvidersFile): Promise<void> {\n const dir = join(homedir(), \".agentproto\")\n await mkdir(dir, { recursive: true })\n // mode 0600 so other local users can't read the keys.\n await writeFile(providersPath(), JSON.stringify(file, null, 2) + \"\\n\", {\n encoding: \"utf8\",\n mode: 0o600,\n })\n}\n\n/** Set (or replace) a provider's key. Returns the env-var it maps to. */\nexport async function setProviderKey(\n provider: string,\n apiKey: string,\n baseUrl?: string,\n): Promise<string> {\n const file = await loadProviders()\n file.providers[provider] = {\n apiKey,\n ...(baseUrl ? { baseUrl } : {}),\n updatedAt: new Date().toISOString(),\n }\n await writeProviders(file)\n return providerEnvVar(provider)\n}\n\n/** Remove a provider's key. Returns true if it existed. */\nexport async function removeProviderKey(provider: string): Promise<boolean> {\n const file = await loadProviders()\n if (!(provider in file.providers)) return false\n delete file.providers[provider]\n await writeProviders(file)\n return true\n}\n\n/**\n * Inject stored provider keys into a target env (default `process.env`).\n * **Explicit env always wins** — a var already set is never overwritten, so a\n * one-off `FOO_API_KEY=… serve` or a CI secret takes precedence over the\n * store. One provider key is written to its canonical env name AND every\n * verified alias ({@link providerEnvAliases}) — so a consumer that reads a\n * different env name for the same provider (e.g. mastracode reads\n * `GOOGLE_API_KEY`, not our canonical `GOOGLE_GENERATIVE_AI_API_KEY`) is\n * satisfied too. Returns the list of provider names actually injected (for a\n * boot log that names providers, never values) — one entry per provider, not\n * per env name.\n */\nexport async function injectProviderKeysIntoEnv(\n env: NodeJS.ProcessEnv = process.env,\n): Promise<string[]> {\n const file = await loadProviders()\n const injected: string[] = []\n for (const [provider, entry] of Object.entries(file.providers)) {\n if (!entry?.apiKey) continue\n // Canonical name first, then any verified aliases the same key satisfies.\n // De-dupe defends against an alias that equals the canonical name.\n const names = new Set([providerEnvVar(provider), ...providerEnvAliases(provider)])\n let didInject = false\n for (const name of names) {\n if (env[name]) continue // explicit env wins — evaluated per env name\n env[name] = entry.apiKey\n didInject = true\n }\n if (!didInject) continue // every name for this provider is already set\n if (entry.baseUrl) env[`${provider.toUpperCase().replace(/[^A-Z0-9]+/g, \"_\")}_BASE_URL`] = entry.baseUrl\n injected.push(provider)\n }\n return injected\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentproto/providers-store",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "@agentproto/providers-store — the `~/.agentproto/providers.json` LLM provider API-key store (mode 0600) and the `injectProviderKeysIntoEnv` helper that lets any process (daemon or standalone gateway) boot with keys set via `agentproto auth provider set`, without ever logging a value. Explicit env always wins.",
5
5
  "keywords": [
6
6
  "agentproto",
@@ -41,7 +41,7 @@
41
41
  "access": "public"
42
42
  },
43
43
  "dependencies": {
44
- "@agentproto/model-catalog": "0.4.0"
44
+ "@agentproto/model-catalog": "0.5.0"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/node": "^25.6.2",