UI acp migratoin: Decouple desktop UI types from generated OpenAPI types (#10109)

This commit is contained in:
Lifei Zhou
2026-06-30 22:36:38 +10:00
committed by GitHub
parent 66e981e29a
commit 808b8ca9ad
77 changed files with 622 additions and 128 deletions
@@ -1,6 +1,7 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { createSession } from '../sessions';
import type { ExtensionConfig, Session } from '../api';
import type { ExtensionConfig } from '../types/extensions';
import type { Session } from '../types/session';
import type { FixedExtensionEntry } from '../components/ConfigContext';
import type { GooseExtension, GooseExtensionEntry } from '@aaif/goose-sdk';
import { getConfiguredGooseExtensions } from '../acp/extensions';
+1 -1
View File
@@ -1,8 +1,8 @@
import { describe, it, expect } from 'vitest';
import { getSessionDisplayName, shouldShowNewChatTitle } from '../sessions';
import { prependUnique } from '../hooks/useNavigationSessions';
import type { Session } from '../api';
import type { SessionListItem } from '../acp/sessions';
import type { Session } from '../types/session';
// Helper to build a minimal Session object for testing.
function makeSession(overrides: Partial<Session> = {}): Session {
@@ -1,8 +1,8 @@
import type { SessionNotification } from '@agentclientprotocol/sdk';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { Session } from '../../api';
import { AppEvents } from '../../constants/events';
import { ChatState } from '../../types/chatState';
import type { Session } from '../../types/session';
import { maybeHandlePlatformEvent } from '../../utils/platform_events';
import { handleAcpSessionNotification } from '../chatNotifications';
import type { AcpChatSessionSnapshot } from '../chatSessionStore';
@@ -1,6 +1,7 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { Message, Session } from '../../api';
import type { Message } from '../../types/message';
import { ChatState } from '../../types/chatState';
import type { Session } from '../../types/session';
import { acpChatSessionController } from '../chatSessionController';
import {
acpChatSessionActions,
@@ -5,8 +5,9 @@ import type {
} from '@agentclientprotocol/sdk';
import { act, renderHook } from '@testing-library/react';
import { afterEach, describe, expect, it } from 'vitest';
import type { Message, Session } from '../../api';
import type { Message } from '../../types/message';
import { ChatState } from '../../types/chatState';
import type { Session } from '../../types/session';
import {
acpElicitationUserInputRequestId,
acpChatSessionActions,
@@ -127,6 +127,7 @@ describe('ACP MCP app helpers', () => {
{
name: 'weather__refresh',
description: 'Refresh weather',
parameters: [],
inputSchema: {
type: 'object',
properties: {
@@ -137,6 +138,7 @@ describe('ACP MCP app helpers', () => {
{
name: 'calendar__refresh',
description: 'Refresh calendar',
parameters: [],
inputSchema: { type: 'object' },
},
],
@@ -150,7 +152,7 @@ describe('ACP MCP app helpers', () => {
name: 'weather__refresh',
description: 'Refresh weather',
parameters: [],
input_schema: {
inputSchema: {
type: 'object',
properties: {
city: { type: 'string' },
+1 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
import type { Message } from '../../api';
import type { Message } from '../../types/message';
import { messageToAcpPromptContent } from '../prompt';
describe('messageToAcpPromptContent', () => {
@@ -1,8 +1,7 @@
import type { GooseSessionNotification_unstable } from '@aaif/goose-sdk';
import type { RequestPermissionRequest, SessionNotification } from '@agentclientprotocol/sdk';
import { describe, expect, it } from 'vitest';
import type { Message, MessageContent } from '../../api';
import type { NotificationEvent } from '../../types/message';
import type { Message, NotificationEvent } from '../../types/message';
import {
createAcpSessionNotificationAdapter,
type AcpChatStateChange,
@@ -97,7 +96,7 @@ function expectOnlyNotificationChange(chatStateChanges: AcpChatStateChange[]): N
return chatStateChange.notification;
}
function firstContent(message: Message): MessageContent {
function firstContent(message: Message): Message['content'][number] {
const content = message.content[0];
expect(content).toBeDefined();
return content;
+1 -1
View File
@@ -1,4 +1,4 @@
import type { Message } from '../../api';
import type { Message } from '../../types/message';
import type { AcpElicitationRequest } from '../elicitationRequests';
import {
type AcpChatStateChange,
+11 -4
View File
@@ -2,7 +2,7 @@ import type {
ContentBlock as AcpContentBlock,
SessionNotification,
} from '@agentclientprotocol/sdk';
import type { Message, MessageContent } from '../../api';
import type { ContentBlock, Message } from '../../types/message';
import {
type AcpChatStateChange,
type AdapterState,
@@ -11,6 +11,8 @@ import {
messagesChange,
} from './shared';
type StreamedContentBlock = Extract<ContentBlock, { type: 'text' | 'image' }>;
export function applyContentChunk(
state: AdapterState,
role: Message['role'],
@@ -91,7 +93,9 @@ export function applyThoughtChunk(
return messagesChange(state);
}
function messageContentFromAcpContentBlock(content: AcpContentBlock): MessageContent | undefined {
function messageContentFromAcpContentBlock(
content: AcpContentBlock
): StreamedContentBlock | undefined {
switch (content.type) {
case 'text':
return {
@@ -151,7 +155,10 @@ function lastMergeableMessageWithRole(
return lastMessage;
}
function hasImageContent(message: Message, image: Extract<MessageContent, { type: 'image' }>) {
function hasImageContent(
message: Message,
image: Extract<StreamedContentBlock, { type: 'image' }>
) {
return message.content.some(
(content) =>
content.type === 'image' && content.data === image.data && content.mimeType === image.mimeType
@@ -173,7 +180,7 @@ function messagesChangeWithLocalSteerConfirmation(
function reconcileLocalSteerTextChunk(
state: AdapterState,
message: Message,
content: MessageContent,
content: StreamedContentBlock,
isSteerChunk: boolean | undefined
): boolean {
if (!isSteerChunk || !message.metadata.steer || message.role !== 'user') {
+2 -2
View File
@@ -1,6 +1,6 @@
import type { ToolCall, ToolCallUpdate } from '@agentclientprotocol/sdk';
import type { Message, TokenState } from '../../api';
import type { NotificationEvent } from '../../types/message';
import type { TokenState } from '../../types/chat';
import type { Message, NotificationEvent } from '../../types/message';
export type AcpChatStateChange =
| { type: 'messages'; messages: Message[] }
+9 -6
View File
@@ -3,7 +3,8 @@ import type {
ToolCall,
ToolCallUpdate,
} from '@agentclientprotocol/sdk';
import type { ContentBlock as ApiContentBlock, Message } from '../../api';
import type { Message } from '../../types/message';
import type { ContentBlock as GooseContentBlock } from '../../types/message';
import { findMessageForChunk } from './messages';
import { toolNotificationChange } from './toolNotifications';
import {
@@ -190,8 +191,8 @@ function toolResultValue(
};
}
function toolResultContent(update: ToolCallUpdate): ApiContentBlock[] {
const content: ApiContentBlock[] = [];
function toolResultContent(update: ToolCallUpdate): GooseContentBlock[] {
const content: GooseContentBlock[] = [];
for (const item of update.content ?? []) {
if (item.type !== 'content') {
@@ -215,7 +216,9 @@ function toolResultContent(update: ToolCallUpdate): ApiContentBlock[] {
return [];
}
function apiContentBlockFromAcpContentBlock(content: AcpContentBlock): ApiContentBlock | undefined {
function apiContentBlockFromAcpContentBlock(
content: AcpContentBlock
): GooseContentBlock | undefined {
switch (content.type) {
case 'text':
return {
@@ -260,7 +263,7 @@ function apiContentBlockFromAcpContentBlock(content: AcpContentBlock): ApiConten
function apiResourceContentsFromAcpResource(
resource: Extract<AcpContentBlock, { type: 'resource' }>['resource']
): Extract<ApiContentBlock, { type: 'resource' }>['resource'] {
): Extract<GooseContentBlock, { type: 'resource' }>['resource'] {
if ('text' in resource) {
return {
uri: resource.uri,
@@ -303,7 +306,7 @@ interface DesktopMcpAppMeta extends Record<string, unknown> {
}
type ToolResultValue = {
content: ApiContentBlock[];
content: GooseContentBlock[];
structuredContent?: unknown;
isError: boolean;
_meta?: DesktopMcpAppMeta;
+6 -2
View File
@@ -1,11 +1,15 @@
import { v7 as uuidv7 } from 'uuid';
import type { Message, Session } from '../api';
import type { GooseExtension } from '@aaif/goose-sdk';
import { AppEvents } from '../constants/events';
import { ChatState } from '../types/chatState';
import type { Session } from '../types/session';
import { errorMessage } from '../utils/conversionUtils';
import { showExtensionLoadResults } from '../utils/extensionErrorUtils';
import { createUserMessage, getPendingToolConfirmationIds } from '../types/message';
import {
createUserMessage,
getPendingToolConfirmationIds,
type Message,
} from '../types/message';
import {
acpChatSessionActions,
acpChatSessionStore,
+3 -2
View File
@@ -1,9 +1,10 @@
import { useEffect, useState } from 'react';
import type { GooseSessionNotification_unstable } from '@aaif/goose-sdk';
import type { RequestPermissionRequest, SessionNotification } from '@agentclientprotocol/sdk';
import type { Message, Session, TokenState } from '../api';
import type { TokenState } from '../types/chat';
import { ChatState } from '../types/chatState';
import type { NotificationEvent } from '../types/message';
import type { Message, NotificationEvent } from '../types/message';
import type { Session } from '../types/session';
import {
createAcpSessionNotificationAdapter,
type AcpChatStateChange,
+1 -3
View File
@@ -1,7 +1,5 @@
import { getAcpClient } from './acpConnection';
import type { DiagnosticsReport } from '../api';
export type DiagnosticsLevel = 'summary' | 'full';
import type { DiagnosticsLevel, DiagnosticsReport } from '../types/diagnostics';
export async function getDiagnosticsReport(
sessionId: string,
+2 -2
View File
@@ -1,4 +1,4 @@
import type { ExtensionConfig, ExtensionEntry } from '../api';
import type { ExtensionConfig, ExtensionEntry } from '../types/extensions';
import type { GooseExtension, GooseExtensionEntry } from '@aaif/goose-sdk';
import { getAcpClient } from './acpConnection';
@@ -115,7 +115,7 @@ export function extensionConfigToGooseExtension(config: ExtensionConfig): GooseE
case 'stdio':
return {
type: 'mcp',
server: { name: config.name, command: config.cmd, args: config.args, env: [] },
server: { name: config.name, command: config.cmd, args: config.args ?? [], env: [] },
envKeys: config.env_keys ?? [],
description: config.description,
timeout: config.timeout,
+5 -17
View File
@@ -1,8 +1,10 @@
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
import type { GooseApp, ToolInfo } from '../api';
import type { ToolListItem } from '@aaif/goose-sdk';
import type { GooseApp } from '../types/apps';
import { getAcpClient } from './acpConnection';
type JsonRecord = Record<string, unknown>;
export type McpAppTool = ToolListItem;
export type McpAppResourceResponse = {
uri: string;
mimeType: string | null;
@@ -60,20 +62,6 @@ function flattenReadResourceResult(result: unknown, fallbackUri: string): McpApp
};
}
function acpToolToToolInfo(value: unknown): ToolInfo | null {
if (!isRecord(value)) return null;
const name = stringField(value, 'name');
if (!name) return null;
const inputSchema = value.inputSchema ?? value.input_schema;
return {
name,
description: stringField(value, 'description') ?? '',
parameters: [],
input_schema: isRecord(inputSchema) ? inputSchema : undefined,
};
}
function acpApp(value: unknown): GooseApp | null {
if (!isRecord(value)) return null;
return value as GooseApp;
@@ -99,10 +87,10 @@ export async function importMcpApp(html: string): Promise<void> {
export async function listMcpAppTools(
sessionId: string,
extensionName?: string
): Promise<ToolInfo[] | null> {
): Promise<McpAppTool[]> {
const client = await getAcpClient();
const response = await client.goose.toolsList_unstable({ sessionId });
const tools = response.tools.map(acpToolToToolInfo).filter((tool): tool is ToolInfo => !!tool);
const tools = response.tools;
if (!extensionName) return tools;
const prefix = `${extensionName}__`;
+1 -1
View File
@@ -1,5 +1,5 @@
import type { RequestPermissionRequest, RequestPermissionResponse } from '@agentclientprotocol/sdk';
import type { Permission } from '../api';
import type { Permission } from '../types/permissions';
import { acpChatSessionActions, acpPermissionUserInputRequestId } from './chatSessionStore';
interface PendingPermissionRequest {
+1 -1
View File
@@ -1,6 +1,6 @@
import type { ContentBlock, PromptResponse } from '@agentclientprotocol/sdk';
import type { SteerSessionRequest_unstable, SteerSessionResponse_unstable } from '@aaif/goose-sdk';
import type { Message } from '../api';
import type { Message } from '../types/message';
import { getAcpClient } from './acpConnection';
export async function acpPromptSession(
+2 -1
View File
@@ -6,7 +6,8 @@ import type {
ProviderTemplateCatalogEntryDto,
ProviderTemplateDto,
} from '@aaif/goose-sdk';
import type { ProviderDetails, ThinkingEffort, UpdateCustomProviderRequest } from '../api';
import type { ProviderDetails, ThinkingEffort } from '../api';
import type { UpdateCustomProviderRequest } from '../types/providers';
import { getAcpClient } from './acpConnection';
export type { CanonicalModelInfoDto, ProviderSecretDto };
+1 -1
View File
@@ -1,4 +1,4 @@
import type { ExtensionConfig } from '../api';
import type { ExtensionConfig } from '../types/extensions';
import { getAcpClient } from './acpConnection';
import { extensionConfigToGooseExtension, gooseExtensionToExtensionConfig } from './extensions';
@@ -1,6 +1,6 @@
import type { GooseSessionNotification_unstable } from '@aaif/goose-sdk';
import type { RequestPermissionRequest, SessionNotification } from '@agentclientprotocol/sdk';
import type { Message } from '../api';
import type { Message } from '../types/message';
import {
applyElicitationRequest as applyElicitationRequestToState,
applyElicitationStatus as applyElicitationStatusToState,
+2 -1
View File
@@ -8,7 +8,8 @@ import type {
import type { GooseExtension, SessionImportSource } from '@aaif/goose-sdk';
import { getAcpClient } from './acpConnection';
import { DEFAULT_CHAT_TITLE } from '../contexts/ChatContext';
import type { ExtensionLoadResult, Session } from '../api';
import type { ExtensionLoadResult } from '../types/extensions';
import type { Session } from '../types/session';
import type { Recipe } from '../recipe';
interface GooseSessionInfoMeta {
+6 -3
View File
@@ -10,7 +10,6 @@ import ChatInput from './ChatInput';
import { ChatInputCard } from './ChatInputCard';
import { ScrollArea, ScrollAreaHandle } from './ui/scroll-area';
import { useFileDrop } from '../hooks/useFileDrop';
import { Message } from '../api';
import { ChatState } from '../types/chatState';
import { ChatType } from '../types/chat';
import { useIsMobile } from '../hooks/use-mobile';
@@ -23,9 +22,13 @@ import { RecipeHeader } from './RecipeHeader';
import { RecipeWarningModal } from './ui/RecipeWarningModal';
import { scanRecipe } from '../recipe';
import type { Recipe } from '../recipe';
import { UserInput } from '../types/message';
import RecipeActivities from './recipes/RecipeActivities';
import { getThinkingMessage, getTextAndImageContent } from '../types/message';
import {
getThinkingMessage,
getTextAndImageContent,
type Message,
type UserInput,
} from '../types/message';
import { substituteParameters } from '../utils/parameterSubstitution';
import { useAutoSubmit } from '../hooks/useAutoSubmit';
import { Goose } from './icons';
+1 -1
View File
@@ -27,7 +27,7 @@ import { Recipe } from '../recipe';
import { MessageQueue, QueuedMessage } from './MessageQueue';
import { detectInterruption } from '../utils/interruptionDetector';
import { DiagnosticsModal } from './ui/Diagnostics';
import { Message } from '../api';
import type { Message } from '../types/message';
import { getInitialWorkingDir } from '../utils/workingDir';
import { getPredefinedModelsFromEnv } from './settings/models/predefinedModelsUtils';
import { trackFileAttached, trackVoiceDictation, trackDiagnosticsOpened } from '../utils/analytics';
+3 -2
View File
@@ -9,9 +9,10 @@ import {
} from '../acp/extensions';
import { pruneDeprecatedBundledExtensions, syncBundledExtensions } from './settings/extensions';
import { nameToKey } from './settings/extensions/utils';
import type { ConfigResponse, ProviderDetails, ExtensionConfig } from '../api';
import type { ConfigResponse, ProviderDetails } from '../api';
import type { ExtensionConfig } from '../types/extensions';
export type { ExtensionConfig } from '../api/types.gen';
export type { ExtensionConfig } from '../types/extensions';
// Define a local version that matches the structure of the imported one
export type FixedExtensionEntry = ExtensionConfig & {
@@ -1,7 +1,7 @@
import { act, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, expect, it, vi } from 'vitest';
import type { ActionRequired } from '../api';
import type { ActionRequired } from '../types/message';
import { IntlTestWrapper } from '../i18n/test-utils';
import ElicitationRequest from './ElicitationRequest';
@@ -1,5 +1,5 @@
import { useState, useEffect, useRef } from 'react';
import { ActionRequired } from '../api';
import type { ActionRequired } from '../types/message';
import { defineMessages, useIntl } from '../i18n';
import { Button } from './ui/button';
import JsonSchemaForm from './ui/JsonSchemaForm';
@@ -11,7 +11,7 @@ import {
import { Button } from './ui/button';
import { extractExtensionName } from './settings/extensions/utils';
import { addExtensionFromDeepLink } from './settings/extensions/deeplink';
import type { ExtensionConfig } from '../api/types.gen';
import type { ExtensionConfig } from '../types/extensions';
import { View, ViewOptions } from '../utils/navigationUtils';
import { useConfig } from './ConfigContext';
import { toastService } from '../toasts';
+1 -1
View File
@@ -15,8 +15,8 @@ import {
getAnyToolConfirmationData,
ToolConfirmationData,
NotificationEvent,
type Message,
} from '../types/message';
import { Message } from '../api';
import ToolCallConfirmation from './ToolCallConfirmation';
import ElicitationRequest from './ElicitationRequest';
import MessageCopyLink from './MessageCopyLink';
@@ -367,7 +367,7 @@ export default function McpAppRenderer({
const tool: Tool = {
name: toolName,
description: match.description || undefined,
inputSchema: (match.input_schema as Tool['inputSchema']) ?? { type: 'object' as const },
inputSchema: (match.inputSchema as Tool['inputSchema']) ?? { type: 'object' as const },
};
toolDefRef.current = tool;
setMcpTool(tool);
@@ -9,10 +9,9 @@
* automatically coalesce into a single network call.
*/
import type { ToolInfo } from '../../api/types.gen';
import { listMcpAppTools } from '../../acp/mcp-apps';
import { listMcpAppTools, type McpAppTool } from '../../acp/mcp-apps';
type ToolsList = Array<ToolInfo>;
type ToolsList = Array<McpAppTool>;
const cache = new Map<string, Promise<ToolsList | null>>();
@@ -16,7 +16,6 @@
import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { defineMessages, useIntl } from '../i18n';
import { Message, SystemNotificationContent } from '../api';
import GooseMessage from './GooseMessage';
import UserMessage from './UserMessage';
import {
@@ -27,7 +26,7 @@ import {
CreditsExhaustedNotification,
getCreditsExhaustedNotification,
} from './context_management/CreditsExhaustedNotification';
import { NotificationEvent } from '../types/message';
import type { Message, NotificationEvent, SystemNotificationContent } from '../types/message';
import LoadingGoose from './LoadingGoose';
import { ChatType } from '../types/chat';
import { identifyConsecutiveToolCalls, isInChain } from '../utils/toolCallChaining';
@@ -4,8 +4,8 @@ import { toast } from 'react-toastify';
import { AppEvents } from '../constants/events';
import { defineMessages, useIntl } from '../i18n';
import { acpExportSession, acpForkSession, acpRenameSession } from '../acp/sessions';
import type { Session } from '../api';
import { getSessionDisplayName } from '../sessions';
import type { Session } from '../types/session';
import { errorMessage } from '../utils/conversionUtils';
import { cn } from '../utils';
import { Button } from './ui/button';
@@ -1,6 +1,6 @@
import { useState, useEffect } from 'react';
import { Button } from './ui/button';
import { Permission } from '../api';
import type { Permission } from '../types/permissions';
import { resolveAcpPermissionRequest } from '../acp/permissionRequests';
import { defineMessages, useIntl } from '../i18n';
@@ -1,4 +1,4 @@
import { ActionRequired } from '../api';
import type { ActionRequired } from '../types/message';
import { defineMessages, useIntl } from '../i18n';
import { snakeToTitleCase } from '../utils';
import ToolApprovalButtons from './ToolApprovalButtons';
@@ -16,7 +16,7 @@ import { LoadingStatus } from './ui/Dot';
import { ChevronRight, ExternalLink } from 'lucide-react';
import { TooltipWrapper } from './settings/providers/subcomponents/buttons/TooltipWrapper';
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
import type { ContentBlock } from '../api';
import type { ContentBlock } from '../types/message';
import McpAppRenderer from './McpApps/McpAppRenderer';
import ToolApprovalButtons from './ToolApprovalButtons';
+1 -2
View File
@@ -1,8 +1,7 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import ImagePreview from './ImagePreview';
import MarkdownContent from './MarkdownContent';
import { getTextAndImageContent } from '../types/message';
import { Message } from '../api';
import { getTextAndImageContent, type Message } from '../types/message';
import MessageCopyLink from './MessageCopyLink';
import { formatMessageTimestamp } from '../utils/timeUtils';
import Edit from './icons/Edit';
+1 -1
View File
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef, useState } from 'react';
import { MainPanelLayout } from '../Layout/MainPanelLayout';
import { Button } from '../ui/button';
import { Download, Play, Upload } from 'lucide-react';
import type { GooseApp } from '../../api';
import type { GooseApp } from '../../types/apps';
import { exportMcpApp, importMcpApp, listMcpApps } from '../../acp/mcp-apps';
import { useChatContext } from '../../contexts/ChatContext';
import { formatAppName } from '../../utils/conversionUtils';
@@ -3,7 +3,7 @@ import { useConfig, type FixedExtensionEntry } from '../ConfigContext';
import { toastService } from '../../toasts';
import { formatExtensionName } from '../settings/extensions/subcomponents/ExtensionList';
import { nameToKey } from '../settings/extensions/utils';
import { ExtensionConfig, getSessionExtensions } from '../../api';
import type { ExtensionConfig } from '../../types/extensions';
import { getSessionExtensions as getAcpSessionExtensions } from '../../acp/session-extensions';
import { addToAgent, removeFromAgent } from '../settings/extensions/agent-api';
import { defineMessages, useIntl } from '../../i18n';
@@ -60,7 +60,7 @@ interface BottomMenuExtensionSelectionProps {
onNextChatExtensionDraftChange?: (draft: NextChatExtensionDraft) => void;
}
type GetSessionExtensionsSignal = Parameters<typeof getSessionExtensions>[0]['signal'];
type GetSessionExtensionsSignal = { aborted: boolean };
const EXTENSION_SORT_DELAY_MS = 800;
@@ -1,6 +1,6 @@
import React from 'react';
import { AlertTriangle, ExternalLink } from 'lucide-react';
import { Message, SystemNotificationContent } from '../../api';
import type { Message, SystemNotificationContent } from '../../types/message';
import { WEB_PROTOCOLS } from '../../utils/urlSecurity';
import { defineMessages, useIntl } from '../../i18n';
@@ -1,5 +1,5 @@
import React from 'react';
import { Message, SystemNotificationContent } from '../../api';
import type { Message, SystemNotificationContent } from '../../types/message';
interface SystemNotificationInlineProps {
notification: SystemNotificationContent;
@@ -1,6 +1,6 @@
import { View, ViewOptions } from '../../utils/navigationUtils';
import ExtensionsSection from '../settings/extensions/ExtensionsSection';
import { ExtensionConfig } from '../../api';
import type { ExtensionConfig } from '../../types/extensions';
import { MainPanelLayout } from '../Layout/MainPanelLayout';
import { Button } from '../ui/button';
import { Plus } from 'lucide-react';
@@ -1,6 +1,7 @@
import { useState, useEffect, useMemo } from 'react';
import { ProviderDetails, UpdateCustomProviderRequest } from '../../api';
import type { ProviderDetails } from '../../api';
import { acpCreateCustomProviderFromRequest, acpListProviderDetails } from '../../acp/providers';
import type { UpdateCustomProviderRequest } from '../../types/providers';
import { Select } from '../ui/Select';
import ProviderConfigForm from './ProviderConfigForm';
import LocalModelPicker from './LocalModelPicker';
@@ -12,9 +12,9 @@ import {
getThinkingContent,
ToolRequestMessageContent,
ToolResponseMessageContent,
type Message,
} from '../../types/message';
import { formatMessageTimestamp } from '../../utils/timeUtils';
import { Message } from '../../api';
const i18n = defineMessages({
errorLoadingDetails: {
@@ -6,7 +6,7 @@ import ExternalBackendSection from './app/ExternalBackendSection';
import AppSettingsSection from './app/AppSettingsSection';
import ConfigSettings from './config/ConfigSettings';
import PromptsSettingsSection from './PromptsSettingsSection';
import { ExtensionConfig } from '../../api';
import type { ExtensionConfig } from '../../types/extensions';
import { MainPanelLayout } from '../Layout/MainPanelLayout';
import {
Bot,
@@ -15,7 +15,7 @@ import {
} from './utils';
import { activateExtensionDefault, deleteExtension, toggleExtensionDefault } from './index';
import { ExtensionConfig } from '../../../api/types.gen';
import type { ExtensionConfig } from '../../../types/extensions';
const i18n = defineMessages({
addCustomExtension: {
@@ -1,5 +1,5 @@
import { toastService } from '../../../toasts';
import { ExtensionConfig } from '../../../api';
import type { ExtensionConfig } from '../../../types/extensions';
import { addSessionExtension, removeSessionExtension } from '../../../acp/session-extensions';
import { errorMessage } from '../../../utils/conversionUtils';
import {
@@ -1,4 +1,4 @@
import type { ExtensionConfig } from '../../../api/types.gen';
import type { ExtensionConfig } from '../../../types/extensions';
import { FixedExtensionEntry } from '../../ConfigContext';
import bundledExtensionsData from './bundled-extensions.json';
import deprecatedBundledExtensionsData from './deprecated-bundled-extensions.json';
@@ -1,4 +1,4 @@
import type { ExtensionConfig } from '../../../api';
import type { ExtensionConfig } from '../../../types/extensions';
import { toastService } from '../../../toasts';
import { DEFAULT_EXTENSION_TIMEOUT } from './utils';
@@ -1,4 +1,4 @@
import type { ExtensionConfig } from '../../../api/types.gen';
import type { ExtensionConfig } from '../../../types/extensions';
import { toastService } from '../../../toasts';
import {
trackExtensionAdded,
@@ -1,6 +1,6 @@
import ExtensionItem from './ExtensionItem';
import builtInExtensionsData from '../../../../built-in-extensions.json';
import { ExtensionConfig } from '../../../../api';
import type { ExtensionConfig } from '../../../../types/extensions';
import { FixedExtensionEntry } from '../../../ConfigContext';
import { combineCmdAndArgs } from '../utils';
import { defineMessages, useIntl } from '../../../../i18n';
@@ -164,7 +164,7 @@ export function getSubtitle(config: ExtensionConfig) {
default:
return {
description: config.description || null,
command: 'cmd' in config ? combineCmdAndArgs(config.cmd, config.args) : null,
command: 'cmd' in config ? combineCmdAndArgs(config.cmd, config.args ?? []) : null,
};
}
}
@@ -1,5 +1,5 @@
import type { FixedExtensionEntry } from '../../ConfigContext';
import type { ExtensionConfig } from '../../../api/types.gen';
import type { ExtensionConfig } from '../../../types/extensions';
import { parse as parseShellQuote } from 'shell-quote';
// Default extension timeout in seconds
@@ -110,7 +110,7 @@ export function extensionToFormData(extension: FixedExtensionEntry): ExtensionFo
extension.type === 'platform'
? 'stdio'
: extension.type,
cmd: extension.type === 'stdio' ? combineCmdAndArgs(extension.cmd, extension.args) : undefined,
cmd: extension.type === 'stdio' ? combineCmdAndArgs(extension.cmd, extension.args ?? []) : undefined,
endpoint:
extension.type === 'streamable_http' || extension.type === 'sse'
? (extension.uri ?? undefined)
@@ -15,7 +15,7 @@ import { getModelDisplayName } from '../predefinedModelsUtils';
import { ModelSettingsPanel } from '../../localInference/ModelSettingsPanel';
import { ScrollArea } from '../../../ui/scroll-area';
import { defineMessages, useIntl } from '../../../../i18n';
import type { Message } from '../../../../api';
import type { Message } from '../../../../types/message';
const i18n = defineMessages({
selectModel: {
@@ -1,7 +1,6 @@
import { useEffect, useMemo, useState } from 'react';
import { Button } from '../../ui/button';
import { ChevronDownIcon, SlidersHorizontal, AlertCircle } from 'lucide-react';
import { PermissionLevel } from '../../../api';
import { listTools, setToolPermissions } from '../../../acp/permissions';
import type { ToolListItem, ToolPermissionLevel } from '../../../acp/permissions';
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '../../ui/dialog';
@@ -80,7 +79,7 @@ export default function PermissionModal({ extensionName, onClose }: PermissionMo
{ value: 'always_allow', label: intl.formatMessage(i18n.alwaysAllow) },
{ value: 'ask_before', label: intl.formatMessage(i18n.askBefore) },
{ value: 'never_allow', label: intl.formatMessage(i18n.neverAllow) },
] as { value: PermissionLevel; label: string }[];
] as { value: ToolPermissionLevel; label: string }[];
const chatContext = useChatContext();
const sessionId = chatContext?.chat.sessionId || '';
@@ -126,7 +125,7 @@ export default function PermissionModal({ extensionName, onClose }: PermissionMo
fetchTools();
}, [extensionName, sessionId]);
const handleSettingChange = (toolName: string, newPermission: PermissionLevel) => {
const handleSettingChange = (toolName: string, newPermission: ToolPermissionLevel) => {
setUpdatedPermissions((prev) => ({
...prev,
[toolName]: newPermission,
@@ -243,7 +242,7 @@ export default function PermissionModal({ extensionName, onClose }: PermissionMo
<DropdownMenuItem
key={option.value}
onSelect={() =>
handleSettingChange(tool.name, option.value as PermissionLevel)
handleSettingChange(tool.name, option.value)
}
>
{option.label}
@@ -2,8 +2,9 @@ import React, { memo, useMemo, useCallback, useState } from 'react';
import { ProviderCard } from './subcomponents/ProviderCard';
import CardContainer from './subcomponents/CardContainer';
import ProviderConfigurationModal from './modal/ProviderConfigurationModal';
import { ProviderDetails, UpdateCustomProviderRequest } from '../../../api';
import type { ProviderDetails } from '../../../api';
import type { CustomProviderConfigDto } from '@aaif/goose-sdk';
import type { UpdateCustomProviderRequest } from '../../../types/providers';
import {
acpCreateCustomProviderFromRequest,
acpGetCustomProvider,
@@ -3,7 +3,7 @@ import { Input } from '../../../../../ui/input';
import { Select } from '../../../../../ui/Select';
import { Button } from '../../../../../ui/button';
import { SecureStorageNotice } from '../SecureStorageNotice';
import { UpdateCustomProviderRequest } from '../../../../../../api';
import type { UpdateCustomProviderRequest } from '../../../../../../types/providers';
import type { ProviderTemplateDto } from '@aaif/goose-sdk';
import { Plus, X, Trash2, AlertTriangle, ExternalLink, Search, Settings } from 'lucide-react';
import { cn } from '../../../../../../utils';
+1 -1
View File
@@ -4,8 +4,8 @@ import { MemoryRouter } from 'react-router-dom';
import type { PropsWithChildren } from 'react';
import { useAutoSubmit } from './useAutoSubmit';
import { ChatState } from '../types/chatState';
import type { Session } from '../api';
import type { UserInput } from '../types/message';
import type { Session } from '../types/session';
function makeSession(overrides: Partial<Session> = {}): Session {
return {
+2 -3
View File
@@ -1,10 +1,9 @@
import { AppEvents } from '../constants/events';
import { useCallback, useEffect, useRef } from 'react';
import { useSearchParams } from 'react-router-dom';
import { Session } from '../api';
import { Message } from '../api';
import { ChatState } from '../types/chatState';
import { UserInput } from '../types/message';
import type { Message, UserInput } from '../types/message';
import type { Session } from '../types/session';
/**
* Auto-submit scenarios:
+8 -2
View File
@@ -3,9 +3,15 @@ import { defineMessages, useIntl } from '../i18n';
import { AppEvents } from '../constants/events';
import { ChatState } from '../types/chatState';
import { Message, Session, TokenState } from '../api';
import type { TokenState } from '../types/chat';
import type { Session } from '../types/session';
import { createUserMessage, NotificationEvent, UserInput } from '../types/message';
import {
createUserMessage,
type Message,
type NotificationEvent,
type UserInput,
} from '../types/message';
import { errorMessage } from '../utils/conversionUtils';
import type { UseChatSessionParams, UseChatSessionResult } from './useChatSessionTypes';
import { resolveAcpElicitationRequest } from '../acp/elicitationRequests';
+3 -2
View File
@@ -1,6 +1,7 @@
import type { Message, Session, TokenState } from '../api';
import type { ChatState } from '../types/chatState';
import type { NotificationEvent, UserInput } from '../types/message';
import type { TokenState } from '../types/chat';
import type { Message, NotificationEvent, UserInput } from '../types/message';
import type { Session } from '../types/session';
export interface UseChatSessionParams {
sessionId: string;
@@ -3,7 +3,7 @@ import { useNavigate, useLocation, useSearchParams } from 'react-router-dom';
import { useChatContext } from '../contexts/ChatContext';
import { getSessionDisplayName } from '../sessions';
import { AppEvents } from '../constants/events';
import type { Session } from '../api';
import type { Session } from '../types/session';
import {
acpGetSessionListItem,
acpListRecentSessions,
+1 -1
View File
@@ -49,7 +49,7 @@ import {
import { UPDATES_ENABLED } from './updates';
import './utils/recipeHash';
import { Client } from './api/client';
import { GooseApp } from './api';
import type { GooseApp } from './types/apps';
import installExtension, { REACT_DEVELOPER_TOOLS } from 'electron-devtools-installer';
import { BLOCKED_PROTOCOLS, WEB_PROTOCOLS } from './utils/urlSecurity';
import { buildCSP } from './utils/csp';
+1 -1
View File
@@ -1,6 +1,6 @@
import Electron, { contextBridge, ipcRenderer, webUtils } from 'electron';
import { Recipe } from './recipe';
import { GooseApp } from './api';
import type { GooseApp } from './types/apps';
import type { Settings, SettingKey } from './utils/settings';
import { defaultSettings } from './utils/settings';
+2 -1
View File
@@ -1,4 +1,5 @@
import { Session, ExtensionConfig } from './api';
import type { Session } from './types/session';
import type { ExtensionConfig } from './types/extensions';
import { DEFAULT_CHAT_TITLE } from './contexts/ChatContext';
import type { setViewType } from './hooks/useNavigation';
import type { FixedExtensionEntry } from './components/ConfigContext';
+46
View File
@@ -0,0 +1,46 @@
export type CspMetadata = {
baseUriDomains?: string[] | null;
connectDomains?: string[] | null;
frameDomains?: string[] | null;
resourceDomains?: string[] | null;
};
export type PermissionsMetadata = {
camera?: boolean;
clipboardWrite?: boolean;
geolocation?: boolean;
microphone?: boolean;
};
export type UiMetadata = {
csp?: CspMetadata | null;
domain?: string | null;
permissions?: PermissionsMetadata;
prefersBorder?: boolean | null;
};
export type ResourceMetadata = {
ui?: UiMetadata | null;
};
export type McpAppResource = {
_meta?: ResourceMetadata | null;
blob?: string | null;
description?: string | null;
mimeType: string;
name: string;
text?: string | null;
uri: string;
};
export type WindowProps = {
height?: number;
resizable?: boolean;
width?: number;
};
export type GooseApp = McpAppResource &
WindowProps & {
mcpServers?: string[];
prd?: string | null;
};
+15 -1
View File
@@ -1,5 +1,19 @@
import { Recipe } from '../recipe';
import { Message } from '../api';
import type { Message } from './message';
export type TokenState = {
accumulatedCacheReadTokens?: number;
accumulatedCacheWriteTokens?: number;
accumulatedCost?: number | null;
accumulatedInputTokens: number;
accumulatedOutputTokens: number;
accumulatedTotalTokens: number;
cacheReadTokens?: number;
cacheWriteTokens?: number;
inputTokens: number;
outputTokens: number;
totalTokens: number;
};
export interface ChatType {
sessionId: string;
+62
View File
@@ -0,0 +1,62 @@
export type DiagnosticsLevel = 'summary' | 'full';
export type SystemInfo = {
app_version: string;
architecture: string;
enabled_extensions: string[];
model?: string | null;
os: string;
os_version: string;
provider?: string | null;
};
export type DiagnosticsConfig = {
configPath: string;
configYaml?: string | null;
truncated: boolean;
};
export type DiagnosticsError = {
message: string;
path?: string | null;
};
export type DiagnosticsExtensions = {
enabled: string[];
};
export type DiagnosticsTextFile = {
content: string;
path: string;
truncated: boolean;
};
export type DiagnosticsLogs = {
llm: DiagnosticsTextFile[];
server?: DiagnosticsTextFile | null;
};
export type DiagnosticsPrompt = {
content: string;
name: string;
};
export type DiagnosticsScheduledRecipe = {
content: string;
path: string;
};
export type DiagnosticsReport = {
config?: DiagnosticsConfig | null;
errors: DiagnosticsError[];
extensions: DiagnosticsExtensions;
generatedAt: string;
level: DiagnosticsLevel;
logs: DiagnosticsLogs;
prompts: DiagnosticsPrompt[];
schedule?: unknown;
scheduledRecipes: DiagnosticsScheduledRecipe[];
schemaVersion: number;
session?: unknown;
system: SystemInfo;
};
+58
View File
@@ -0,0 +1,58 @@
import type { RecipeExtensionDto } from '@aaif/goose-sdk';
export type Envs = Record<string, string>;
type LegacySseExtensionConfig = {
description?: string | null;
name: string;
type: 'sse';
uri?: string | null;
};
type FrontendTool = {
_meta?: Record<string, unknown>;
annotations?: Record<string, unknown>;
description?: string;
execution?: Record<string, unknown>;
icons?: unknown[];
inputSchema: Record<string, unknown>;
name: string;
outputSchema?: Record<string, unknown>;
title?: string;
};
type FrontendExtensionConfig = {
available_tools?: string[] | null;
bundled?: boolean | null;
description?: string | null;
instructions?: string | null;
name: string;
tools: FrontendTool[];
type: 'frontend';
};
type InlinePythonExtensionConfig = {
available_tools?: string[] | null;
code: string;
dependencies?: string[] | null;
description?: string | null;
name: string;
timeout?: number | null;
type: 'inline_python';
};
export type ExtensionConfig =
| RecipeExtensionDto
| LegacySseExtensionConfig
| FrontendExtensionConfig
| InlinePythonExtensionConfig;
export type ExtensionEntry = ExtensionConfig & {
enabled: boolean;
};
export type ExtensionLoadResult = {
error?: string | null;
name: string;
success: boolean;
};
+228 -8
View File
@@ -1,11 +1,231 @@
import {
Message,
MessageEvent,
ActionRequired,
ToolRequest,
ToolResponse,
ToolConfirmationRequest,
} from '../api';
import type { TokenState } from './chat';
type JsonObject = Record<string, unknown>;
export type Role = 'user' | 'assistant';
export type Annotations = {
audience?: Role[];
lastModified?: string;
priority?: number;
};
type ContentAnnotations =
| {
audience?: Role[];
lastModified?: string;
priority?: number;
_meta?: JsonObject;
}
| JsonObject;
export type TextContent = {
_meta?: JsonObject;
annotations?: Annotations | JsonObject;
text: string;
};
export type ImageContent = {
_meta?: JsonObject;
annotations?: Annotations | JsonObject;
data: string;
mimeType: string;
};
export type ContentBlock =
| ({ type: 'text' } & RawTextContent)
| ({ type: 'image' } & RawImageContent)
| ({ type: 'resource' } & RawEmbeddedResource)
| ({ type: 'audio' } & RawAudioContent)
| ({ type: 'resource_link' } & RawResource);
type RawTextContent = {
_meta?: JsonObject;
annotations?: ContentAnnotations;
text: string;
};
type RawImageContent = {
_meta?: JsonObject;
annotations?: ContentAnnotations;
data: string;
mimeType: string;
};
type RawAudioContent = {
data: string;
mimeType: string;
};
type RawEmbeddedResource = {
_meta?: JsonObject;
resource: ResourceContents;
};
type RawResource = {
_meta?: JsonObject;
description?: string;
icons?: ContentIcon[];
mimeType?: string;
name: string;
size?: number;
title?: string;
uri: string;
};
type ResourceContents =
| {
_meta?: JsonObject;
mimeType?: string;
text: string;
uri: string;
}
| {
_meta?: JsonObject;
blob: string;
mimeType?: string;
uri: string;
};
type ContentIcon = {
mimeType?: string;
sizes?: string[];
src: string;
theme?: 'light' | 'dark' | JsonObject;
};
export type SystemNotificationType = 'thinkingMessage' | 'inlineMessage' | 'creditsExhausted';
export type SystemNotificationContent = {
data?: unknown;
msg: string;
notificationType: SystemNotificationType;
};
export type ActionRequired = {
data: ActionRequiredData;
};
export type ActionRequiredData =
| {
actionType: 'toolConfirmation';
arguments: JsonObject;
id: string;
prompt?: string | null;
toolName: string;
}
| {
actionType: 'elicitation';
id: string;
message: string;
requested_schema: unknown;
}
| {
action?: string;
actionType: 'elicitationResponse';
id: string;
user_data: unknown;
};
export type FrontendToolRequest = {
id: string;
toolCall: JsonObject;
};
export type ThinkingContent = {
signature: string;
thinking: string;
};
export type RedactedThinkingContent = {
data: string;
};
export type ToolConfirmationRequest = {
arguments: JsonObject;
id: string;
prompt?: string | null;
toolName: string;
};
export type ToolRequest = {
_meta?: JsonObject;
id: string;
metadata?: JsonObject;
toolCall: JsonObject;
};
export type ToolResponse = {
id: string;
metadata?: JsonObject;
toolResult: JsonObject;
};
export type InferenceMetadata = {
provider: string;
requestedModel: string;
resolvedModel?: string | null;
};
export type MessageMetadata = {
agentVisible: boolean;
inference?: InferenceMetadata | null;
steer?: boolean;
userVisible: boolean;
};
export type MessageContent =
| (TextContent & { type: 'text' })
| (ImageContent & { type: 'image' })
| (ToolRequest & { type: 'toolRequest' })
| (ToolResponse & { type: 'toolResponse' })
| (ToolConfirmationRequest & { type: 'toolConfirmationRequest' })
| (ActionRequired & { type: 'actionRequired' })
| (FrontendToolRequest & { type: 'frontendToolRequest' })
| (ThinkingContent & { type: 'thinking' })
| (RedactedThinkingContent & { type: 'redactedThinking' })
| (SystemNotificationContent & { type: 'systemNotification' });
export type Message = {
content: MessageContent[];
created: number;
id?: string | null;
metadata: MessageMetadata;
role: Role;
};
export type Conversation = Message[];
export type MessageEvent =
| {
message: Message;
token_state: TokenState;
type: 'Message';
}
| {
error: string;
type: 'Error';
}
| {
reason: string;
token_state: TokenState;
type: 'Finish';
}
| {
message: JsonObject;
request_id: string;
type: 'Notification';
}
| {
conversation: Conversation;
type: 'UpdateConversation';
}
| {
request_ids: string[];
type: 'ActiveRequests';
}
| {
type: 'Ping';
};
export type ToolRequestMessageContent = ToolRequest & { type: 'toolRequest' };
export type ToolResponseMessageContent = ToolResponse & { type: 'toolResponse' };
+1
View File
@@ -0,0 +1 @@
export type Permission = 'always_allow' | 'allow_once' | 'cancel' | 'deny_once' | 'always_deny';
+13
View File
@@ -0,0 +1,13 @@
export type UpdateCustomProviderRequest = {
api_key: string;
api_url: string;
base_path?: string | null;
catalog_provider_id?: string | null;
display_name: string;
engine: string;
headers?: Record<string, string> | null;
models: string[];
preserves_thinking?: boolean | null;
requires_auth?: boolean;
supports_streaming?: boolean | null;
};
+60
View File
@@ -0,0 +1,60 @@
import type { Recipe } from '../recipe';
import type { Message } from './message';
export type ExtensionData = Record<string, unknown>;
export type GooseMode = 'auto' | 'approve' | 'smart_approve' | 'chat';
export type ModelConfig = {
context_limit?: number | null;
max_tokens?: number | null;
model_name: string;
reasoning?: boolean | null;
request_params?: Record<string, unknown> | null;
temperature?: number | null;
toolshim: boolean;
toolshim_model?: string | null;
};
export type Usage = {
cache_read_input_tokens?: number | null;
cache_write_input_tokens?: number | null;
input_tokens?: number | null;
output_tokens?: number | null;
total_tokens?: number | null;
};
export type SessionType =
| 'user'
| 'scheduled'
| 'sub_agent'
| 'hidden'
| 'terminal'
| 'gateway'
| 'acp';
export type Session = {
accumulated_cost?: number | null;
accumulated_usage?: Usage;
archived_at?: string | null;
conversation?: Message[] | null;
created_at: string;
extension_data: ExtensionData;
goose_mode?: GooseMode;
id: string;
last_message_at?: string | null;
last_message_snippet?: string | null;
message_count: number;
model_config?: ModelConfig | null;
name: string;
project_id?: string | null;
provider_name?: string | null;
recipe?: Recipe | null;
schedule_id?: string | null;
session_type?: SessionType;
updated_at: string;
usage?: Usage;
user_recipe_values?: Record<string, string> | null;
user_set_name?: boolean;
working_dir: string;
};
+1 -1
View File
@@ -2,7 +2,7 @@
* Shared constants and utilities for extension error handling
*/
import { ExtensionLoadResult } from '../api/types.gen';
import type { ExtensionLoadResult } from '../types/extensions';
import { toastService, ExtensionLoadingStatus } from '../toasts';
export const MAX_ERROR_MESSAGE_LENGTH = 70;
+1 -1
View File
@@ -1,4 +1,4 @@
import type { ExtensionConfig } from '../api';
import type { ExtensionConfig } from '../types/extensions';
import type { FixedExtensionEntry } from '../components/ConfigContext';
export type NextChatExtensionDraft = {
+1 -1
View File
@@ -1,4 +1,4 @@
import type { GooseApp } from '../api';
import type { GooseApp } from '../types/apps';
import { listMcpApps } from '../acp/mcp-apps';
interface PlatformEventData {
+6 -2
View File
@@ -1,5 +1,9 @@
import { getToolRequests, getTextAndImageContent, getToolResponses } from '../types/message';
import { Message } from '../api';
import {
getToolRequests,
getTextAndImageContent,
getToolResponses,
type Message,
} from '../types/message';
export function identifyConsecutiveToolCalls(messages: Message[]): number[][] {
const chains: number[][] = [];