chore: Initial pass at cleanup of initializeAgent (#1888)

This commit is contained in:
Alex Hancock
2025-03-27 15:40:27 -04:00
committed by GitHub
parent ca41c6ba53
commit 9c290c569f
7 changed files with 23 additions and 243 deletions

View File

@@ -10,7 +10,7 @@
"license": {
"name": "Apache-2.0"
},
"version": "1.0.15"
"version": "1.0.16"
},
"paths": {
"/config": {

View File

@@ -1,188 +0,0 @@
import { useConfig, FixedExtensionEntry } from '../components/ConfigContext';
import { getApiUrl, getSecretKey } from '../config';
import { ExtensionConfig } from '../api';
import { toast } from 'react-toastify';
import React, { useState } from 'react';
import { initializeAgent as startAgent, replaceWithShims } from './utils';
import { toastError, toastInfo, toastLoading, toastSuccess } from '../toasts';
// extensionUpdate = an extension was newly added or updated so we should attempt to add it
export const useAgent = () => {
const { getExtensions, read } = useConfig();
const [isUpdating, setIsUpdating] = useState(false);
// whenever we change the model, we must call this
const initializeAgent = async (provider: string, model: string) => {
try {
console.log('Initializing agent with provider', provider, 'model', model);
const response = await startAgent(model, provider);
if (!response.ok) {
throw new Error(`Failed to initialize agent: ${response.statusText}`);
}
return true;
} catch (error) {
console.error('Failed to initialize agent:', error);
toastError({
title: 'Failed to initialize agent',
traceback: error instanceof Error ? error.message : 'Unknown error',
});
return false;
}
};
const updateAgent = async (extensionUpdate?: ExtensionConfig) => {
setIsUpdating(true);
try {
// need to initialize agent first (i dont get why but if we dont do this, we get a 428)
// note: we must write the value for GOOSE_MODEL and GOOSE_PROVIDER in the config before updating agent
const goose_model = (await read('GOOSE_MODEL', false)) as string;
const goose_provider = (await read('GOOSE_PROVIDER', false)) as string;
console.log(
`Starting agent with GOOSE_MODEL=${goose_model} and GOOSE_PROVIDER=${goose_provider}`
);
// Initialize the agent if it's a model change
if (goose_model && goose_provider) {
const success = await initializeAgent(goose_provider, goose_model);
if (!success) {
console.error('Failed to initialize agent during model change');
return false;
}
}
if (extensionUpdate) {
await addExtensionToAgent(extensionUpdate);
}
return true;
} catch (error) {
console.error('Error updating agent:', error);
return false;
} finally {
setIsUpdating(false);
}
};
// TODO: set 'enabled' to false if we fail to start / add the extension
// only for non-builtins
// TODO: try to add some descriptive error messages for common failure modes
const addExtensionToAgent = async (
extension: ExtensionConfig,
silent: boolean = false
): Promise<Response> => {
if (extension.type == 'stdio') {
console.log('extension command', extension.cmd);
extension.cmd = await replaceWithShims(extension.cmd);
console.log('next ext command', extension.cmd);
}
try {
let toastId;
if (!silent) {
toastId = toastLoading({
title: extension.name,
msg: 'Adding extension...',
toastOptions: { position: 'top-center' },
});
toastInfo({
msg: 'Press the escape key to continue using goose while extension loads',
});
}
const response = await fetch(getApiUrl('/extensions/add'), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Secret-Key': getSecretKey(),
},
body: JSON.stringify(extension),
});
// Handle non-OK responses
if (!response.ok) {
const errorMsg = `Server returned ${response.status}: ${response.statusText}`;
console.error(errorMsg);
// Special handling for 428 Precondition Required (agent not initialized)
if (response.status === 428) {
if (!silent) {
if (toastId) toast.dismiss(toastId);
toastError({
msg: 'Agent is not initialized. Please initialize the agent first.',
});
}
return response;
}
if (!silent) {
if (toastId) toast.dismiss(toastId);
toastError({
title: extension.name,
msg: 'Failed to add extension',
traceback: errorMsg,
});
}
return response;
}
// Parse response JSON safely
let data;
try {
const text = await response.text();
data = text ? JSON.parse(text) : { error: false };
} catch (error) {
console.warn('Could not parse response as JSON, assuming success', error);
data = { error: false };
}
console.log('Response data:', data);
if (!data.error) {
if (!silent) {
if (toastId) toast.dismiss(toastId);
toastSuccess({
title: extension.name,
msg: 'Successfully added extension',
});
}
return response;
}
console.log('Error trying to send a request to the extensions endpoint');
const errorMessage = `Error adding ${extension.name} extension${data.message ? `. ${data.message}` : ''}`;
console.error(errorMessage);
if (toastId) toast.dismiss(toastId);
toastError({
title: extension.name,
msg: 'Failed to add extension',
traceback: data.message,
});
return response;
} catch (error) {
console.log('Got some other error');
const errorMessage = `Failed to add ${extension.name} extension: ${error instanceof Error ? error.message : 'Unknown error'}`;
console.error(errorMessage);
toastError({
title: extension.name,
msg: 'Failed to add extension',
traceback: error.message,
});
throw error;
}
};
return {
updateAgent,
addExtensionToAgent,
initializeAgent,
isUpdating,
};
};

View File

@@ -15,19 +15,3 @@ export async function initializeAgent(model: string, provider: string) {
});
return response;
}
export async function replaceWithShims(cmd: string) {
const binaryPathMap: Record<string, string> = {
goosed: await window.electron.getBinaryPath('goosed'),
jbang: await window.electron.getBinaryPath('jbang'),
npx: await window.electron.getBinaryPath('npx'),
uvx: await window.electron.getBinaryPath('uvx'),
};
if (binaryPathMap[cmd]) {
console.log('--------> Replacing command with shim ------>', cmd, binaryPathMap[cmd]);
cmd = binaryPathMap[cmd];
}
return cmd;
}

View File

@@ -1,6 +1,7 @@
import { ExtensionConfig } from '../../../api/types.gen';
import { getApiUrl, getSecretKey } from '../../../config';
import { toastService, ToastServiceOptions } from '../../../toasts';
import { replaceWithShims } from './utils';
/**
* Makes an API call to the extension endpoints
@@ -166,19 +167,3 @@ export async function removeFromAgent(
throw error;
}
}
// Update the path to the binary based on the command
async function replaceWithShims(cmd: string): Promise<string> {
const binaryPathMap: Record<string, string> = {
goosed: await window.electron.getBinaryPath('goosed'),
npx: await window.electron.getBinaryPath('npx'),
uvx: await window.electron.getBinaryPath('uvx'),
};
if (binaryPathMap[cmd]) {
console.log('--------> Replacing command with shim ------>', cmd, binaryPathMap[cmd]);
return binaryPathMap[cmd];
}
return cmd;
}

View File

@@ -127,3 +127,19 @@ export function extractExtensionConfig(fixedEntry: FixedExtensionEntry): Extensi
const { enabled, ...extensionConfig } = fixedEntry;
return extensionConfig;
}
export async function replaceWithShims(cmd: string) {
const binaryPathMap: Record<string, string> = {
goosed: await window.electron.getBinaryPath('goosed'),
jbang: await window.electron.getBinaryPath('jbang'),
npx: await window.electron.getBinaryPath('npx'),
uvx: await window.electron.getBinaryPath('uvx'),
};
if (binaryPathMap[cmd]) {
console.log('--------> Replacing command with shim ------>', cmd, binaryPathMap[cmd]);
cmd = binaryPathMap[cmd];
}
return cmd;
}

View File

@@ -4,7 +4,7 @@ import BackButton from '../../ui/BackButton';
import ProviderGrid from './ProviderGrid';
import { useConfig } from '../../ConfigContext';
import { ProviderDetails } from '../../../api/types.gen';
import { useAgent } from '../../../agent/UpdateAgent';
import { initializeAgent } from '../../../agent/';
import WelcomeGooseLogo from '../../WelcomeGooseLogo';
interface ProviderSettingsProps {
@@ -14,7 +14,6 @@ interface ProviderSettingsProps {
export default function ProviderSettings({ onClose, isOnboarding }: ProviderSettingsProps) {
const { getProviders, upsert } = useConfig();
const { initializeAgent } = useAgent();
const [loading, setLoading] = useState(true);
const [providers, setProviders] = useState<ProviderDetails[]>([]);
const initialLoadDone = useRef(false);
@@ -70,7 +69,7 @@ export default function ProviderSettings({ onClose, isOnboarding }: ProviderSett
);
// initialize agent
await initializeAgent(provider_name, model);
await initializeAgent({ provider: provider.name, model });
} catch (error) {
console.error(`Failed to initialize with provider ${provider_name}:`, error);
}

View File

@@ -3,6 +3,7 @@ import { loadAndAddStoredExtensions } from '../extensions';
import { GOOSE_PROVIDER, GOOSE_MODEL } from '../env_vars';
import { Model } from '../components/settings/models/ModelContext';
import { gooseModels } from '../components/settings/models/GooseModels';
import { initializeAgent } from '../agent';
export function getStoredProvider(config: any): string | null {
return config.GOOSE_PROVIDER || localStorage.getItem(GOOSE_PROVIDER);
@@ -32,23 +33,6 @@ export interface Provider {
requiredKeys: string[]; // List of required keys
}
const addAgent = async (provider: string, model: string) => {
const response = await fetch(getApiUrl('/agent'), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Secret-Key': getSecretKey(),
},
body: JSON.stringify({ provider: provider, model: model }),
});
if (!response.ok) {
throw new Error(`Failed to add agent: ${response.statusText}`);
}
return response;
};
// Desktop-specific system prompt extension
const desktopPrompt = `You are being accessed through the Goose Desktop application.
@@ -84,7 +68,7 @@ There may be (but not always) some tools mentioned in the instructions which you
export const initializeSystem = async (provider: string, model: string) => {
try {
console.log('initializing agent with provider', provider, 'model', model);
await addAgent(provider.toLowerCase().replace(/ /g, '_'), model);
await initializeAgent({ provider, model });
// Sync the model state with React
const syncedModel = syncModelWithAgent(provider, model);
@@ -119,7 +103,7 @@ export const initializeSystem = async (provider: string, model: string) => {
// This will go away after the release of settings v2 as we now handle this via
//
// initializeBuildInExtensions
// initializeBuiltInExtensions
// syncBuiltInExtensions
if (!process.env.ALPHA) {
loadAndAddStoredExtensions().catch((error) => {