Revert "refactor: convert desktop v1 and goose-server extensions to ACP+ (#9448)" (#9564)

This commit is contained in:
Alex Hancock
2026-06-02 09:44:18 -04:00
committed by GitHub
parent 586bb15d40
commit 942a4564e3
13 changed files with 826 additions and 124 deletions
+302
View File
@@ -47,6 +47,45 @@
}
}
},
"/agent/add_extension": {
"post": {
"tags": [
"super::routes::agent"
],
"operationId": "agent_add_extension",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AddExtensionRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Extension added",
"content": {
"text/plain": {
"schema": {
"type": "string"
}
}
}
},
"401": {
"description": "Unauthorized - invalid secret key"
},
"424": {
"description": "Agent not initialized"
},
"500": {
"description": "Internal server error"
}
}
}
},
"/agent/call_tool": {
"post": {
"tags": [
@@ -329,6 +368,45 @@
}
}
},
"/agent/remove_extension": {
"post": {
"tags": [
"super::routes::agent"
],
"operationId": "agent_remove_extension",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RemoveExtensionRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Extension removed",
"content": {
"text/plain": {
"schema": {
"type": "string"
}
}
}
},
"401": {
"description": "Unauthorized - invalid secret key"
},
"424": {
"description": "Agent not initialized"
},
"500": {
"description": "Internal server error"
}
}
}
},
"/agent/restart": {
"post": {
"tags": [
@@ -899,6 +977,102 @@
}
}
},
"/config/extensions": {
"get": {
"tags": [
"super::routes::config_management"
],
"operationId": "get_extensions",
"responses": {
"200": {
"description": "All extensions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ExtensionResponse"
}
}
}
},
"500": {
"description": "Internal server error"
}
}
},
"post": {
"tags": [
"super::routes::config_management"
],
"operationId": "add_extension",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ExtensionQuery"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Extension added or updated successfully",
"content": {
"text/plain": {
"schema": {
"type": "string"
}
}
}
},
"400": {
"description": "Invalid request"
},
"422": {
"description": "Could not serialize config.yaml"
},
"500": {
"description": "Internal server error"
}
}
}
},
"/config/extensions/{name}": {
"delete": {
"tags": [
"super::routes::config_management"
],
"operationId": "remove_extension",
"parameters": [
{
"name": "name",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Extension removed successfully",
"content": {
"text/plain": {
"schema": {
"type": "string"
}
}
}
},
"404": {
"description": "Extension not found"
},
"500": {
"description": "Internal server error"
}
}
}
},
"/config/permissions": {
"post": {
"tags": [
@@ -3445,6 +3619,51 @@
]
}
},
"/sessions/{session_id}/extensions": {
"get": {
"tags": [
"Session Management"
],
"operationId": "get_session_extensions",
"parameters": [
{
"name": "session_id",
"in": "path",
"description": "Unique identifier for the session",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Session extensions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SessionExtensionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key"
},
"404": {
"description": "Session not found"
},
"500": {
"description": "Internal server error"
}
},
"security": [
{
"api_key": []
}
]
}
},
"/sessions/{session_id}/fork": {
"post": {
"tags": [
@@ -3922,6 +4141,21 @@
"propertyName": "actionType"
}
},
"AddExtensionRequest": {
"type": "object",
"required": [
"session_id",
"config"
],
"properties": {
"config": {
"$ref": "#/components/schemas/ExtensionConfig"
},
"session_id": {
"type": "string"
}
}
},
"Annotations": {
"type": "object",
"properties": {
@@ -5271,6 +5505,45 @@
}
}
},
"ExtensionQuery": {
"type": "object",
"required": [
"name",
"config",
"enabled"
],
"properties": {
"config": {
"$ref": "#/components/schemas/ExtensionConfig"
},
"enabled": {
"type": "boolean"
},
"name": {
"type": "string"
}
}
},
"ExtensionResponse": {
"type": "object",
"required": [
"extensions"
],
"properties": {
"extensions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ExtensionEntry"
}
},
"warnings": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"FeaturesResponse": {
"type": "object",
"required": [
@@ -7294,6 +7567,21 @@
}
}
},
"RemoveExtensionRequest": {
"type": "object",
"required": [
"name",
"session_id"
],
"properties": {
"name": {
"type": "string"
},
"session_id": {
"type": "string"
}
}
},
"RepoVariantsResponse": {
"type": "object",
"required": [
@@ -7948,6 +8236,20 @@
}
}
},
"SessionExtensionsResponse": {
"type": "object",
"required": [
"extensions"
],
"properties": {
"extensions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ExtensionConfig"
}
}
}
},
"SessionInsights": {
"type": "object",
"required": [
+3 -88
View File
@@ -1,96 +1,11 @@
import type { ExtensionEntry, ExtensionConfig } from '../api';
import type { ExtensionResponse, ExtensionEntry } from '../api';
import { getAcpClient } from './acpConnection';
export interface ConfiguredExtensionsResponse {
extensions: ExtensionEntry[];
warnings: string[];
}
/**
* Fetch all configured extensions via ACP (`_goose/unstable/config/extensions/list`).
*/
export async function getConfiguredExtensions(): Promise<ConfiguredExtensionsResponse> {
export async function getConfiguredExtensions(): Promise<ExtensionResponse> {
const client = await getAcpClient();
const response = await client.goose.configExtensionsList_unstable({});
return {
extensions: response.extensions as ExtensionEntry[],
warnings: response.warnings ?? [],
warnings: response.warnings,
};
}
/**
* Add (or update) an extension in the user's global goose config via ACP
* (`_goose/unstable/config/extensions/add`).
*/
export async function addConfiguredExtension(
name: string,
config: ExtensionConfig,
enabled: boolean
): Promise<void> {
const client = await getAcpClient();
// Server expects a JSON object matching one of the ExtensionConfig variants,
// and injects `name` itself. We strip `name` from the body to match that shape.
const extensionConfig = { ...config } as Record<string, unknown>;
delete extensionConfig.name;
await client.goose.configExtensionsAdd_unstable({
name,
extensionConfig,
enabled,
});
}
/**
* Remove an extension from the user's global goose config via ACP
* (`_goose/unstable/config/extensions/remove`). The server normalizes the
* supplied `configKey` via `name_to_key`, so passing the raw extension name
* is sufficient and matches how the previous REST route worked.
*/
export async function removeConfiguredExtension(name: string): Promise<void> {
const client = await getAcpClient();
await client.goose.configExtensionsRemove_unstable({
configKey: name,
});
}
/**
* Add an extension to a running session's agent via ACP
* (`_goose/unstable/session/extensions/add`).
*/
export async function addSessionExtension(
sessionId: string,
config: ExtensionConfig
): Promise<void> {
const client = await getAcpClient();
await client.goose.sessionExtensionsAdd_unstable({
sessionId,
config,
});
}
/**
* Remove an extension from a running session's agent via ACP
* (`_goose/unstable/session/extensions/remove`).
*/
export async function removeSessionExtension(
sessionId: string,
name: string
): Promise<void> {
const client = await getAcpClient();
await client.goose.sessionExtensionsRemove_unstable({
sessionId,
name,
});
}
/**
* Fetch the list of extensions associated with a given session via ACP
* (`_goose/unstable/session/extensions/list`).
*/
export async function getSessionExtensions(
sessionId: string
): Promise<ExtensionEntry[]> {
const client = await getAcpClient();
const response = await client.goose.sessionExtensionsList_unstable({ sessionId });
return response.extensions as ExtensionEntry[];
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+206
View File
@@ -25,6 +25,11 @@ export type ActionRequiredData = {
user_data: unknown;
};
export type AddExtensionRequest = {
config: ExtensionConfig;
session_id: string;
};
export type Annotations = {
audience?: Array<Role>;
lastModified?: string;
@@ -488,6 +493,17 @@ export type ExtensionLoadResult = {
success: boolean;
};
export type ExtensionQuery = {
config: ExtensionConfig;
enabled: boolean;
name: string;
};
export type ExtensionResponse = {
extensions: Array<ExtensionEntry>;
warnings?: Array<string>;
};
export type FeaturesResponse = {
/**
* Map of feature name to enabled status
@@ -1136,6 +1152,11 @@ export type RedactedThinkingContent = {
data: string;
};
export type RemoveExtensionRequest = {
name: string;
session_id: string;
};
export type RepoVariantsResponse = {
available_memory_bytes: number;
downloaded_quants: Array<string>;
@@ -1327,6 +1348,10 @@ export type SessionDisplayInfo = {
workingDir: string;
};
export type SessionExtensionsResponse = {
extensions: Array<ExtensionConfig>;
};
export type SessionInsights = {
totalSessions: number;
totalTokens: number;
@@ -1756,6 +1781,37 @@ export type ConfirmToolActionResponses = {
200: unknown;
};
export type AgentAddExtensionData = {
body: AddExtensionRequest;
path?: never;
query?: never;
url: '/agent/add_extension';
};
export type AgentAddExtensionErrors = {
/**
* Unauthorized - invalid secret key
*/
401: unknown;
/**
* Agent not initialized
*/
424: unknown;
/**
* Internal server error
*/
500: unknown;
};
export type AgentAddExtensionResponses = {
/**
* Extension added
*/
200: string;
};
export type AgentAddExtensionResponse = AgentAddExtensionResponses[keyof AgentAddExtensionResponses];
export type CallToolData = {
body: CallToolRequest;
path?: never;
@@ -1926,6 +1982,37 @@ export type ReadResourceResponses = {
export type ReadResourceResponse2 = ReadResourceResponses[keyof ReadResourceResponses];
export type AgentRemoveExtensionData = {
body: RemoveExtensionRequest;
path?: never;
query?: never;
url: '/agent/remove_extension';
};
export type AgentRemoveExtensionErrors = {
/**
* Unauthorized - invalid secret key
*/
401: unknown;
/**
* Agent not initialized
*/
424: unknown;
/**
* Internal server error
*/
500: unknown;
};
export type AgentRemoveExtensionResponses = {
/**
* Extension removed
*/
200: string;
};
export type AgentRemoveExtensionResponse = AgentRemoveExtensionResponses[keyof AgentRemoveExtensionResponses];
export type RestartAgentData = {
body: RestartAgentRequest;
path?: never;
@@ -2361,6 +2448,89 @@ export type UpdateCustomProviderResponses = {
export type UpdateCustomProviderResponse = UpdateCustomProviderResponses[keyof UpdateCustomProviderResponses];
export type GetExtensionsData = {
body?: never;
path?: never;
query?: never;
url: '/config/extensions';
};
export type GetExtensionsErrors = {
/**
* Internal server error
*/
500: unknown;
};
export type GetExtensionsResponses = {
/**
* All extensions retrieved successfully
*/
200: ExtensionResponse;
};
export type GetExtensionsResponse = GetExtensionsResponses[keyof GetExtensionsResponses];
export type AddExtensionData = {
body: ExtensionQuery;
path?: never;
query?: never;
url: '/config/extensions';
};
export type AddExtensionErrors = {
/**
* Invalid request
*/
400: unknown;
/**
* Could not serialize config.yaml
*/
422: unknown;
/**
* Internal server error
*/
500: unknown;
};
export type AddExtensionResponses = {
/**
* Extension added or updated successfully
*/
200: string;
};
export type AddExtensionResponse = AddExtensionResponses[keyof AddExtensionResponses];
export type RemoveExtensionData = {
body?: never;
path: {
name: string;
};
query?: never;
url: '/config/extensions/{name}';
};
export type RemoveExtensionErrors = {
/**
* Extension not found
*/
404: unknown;
/**
* Internal server error
*/
500: unknown;
};
export type RemoveExtensionResponses = {
/**
* Extension removed successfully
*/
200: string;
};
export type RemoveExtensionResponse = RemoveExtensionResponses[keyof RemoveExtensionResponses];
export type UpsertPermissionsData = {
body: UpsertPermissionsQuery;
path?: never;
@@ -4326,6 +4496,42 @@ export type ExportSessionResponses = {
export type ExportSessionResponse = ExportSessionResponses[keyof ExportSessionResponses];
export type GetSessionExtensionsData = {
body?: never;
path: {
/**
* Unique identifier for the session
*/
session_id: string;
};
query?: never;
url: '/sessions/{session_id}/extensions';
};
export type GetSessionExtensionsErrors = {
/**
* Unauthorized - Invalid or missing API key
*/
401: unknown;
/**
* Session not found
*/
404: unknown;
/**
* Internal server error
*/
500: unknown;
};
export type GetSessionExtensionsResponses = {
/**
* Session extensions retrieved successfully
*/
200: SessionExtensionsResponse;
};
export type GetSessionExtensionsResponse = GetSessionExtensionsResponses[keyof GetSessionExtensionsResponses];
export type ForkSessionData = {
body: ForkRequest;
path: {
+18 -9
View File
@@ -1,16 +1,21 @@
import React, { createContext, useContext, useState, useEffect, useMemo, useCallback } from 'react';
import { readAllConfig, readConfig, removeConfig, upsertConfig, providers } from '../api';
import {
getConfiguredExtensions,
addConfiguredExtension,
removeConfiguredExtension,
} from '../acp/extensions';
readAllConfig,
readConfig,
removeConfig,
upsertConfig,
addExtension as apiAddExtension,
removeExtension as apiRemoveExtension,
providers,
} from '../api';
import { getConfiguredExtensions } from '../acp/extensions';
import { pruneDeprecatedBundledExtensions, syncBundledExtensions } from './settings/extensions';
import type {
ConfigResponse,
UpsertConfigQuery,
ConfigKeyQuery,
ProviderDetails,
ExtensionQuery,
ExtensionConfig,
} from '../api';
@@ -108,7 +113,10 @@ export const ConfigProvider: React.FC<ConfigProviderProps> = ({ children }) => {
const addExtension = useCallback(
async (name: string, config: ExtensionConfig, enabled: boolean) => {
await addConfiguredExtension(name, config, enabled);
const query: ExtensionQuery = { name, config, enabled };
await apiAddExtension({
body: query,
});
await reloadConfig();
// Refresh extensions list after successful addition
await refreshExtensions();
@@ -118,7 +126,7 @@ export const ConfigProvider: React.FC<ConfigProviderProps> = ({ children }) => {
const removeExtension = useCallback(
async (name: string) => {
await removeConfiguredExtension(name);
await apiRemoveExtension({ path: { name: name } });
await reloadConfig();
// Refresh extensions list after successful removal
await refreshExtensions();
@@ -198,10 +206,11 @@ export const ConfigProvider: React.FC<ConfigProviderProps> = ({ children }) => {
config: ExtensionConfig,
enabled: boolean
) => {
await addConfiguredExtension(name, config, enabled);
const query: ExtensionQuery = { name, config, enabled };
await apiAddExtension({ body: query });
};
const removeExtensionForSync = async (name: string) => {
await removeConfiguredExtension(name);
await apiRemoveExtension({ path: { name } });
};
extensions = await pruneDeprecatedBundledExtensions(extensions, removeExtensionForSync);
await syncBundledExtensions(extensions, addExtensionForSync);
@@ -8,8 +8,7 @@ import { FixedExtensionEntry, useConfig } from '../ConfigContext';
import { toastService } from '../../toasts';
import { formatExtensionName } from '../settings/extensions/subcomponents/ExtensionList';
import { nameToKey } from '../settings/extensions/utils';
import { ExtensionConfig } from '../../api';
import { getSessionExtensions } from '../../acp/extensions';
import { ExtensionConfig, getSessionExtensions } from '../../api';
import { addToAgent, removeFromAgent } from '../settings/extensions/agent-api';
import {
setExtensionOverride,
@@ -120,9 +119,14 @@ export const BottomMenuExtensionSelection = ({ sessionId }: BottomMenuExtensionS
}
try {
const extensions = await getSessionExtensions(sessionId);
setSessionExtensions(extensions);
setIsSessionExtensionsLoaded(true);
const response = await getSessionExtensions({
path: { session_id: sessionId },
});
if (response.data?.extensions) {
setSessionExtensions(response.data.extensions);
setIsSessionExtensionsLoaded(true);
}
} catch (error) {
console.error('Failed to fetch session extensions:', error);
setIsSessionExtensionsLoaded(true);
@@ -193,8 +197,13 @@ export const BottomMenuExtensionSelection = ({ sessionId }: BottomMenuExtensionS
}
sortTimeoutRef.current = setTimeout(async () => {
const extensions = await getSessionExtensions(sessionId);
setSessionExtensions(extensions);
const response = await getSessionExtensions({
path: { session_id: sessionId },
});
if (response.data?.extensions) {
setSessionExtensions(response.data.extensions);
}
setPendingSort(false);
setIsTransitioning(false);
setTogglingExtension(null);
@@ -1,6 +1,5 @@
import { toastService } from '../../../toasts';
import { ExtensionConfig } from '../../../api';
import { addSessionExtension, removeSessionExtension } from '../../../acp/extensions';
import { agentAddExtension, ExtensionConfig, agentRemoveExtension } from '../../../api';
import { errorMessage } from '../../../utils/conversionUtils';
import {
createExtensionRecoverHints,
@@ -21,7 +20,10 @@ export async function addToAgent(
: 0;
try {
await addSessionExtension(sessionId, extensionConfig);
await agentAddExtension({
body: { session_id: sessionId, config: extensionConfig },
throwOnError: true,
});
if (showToast) {
toastService.dismiss(toastId);
toastService.success({
@@ -59,7 +61,10 @@ export async function removeFromAgent(
: 0;
try {
await removeSessionExtension(sessionId, extensionName);
await agentRemoveExtension({
body: { session_id: sessionId, name: extensionName },
throwOnError: true,
});
if (showToast) {
toastService.dismiss(toastId);
toastService.success({