fix: specify extension env vars only by name (#2249)

Co-authored-by: Lily Delalande <ldelalande@block.xyz>
This commit is contained in:
Alex Hancock
2025-04-17 16:57:11 -04:00
committed by GitHub
parent 9b605dbdd7
commit 571ba86157
2 changed files with 53 additions and 4 deletions
@@ -2,6 +2,8 @@ import { ExtensionConfig } from '../../../api/types.gen';
import { getApiUrl, getSecretKey } from '../../../config';
import { toastService, ToastServiceOptions } from '../../../toasts';
import { replaceWithShims } from './utils';
import { saveEnvVarsToKeyring } from './extension-manager';
import type { AgentExtensionConfig } from './extension-manager';
interface ApiResponse {
error?: boolean;
@@ -141,13 +143,17 @@ export async function addToAgent(
options: ToastServiceOptions = {}
): Promise<Response> {
try {
if (extension.type === 'stdio') {
extension.cmd = await replaceWithShims(extension.cmd);
await saveEnvVarsToKeyring(extension);
const ext = toAgentExtensionConfig(extension);
if (ext.type === 'stdio') {
ext.cmd = await replaceWithShims(ext.cmd);
}
extension.name = sanitizeName(extension.name);
ext.name = sanitizeName(ext.name);
return await extensionApiCall('/extensions/add', extension, options);
return await extensionApiCall('/extensions/add', ext, options);
} catch (error) {
// Check if this is a 428 error and make the message more descriptive
if (error.message && error.message.includes('428')) {
@@ -179,3 +185,32 @@ export async function removeFromAgent(
function sanitizeName(name: string) {
return name.toLowerCase().replace(/-/g, '').replace(/_/g, '').replace(/\s/g, '');
}
export function toAgentExtensionConfig(config: ExtensionConfig): AgentExtensionConfig {
// Use type narrowing to handle different variants of the union type
if ('type' in config) {
switch (config.type) {
case 'sse': {
const { envs, ...rest } = config;
return {
...rest,
env_keys: envs ? Object.keys(envs) : undefined,
};
}
case 'stdio': {
const { envs, ...rest } = config;
return {
...rest,
env_keys: envs ? Object.keys(envs) : undefined,
};
}
case 'builtin':
case 'frontend':
// These types don't have envs field, so just return as is
return config;
}
}
// This should never happen due to the union type constraint
throw new Error('Invalid extension configuration type');
}
@@ -1,6 +1,12 @@
import type { ExtensionConfig } from '../../../api/types.gen';
import { toastService, ToastServiceOptions } from '../../../toasts';
import { addToAgent, removeFromAgent } from './agent-api';
import { upsertConfig } from '../../../api';
// TODO: unify config.yaml and the agent /extensions/add API's notion of env vars
export type AgentExtensionConfig = ExtensionConfig & {
env_keys?: string[];
};
interface ActivateExtensionProps {
addToConfig: (name: string, extensionConfig: ExtensionConfig, enabled: boolean) => Promise<void>;
@@ -283,3 +289,11 @@ export async function deleteExtension({ name, removeFromConfig }: DeleteExtensio
throw agentRemoveError;
}
}
export async function saveEnvVarsToKeyring(extension: ExtensionConfig) {
if (extension.type === 'stdio' || extension.type === 'sse') {
for (const [key, value] of Object.entries(extension.envs || {})) {
await upsertConfig({ body: { key, value, is_secret: true } });
}
}
}