fix(cli,core): resolve lint and type errors in agent stream and core types

This commit is contained in:
Michael Bleigh
2026-03-24 19:10:09 -07:00
parent f853d2f9da
commit 0afe5117a4
6 changed files with 373 additions and 197 deletions
+23 -45
View File
@@ -1093,6 +1093,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
}, [config]);
const useAgentProtocol = config.getExperimentalUseAgentProtocol();
const useActiveStream = useAgentProtocol ? useAgentStream : useGeminiStream;
const {
streamingState,
@@ -1113,50 +1114,27 @@ Logging in with Google... Restarting Gemini CLI to continue.
backgroundShells,
dismissBackgroundShell,
retryStatus,
// eslint-disable-next-line react-hooks/rules-of-hooks
} = useAgentProtocol
? useAgentStream(
config.getGeminiClient(),
historyManager.history,
historyManager.addItem,
config,
settings,
setDebugMessage,
handleSlashCommand,
shellModeActive,
getPreferredEditor,
onAuthError,
performMemoryRefresh,
modelSwitchedFromQuotaError,
setModelSwitchedFromQuotaError,
onCancelSubmit,
setEmbeddedShellFocused,
terminalWidth,
terminalHeight,
embeddedShellFocused,
consumePendingHints,
)
: useGeminiStream(
config.getGeminiClient(),
historyManager.history,
historyManager.addItem,
config,
settings,
setDebugMessage,
handleSlashCommand,
shellModeActive,
getPreferredEditor,
onAuthError,
performMemoryRefresh,
modelSwitchedFromQuotaError,
setModelSwitchedFromQuotaError,
onCancelSubmit,
setEmbeddedShellFocused,
terminalWidth,
terminalHeight,
embeddedShellFocused,
consumePendingHints,
);
} = useActiveStream(
config.getGeminiClient(),
historyManager.history,
historyManager.addItem,
config,
settings,
setDebugMessage,
handleSlashCommand,
shellModeActive,
getPreferredEditor,
onAuthError,
performMemoryRefresh,
modelSwitchedFromQuotaError,
setModelSwitchedFromQuotaError,
onCancelSubmit,
setEmbeddedShellFocused,
terminalWidth,
terminalHeight,
embeddedShellFocused,
consumePendingHints,
);
const pendingHistoryItems = useMemo(
() => [...pendingSlashCommandHistoryItems, ...pendingGeminiHistoryItems],
@@ -1729,7 +1707,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
if (keyMatchers[Command.QUIT](key)) {
// If the user presses Ctrl+C, we want to cancel any ongoing requests.
// This should happen regardless of the count.
cancelOngoingRequest?.();
void cancelOngoingRequest?.();
handleCtrlCPress();
return true;
@@ -4,14 +4,14 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {
describe,
it,
expect,
vi,
beforeEach,
} from 'vitest';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { act } from 'react';
import {
type Config,
type GeminiClient,
LegacyAgentSession as MockLegacyAgentSession,
} from '@google/gemini-cli-core';
import { type LoadedSettings } from '../../config/settings.js';
import { renderHookWithProviders } from '../../test-utils/render.js';
// --- MOCKS ---
@@ -47,9 +47,9 @@ vi.mock('./useLogger.js', () => ({
}));
vi.mock('../contexts/SessionContext.js', async (importOriginal) => {
const actual = await importOriginal();
const actual = await importOriginal<Record<string, unknown>>();
return {
...(actual as any),
...actual,
useSessionStats: vi.fn(() => ({
startNewPrompt: vi.fn(),
})),
@@ -58,19 +58,18 @@ vi.mock('../contexts/SessionContext.js', async (importOriginal) => {
// Mock core classes properly
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
const actual = await importOriginal() as any;
const actual = await importOriginal<Record<string, unknown>>();
return {
...actual,
LegacyAgentSession: vi.fn().mockImplementation(() => mockLegacyAgentSession),
LegacyAgentSession: vi
.fn()
.mockImplementation(() => mockLegacyAgentSession),
};
});
// --- END MOCKS ---
import { useAgentStream } from './useAgentStream.js';
import {
LegacyAgentSession as MockLegacyAgentSession,
} from '@google/gemini-cli-core';
import { MessageType, StreamingState } from '../types.js';
describe('useAgentStream', () => {
@@ -89,14 +88,14 @@ describe('useAgentStream', () => {
getExperimentalUseAgentProtocol: () => true,
getApprovalMode: () => 'default',
getMessageBus: () => ({}),
} as any;
} as Config;
const mockSettings = {
merged: {
billing: { overageStrategy: 'stop' },
ui: { errorVerbosity: 'full' },
},
} as any;
} as unknown as LoadedSettings;
beforeEach(() => {
vi.clearAllMocks();
@@ -105,7 +104,7 @@ describe('useAgentStream', () => {
it('should initialize LegacyAgentSession on mount', async () => {
await renderHookWithProviders(() =>
useAgentStream(
{} as any,
{} as GeminiClient,
[],
mockAddItem,
mockConfig,
@@ -132,7 +131,7 @@ describe('useAgentStream', () => {
it('should call session.send when submitQuery is called', async () => {
const { result } = await renderHookWithProviders(() =>
useAgentStream(
{} as any,
{} as GeminiClient,
[],
mockAddItem,
mockConfig,
@@ -168,7 +167,7 @@ describe('useAgentStream', () => {
it('should update streamingState based on agent_start and agent_end events', async () => {
const { result } = await renderHookWithProviders(() =>
useAgentStream(
{} as any,
{} as GeminiClient,
[],
mockAddItem,
mockConfig,
@@ -188,7 +187,8 @@ describe('useAgentStream', () => {
),
);
const eventHandler = (mockLegacyAgentSession.subscribe as any).mock.calls[0][0];
const eventHandler = vi.mocked(mockLegacyAgentSession.subscribe).mock
.calls[0][0];
expect(result.current.streamingState).toBe(StreamingState.Idle);
@@ -206,7 +206,7 @@ describe('useAgentStream', () => {
it('should accumulate text content and update pendingHistoryItems', async () => {
const { result } = await renderHookWithProviders(() =>
useAgentStream(
{} as any,
{} as GeminiClient,
[],
mockAddItem,
mockConfig,
@@ -226,7 +226,8 @@ describe('useAgentStream', () => {
),
);
const eventHandler = (mockLegacyAgentSession.subscribe as any).mock.calls[0][0];
const eventHandler = vi.mocked(mockLegacyAgentSession.subscribe).mock
.calls[0][0];
act(() => {
eventHandler({
@@ -256,7 +257,7 @@ describe('useAgentStream', () => {
it('should process thought events and update thought state', async () => {
const { result } = await renderHookWithProviders(() =>
useAgentStream(
{} as any,
{} as GeminiClient,
[],
mockAddItem,
mockConfig,
@@ -276,7 +277,8 @@ describe('useAgentStream', () => {
),
);
const eventHandler = (mockLegacyAgentSession.subscribe as any).mock.calls[0][0];
const eventHandler = vi.mocked(mockLegacyAgentSession.subscribe).mock
.calls[0][0];
act(() => {
eventHandler({
@@ -293,15 +295,17 @@ describe('useAgentStream', () => {
});
it('should display error message when a tool call requires approval', async () => {
let eventHandler: (event: any) => void = () => {};
vi.spyOn(mockLegacyAgentSession, 'subscribe').mockImplementation((handler) => {
eventHandler = handler;
return () => {};
});
let eventHandler: (event: unknown) => void = () => {};
vi.spyOn(mockLegacyAgentSession, 'subscribe').mockImplementation(
(handler) => {
eventHandler = handler;
return () => {};
},
);
await renderHookWithProviders(() =>
useAgentStream(
{} as any,
{} as GeminiClient,
[],
mockAddItem,
mockConfig,
@@ -325,7 +329,8 @@ describe('useAgentStream', () => {
eventHandler({
type: 'error',
status: 'UNIMPLEMENTED',
message: 'TODO: Tool approvals not yet implemented, please switch to YOLO mode to test.',
message:
'TODO: Tool approvals not yet implemented, please switch to YOLO mode to test.',
fatal: true,
});
});
@@ -342,7 +347,7 @@ describe('useAgentStream', () => {
it('should call session.abort when cancelOngoingRequest is called', async () => {
const { result } = await renderHookWithProviders(() =>
useAgentStream(
{} as any,
{} as GeminiClient,
[],
mockAddItem,
mockConfig,
+263 -110
View File
@@ -8,25 +8,30 @@ import { useState, useRef, useCallback, useEffect, useMemo } from 'react';
import {
getErrorMessage,
MessageSenderType,
ApprovalMode,
debugLogger,
LegacyAgentSession,
geminiPartsToContentParts,
parseThought,
CoreToolCallStatus,
} from '@google/gemini-cli-core';
import type {
Config,
EditorType,
GeminiClient,
ThoughtSummary,
RetryAttemptPayload,
AgentEvent,
import {
type Config,
type GeminiClient,
type ApprovalMode,
Kind,
type EditorType,
type ThoughtSummary,
type RetryAttemptPayload,
type AgentEvent,
BaseDeclarativeTool,
type ToolResult,
} from '@google/gemini-cli-core';
import { type PartListUnion } from '@google/genai';
import type {
HistoryItem,
HistoryItemWithoutId,
LoopDetectionConfirmationRequest,
SlashCommandProcessorResult,
} from '../types.js';
import { StreamingState, MessageType } from '../types.js';
import { findLastSafeSplitPoint } from '../utils/markdownUtilities.js';
@@ -35,18 +40,47 @@ import { type BackgroundShell } from './shellCommandProcessor.js';
import type { UseHistoryManagerReturn } from './useHistoryManager.js';
import { useLogger } from './useLogger.js';
import { mapToDisplay as mapTrackedToolCallsToDisplay } from './toolMapping.js';
import {
useToolScheduler,
} from './useToolScheduler.js';
import type {
TrackedToolCall,
} from './useToolScheduler.js';
import { useToolScheduler } from './useToolScheduler.js';
import type { TrackedToolCall } from './useToolScheduler.js';
import { useSessionStats } from '../contexts/SessionContext.js';
import type { LoadedSettings } from '../../config/settings.js';
import type { SlashCommandProcessorResult } from '../types.js';
import { useStateAndRef } from './useStateAndRef.js';
class DummyTool extends BaseDeclarativeTool<
Record<string, unknown>,
ToolResult
> {
constructor(
name: string,
description: string,
displayName: string,
isOutputMarkdown: boolean,
kind: Kind,
messageBus: import('@google/gemini-cli-core').MessageBus,
) {
super(
name,
displayName,
description,
kind,
undefined,
messageBus,
isOutputMarkdown,
false,
);
}
protected createInvocation(params: Record<string, unknown>) {
return {
getDescription: () => this.description,
params,
execute: async () => ({ llmContent: [], returnDisplay: '' }),
toolLocations: () => [],
shouldConfirmExecute: async (): Promise<false> => false,
};
}
}
/**
* useAgentStream implements the interactive agent loop using the LegacyAgentSession (AgentProtocol).
* It attempts to maintain parity with useGeminiStream while consolidating model/tool orchestration
@@ -67,7 +101,9 @@ export const useAgentStream = (
_onAuthError: (error: string) => void,
_performMemoryRefresh: () => Promise<void>,
_modelSwitchedFromQuotaError: boolean,
_setModelSwitchedFromQuotaError: React.Dispatch<React.SetStateAction<boolean>>,
_setModelSwitchedFromQuotaError: React.Dispatch<
React.SetStateAction<boolean>
>,
onCancelSubmit: (shouldRestorePrompt?: boolean) => void,
_setShellInputFocused: (value: boolean) => void,
_terminalWidth: number,
@@ -76,9 +112,7 @@ export const useAgentStream = (
_consumeUserHint?: () => string | null,
) => {
const [initError] = useState<string | null>(null);
const [retryStatus] = useState<RetryAttemptPayload | null>(
null,
);
const [retryStatus] = useState<RetryAttemptPayload | null>(null);
const [streamingState, setStreamingState] = useState<StreamingState>(
StreamingState.Idle,
);
@@ -92,8 +126,9 @@ export const useAgentStream = (
const [pendingHistoryItem, pendingHistoryItemRef, setPendingHistoryItem] =
useStateAndRef<HistoryItemWithoutId | null>(null);
const [trackedTools, , setTrackedTools] =
useStateAndRef<TrackedToolCall[]>([]);
const [trackedTools, , setTrackedTools] = useStateAndRef<TrackedToolCall[]>(
[],
);
const [pushedToolCallIds, pushedToolCallIdsRef, setPushedToolCallIds] =
useStateAndRef<Set<string>>(new Set());
const [_isFirstToolInGroup, isFirstToolInGroupRef, setIsFirstToolInGroup] =
@@ -123,20 +158,19 @@ export const useAgentStream = (
const isBackgroundShellVisible = false;
const toggleBackgroundShell = useCallback(() => {}, []);
const backgroundCurrentShell = undefined;
const backgroundShells = new Map<number, BackgroundShell>();
const backgroundShells = useMemo(
() => new Map<number, BackgroundShell>(),
[],
);
const dismissBackgroundShell = useCallback(async (_pid: number) => {}, []);
// TODO: Support LoopDetection confirmation requests
const [
loopDetectionConfirmationRequest,
] = useState<LoopDetectionConfirmationRequest | null>(null);
const [loopDetectionConfirmationRequest] =
useState<LoopDetectionConfirmationRequest | null>(null);
const flushPendingText = useCallback(() => {
if (pendingHistoryItemRef.current) {
addItem(
pendingHistoryItemRef.current,
userMessageTimestampRef.current,
);
addItem(pendingHistoryItemRef.current, userMessageTimestampRef.current);
setPendingHistoryItem(null);
geminiMessageBufferRef.current = '';
}
@@ -206,84 +240,201 @@ export const useAgentStream = (
}
}
break;
case 'tool_request':
case 'tool_request': {
flushPendingText();
setTrackedTools((prev) => [
...prev,
{
request: {
callId: event.requestId,
name: event.name,
args: event.args,
isClientInitiated: false,
originalRequestName: event.name,
},
status: 'scheduled',
tool: {
displayName: (event._meta?.['displayName'] as string) ?? event.name,
isOutputMarkdown: (event._meta?.['isOutputMarkdown'] as boolean) ?? false,
kind: event._meta?.['kind'] as any,
},
invocation: {
getDescription: () => (event._meta?.['description'] as string) ?? '',
},
} as unknown as TrackedToolCall,
]);
const legacyState = event._meta?.legacyState;
const displayName = legacyState?.displayName ?? event.name;
const isOutputMarkdown = legacyState?.isOutputMarkdown ?? false;
const desc = legacyState?.description ?? '';
const args =
event.args && typeof event.args === 'object' ? event.args : {};
const fallbackKind = Kind.Other;
const messageBus = config.getMessageBus();
const tool =
config.getToolRegistry().getTool(event.name) ||
new DummyTool(
event.name,
desc,
displayName,
isOutputMarkdown,
fallbackKind,
messageBus,
);
const invocation = tool.build(args);
const newCall: TrackedToolCall = {
request: {
callId: event.requestId,
name: event.name,
args,
isClientInitiated: false,
originalRequestName: event.name,
prompt_id: '',
},
status: CoreToolCallStatus.Scheduled,
tool,
invocation,
};
setTrackedTools((prev) => [...prev, newCall]);
break;
case 'tool_update':
}
case 'tool_update': {
setTrackedTools((prev) =>
prev.map((tc) =>
tc.request.callId === event.requestId
? ({
prev.map((tc): TrackedToolCall => {
if (tc.request.callId !== event.requestId) return tc;
const legacyState = event._meta?.legacyState;
const evtStatus = legacyState?.status;
let status = tc.status;
if (evtStatus === 'executing')
status = CoreToolCallStatus.Executing;
else if (evtStatus === 'error') status = CoreToolCallStatus.Error;
else if (evtStatus === 'success')
status = CoreToolCallStatus.Success;
const liveOutput =
event.displayContent?.[0]?.type === 'text'
? event.displayContent[0].text
: 'liveOutput' in tc
? tc.liveOutput
: undefined;
const progressMessage =
legacyState?.progressMessage ??
('progressMessage' in tc ? tc.progressMessage : undefined);
const progress =
legacyState?.progress ??
('progress' in tc ? tc.progress : undefined);
const progressTotal =
legacyState?.progressTotal ??
('progressTotal' in tc ? tc.progressTotal : undefined);
const pid =
legacyState?.pid ?? ('pid' in tc ? tc.pid : undefined);
const desc =
legacyState?.description ??
('invocation' in tc && tc.invocation
? tc.invocation.getDescription()
: '');
const invocation =
'invocation' in tc && tc.invocation
? { ...tc.invocation, getDescription: () => desc }
: undefined;
const inProgressFields = {
pid,
liveOutput,
progress,
progressTotal,
progressMessage,
invocation,
};
const response =
'response' in tc && tc.response
? tc.response
: { callId: tc.request.callId, responseParts: [] };
const responseSubmittedToGemini =
'responseSubmittedToGemini' in tc
? tc.responseSubmittedToGemini
: false;
switch (status) {
case CoreToolCallStatus.Executing:
return {
...tc,
status: (event.data?.['status'] as any) ?? tc.status,
liveOutput:
event.displayContent?.[0]?.type === 'text'
? event.displayContent[0].text
: (tc as any).liveOutput,
progressMessage:
(event.data?.['progressMessage'] as string | undefined) ??
(tc as any).progressMessage,
progress:
(event.data?.['progress'] as number | undefined) ??
(tc as any).progress,
progressTotal:
(event.data?.['progressTotal'] as number | undefined) ??
(tc as any).progressTotal,
pid:
(event.data?.['pid'] as number | undefined) ??
(tc as any).pid,
invocation: {
getDescription: () =>
(event._meta?.['description'] as string) ??
(tc as any).invocation?.getDescription(),
},
} as unknown as TrackedToolCall)
: tc,
),
...inProgressFields,
status: CoreToolCallStatus.Executing,
};
case CoreToolCallStatus.Error:
return {
...tc,
...inProgressFields,
status: CoreToolCallStatus.Error,
response,
responseSubmittedToGemini,
};
case CoreToolCallStatus.Success:
return {
...tc,
...inProgressFields,
status: CoreToolCallStatus.Success,
response,
responseSubmittedToGemini,
};
case CoreToolCallStatus.Scheduled:
return {
...tc,
...inProgressFields,
status: CoreToolCallStatus.Scheduled,
};
case CoreToolCallStatus.Validating:
return {
...tc,
...inProgressFields,
status: CoreToolCallStatus.Validating,
};
case CoreToolCallStatus.AwaitingApproval:
return {
...tc,
...inProgressFields,
status: CoreToolCallStatus.AwaitingApproval,
};
case CoreToolCallStatus.Cancelled:
return {
...tc,
...inProgressFields,
status: CoreToolCallStatus.Cancelled,
};
default:
return tc;
}
}),
);
break;
case 'tool_response':
}
case 'tool_response': {
setTrackedTools((prev) =>
prev.map((tc) =>
tc.request.callId === event.requestId
? ({
...tc,
status: event.isError ? 'error' : 'success',
response: {
resultDisplay:
event._meta?.['resultDisplay'] ??
(event.displayContent?.[0]?.type === 'text'
? event.displayContent[0].text
: undefined),
outputFile: event._meta?.['outputFile'] as string | undefined,
},
responseSubmittedToGemini: true,
} as unknown as TrackedToolCall)
: tc,
),
prev.map((tc): TrackedToolCall => {
if (tc.request.callId !== event.requestId) return tc;
const legacyState = event._meta?.legacyState;
const outputFile = legacyState?.outputFile;
const resultDisplay =
event.displayContent?.[0]?.type === 'text'
? event.displayContent[0].text
: undefined;
const response = {
callId: tc.request.callId,
responseParts: [],
resultDisplay,
outputFile,
...(event.isError
? { error: 'Tool error', errorType: 'UNKNOWN' }
: {}),
};
if (event.isError) {
return {
...tc,
status: CoreToolCallStatus.Error,
response,
responseSubmittedToGemini: true,
};
} else {
return {
...tc,
status: CoreToolCallStatus.Success,
response,
responseSubmittedToGemini: true,
};
}
}),
);
break;
}
case 'error':
addItem(
{ type: MessageType.ERROR, text: event.message },
@@ -294,7 +445,7 @@ export const useAgentStream = (
break;
}
},
[addItem, flushPendingText, pendingHistoryItemRef, setPendingHistoryItem],
[addItem, flushPendingText, setPendingHistoryItem, setTrackedTools, config],
);
useEffect(() => {
@@ -316,7 +467,7 @@ export const useAgentStream = (
const submitQuery = useCallback(
async (
query: PartListUnion,
query: Array<import('@google/gemini-cli-core').Part> | string,
options?: { isContinuation: boolean },
_prompt_id?: string,
) => {
@@ -335,7 +486,7 @@ export const useAgentStream = (
}
const parts = geminiPartsToContentParts(
typeof query === 'string' ? [{ text: query }] : (query as any[]),
typeof query === 'string' ? [{ text: query }] : query,
);
try {
@@ -399,13 +550,14 @@ export const useAgentStream = (
}
const isLastInBatch =
toolsToPush[toolsToPush.length - 1] === trackedTools[trackedTools.length - 1];
toolsToPush[toolsToPush.length - 1] ===
trackedTools[trackedTools.length - 1];
const historyItem = mapTrackedToolCallsToDisplay(toolsToPush, {
borderTop: isFirstToolInGroupRef.current,
borderBottom: isLastInBatch,
...getToolGroupBorderAppearance(
{ type: 'tool_group', tools: trackedTools as any[] },
{ type: 'tool_group', tools: trackedTools },
activePtyId,
!!_isShellFocused,
[],
@@ -437,7 +589,7 @@ export const useAgentStream = (
const items: HistoryItemWithoutId[] = [];
const appearance = getToolGroupBorderAppearance(
{ type: 'tool_group', tools: trackedTools as any[] },
{ type: 'tool_group', tools: trackedTools },
activePtyId,
!!_isShellFocused,
[],
@@ -488,17 +640,18 @@ export const useAgentStream = (
}, [
trackedTools,
pushedToolCallIds,
isFirstToolInGroupRef,
activePtyId,
_isShellFocused,
backgroundShells,
]);
const pendingHistoryItems = useMemo(() => {
return [pendingHistoryItem, ...pendingToolGroupItems].filter(
(i): i is HistoryItemWithoutId => i !== undefined && i !== null,
);
}, [pendingHistoryItem, pendingToolGroupItems]);
const pendingHistoryItems = useMemo(
() =>
[pendingHistoryItem, ...pendingToolGroupItems].filter(
(i): i is HistoryItemWithoutId => i !== undefined && i !== null,
),
[pendingHistoryItem, pendingToolGroupItems],
);
return {
streamingState,
@@ -31,9 +31,10 @@ export type CancelAllFn = (signal: AbortSignal) => void;
* The shape expected by useGeminiStream.
* It matches the Core ToolCall structure + the UI metadata flag.
*/
export type TrackedToolCall = ToolCall & {
responseSubmittedToGemini?: boolean;
};
export type Tracked<T> = T extends unknown
? T & { responseSubmittedToGemini?: boolean }
: never;
export type TrackedToolCall = Tracked<ToolCall>;
// Narrowed types for specific statuses (used by useGeminiStream)
export type TrackedScheduledToolCall = Extract<
@@ -211,7 +211,9 @@ class LegacyAgentProtocol implements AgentProtocol {
this._emit(toolUpdates);
};
this._config.getMessageBus().subscribe(MessageBusType.TOOL_CALLS_UPDATE, handleToolCallsUpdate);
this._config
.getMessageBus()
.subscribe(MessageBusType.TOOL_CALLS_UPDATE, handleToolCallsUpdate);
try {
while (true) {
@@ -246,17 +248,23 @@ class LegacyAgentProtocol implements AgentProtocol {
toolCallRequests.push(event.value);
}
const translatedEvents = translateEvent(event, this._translationState);
const translatedEvents = translateEvent(
event,
this._translationState,
);
for (const ev of translatedEvents) {
if (ev.type === 'tool_request') {
const tool = this._config.getToolRegistry().getTool(ev.name);
const invocation = tool?.build(ev.args);
ev._meta = {
displayName: tool?.displayName ?? ev.name,
description: invocation?.getDescription() ?? tool?.description ?? '',
isOutputMarkdown: tool?.isOutputMarkdown ?? false,
kind: tool?.kind,
legacyState: {
displayName: tool?.displayName ?? ev.name,
description:
invocation?.getDescription() ?? tool?.description ?? '',
isOutputMarkdown: tool?.isOutputMarkdown ?? false,
kind: tool?.kind,
},
};
}
}
@@ -371,7 +379,9 @@ class LegacyAgentProtocol implements AgentProtocol {
currentParts = toolResponseParts;
}
} finally {
this._config.getMessageBus().unsubscribe(MessageBusType.TOOL_CALLS_UPDATE, handleToolCallsUpdate);
this._config
.getMessageBus()
.unsubscribe(MessageBusType.TOOL_CALLS_UPDATE, handleToolCallsUpdate);
}
}
+29
View File
@@ -182,6 +182,16 @@ export interface ToolRequest {
name: string;
/** The arguments for the tool. */
args: Record<string, unknown>;
/** UI specific metadata */
_meta?: {
legacyState?: {
displayName?: string;
isOutputMarkdown?: boolean;
description?: string;
kind?: string;
};
[key: string]: unknown;
};
}
/**
@@ -194,6 +204,18 @@ export interface ToolUpdate {
displayContent?: ContentPart[];
content?: ContentPart[];
data?: Record<string, unknown>;
/** UI specific metadata */
_meta?: {
legacyState?: {
status?: string;
progressMessage?: string;
progress?: number;
progressTotal?: number;
pid?: number;
description?: string;
};
[key: string]: unknown;
};
}
export interface ToolResponse {
@@ -207,6 +229,13 @@ export interface ToolResponse {
data?: Record<string, unknown>;
/** When true, the tool call encountered an error that will be sent to the model. */
isError?: boolean;
/** UI specific metadata */
_meta?: {
legacyState?: {
outputFile?: string;
};
[key: string]: unknown;
};
}
export type ElicitationRequest = {