feat(cli): add streamlined gemini gemma local model setup (#25498)

Co-authored-by: Abhijit Balaji <abhijitbalaji@google.com>
Co-authored-by: Samee Zahid <sameez@google.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
This commit is contained in:
Samee Zahid
2026-04-20 16:57:56 -07:00
committed by GitHub
parent 6afc47f81c
commit 1d383a4a8e
31 changed files with 2509 additions and 12 deletions
@@ -0,0 +1,41 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { CommandKind, type SlashCommand } from './types.js';
import { MessageType, type HistoryItemGemmaStatus } from '../types.js';
import { checkGemmaStatus } from '../../commands/gemma/status.js';
import { GEMMA_MODEL_NAME } from '../../commands/gemma/constants.js';
export const gemmaStatusCommand: SlashCommand = {
name: 'gemma',
description: 'Check local Gemma model routing status',
kind: CommandKind.BUILT_IN,
autoExecute: true,
isSafeConcurrent: true,
action: async (context) => {
const port =
parseInt(
context.services.settings.merged.experimental?.gemmaModelRouter?.classifier?.host?.match(
/:(\d+)/,
)?.[1] ?? '',
10,
) || undefined;
const status = await checkGemmaStatus(port);
const item: Omit<HistoryItemGemmaStatus, 'id'> = {
type: MessageType.GEMMA_STATUS,
binaryInstalled: status.binaryInstalled,
binaryPath: status.binaryPath,
modelName: GEMMA_MODEL_NAME,
modelDownloaded: status.modelDownloaded,
serverRunning: status.serverRunning,
serverPid: status.serverPid,
serverPort: status.port,
settingsEnabled: status.settingsEnabled,
allPassing: status.allPassing,
};
context.ui.addItem(item);
},
};
@@ -32,6 +32,7 @@ import { ToolsList } from './views/ToolsList.js';
import { SkillsList } from './views/SkillsList.js';
import { AgentsStatus } from './views/AgentsStatus.js';
import { McpStatus } from './views/McpStatus.js';
import { GemmaStatus } from './views/GemmaStatus.js';
import { ChatList } from './views/ChatList.js';
import { ModelMessage } from './messages/ModelMessage.js';
import { ThinkingMessage } from './messages/ThinkingMessage.js';
@@ -228,6 +229,9 @@ export const HistoryItemDisplay: React.FC<HistoryItemDisplayProps> = ({
{itemForDisplay.type === 'mcp_status' && (
<McpStatus {...itemForDisplay} serverStatus={getMCPServerStatus} />
)}
{itemForDisplay.type === 'gemma_status' && (
<GemmaStatus {...itemForDisplay} />
)}
{itemForDisplay.type === 'chat_list' && (
<ChatList chats={itemForDisplay.chats} />
)}
@@ -0,0 +1,120 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { Box, Text } from 'ink';
import type React from 'react';
import { theme } from '../../semantic-colors.js';
import type { HistoryItemGemmaStatus } from '../../types.js';
type GemmaStatusProps = Omit<HistoryItemGemmaStatus, 'id' | 'type'>;
const StatusDot: React.FC<{ ok: boolean }> = ({ ok }) => (
<Text color={ok ? theme.status.success : theme.status.error}>
{ok ? '\u25CF' : '\u25CB'}
</Text>
);
export const GemmaStatus: React.FC<GemmaStatusProps> = ({
binaryInstalled,
binaryPath,
modelName,
modelDownloaded,
serverRunning,
serverPid,
serverPort,
settingsEnabled,
allPassing,
}) => (
<Box flexDirection="column">
<Text bold>Gemma Local Model Routing</Text>
<Box height={1} />
<Box>
<StatusDot ok={binaryInstalled} />
<Text>
{' '}
<Text bold>Binary: </Text>
{binaryInstalled ? (
<Text color={theme.text.secondary}>{binaryPath}</Text>
) : (
<Text color={theme.status.error}>Not installed</Text>
)}
</Text>
</Box>
<Box>
<StatusDot ok={modelDownloaded} />
<Text>
{' '}
<Text bold>Model: </Text>
{modelDownloaded ? (
<Text>{modelName}</Text>
) : (
<Text color={theme.status.error}>{modelName} not found</Text>
)}
</Text>
</Box>
<Box>
<StatusDot ok={serverRunning} />
<Text>
{' '}
<Text bold>Server: </Text>
{serverRunning ? (
<Text>
port {serverPort}
{serverPid ? (
<Text color={theme.text.secondary}> (PID {serverPid})</Text>
) : null}
</Text>
) : (
<Text color={theme.status.error}>
not running on port {serverPort}
</Text>
)}
</Text>
</Box>
<Box>
<StatusDot ok={settingsEnabled} />
<Text>
{' '}
<Text bold>Settings: </Text>
{settingsEnabled ? (
<Text>enabled</Text>
) : (
<Text color={theme.status.error}>not enabled</Text>
)}
</Text>
</Box>
<Box marginTop={1}>
<Text bold>Active for: </Text>
{allPassing ? (
<Text color={theme.status.success}>[routing]</Text>
) : (
<Text color={theme.text.secondary}>none</Text>
)}
</Box>
<Box marginTop={1}>
{allPassing ? (
<Box flexDirection="column">
<Text color={theme.text.secondary}>
Simple requests route to Flash, complex requests to Pro.
</Text>
<Text color={theme.text.secondary}>
This happens automatically on every request.
</Text>
</Box>
) : (
<Text color={theme.status.warning}>
Run &quot;gemini gemma setup&quot; to install and configure.
</Text>
)}
</Box>
</Box>
);
+15
View File
@@ -355,6 +355,19 @@ export interface JsonMcpResource {
description?: string;
}
export type HistoryItemGemmaStatus = HistoryItemBase & {
type: 'gemma_status';
binaryInstalled: boolean;
binaryPath: string | null;
modelName: string;
modelDownloaded: boolean;
serverRunning: boolean;
serverPid: number | null;
serverPort: number;
settingsEnabled: boolean;
allPassing: boolean;
};
export type HistoryItemMcpStatus = HistoryItemBase & {
type: 'mcp_status';
servers: Record<string, MCPServerConfig>;
@@ -404,6 +417,7 @@ export type HistoryItemWithoutId =
| HistoryItemSkillsList
| HistoryItemAgentsList
| HistoryItemMcpStatus
| HistoryItemGemmaStatus
| HistoryItemChatList
| HistoryItemThinking
| HistoryItemHint
@@ -430,6 +444,7 @@ export enum MessageType {
SKILLS_LIST = 'skills_list',
AGENTS_LIST = 'agents_list',
MCP_STATUS = 'mcp_status',
GEMMA_STATUS = 'gemma_status',
CHAT_LIST = 'chat_list',
HINT = 'hint',
}