diff --git a/docs/architecture.md b/docs/architecture.md index df3cc9b..b4f9a38 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -391,7 +391,7 @@ Everything else is immutable CAS content. providers: openrouter: baseUrl: "https://openrouter.ai/api/v1" - apiKeyEnv: "OPENROUTER_API_KEY" + apiKey: "sk-..." models: sonnet: diff --git a/docs/builtin-agent-research.md b/docs/builtin-agent-research.md index 63690f5..a76ae7b 100644 --- a/docs/builtin-agent-research.md +++ b/docs/builtin-agent-research.md @@ -402,7 +402,7 @@ workflow 怎么配置和使用 model? ```136:160:packages/workflow-protocol/src/types.ts export type ProviderConfig = { baseUrl: string; - apiKeyEnv: string; + apiKey: string; }; export type ModelConfig = { @@ -429,7 +429,7 @@ export type WorkflowConfig = { export function resolveModel(config: WorkflowConfig, alias: ModelAlias): ResolvedLlmProvider { const modelEntry = config.models[alias]; const providerEntry = config.providers[modelEntry.provider]; - const apiKey = process.env[providerEntry.apiKeyEnv]; + const apiKey = providerEntry.apiKey; return { baseUrl: providerEntry.baseUrl, apiKey, model: modelEntry.name }; } ``` diff --git a/docs/wf-stateless-design.md b/docs/wf-stateless-design.md index 257891d..6a0b527 100644 --- a/docs/wf-stateless-design.md +++ b/docs/wf-stateless-design.md @@ -280,13 +280,13 @@ threads.yaml: { "01J7K9M2XNPQR5VWBCDF8G3H4T": "8FWKR3TN5V1QA" } providers: openai: baseUrl: "https://api.openai.com/v1" - apiKeyEnv: "OPENAI_API_KEY" + apiKey: "sk-..." anthropic: baseUrl: "https://api.anthropic.com/v1" - apiKeyEnv: "ANTHROPIC_API_KEY" + apiKey: "sk-ant-..." openrouter: baseUrl: "https://openrouter.ai/api/v1" - apiKeyEnv: "OPENROUTER_API_KEY" + apiKey: "sk-or-..." models: sonnet: @@ -465,7 +465,7 @@ type Scenario = string; // e.g. "extract" type ProviderConfig = { baseUrl: string; - apiKeyEnv: string; // env var name to read API key from + apiKey: string; // API key stored directly }; type ModelConfig = { diff --git a/packages/cli-workflow/README.md b/packages/cli-workflow/README.md index 2b9e2fd..92f7eaf 100644 --- a/packages/cli-workflow/README.md +++ b/packages/cli-workflow/README.md @@ -119,7 +119,7 @@ uwf setup --provider openai --base-url https://api.openai.com/v1 \ --api-key sk-... --model gpt-4o --agent hermes ``` -Config: `~/.uncaged/workflow/config.yaml`. API keys: `~/.uncaged/workflow/.env`. +Config: `~/.uncaged/workflow/config.yaml` (includes API keys). ### Skill diff --git a/packages/cli-workflow/src/__tests__/setup-validate.test.ts b/packages/cli-workflow/src/__tests__/setup-validate.test.ts index f96baa3..7221775 100644 --- a/packages/cli-workflow/src/__tests__/setup-validate.test.ts +++ b/packages/cli-workflow/src/__tests__/setup-validate.test.ts @@ -129,9 +129,8 @@ describe("cmdSetup with validation", () => { const result = await cmdSetup(setupArgs()); expect(result.validation).toEqual({ ok: true, value: undefined }); - // Config files should still be written + // Config file should still be written expect(result.configPath).toBeTruthy(); - expect(result.envPath).toBeTruthy(); }); test("includes validation failure — config still saved", async () => { @@ -143,8 +142,7 @@ describe("cmdSetup with validation", () => { expect(result.validation).toBeDefined(); expect((result.validation as { ok: boolean }).ok).toBe(false); - // Config files should still be written despite validation failure + // Config file should still be written despite validation failure expect(result.configPath).toBeTruthy(); - expect(result.envPath).toBeTruthy(); }); }); diff --git a/packages/cli-workflow/src/commands/setup.ts b/packages/cli-workflow/src/commands/setup.ts index 953a67e..99873e4 100644 --- a/packages/cli-workflow/src/commands/setup.ts +++ b/packages/cli-workflow/src/commands/setup.ts @@ -85,10 +85,6 @@ function getConfigPath(root: string): string { return join(root, "config.yaml"); } -function getEnvPath(root: string): string { - return join(root, ".env"); -} - /** * Load existing config.yaml or return empty structure. */ @@ -106,37 +102,6 @@ function loadExistingConfig(configPath: string): Record { return {}; } -/** - * Load existing .env as key=value map. - */ -function loadEnvFile(envPath: string): Record { - const env: Record = {}; - try { - if (existsSync(envPath)) { - for (const line of readFileSync(envPath, "utf8").split("\n")) { - const trimmed = line.trim(); - if (trimmed === "" || trimmed.startsWith("#")) continue; - const eq = trimmed.indexOf("="); - if (eq > 0) { - env[trimmed.slice(0, eq)] = trimmed.slice(eq + 1); - } - } - } - } catch { - // ignore - } - return env; -} - -function saveEnvFile(envPath: string, env: Record): void { - const lines = Object.entries(env).map(([k, v]) => `${k}=${v}`); - writeFileSync(envPath, `${lines.join("\n")}\n`, "utf8"); -} - -function apiKeyEnvName(providerName: string): string { - return `${providerName.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_API_KEY`; -} - // ────────────────────────────────────────────────────────────────────────────── // Extracted helpers — _discoverAgents // ────────────────────────────────────────────────────────────────────────────── @@ -397,8 +362,7 @@ function mergeConfig(existing: Record, args: SetupArgs): Record : {} ) as Record; - const envName = apiKeyEnvName(args.provider); - providers[args.provider] = { baseUrl: args.baseUrl, apiKeyEnv: envName }; + providers[args.provider] = { baseUrl: args.baseUrl, apiKey: args.apiKey }; const models = ( typeof existing.models === "object" && existing.models !== null @@ -437,25 +401,17 @@ export async function cmdSetup(args: SetupArgs): Promise mkdirSync(storageRoot, { recursive: true }); const configPath = getConfigPath(storageRoot); - const envPath = getEnvPath(storageRoot); const existing = loadExistingConfig(configPath); const merged = mergeConfig(existing, args); writeFileSync(configPath, stringify(merged, { indent: 2 }), "utf8"); - // Write API key to .env - const envName = apiKeyEnvName(args.provider); - const envData = loadEnvFile(envPath); - envData[envName] = args.apiKey; - saveEnvFile(envPath, envData); - // Validate model connectivity const validation = await validateModel(args.baseUrl, args.apiKey, args.model); return { configPath, - envPath, provider: args.provider, model: args.model, defaultAgent: merged.defaultAgent, diff --git a/packages/workflow-protocol/README.md b/packages/workflow-protocol/README.md index 466890b..3f0644f 100644 --- a/packages/workflow-protocol/README.md +++ b/packages/workflow-protocol/README.md @@ -100,7 +100,7 @@ type ProviderAlias = string; type ModelAlias = string; type AgentAlias = string; -type ProviderConfig = { baseUrl: string; apiKeyEnv: string }; +type ProviderConfig = { baseUrl: string; apiKey: string }; type ModelConfig = { provider: ProviderAlias; name: string; diff --git a/packages/workflow-protocol/src/types.ts b/packages/workflow-protocol/src/types.ts index aa6d5c1..65de96c 100644 --- a/packages/workflow-protocol/src/types.ts +++ b/packages/workflow-protocol/src/types.ts @@ -151,7 +151,7 @@ export type Scenario = string; export type ProviderConfig = { baseUrl: string; - apiKeyEnv: string; + apiKey: string; }; export type ModelConfig = { diff --git a/packages/workflow-util-agent/src/extract.ts b/packages/workflow-util-agent/src/extract.ts index 712046f..64807d2 100644 --- a/packages/workflow-util-agent/src/extract.ts +++ b/packages/workflow-util-agent/src/extract.ts @@ -1,8 +1,7 @@ import { getSchema, validate } from "@uncaged/json-cas"; import type { CasRef, ModelAlias, WorkflowConfig } from "@uncaged/workflow-protocol"; -import { config as loadDotenv } from "dotenv"; -import { createAgentStore, getEnvPath, resolveStorageRoot } from "./storage.js"; +import { createAgentStore, resolveStorageRoot } from "./storage.js"; export type ResolvedLlmProvider = { baseUrl: string; @@ -38,9 +37,9 @@ export function resolveModel(config: WorkflowConfig, alias: ModelAlias): Resolve if (providerEntry === undefined) { throw new Error(`unknown provider "${modelEntry.provider}" for model "${alias}"`); } - const apiKey = process.env[providerEntry.apiKeyEnv]; + const apiKey = providerEntry.apiKey; if (apiKey === undefined || apiKey === "") { - throw new Error(`missing API key env var: ${providerEntry.apiKeyEnv}`); + throw new Error(`missing API key for provider: ${modelEntry.provider}`); } return { baseUrl: providerEntry.baseUrl, @@ -130,7 +129,7 @@ export type ExtractResult = { /** * Call an OpenAI-compatible LLM to extract structured output matching outputSchema. - * Loads config.yaml and .env from the workflow storage root. + * Loads config.yaml from the workflow storage root. */ export async function extract( rawOutput: string, @@ -138,7 +137,6 @@ export async function extract( config: WorkflowConfig, ): Promise { const storageRoot = resolveStorageRoot(); - loadDotenv({ path: getEnvPath(storageRoot) }); const { store } = await createAgentStore(storageRoot); const schema = getSchema(store, outputSchema); diff --git a/packages/workflow-util-agent/src/storage.ts b/packages/workflow-util-agent/src/storage.ts index 8f1c934..7d94a40 100644 --- a/packages/workflow-util-agent/src/storage.ts +++ b/packages/workflow-util-agent/src/storage.ts @@ -84,11 +84,11 @@ function normalizeProviders(raw: unknown): Record throw new Error(`config.providers.${name} must be a mapping`); } const baseUrl = entry.baseUrl; - const apiKeyEnv = entry.apiKeyEnv; - if (typeof baseUrl !== "string" || typeof apiKeyEnv !== "string") { - throw new Error(`config.providers.${name} requires baseUrl and apiKeyEnv`); + const apiKey = entry.apiKey; + if (typeof baseUrl !== "string" || typeof apiKey !== "string") { + throw new Error(`config.providers.${name} requires baseUrl and apiKey`); } - providers[name] = { baseUrl, apiKeyEnv }; + providers[name] = { baseUrl, apiKey }; } return providers; }