@agentproto/runtime 0.1.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.
@@ -0,0 +1,57 @@
1
+ /**
2
+ * `~/.agentproto/providers.json` — the provider API-key store (mode 0600).
3
+ *
4
+ * Distinct from `credentials.json` (host-binding OAuth tokens for
5
+ * `serve --connect`) and from per-adapter `setup[]` tokens. This file holds
6
+ * the LLM/model **provider** keys (anthropic, openrouter, openai, …) that the
7
+ * model gateways in spawned agents read from the environment.
8
+ *
9
+ * Why a store at all: before this, the only way to give the daemon a provider
10
+ * key was an ambient `export FOO_API_KEY=…` in whatever shell launched
11
+ * `serve`. That's invisible, easy to forget, and per-shell. Storing the keys
12
+ * here (0600, same-user-only) lets `serve` inject them at boot — set once,
13
+ * works for every daemon. Explicit env still wins (see injectProviderKeysIntoEnv).
14
+ *
15
+ * Keys never leave this file except into the daemon's own process env; they
16
+ * are never logged. A browser-loaded localhost page can't read a 0600 file —
17
+ * the same defence the runtime.json bearer token relies on.
18
+ */
19
+ /**
20
+ * Canonical provider → environment-variable name. The spawned model gateways
21
+ * (Mastra in mastra-agent, hermes/opencode's routers) read the provider key
22
+ * from these env names. Aligned with the adapter manifests' `models.env`
23
+ * maps + the common SDK conventions.
24
+ */
25
+ declare const PROVIDER_ENV_VARS: Readonly<Record<string, string>>;
26
+ type KnownProvider = keyof typeof PROVIDER_ENV_VARS;
27
+ interface ProviderEntry {
28
+ /** The provider API key (or gateway key). */
29
+ apiKey: string;
30
+ /** Optional custom base URL (self-hosted / proxy). */
31
+ baseUrl?: string;
32
+ /** Wall-clock ISO timestamp the key was last set. */
33
+ updatedAt: string;
34
+ }
35
+ interface ProvidersFile {
36
+ version: 1;
37
+ providers: Record<string, ProviderEntry>;
38
+ }
39
+ declare function providersPath(): string;
40
+ /** Resolve the env-var name for a provider (canonical map, else
41
+ * `<PROVIDER>_API_KEY` upper-snake fallback so new providers still work). */
42
+ declare function providerEnvVar(provider: string): string;
43
+ declare function loadProviders(): Promise<ProvidersFile>;
44
+ /** Set (or replace) a provider's key. Returns the env-var it maps to. */
45
+ declare function setProviderKey(provider: string, apiKey: string, baseUrl?: string): Promise<string>;
46
+ /** Remove a provider's key. Returns true if it existed. */
47
+ declare function removeProviderKey(provider: string): Promise<boolean>;
48
+ /**
49
+ * Inject stored provider keys into a target env (default `process.env`).
50
+ * **Explicit env always wins** — a var already set is never overwritten, so a
51
+ * one-off `FOO_API_KEY=… serve` or a CI secret takes precedence over the
52
+ * store. Returns the list of provider names actually injected (for a boot log
53
+ * that names providers, never values).
54
+ */
55
+ declare function injectProviderKeysIntoEnv(env?: NodeJS.ProcessEnv): Promise<string[]>;
56
+
57
+ export { type KnownProvider, PROVIDER_ENV_VARS, type ProviderEntry, type ProvidersFile, injectProviderKeysIntoEnv, loadProviders, providerEnvVar, providersPath, removeProviderKey, setProviderKey };
@@ -0,0 +1,86 @@
1
+ import { readFile, mkdir, writeFile } from 'fs/promises';
2
+ import { homedir } from 'os';
3
+ import { resolve, join } from 'path';
4
+
5
+ /**
6
+ * @agentproto/runtime v0.1.0-alpha
7
+ * Long-running gateway: MCP server + HTTP transport + HEARTBEAT autonomy + conversation persistence over a workspace dir.
8
+ */
9
+
10
+ var PROVIDER_ENV_VARS = {
11
+ anthropic: "ANTHROPIC_API_KEY",
12
+ openrouter: "OPENROUTER_API_KEY",
13
+ openai: "OPENAI_API_KEY",
14
+ google: "GOOGLE_GENERATIVE_AI_API_KEY",
15
+ groq: "GROQ_API_KEY",
16
+ mistral: "MISTRAL_API_KEY",
17
+ // Vercel AI Gateway — a gateway provider, like openrouter, that fronts many
18
+ // upstream model families behind one key.
19
+ "vercel-ai-gateway": "AI_GATEWAY_API_KEY"
20
+ };
21
+ function emptyFile() {
22
+ return { version: 1, providers: {} };
23
+ }
24
+ function providersPath() {
25
+ return resolve(homedir(), ".agentproto", "providers.json");
26
+ }
27
+ function providerEnvVar(provider) {
28
+ return PROVIDER_ENV_VARS[provider] ?? `${provider.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}_API_KEY`;
29
+ }
30
+ async function loadProviders() {
31
+ try {
32
+ const raw = await readFile(providersPath(), "utf8");
33
+ const parsed = JSON.parse(raw);
34
+ if (!parsed || typeof parsed !== "object" || !("providers" in parsed) || typeof parsed.providers !== "object" || parsed.providers === null) {
35
+ return emptyFile();
36
+ }
37
+ return {
38
+ version: 1,
39
+ providers: parsed.providers
40
+ };
41
+ } catch {
42
+ return emptyFile();
43
+ }
44
+ }
45
+ async function writeProviders(file) {
46
+ const dir = join(homedir(), ".agentproto");
47
+ await mkdir(dir, { recursive: true });
48
+ await writeFile(providersPath(), JSON.stringify(file, null, 2) + "\n", {
49
+ encoding: "utf8",
50
+ mode: 384
51
+ });
52
+ }
53
+ async function setProviderKey(provider, apiKey, baseUrl) {
54
+ const file = await loadProviders();
55
+ file.providers[provider] = {
56
+ apiKey,
57
+ ...baseUrl ? { baseUrl } : {},
58
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
59
+ };
60
+ await writeProviders(file);
61
+ return providerEnvVar(provider);
62
+ }
63
+ async function removeProviderKey(provider) {
64
+ const file = await loadProviders();
65
+ if (!(provider in file.providers)) return false;
66
+ delete file.providers[provider];
67
+ await writeProviders(file);
68
+ return true;
69
+ }
70
+ async function injectProviderKeysIntoEnv(env = process.env) {
71
+ const file = await loadProviders();
72
+ const injected = [];
73
+ for (const [provider, entry] of Object.entries(file.providers)) {
74
+ if (!entry?.apiKey) continue;
75
+ const name = providerEnvVar(provider);
76
+ if (env[name]) continue;
77
+ env[name] = entry.apiKey;
78
+ if (entry.baseUrl) env[`${provider.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}_BASE_URL`] = entry.baseUrl;
79
+ injected.push(provider);
80
+ }
81
+ return injected;
82
+ }
83
+
84
+ export { PROVIDER_ENV_VARS, injectProviderKeysIntoEnv, loadProviders, providerEnvVar, providersPath, removeProviderKey, setProviderKey };
85
+ //# sourceMappingURL=providers-store.mjs.map
86
+ //# sourceMappingURL=providers-store.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/providers-store.ts"],"names":[],"mappings":"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","file":"providers-store.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\"\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 */\nexport const PROVIDER_ENV_VARS: Readonly<Record<string, string>> = {\n anthropic: \"ANTHROPIC_API_KEY\",\n openrouter: \"OPENROUTER_API_KEY\",\n openai: \"OPENAI_API_KEY\",\n google: \"GOOGLE_GENERATIVE_AI_API_KEY\",\n groq: \"GROQ_API_KEY\",\n mistral: \"MISTRAL_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\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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentproto/runtime",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "@agentproto/runtime — long-running gateway that turns an agentproto workspace into a live runtime. Composes @agentproto/mcp-server (CRUD verbs) with HTTP transport, HEARTBEAT.md autonomy loop, and append-only conversation persistence. Drop a workspace dir, point your MCP client at it, and the agent ticks on its own.",
5
5
  "keywords": [
6
6
  "agentproto",
@@ -56,6 +56,11 @@
56
56
  "import": "./dist/config.mjs",
57
57
  "default": "./dist/config.mjs"
58
58
  },
59
+ "./providers-store": {
60
+ "types": "./dist/providers-store.d.ts",
61
+ "import": "./dist/providers-store.mjs",
62
+ "default": "./dist/providers-store.mjs"
63
+ },
59
64
  "./mcp-imports": {
60
65
  "types": "./dist/mcp-imports.d.ts",
61
66
  "import": "./dist/mcp-imports.mjs",
@@ -80,9 +85,11 @@
80
85
  "gray-matter": "^4.0.3",
81
86
  "ws": "^8.18.0",
82
87
  "zod": "^4.4.3",
88
+ "@agentproto/acp": "0.2.0",
89
+ "@agentproto/adapter-kit": "0.1.0",
83
90
  "@agentproto/agent": "0.2.0",
84
- "@agentproto/manifest": "0.2.0",
85
- "@agentproto/mcp-server": "0.2.0"
91
+ "@agentproto/mcp-server": "0.2.1",
92
+ "@agentproto/manifest": "0.2.0"
86
93
  },
87
94
  "devDependencies": {
88
95
  "@types/node": "^25.6.2",