fix: load SDK key from .env for AI Gateway in dev mode#349
Conversation
Fixes #348: AI gateway not injecting API key The dev command was setting AGENTUITY_TRANSPORT_URL but not loading AGENTUITY_SDK_KEY from the project's .env file. This caused the AI SDK patches to fail to inject the API key, resulting in 'API key is missing' errors when using @ai-sdk/openai or @ai-sdk/anthropic. Changes: - Load SDK key from .env files using loadProjectSDKKey() before starting the dev server - Add warning message when SDK key is missing but project is configured - Add unit tests for AI SDK env injection in bundled code - Add integration suite tests for AI Gateway functionality Amp-Thread-ID: https://ampcode.com/threads/T-019b6ab9-5844-7573-9a23-bfae12608c1f Co-authored-by: Amp <amp@ampcode.com>
📝 WalkthroughWalkthroughThe PR adds AI Gateway API key injection by introducing an agent-based gateway check, test suite for gateway functionality, CLI integration to load project SDK keys at runtime, and comprehensive build-time patching tests ensuring API key and transport URL injection during bundling. Changes
Sequence Diagram(s)sequenceDiagram
participant CLI as CLI Dev Command
participant Config as Project Config
participant Build as Build System
participant Bundle as Bundled Output
participant Runtime as Runtime
CLI->>Config: loadProjectSDKKey()
Config-->>CLI: AGENTUITY_SDK_KEY
Note over CLI: Set process.env.AGENTUITY_SDK_KEY
CLI->>Build: Trigger build with env vars set
Build->>Build: Apply patching plugin
Build->>Bundle: Inject AGENTUITY_SDK_KEY<br/>AGENTUITY_TRANSPORT_URL<br/>Gateway URL
Build-->>CLI: Bundled output with patches
Note over Runtime: Application starts
Runtime->>Runtime: Load bundled module
Runtime->>Runtime: createOpenAI() called
rect rgb(220, 240, 255)
Note over Runtime: Patched createOpenAI
Runtime->>Runtime: Check env vars present
Runtime->>Runtime: Inject apiKey from AGENTUITY_SDK_KEY
Runtime->>Runtime: Inject baseURL from AGENTUITY_TRANSPORT_URL
end
Runtime->>Runtime: Model creation succeeds
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
packages/cli/test/build/ai-sdk-env-injection.test.ts (1)
174-185: Consider explicit env var exclusion pattern.Setting env vars to
undefinedin the spawn env object should work, but behavior can vary. For more explicit clearing, consider filtering the env:🔎 Alternative pattern
const proc = Bun.spawn(['bun', 'run', testScriptPath], { cwd: tempDir, stdout: 'pipe', stderr: 'pipe', env: { - ...process.env, - // Clear any existing env vars to ensure clean test - AGENTUITY_SDK_KEY: undefined, - AGENTUITY_TRANSPORT_URL: undefined, - OPENAI_API_KEY: undefined, + ...Object.fromEntries( + Object.entries(process.env).filter( + ([k]) => !['AGENTUITY_SDK_KEY', 'AGENTUITY_TRANSPORT_URL', 'OPENAI_API_KEY'].includes(k) + ) + ), }, });apps/testing/integration-suite/src/agent/ai-sdk/gateway-check.ts (1)
88-113: Unused variable may trigger lint warning.The
modelvariable on line 92 is intentionally unused (verifying creation doesn't throw). Per coding guidelines, ensure all warnings are zero before committing. Consider prefixing with underscore to indicate intentional non-use:🔎 Suggested fix
case 'create-model': { // Test creating a model instance (doesn't make API call) try { const openai = createOpenAI({}); - const model = openai('gpt-4o-mini'); + const _model = openai('gpt-4o-mini'); // Model created successfully return {apps/testing/integration-suite/src/test/ai-sdk-gateway.ts (1)
12-14: Consider using path aliases for consistency.Per coding guidelines, prefer
@test/*path aliases instead of relative paths for imports within the integration suite:🔎 Suggested change
-import { test } from './suite'; -import { assert, assertEqual, assertTruthy } from './helpers'; +import { test } from '@test/suite'; +import { assert, assertEqual, assertTruthy } from '@test/helpers'; import aiSdkGatewayCheckAgent from '@agents/ai-sdk/gateway-check';
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
apps/testing/integration-suite/src/generated/registry.tsis excluded by!**/generated/**
📒 Files selected for processing (5)
apps/testing/integration-suite/app.tsapps/testing/integration-suite/src/agent/ai-sdk/gateway-check.tsapps/testing/integration-suite/src/test/ai-sdk-gateway.tspackages/cli/src/cmd/dev/index.tspackages/cli/test/build/ai-sdk-env-injection.test.ts
🧰 Additional context used
📓 Path-based instructions (11)
apps/testing/integration-suite/src/**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (apps/testing/integration-suite/AGENTS.md)
Use path aliases for imports: @agents/* and @test/* instead of relative paths
Files:
apps/testing/integration-suite/src/agent/ai-sdk/gateway-check.tsapps/testing/integration-suite/src/test/ai-sdk-gateway.ts
apps/testing/integration-suite/src/agent/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/testing/integration-suite/AGENTS.md)
apps/testing/integration-suite/src/agent/**/*.{ts,tsx}: Schema definitions must use s.record(keyType, valueType) with two arguments, not one
Schema definitions must use s.union(typeA, typeB) with arguments, not array s.union([typeA, typeB])
Agent schema pattern must include input and output objects with operation field for multi-operation agents
Session state uses ctx.session.state.set(key, value) and ctx.session.state.get(key) for request-scoped state
Thread state uses ctx.thread.state.set(key, value) and ctx.thread.state.get(key) for conversation-scoped persistent state
StructuredError uses pattern StructuredError(tag, message)<{ shape }>() with properties accessed directly on error instance
Error handling in agent handlers must use try-catch blocks and can transform errors into success responses
Agent eval creation uses agent.createEval(name, config) with handler returning success:true and metadata:{reason}
Agent eval results use passed:boolean for binary pass/fail or score:number (0.0-1.0) for scored results
Background tasks use ctx.waitUntil() with async function or promise, executing after response is sent
Session event listeners use ctx.session.addEventListener(eventName, callback) for 'completed' event
Thread event listeners use ctx.thread.addEventListener(eventName, callback) for 'destroyed' event
Each agent export must have a unique variable name to prevent naming collisions
Files:
apps/testing/integration-suite/src/agent/ai-sdk/gateway-check.ts
**/*.{ts,tsx,js,jsx,json}
📄 CodeRabbit inference engine (AGENTS.md)
Use Prettier for code formatting with tabs (width 3), single quotes, and semicolons
Files:
apps/testing/integration-suite/src/agent/ai-sdk/gateway-check.tsapps/testing/integration-suite/app.tspackages/cli/src/cmd/dev/index.tsapps/testing/integration-suite/src/test/ai-sdk-gateway.tspackages/cli/test/build/ai-sdk-env-injection.test.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use TypeScript strict mode with ESNext target and bundler moduleResolution
Files:
apps/testing/integration-suite/src/agent/ai-sdk/gateway-check.tsapps/testing/integration-suite/app.tspackages/cli/src/cmd/dev/index.tsapps/testing/integration-suite/src/test/ai-sdk-gateway.tspackages/cli/test/build/ai-sdk-env-injection.test.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}: UseStructuredErrorfrom@agentuity/corefor error handling
Ensure all errors AND warnings must be zero before committing
Files:
apps/testing/integration-suite/src/agent/ai-sdk/gateway-check.tsapps/testing/integration-suite/app.tspackages/cli/src/cmd/dev/index.tsapps/testing/integration-suite/src/test/ai-sdk-gateway.tspackages/cli/test/build/ai-sdk-env-injection.test.ts
apps/testing/integration-suite/**/app.ts
📄 CodeRabbit inference engine (apps/testing/integration-suite/AGENTS.md)
Test files must be imported in app.ts to register tests with the test suite
Files:
apps/testing/integration-suite/app.ts
packages/cli/src/cmd/*/index.ts
📄 CodeRabbit inference engine (packages/cli/AGENTS.md)
packages/cli/src/cmd/*/index.ts: Each command must be a directory insrc/cmd/with anindex.tsfile as the main entry point
Always define interfaces for command options instead of usinganytype
Files:
packages/cli/src/cmd/dev/index.ts
packages/cli/src/cmd/**/*.ts
📄 CodeRabbit inference engine (packages/cli/AGENTS.md)
packages/cli/src/cmd/**/*.ts: Usetui.*helpers for formatted output instead of raw console logs
Usectx.loggerfor logging; calllogger.fatal()to log and exit with code 1
Files:
packages/cli/src/cmd/dev/index.ts
packages/cli/src/**/*.ts
📄 CodeRabbit inference engine (packages/cli/AGENTS.md)
Use
Bun.file(f).exists()instead ofexistsSync(f)for file existence checks
Files:
packages/cli/src/cmd/dev/index.ts
apps/testing/integration-suite/src/test/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/testing/integration-suite/AGENTS.md)
apps/testing/integration-suite/src/test/**/*.{ts,tsx}: Use uniqueId() helper for all keys, namespaces, and identifiers in tests to ensure test isolation
Test files must follow the pattern: setup section with uniqueId(), execute section calling agent.run(), and assert section with assertion helpers
Import test registration function and assertion helpers from @test/suite and @test/helpers
Use decodeKVValue() helper from @test/helpers/kv to decode stringified Uint8Arrays when reading KV values
Tests must be registered by calling test() function from @test/suite, not just defined
Files:
apps/testing/integration-suite/src/test/ai-sdk-gateway.ts
packages/*/test/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
packages/*/test/**/*.{ts,tsx}: Place tests intest/folder (never insrc/or__tests__/)
Import from../src/in test files
Use@agentuity/test-utilsfor mocks in tests
Files:
packages/cli/test/build/ai-sdk-env-injection.test.ts
🧠 Learnings (1)
📚 Learning: 2025-12-21T00:31:41.858Z
Learnt from: jhaynie
Repo: agentuity/sdk PR: 274
File: packages/cli/src/cmd/build/vite/server-bundler.ts:12-41
Timestamp: 2025-12-21T00:31:41.858Z
Learning: In Bun runtime, BuildMessage and ResolveMessage are global types and are not exported from the bun module. Do not import { BuildMessage } from 'bun' or similar; these types are available globally and should be used without import. This applies to all TypeScript files that target the Bun runtime within the repository.
Applied to files:
apps/testing/integration-suite/src/agent/ai-sdk/gateway-check.tsapps/testing/integration-suite/app.tspackages/cli/src/cmd/dev/index.tsapps/testing/integration-suite/src/test/ai-sdk-gateway.tspackages/cli/test/build/ai-sdk-env-injection.test.ts
🧬 Code graph analysis (2)
packages/cli/src/cmd/dev/index.ts (1)
packages/cli/src/config.ts (1)
loadProjectSDKKey(618-655)
packages/cli/test/build/ai-sdk-env-injection.test.ts (1)
packages/cli/src/cmd/build/patch/index.ts (1)
applyPatch(16-101)
🪛 ast-grep (0.40.3)
packages/cli/test/build/ai-sdk-env-injection.test.ts
[warning] 81-81: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(modulePath)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html
(regexp-from-variable)
[warning] 131-131: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(modulePath)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html
(regexp-from-variable)
🔇 Additional comments (10)
packages/cli/src/cmd/dev/index.ts (1)
15-15: Import addition is correct.The
loadProjectSDKKeyfunction is properly imported alongside existing config utilities.packages/cli/test/build/ai-sdk-env-injection.test.ts (4)
1-6: Good test structure with appropriate imports.The test file correctly imports from
bun:testand uses the project's patch utilities for integration testing.
21-61: Test setup is well-structured.The
beforeAllproperly:
- Creates an isolated temp directory
- Sets up a realistic project structure with
package.jsonand source files- Installs dependencies using
bun install --quietGood isolation pattern for integration testing.
72-92: Static analysis ReDoS warning is a false positive.The
modulePathis constructed from internal patch definitions (patch.module,patch.filename), not user input. The resulting regex pattern (e.g.,node_modules/@ai-sdk/openai/.*) is simple without nested quantifiers, so there's no ReDoS risk here.
198-218: Effective patch structure validation.This test verifies the patch logic guards against missing env vars by inspecting the actual patch code structure. This is a valuable safeguard against regressions in the patch template.
apps/testing/integration-suite/app.ts (1)
34-34: Test module registration follows the established pattern.The import correctly registers the AI SDK gateway tests with the test suite, as per coding guidelines.
apps/testing/integration-suite/src/agent/ai-sdk/gateway-check.ts (2)
17-31: Agent schema correctly follows multi-operation pattern.The schema properly defines:
- Input with
operationfield for operation dispatch- Output with structured fields including optional
errorAs per coding guidelines, the agent uses a unique variable name (
aiSdkGatewayCheckAgent) to prevent naming collisions.
52-86: Create-provider operation correctly tests issue #348 scenario.The operation:
- Calls
createOpenAI({})without explicit apiKey- Correctly identifies the specific API key missing error pattern
- Returns clear regression message if gateway injection fails
Good regression test design.
apps/testing/integration-suite/src/test/ai-sdk-gateway.ts (2)
17-35: Graceful handling of unconfigured environments.The test correctly:
- Asserts success when env vars are present
- Logs diagnostic info when not configured (helpful for debugging)
- Doesn't fail the test in unconfigured environments
This is good test design for CI environments that may not have gateway credentials.
79-93: Good invariant validation test.The consistency test properly validates:
- Result field types are correct
- Logical invariant:
successimplies bothhasSDKKeyandhasTransportUrlare trueThis catches potential schema or logic inconsistencies early.
| // Load SDK key from project .env files for AI Gateway routing | ||
| // This must be set so the bundled AI SDK patches can inject the API key | ||
| if (!process.env.AGENTUITY_SDK_KEY) { | ||
| const sdkKey = await loadProjectSDKKey(logger, rootDir); | ||
| if (sdkKey) { | ||
| process.env.AGENTUITY_SDK_KEY = sdkKey; | ||
| } else if (project) { | ||
| tui.warn( | ||
| 'AGENTUITY_SDK_KEY not found in .env file. Numerous features will be unavailable.' | ||
| ); | ||
| tui.bullet( | ||
| `Run "${getCommand('cloud env pull')}" to sync your SDK key, or add AGENTUITY_SDK_KEY to your .env file.` | ||
| ); | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, check if the file exists and view the relevant lines
wc -l packages/cli/src/cmd/dev/index.tsRepository: agentuity/sdk
Length of output: 94
🏁 Script executed:
# View the code at lines 712-726
sed -n '712,726p' packages/cli/src/cmd/dev/index.tsRepository: agentuity/sdk
Length of output: 664
🏁 Script executed:
# Check for the loadProjectSDKKey import
head -50 packages/cli/src/cmd/dev/index.ts | grep -n "loadProjectSDKKey"Repository: agentuity/sdk
Length of output: 142
🏁 Script executed:
# Search for all imports in the file
head -30 packages/cli/src/cmd/dev/index.tsRepository: agentuity/sdk
Length of output: 1370
🏁 Script executed:
# Check context around lines 712-726 (view broader range)
sed -n '705,730p' packages/cli/src/cmd/dev/index.tsRepository: agentuity/sdk
Length of output: 1044
🏁 Script executed:
# Search for existsSync usage to check guideline compliance
rg "existsSync" packages/cli/src/cmd/dev/index.ts -nRepository: agentuity/sdk
Length of output: 207
🏁 Script executed:
# Verify loadProjectSDKKey is exported from config
rg "loadProjectSDKKey" packages/cli/src/config -A 3Repository: agentuity/sdk
Length of output: 120
🏁 Script executed:
# Check the logger context - verify it's available in this scope
sed -n '700,715p' packages/cli/src/cmd/dev/index.tsRepository: agentuity/sdk
Length of output: 607
🏁 Script executed:
# Find the config file
fd "config" packages/cli/src --type f -e tsRepository: agentuity/sdk
Length of output: 192
🏁 Script executed:
# Search for loadProjectSDKKey definition
rg "loadProjectSDKKey" packages/cli/src -A 5 -B 1Repository: agentuity/sdk
Length of output: 5881
🏁 Script executed:
# Check the broader function context to see if logger is available
sed -n '650,680p' packages/cli/src/cmd/dev/index.tsRepository: agentuity/sdk
Length of output: 797
🏁 Script executed:
# Check for logger availability in the function scope
sed -n '550,580p' packages/cli/src/cmd/dev/index.ts | head -20Repository: agentuity/sdk
Length of output: 544
🏁 Script executed:
# Verify the function signature/context where SDK key loading happens
sed -n '560,575p' packages/cli/src/cmd/dev/index.tsRepository: agentuity/sdk
Length of output: 449
🏁 Script executed:
# Check the actual function definition to see if logger is defined
rg "const handler = " packages/cli/src/cmd/dev/index.ts -A 10Repository: agentuity/sdk
Length of output: 39
🏁 Script executed:
# Search for where this code block is - find the function that contains it
rg "Load SDK key from project" packages/cli/src/cmd/dev/index.ts -B 50 | head -100Repository: agentuity/sdk
Length of output: 1425
🏁 Script executed:
# Check the command handler structure
sed -n '200,250p' packages/cli/src/cmd/dev/index.tsRepository: agentuity/sdk
Length of output: 1699
🏁 Script executed:
# Look for the main export and handler
tail -300 packages/cli/src/cmd/dev/index.ts | head -100Repository: agentuity/sdk
Length of output: 3412
🏁 Script executed:
# Get the full context of the function containing the SDK key loading
sed -n '150,170p' packages/cli/src/cmd/dev/index.tsRepository: agentuity/sdk
Length of output: 742
🏁 Script executed:
# Check logger parameter in the function signature
rg "export const handler" packages/cli/src/cmd/dev/index.ts -A 5Repository: agentuity/sdk
Length of output: 39
🏁 Script executed:
# Check line 193 to see the existsSync usage context
sed -n '188,198p' packages/cli/src/cmd/dev/index.tsRepository: agentuity/sdk
Length of output: 262
🏁 Script executed:
# Check line 262 existsSync usage context
sed -n '258,268p' packages/cli/src/cmd/dev/index.tsRepository: agentuity/sdk
Length of output: 385
🏁 Script executed:
# Check the full loadProjectSDKKey implementation
sed -n '/export async function loadProjectSDKKey/,/^}/p' packages/cli/src/config.ts | head -40Repository: agentuity/sdk
Length of output: 1437
🏁 Script executed:
# Verify the logger is properly available in the handler context
rg "handler = async \(ctx" packages/cli/src/cmd/dev/index.ts -A 3Repository: agentuity/sdk
Length of output: 39
🏁 Script executed:
# Check if ctx.logger is used elsewhere in this handler
sed -n '150,250p' packages/cli/src/cmd/dev/index.ts | grep -n "ctx.logger\|logger"Repository: agentuity/sdk
Length of output: 346
LGTM but fix existsSync violations before committing.
The SDK key loading logic correctly addresses the issue:
- Guards against overwriting an already-set
AGENTUITY_SDK_KEY - Loads from project-specific
.envfiles vialoadProjectSDKKey - Provides actionable guidance when the key is missing
However, the code uses existsSync() on lines 193 and 262, which violates the coding guideline requiring Bun.file(f).exists() for file existence checks in packages/cli/src/**/*.ts. Replace these usages and also update the existsSync() call in loadProjectSDKKey() in packages/cli/src/config.ts.
Summary
Fixes #348: AI gateway not injecting API key
Problem
When running
agentuity dev, the AI SDK patches check forAGENTUITY_SDK_KEYandAGENTUITY_TRANSPORT_URLat runtime to inject API keys for the AI Gateway. However, onlyAGENTUITY_TRANSPORT_URLwas being set -AGENTUITY_SDK_KEYwas never loaded from the project's.envfile.This caused the AI SDK patches to fail, resulting in errors like:
Solution
.envfiles usingloadProjectSDKKey()before starting the dev serverChanges
Testing
How to Verify
agentuity new.envcontainsAGENTUITY_SDK_KEY=<your-key>@ai-sdk/openaiwithgenerateText()agentuity devSummary by CodeRabbit
Release Notes
New Features
Tests
✏️ Tip: You can customize this high-level summary in your review settings.