refactor(cli): remove unsafe type assertions in commands (Phase 5)

Replaces 'as Type' assertions with runtime type guards and safe property access in CLI commands (extensions, skills, mcp, hooks).

Fixes #19733
This commit is contained in:
mkorwel
2026-02-20 22:50:30 +00:00
committed by Matt Korwel
parent 706d4d4707
commit 5605670d6d
22 changed files with 217 additions and 182 deletions
@@ -5,7 +5,7 @@
*/
import type { CommandModule } from 'yargs';
import type { ExtensionSettingScope } from '../../config/extensions/extensionSettings.js';
import { ExtensionSettingScope } from '../../config/extensions/extensionSettings.js';
import {
configureAllExtensions,
configureExtension,
@@ -64,6 +64,10 @@ export const configureCommand: CommandModule<object, ConfigureArgs> = {
}
const extensionManager = await getExtensionManager();
const effectiveScope =
scope === 'workspace'
? ExtensionSettingScope.WORKSPACE
: ExtensionSettingScope.USER;
// Case 1: Configure specific setting for an extension
if (name && setting) {
@@ -71,26 +75,16 @@ export const configureCommand: CommandModule<object, ConfigureArgs> = {
extensionManager,
name,
setting,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
scope as ExtensionSettingScope,
effectiveScope,
);
}
// Case 2: Configure all settings for an extension
else if (name) {
await configureExtension(
extensionManager,
name,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
scope as ExtensionSettingScope,
);
await configureExtension(extensionManager, name, effectiveScope);
}
// Case 3: Configure all extensions
else {
await configureAllExtensions(
extensionManager,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
scope as ExtensionSettingScope,
);
await configureAllExtensions(extensionManager, effectiveScope);
}
await exitCli();
@@ -77,11 +77,11 @@ export const disableCommand: CommandModule = {
return true;
}),
handler: async (argv) => {
const name = argv['name'];
const scope = argv['scope'];
await handleDisable({
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
name: argv['name'] as string,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
scope: argv['scope'] as string,
name: typeof name === 'string' ? name : '',
scope: typeof scope === 'string' ? scope : undefined,
});
await exitCli();
},
@@ -104,11 +104,11 @@ export const enableCommand: CommandModule = {
return true;
}),
handler: async (argv) => {
const name = argv['name'];
const scope = argv['scope'];
await handleEnable({
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
name: argv['name'] as string,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
scope: argv['scope'] as string,
name: typeof name === 'string' ? name : '',
scope: typeof scope === 'string' ? scope : undefined,
});
await exitCli();
},
+16 -12
View File
@@ -209,19 +209,23 @@ export const installCommand: CommandModule = {
return true;
}),
handler: async (argv) => {
const source = argv['source'];
const ref = argv['ref'];
const autoUpdate = argv['auto-update'];
const allowPreRelease = argv['pre-release'];
const consent = argv['consent'];
await handleInstall({
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
source: argv['source'] as string,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
ref: argv['ref'] as string | undefined,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
autoUpdate: argv['auto-update'] as boolean | undefined,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
allowPreRelease: argv['pre-release'] as boolean | undefined,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
consent: argv['consent'] as boolean | undefined,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
skipSettings: argv['skip-settings'] as boolean | undefined,
source: typeof source === 'string' ? source : '',
ref: typeof ref === 'string' ? ref : undefined,
autoUpdate: typeof autoUpdate === 'boolean' ? autoUpdate : undefined,
allowPreRelease:
typeof allowPreRelease === 'boolean' ? allowPreRelease : undefined,
consent: typeof consent === 'boolean' ? consent : undefined,
skipSettings:
typeof argv['skip-settings'] === 'boolean'
? argv['skip-settings']
: undefined,
});
await exitCli();
},
+4 -4
View File
@@ -78,11 +78,11 @@ export const linkCommand: CommandModule = {
})
.check((_) => true),
handler: async (argv) => {
const path = argv['path'];
const consent = argv['consent'];
await handleLink({
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
path: argv['path'] as string,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
consent: argv['consent'] as boolean | undefined,
path: typeof path === 'string' ? path : '',
consent: typeof consent === 'boolean' ? consent : undefined,
});
await exitCli();
},
+5 -2
View File
@@ -60,9 +60,12 @@ export const listCommand: CommandModule = {
default: 'text',
}),
handler: async (argv) => {
const outputFormat = argv['output-format'];
await handleList({
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
outputFormat: argv['output-format'] as 'text' | 'json',
outputFormat:
outputFormat === 'text' || outputFormat === 'json'
? outputFormat
: undefined,
});
await exitCli();
},
+4 -4
View File
@@ -97,11 +97,11 @@ export const newCommand: CommandModule = {
});
},
handler: async (args) => {
const path = args['path'];
const template = args['template'];
await handleNew({
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
path: args['path'] as string,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
template: args['template'] as string | undefined,
path: typeof path === 'string' ? path : '',
template: typeof template === 'string' ? template : undefined,
});
await exitCli();
},
@@ -91,11 +91,14 @@ export const uninstallCommand: CommandModule = {
return true;
}),
handler: async (argv) => {
const names = argv['names'];
let namesArray: string[] = [];
if (Array.isArray(names)) {
namesArray = names.filter((n): n is string => typeof n === 'string');
}
await handleUninstall({
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
names: argv['names'] as string[] | undefined,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
all: argv['all'] as boolean,
names: namesArray,
all: typeof argv['all'] === 'boolean' ? argv['all'] : false,
});
await exitCli();
},
@@ -157,11 +157,11 @@ export const updateCommand: CommandModule = {
return true;
}),
handler: async (argv) => {
const name = argv['name'];
const all = argv['all'];
await handleUpdate({
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
name: argv['name'] as string | undefined,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
all: argv['all'] as boolean | undefined,
name: typeof name === 'string' ? name : undefined,
all: typeof all === 'boolean' ? all : undefined,
});
await exitCli();
},
@@ -98,9 +98,9 @@ export const validateCommand: CommandModule = {
demandOption: true,
}),
handler: async (args) => {
const path = args['path'];
await handleValidate({
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
path: args['path'] as string,
path: typeof path === 'string' ? path : '',
});
await exitCli();
},
+28 -39
View File
@@ -12,10 +12,6 @@ import { loadSettings, SettingScope } from '../../config/settings.js';
import { exitCli } from '../utils.js';
import stripJsonComments from 'strip-json-comments';
interface MigrateArgs {
fromClaude: boolean;
}
/**
* Mapping from Claude Code event names to Gemini event names
*/
@@ -62,16 +58,19 @@ function transformMatcher(matcher: string | undefined): string | undefined {
return transformed;
}
function isRecord(obj: unknown): obj is Record<string, unknown> {
return typeof obj === 'object' && obj !== null && !Array.isArray(obj);
}
/**
* Migrate a Claude Code hook configuration to Gemini format
*/
function migrateClaudeHook(claudeHook: unknown): unknown {
if (!claudeHook || typeof claudeHook !== 'object') {
if (!isRecord(claudeHook)) {
return claudeHook;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const hook = claudeHook as Record<string, unknown>;
const hook = claudeHook;
const migrated: Record<string, unknown> = {};
// Map command field
@@ -106,18 +105,15 @@ function migrateClaudeHook(claudeHook: unknown): unknown {
* Migrate Claude Code hooks configuration to Gemini format
*/
function migrateClaudeHooks(claudeConfig: unknown): Record<string, unknown> {
if (!claudeConfig || typeof claudeConfig !== 'object') {
if (!isRecord(claudeConfig)) {
return {};
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const config = claudeConfig as Record<string, unknown>;
const geminiHooks: Record<string, unknown> = {};
// Check if there's a hooks section
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const hooksSection = config['hooks'] as Record<string, unknown> | undefined;
if (!hooksSection || typeof hooksSection !== 'object') {
const hooksSection = claudeConfig['hooks'];
if (!isRecord(hooksSection)) {
return {};
}
@@ -131,31 +127,25 @@ function migrateClaudeHooks(claudeConfig: unknown): Record<string, unknown> {
// Migrate each hook definition
const migratedDefinitions = eventConfig.map((def: unknown) => {
if (!def || typeof def !== 'object') {
if (!isRecord(def)) {
return def;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const definition = def as Record<string, unknown>;
const migratedDef: Record<string, unknown> = {};
// Transform matcher
if (
'matcher' in definition &&
// eslint-disable-next-line no-restricted-syntax
typeof definition['matcher'] === 'string'
) {
migratedDef['matcher'] = transformMatcher(definition['matcher']);
if ('matcher' in def && typeof def['matcher'] === 'string') {
migratedDef['matcher'] = transformMatcher(def['matcher']);
}
// Copy sequential flag
if ('sequential' in definition) {
migratedDef['sequential'] = definition['sequential'];
if ('sequential' in def) {
migratedDef['sequential'] = def['sequential'];
}
// Migrate hooks array
if ('hooks' in definition && Array.isArray(definition['hooks'])) {
migratedDef['hooks'] = definition['hooks'].map(migrateClaudeHook);
if ('hooks' in def && Array.isArray(def['hooks'])) {
migratedDef['hooks'] = def['hooks'].map(migrateClaudeHook);
}
return migratedDef;
@@ -186,11 +176,11 @@ export async function handleMigrateFromClaude() {
sourceFile = claudeLocalSettingsPath;
try {
const content = fs.readFileSync(claudeLocalSettingsPath, 'utf-8');
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
claudeSettings = JSON.parse(stripJsonComments(content)) as Record<
string,
unknown
>;
const parsed = JSON.parse(stripJsonComments(content));
if (isRecord(parsed)) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion
claudeSettings = parsed as any;
}
} catch (error) {
debugLogger.error(
`Error reading ${claudeLocalSettingsPath}: ${getErrorMessage(error)}`,
@@ -200,11 +190,11 @@ export async function handleMigrateFromClaude() {
sourceFile = claudeSettingsPath;
try {
const content = fs.readFileSync(claudeSettingsPath, 'utf-8');
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
claudeSettings = JSON.parse(stripJsonComments(content)) as Record<
string,
unknown
>;
const parsed = JSON.parse(stripJsonComments(content));
if (isRecord(parsed)) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion
claudeSettings = parsed as any;
}
} catch (error) {
debugLogger.error(
`Error reading ${claudeSettingsPath}: ${getErrorMessage(error)}`,
@@ -268,9 +258,8 @@ export const migrateCommand: CommandModule = {
default: false,
}),
handler: async (argv) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const args = argv as unknown as MigrateArgs;
if (args.fromClaude) {
const fromClaude = argv['from-claude'];
if (typeof fromClaude === 'boolean' && fromClaude) {
await handleMigrateFromClaude();
} else {
debugLogger.log(
+45 -29
View File
@@ -211,40 +211,56 @@ export const addCommand: CommandModule = {
})
.middleware((argv) => {
// Handle -- separator args as server args if present
if (argv['--']) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const existingArgs = (argv['args'] as Array<string | number>) || [];
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
argv['args'] = [...existingArgs, ...(argv['--'] as string[])];
if (argv['--'] && Array.isArray(argv['--'])) {
const args = argv['args'];
const existingArgs = Array.isArray(args) ? args : [];
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
argv['args'] = [...existingArgs, ...argv['--']];
}
}),
handler: async (argv) => {
const name = argv['name'];
const commandOrUrl = argv['commandOrUrl'];
const args = argv['args'];
const scope = argv['scope'];
const transport = argv['transport'];
const env = argv['env'];
const header = argv['header'];
const timeout = argv['timeout'];
const trust = argv['trust'];
const description = argv['description'];
const includeTools = argv['includeTools'];
const excludeTools = argv['excludeTools'];
const argsVal = Array.isArray(args)
? args.filter(
(a): a is string | number =>
typeof a === 'string' || typeof a === 'number',
)
: undefined;
await addMcpServer(
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
argv['name'] as string,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
argv['commandOrUrl'] as string,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
argv['args'] as Array<string | number>,
typeof name === 'string' ? name : '',
typeof commandOrUrl === 'string' ? commandOrUrl : '',
argsVal,
{
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
scope: argv['scope'] as string,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
transport: argv['transport'] as string,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
env: argv['env'] as string[],
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
header: argv['header'] as string[],
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
timeout: argv['timeout'] as number | undefined,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
trust: argv['trust'] as boolean | undefined,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
description: argv['description'] as string | undefined,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
includeTools: argv['includeTools'] as string[] | undefined,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
excludeTools: argv['excludeTools'] as string[] | undefined,
scope: typeof scope === 'string' ? scope : 'project',
transport: typeof transport === 'string' ? transport : 'stdio',
env: Array.isArray(env)
? env.filter((e): e is string => typeof e === 'string')
: undefined,
header: Array.isArray(header)
? header.filter((h): h is string => typeof h === 'string')
: undefined,
timeout: typeof timeout === 'number' ? timeout : undefined,
trust: typeof trust === 'boolean' ? trust : undefined,
description: typeof description === 'string' ? description : undefined,
includeTools: Array.isArray(includeTools)
? includeTools.filter((t): t is string => typeof t === 'string')
: undefined,
excludeTools: Array.isArray(excludeTools)
? excludeTools.filter((t): t is string => typeof t === 'string')
: undefined,
},
);
await exitCli();
+12 -2
View File
@@ -112,7 +112,12 @@ export const enableCommand: CommandModule<object, Args> = {
default: false,
}),
handler: async (argv) => {
await handleEnable(argv as Args);
const name = argv['name'];
const session = argv['session'];
await handleEnable({
name: typeof name === 'string' ? name : '',
session: typeof session === 'boolean' ? session : undefined,
});
await exitCli();
},
};
@@ -133,7 +138,12 @@ export const disableCommand: CommandModule<object, Args> = {
default: false,
}),
handler: async (argv) => {
await handleDisable(argv as Args);
const name = argv['name'];
const session = argv['session'];
await handleDisable({
name: typeof name === 'string' ? name : '',
session: typeof session === 'boolean' ? session : undefined,
});
await exitCli();
},
};
+4 -4
View File
@@ -55,10 +55,10 @@ export const removeCommand: CommandModule = {
choices: ['user', 'project'],
}),
handler: async (argv) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
await removeMcpServer(argv['name'] as string, {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
scope: argv['scope'] as string,
const name = argv['name'];
const scope = argv['scope'];
await removeMcpServer(typeof name === 'string' ? name : '', {
scope: typeof scope === 'string' ? scope : 'project',
});
await exitCli();
},
+2 -2
View File
@@ -48,13 +48,13 @@ export const disableCommand: CommandModule = {
choices: ['user', 'workspace'],
}),
handler: async (argv) => {
const name = argv['name'];
const scope =
argv['scope'] === 'workspace'
? SettingScope.Workspace
: SettingScope.User;
await handleDisable({
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
name: argv['name'] as string,
name: typeof name === 'string' ? name : '',
scope,
});
await exitCli();
+2 -2
View File
@@ -39,9 +39,9 @@ export const enableCommand: CommandModule = {
demandOption: true,
}),
handler: async (argv) => {
const name = argv['name'];
await handleEnable({
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
name: argv['name'] as string,
name: typeof name === 'string' ? name : '',
});
await exitCli();
},
+9 -8
View File
@@ -104,15 +104,16 @@ export const installCommand: CommandModule = {
return true;
}),
handler: async (argv) => {
const source = argv['source'];
const scope = argv['scope'];
const path = argv['path'];
const consent = argv['consent'];
await handleInstall({
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
source: argv['source'] as string,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
scope: argv['scope'] as 'user' | 'workspace',
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
path: argv['path'] as string | undefined,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
consent: argv['consent'] as boolean | undefined,
source: typeof source === 'string' ? source : '',
scope: scope === 'user' || scope === 'workspace' ? scope : undefined,
path: typeof path === 'string' ? path : undefined,
consent: typeof consent === 'boolean' ? consent : undefined,
});
await exitCli();
},
+7 -6
View File
@@ -82,13 +82,14 @@ export const linkCommand: CommandModule = {
return true;
}),
handler: async (argv) => {
const path = argv['path'];
const scope = argv['scope'];
const consent = argv['consent'];
await handleLink({
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
path: argv['path'] as string,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
scope: argv['scope'] as 'user' | 'workspace',
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
consent: argv['consent'] as boolean | undefined,
path: typeof path === 'string' ? path : '',
scope: scope === 'user' || scope === 'workspace' ? scope : undefined,
consent: typeof consent === 'boolean' ? consent : undefined,
});
await exitCli();
},
+3 -3
View File
@@ -20,7 +20,7 @@ export async function handleList(args: { all?: boolean }) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
{
debug: false,
} as Partial<CliArgs> as CliArgs,
} as unknown as CliArgs,
{ cwd: workspaceDir },
);
@@ -72,8 +72,8 @@ export const listCommand: CommandModule = {
default: false,
}),
handler: async (argv) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
await handleList({ all: argv['all'] as boolean });
const all = argv['all'];
await handleList({ all: typeof all === 'boolean' ? all : false });
await exitCli();
},
};
@@ -62,11 +62,12 @@ export const uninstallCommand: CommandModule = {
return true;
}),
handler: async (argv) => {
const name = argv['name'];
const scope = argv['scope'];
await handleUninstall({
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
name: argv['name'] as string,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
scope: argv['scope'] as 'user' | 'workspace',
name: typeof name === 'string' ? name : '',
scope: scope === 'user' || scope === 'workspace' ? scope : undefined,
});
await exitCli();
},
@@ -64,6 +64,10 @@ import {
} from '../components/LogoutConfirmationDialog.js';
import { runExitCleanup } from '../../utils/cleanup.js';
function isRecord(obj: unknown): obj is Record<string, unknown> {
return typeof obj === 'object' && obj !== null && !Array.isArray(obj);
}
interface SlashCommandProcessorActions {
openAuthDialog: () => void;
openThemeDialog: () => void;
@@ -501,17 +505,14 @@ export const useSlashCommandProcessor = (
actions.openModelDialog();
return { type: 'handled' };
case 'agentConfig': {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const props = result.props as Record<string, unknown>;
const props = result.props;
if (
!props ||
// eslint-disable-next-line no-restricted-syntax
!isRecord(props) ||
typeof props['name'] !== 'string' ||
// eslint-disable-next-line no-restricted-syntax
typeof props['displayName'] !== 'string' ||
!props['definition']
) {
throw new Error(
) { throw new Error(
'Received invalid properties for agentConfig dialog action.',
);
}
@@ -524,12 +525,15 @@ export const useSlashCommandProcessor = (
);
return { type: 'handled' };
}
case 'permissions':
case 'permissions': {
const props = result.props;
actions.openPermissionsDialog(
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
result.props as { targetDirectory?: string },
isRecord(props)
? (props as { targetDirectory?: string })
: undefined,
);
return { type: 'handled' };
}
case 'help':
return { type: 'handled' };
default: {
+29 -20
View File
@@ -92,7 +92,6 @@ import {
type TrackedCompletedToolCall,
type TrackedCancelledToolCall,
type TrackedWaitingToolCall,
type TrackedExecutingToolCall,
} from './useToolScheduler.js';
import { theme } from '../semantic-colors.js';
import { getToolGroupBorderAppearance } from '../utils/borderStyles.js';
@@ -102,6 +101,10 @@ import { useSessionStats } from '../contexts/SessionContext.js';
import { useKeypress } from './useKeypress.js';
import type { LoadedSettings } from '../../config/settings.js';
function isRecord(obj: unknown): obj is Record<string, unknown> {
return typeof obj === 'object' && obj !== null && !Array.isArray(obj);
}
type ToolResponseWithParts = ToolCallResponseInfo & {
llmContent?: PartListUnion;
};
@@ -842,9 +845,11 @@ export const useGeminiStream = (
// If it is a shell command, we update the status to Canceled and clear the output
// to avoid artifacts, then add it to history immediately.
if (isShellCommand) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const toolGroup = pendingHistoryItemRef.current as HistoryItemToolGroup;
if (
isShellCommand &&
pendingHistoryItemRef.current.type === 'tool_group'
) {
const toolGroup = pendingHistoryItemRef.current;
const updatedTools = toolGroup.tools.map((tool) => {
if (tool.name === SHELL_COMMAND_NAME) {
return {
@@ -855,7 +860,7 @@ export const useGeminiStream = (
}
return tool;
});
addItem({ ...toolGroup, tools: updatedTools } as HistoryItemWithoutId);
addItem({ ...toolGroup, tools: updatedTools });
} else {
addItem(pendingHistoryItemRef.current);
}
@@ -1069,11 +1074,15 @@ export const useGeminiStream = (
const splitPoint = findLastSafeSplitPoint(newGeminiMessageBuffer);
if (splitPoint === newGeminiMessageBuffer.length) {
// Update the existing message with accumulated content
setPendingHistoryItem((item) => ({
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
type: item?.type as 'gemini' | 'gemini_content',
text: newGeminiMessageBuffer,
}));
setPendingHistoryItem((item) => {
if (item?.type === 'gemini' || item?.type === 'gemini_content') {
return {
type: item.type,
text: newGeminiMessageBuffer,
};
}
return item;
});
} else {
// This indicates that we need to split up this Gemini Message.
// Splitting a message is primarily a performance consideration. There is a
@@ -1086,16 +1095,16 @@ export const useGeminiStream = (
const beforeText = newGeminiMessageBuffer.substring(0, splitPoint);
const afterText = newGeminiMessageBuffer.substring(splitPoint);
if (beforeText.length > 0) {
addItem(
{
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
type: pendingHistoryItemRef.current?.type as
| 'gemini'
| 'gemini_content',
text: beforeText,
},
userMessageTimestamp,
);
const type = pendingHistoryItemRef.current?.type;
if (type === 'gemini' || type === 'gemini_content') {
addItem(
{
type,
text: beforeText,
},
userMessageTimestamp,
);
}
}
setPendingHistoryItem({ type: 'gemini_content', text: afterText });
newGeminiMessageBuffer = afterText;