mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-13 13:22:35 -07:00
Merge branch 'main' into memory_usage3
This commit is contained in:
@@ -162,5 +162,7 @@ instructions for the other section.
|
||||
|
||||
## Finalize
|
||||
|
||||
- After making changes, run `npm run format` ONLY to ensure consistency.
|
||||
- After making changes, if `npm run format` fails, it may be necessary to run
|
||||
`npm install` first to ensure all formatting dependencies are available.
|
||||
Then, run `npm run format` to ensure consistency.
|
||||
- Delete any temporary files created during the process.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: docs-writer
|
||||
description:
|
||||
Always use this skill when the task involves writing, reviewing, or editing
|
||||
Always use this skill when the task involves writing, reviewing, or editing
|
||||
files in the `/docs` directory or any `.md` files in the repository.
|
||||
---
|
||||
|
||||
@@ -24,7 +24,7 @@ approach.
|
||||
|
||||
- **Perspective and tense:** Address the reader as "you." Use active voice and
|
||||
present tense (e.g., "The API returns...").
|
||||
- **Tone:** Professional, friendly, and direct.
|
||||
- **Tone:** Professional, friendly, and direct.
|
||||
- **Clarity:** Use simple vocabulary. Avoid jargon, slang, and marketing hype.
|
||||
- **Global Audience:** Write in standard US English. Avoid idioms and cultural
|
||||
references.
|
||||
@@ -47,8 +47,8 @@ Write precisely to ensure your instructions are unambiguous.
|
||||
"foo" or "bar."
|
||||
- **Quota and limit terminology:** For any content involving resource capacity
|
||||
or using the word "quota" or "limit", strictly adhere to the guidelines in
|
||||
the `quota-limit-style-guide.md` resource file. Generally, Use "quota" for the
|
||||
administrative bucket and "limit" for the numerical ceiling.
|
||||
the `quota-limit-style-guide.md` resource file. Generally, Use "quota" for
|
||||
the administrative bucket and "limit" for the numerical ceiling.
|
||||
|
||||
### Formatting and syntax
|
||||
Apply consistent formatting to make documentation visually organized and
|
||||
@@ -120,7 +120,7 @@ accessible.
|
||||
> This is an experimental feature currently under active development.
|
||||
|
||||
- **Headings:** Use hierarchical headings to support the user journey.
|
||||
- **Procedures:**
|
||||
- **Procedures:**
|
||||
- Introduce lists of steps with a complete sentence.
|
||||
- Start each step with an imperative verb.
|
||||
- Number sequential steps; use bullets for non-sequential lists.
|
||||
@@ -134,7 +134,7 @@ accessible.
|
||||
|
||||
## Phase 2: Preparation
|
||||
Before modifying any documentation, thoroughly investigate the request and the
|
||||
surrounding context.
|
||||
surrounding context.
|
||||
|
||||
1. **Clarify:** Understand the core request. Differentiate between writing new
|
||||
content and editing existing content. If the request is ambiguous (e.g.,
|
||||
@@ -145,6 +145,8 @@ surrounding context.
|
||||
4. **Connect:** Identify all referencing pages if changing behavior. Check if
|
||||
`docs/sidebar.json` needs updates.
|
||||
5. **Plan:** Create a step-by-step plan before making changes.
|
||||
6. **Audit Docset:** If asked to audit the documentation, follow the procedural
|
||||
guide in [docs-auditing.md](./references/docs-auditing.md).
|
||||
|
||||
## Phase 3: Execution
|
||||
Implement your plan by either updating existing files or creating new ones
|
||||
@@ -157,7 +159,7 @@ documentation.
|
||||
|
||||
- **Gaps:** Identify areas where the documentation is incomplete or no longer
|
||||
reflects existing code.
|
||||
- **Structure:** Apply "Structure (New Docs)" rules (BLUF, headings, etc.) when
|
||||
- **Structure:** Apply "Structure (New Docs)" rules (BLUF, headings, etc.) when
|
||||
adding new sections to existing pages.
|
||||
- **Headers**: If you change a header, you must check for links that lead to
|
||||
that header and update them.
|
||||
@@ -168,15 +170,16 @@ documentation.
|
||||
documents.
|
||||
|
||||
## Phase 4: Verification and finalization
|
||||
Perform a final quality check to ensure that all changes are correctly formatted
|
||||
and that all links are functional.
|
||||
Perform a final quality check to ensure that all changes are correctly
|
||||
formatted and that all links are functional.
|
||||
|
||||
1. **Accuracy:** Ensure content accurately reflects the implementation and
|
||||
technical behavior.
|
||||
2. **Self-review:** Re-read changes for formatting, correctness, and flow.
|
||||
3. **Link check:** Verify all new and existing links leading to or from modified
|
||||
pages. If you changed a header, ensure that any links that lead to it are
|
||||
updated.
|
||||
4. **Format:** Once all changes are complete, ask to execute `npm run format`
|
||||
to ensure consistent formatting across the project. If the user confirms,
|
||||
execute the command.
|
||||
3. **Link check:** Verify all new and existing links leading to or from
|
||||
modified pages. If you changed a header, ensure that any links that lead to
|
||||
it are updated.
|
||||
4. **Format:** If `npm run format` fails, it may be necessary to run `npm
|
||||
install` first to ensure all formatting dependencies are available. Once all
|
||||
changes are complete, ask to execute `npm run format` to ensure consistent
|
||||
formatting across the project. If the user confirms, execute the command.
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
# Procedural Guide: Auditing the Docset
|
||||
|
||||
This guide outlines the process for auditing the Gemini CLI documentation for
|
||||
correctness and adherence to style guidelines. This process involves both an
|
||||
"Editor" and "Technical Writer" phase.
|
||||
|
||||
## Objective
|
||||
|
||||
To ensure all public-facing documentation is accurate, up-to-date, adheres to
|
||||
the Gemini CLI documentation style guide, and reflects the current state of the
|
||||
codebase.
|
||||
|
||||
## Phase 1: Editor Audit
|
||||
|
||||
**Role:** The editor is responsible for identifying potential issues based on
|
||||
style guide violations and technical inaccuracies.
|
||||
|
||||
### Steps
|
||||
|
||||
1. **Identify Documentation Scope:**
|
||||
- Read `docs/sidebar.json` to get a list of all viewable documentation
|
||||
pages.
|
||||
- For each entry with a `slug`, convert it into a file path (e.g., `docs` ->
|
||||
`docs/index.md`, `docs/get-started` -> `docs/get-started.md`). Ignore
|
||||
entries with `link` properties.
|
||||
|
||||
2. **Prepare Audit Results File:**
|
||||
- Create a new Markdown file named `audit-results-[YYYY-MM-DD].md` (e.g.,
|
||||
`audit-results-2026-03-13.md`). This file will contain all identified
|
||||
violations and recommendations.
|
||||
|
||||
3. **Retrieve Style Guidelines:**
|
||||
- Familiarize yourself with the `docs-writer` skill instructions and the
|
||||
included style guidelines.
|
||||
|
||||
4. **Audit Each Document:**
|
||||
- For each documentation file identified in Step 1, read its content.
|
||||
- **Review against Style Guide:**
|
||||
- **Voice and Tone Violations:**
|
||||
- **Unprofessional Tone:** Identify phrasing that is overly casual,
|
||||
defensive, or lacks a professional and friendly demeanor.
|
||||
- **Indirectness or Vagueness:** Identify sentences that are
|
||||
unnecessarily wordy or fail to be concise and direct.
|
||||
- **Incorrect Pronoun:** Identify any use of third-person pronouns
|
||||
(e.g., "we," "they," "the user") when referring to the reader, instead
|
||||
of the second-person pronoun **"you"**.
|
||||
- **Passive Voice:** Identify sentences written in the passive voice.
|
||||
- **Incorrect Tense:** Identify the use of past or future tense verbs,
|
||||
instead of the **present tense**.
|
||||
- **Poor Vocabulary:** Identify the use of jargon, slang, or overly
|
||||
informal language.
|
||||
- **Language and Grammar Violations:**
|
||||
- **Lack of Conciseness:** Identify unnecessarily long phrases or
|
||||
sentences.
|
||||
- **Punctuation Errors:** Identify incorrect or missing punctuation.
|
||||
- **Ambiguous Dates:** Identify dates that could be misinterpreted
|
||||
(e.g., "next Monday" instead of "April 15, 2026").
|
||||
- **Abbreviation Usage:** Identify the use of abbreviations that should
|
||||
be spelled out (e.g., "e.g." instead of "for example").
|
||||
- **Terminology:** Check for incorrect or inconsistent use of
|
||||
product-specific terms (e.g., "quota" vs. "limit").
|
||||
- **Formatting and Syntax Violations:**
|
||||
- **Missing Overview:** Check for the absence of a brief overview
|
||||
paragraph at the start of the document.
|
||||
- **Line Length:** Identify any lines of text that exceed **80
|
||||
characters** (text wrap violation).
|
||||
- **Casing:** Identify incorrect casing for headings, titles, or named
|
||||
entities (e.g., product names like `Gemini CLI`).
|
||||
- **List Formatting:** Identify incorrectly formatted lists (e.g.,
|
||||
inconsistent indentation or numbering).
|
||||
- **Incorrect Emphasis:** Identify incorrect use of bold text (should
|
||||
only be used for UI elements) or code font (should be used for code,
|
||||
file names, or command-line input).
|
||||
- **Link Quality:** Identify links with non-descriptive anchor text
|
||||
(e.g., "click here").
|
||||
- **Image Alt Text:** Identify images with missing or poor-quality
|
||||
(non-descriptive) alt text.
|
||||
- **Structure Violations:**
|
||||
- **Missing BLUF:** Check for the absence of a "Bottom Line Up Front"
|
||||
summary at the start of complex sections or documents.
|
||||
- **Experimental Feature Notes:** Identify experimental features that
|
||||
are not clearly labeled with a standard note.
|
||||
- **Heading Hierarchy:** Check for skipped heading levels (e.g., going
|
||||
from `##` to `####`).
|
||||
- **Procedure Clarity:** Check for procedural steps that do not start
|
||||
with an imperative verb or where a condition is placed _after_ the
|
||||
instruction.
|
||||
- **Element Misuse:** Identify the incorrect or inappropriate use of
|
||||
special elements (e.g., Notes, Warnings, Cautions).
|
||||
- **Table of Contents:** Identify the presence of a dynamically
|
||||
generated or manually included table of contents.
|
||||
- **Missing Next Steps:** Check for procedural documents that lack a
|
||||
"Next steps" section (if applicable).
|
||||
- **Verify Code Accuracy (if applicable):**
|
||||
- If the document contains code snippets (e.g., shell commands, API calls,
|
||||
file paths, Docker image versions), use `grep_search` and `read_file`
|
||||
within the `packages/` directory (or other relevant parts of the
|
||||
codebase) to ensure the code is still accurate and up-to-date. Pay close
|
||||
attention to version numbers, package names, and command syntax.
|
||||
- **Record Findings:** For each **violation** or inaccuracy found:
|
||||
- Note the file path.
|
||||
- Describe the violation (e.g., "Violation (Language and Grammar): Uses
|
||||
'e.g.'").
|
||||
- Provide a clear and actionable recommendation to correct the issue.
|
||||
(e.g., "Recommendation: Replace 'e.g.' with 'for example'." or
|
||||
"Recommendation: Replace '...' with '...' in active voice.).
|
||||
- Append these findings to `audit-results-[YYYY-MM-DD].md`.
|
||||
|
||||
## Phase 2: Software Engineer Audit
|
||||
|
||||
**Role:** The software engineer is responsible for finding undocumented features
|
||||
by auditing the codebase and recent changelogs, and passing these findings to
|
||||
the technical writer.
|
||||
|
||||
### Steps
|
||||
|
||||
1. **Proactive Codebase Audit:**
|
||||
- Audit high-signal areas of the codebase to identify undocumented features.
|
||||
You MUST review:
|
||||
- `packages/cli/src/commands/`
|
||||
- `packages/core/src/tools/`
|
||||
- `packages/cli/src/config/settings.ts`
|
||||
|
||||
2. **Review Recent Updates:**
|
||||
- Check recent changelogs in stable and announcements within the
|
||||
documentation to see if newly introduced features are documented properly.
|
||||
|
||||
3. **Evaluate and Record Findings:**
|
||||
- Determine if these features are adequately covered in the docs. They do
|
||||
not need to be documented word for word, but major features that customers
|
||||
should care about probably should have an article.
|
||||
- Append your findings to the `audit-results-[YYYY-MM-DD].md` file,
|
||||
providing a brief description of the feature and where it should be
|
||||
documented.
|
||||
|
||||
## Phase 3: Technical Writer Implementation
|
||||
|
||||
**Role:** The technical writer handles input from both the editor and the
|
||||
software engineer, makes appropriate decisions about what to change, and
|
||||
implements the approved changes.
|
||||
|
||||
### Steps
|
||||
|
||||
1. **Review Audit Results:**
|
||||
- Read `audit-results-[YYYY-MM-DD].md` to understand all identified issues,
|
||||
undocumented features, and recommendations from both the Editor and
|
||||
Software Engineer phases.
|
||||
|
||||
2. **Make Decisions and Log Reasoning:**
|
||||
- Create or update an implementation log (e.g.,
|
||||
`audit-implementation-log-[YYYY-MM-DD].md`).
|
||||
- Make sure the logs are updated for all steps, documenting your reasoning
|
||||
for each recommendation (why it was accepted, modified, or rejected). This
|
||||
is required for a final check by a human in the PR.
|
||||
|
||||
3. **Implement Changes:**
|
||||
- For each approved recommendation:
|
||||
- Read the target documentation file.
|
||||
- Apply the recommended change using the `replace` tool. Pay close
|
||||
attention to `old_string` for exact matches, including whitespace and
|
||||
newlines. For multiple occurrences of the same simple string (e.g.,
|
||||
"e.g."), use `allow_multiple: true`.
|
||||
- **String replacement safeguards:** When applying these fixes across the
|
||||
docset, you must verify the following:
|
||||
- **Preserve Code Blocks:** Explicitly verify that no code blocks,
|
||||
inline code snippets, terminal commands, or file paths have been
|
||||
erroneously capitalized or modified.
|
||||
- **Preserve Literal Strings:** Never alter the wording of literal error
|
||||
messages, UI quotes, or system logs. For example, if a style rule says
|
||||
to remove the word "please", you must NOT remove it if it appears
|
||||
inside a quoted error message (e.g.,
|
||||
`Error: Please contact your administrator`).
|
||||
- **Verify Sentence Casing:** When removing filler words (like "please")
|
||||
from the beginning of a sentence or list item, always verify that the
|
||||
new first word of the sentence is properly capitalized.
|
||||
- For structural changes (e.g., adding an overview paragraph), use
|
||||
`replace` or `write_file` as appropriate.
|
||||
- For broken links, determine the correct new path or update the link
|
||||
text.
|
||||
- For creating new files (e.g., `docs/get-started.md` to fix a broken
|
||||
link, or a new feature article), use `write_file`.
|
||||
|
||||
4. **Execute Auto-Generation Scripts:**
|
||||
- Some documentation pages are auto-generated from the codebase and should
|
||||
be updated using npm scripts rather than manual edits. After implementing
|
||||
manual changes (especially if you edited settings or configurations based
|
||||
on SWE recommendations), ensure you run:
|
||||
- `npm run docs:settings` to generate/update the configuration reference.
|
||||
- `npm run docs:keybindings` to generate/update the keybindings reference.
|
||||
|
||||
5. **Format Code:**
|
||||
- **Dependencies:** If `npm run format` fails, it may be necessary to run
|
||||
`npm install` first to ensure all formatting dependencies are available.
|
||||
- After all changes have been implemented, run `npm run format` to ensure
|
||||
consistent formatting across the project.
|
||||
@@ -0,0 +1,50 @@
|
||||
name: 'Weekly Docs Audit'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Runs every Monday at 00:00 UTC
|
||||
- cron: '0 0 * * MON'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
audit-docs:
|
||||
runs-on: 'ubuntu-latest'
|
||||
permissions:
|
||||
contents: 'write'
|
||||
pull-requests: 'write'
|
||||
|
||||
steps:
|
||||
- name: 'Checkout repository'
|
||||
uses: 'actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5'
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: 'main'
|
||||
|
||||
- name: 'Set up Node.js'
|
||||
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020'
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: 'Run Docs Audit with Gemini'
|
||||
uses: 'google-github-actions/run-gemini-cli@a3bf79042542528e91937b3a3a6fbc4967ee3c31'
|
||||
with:
|
||||
gemini_api_key: '${{ secrets.GEMINI_API_KEY }}'
|
||||
prompt: |
|
||||
Activate the 'docs-writer' skill.
|
||||
|
||||
**Task:** Execute the docs audit procedure, as defined in your 'docs-auditing.md' reference.
|
||||
|
||||
- name: 'Create Pull Request with Audit Results'
|
||||
uses: 'peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c'
|
||||
with:
|
||||
token: '${{ secrets.GEMINI_CLI_ROBOT_GITHUB_PAT }}'
|
||||
commit-message: 'docs: weekly audit results for ${{ github.run_id }}'
|
||||
title: 'Docs Audit for Week of ${{ github.event.schedule }}'
|
||||
body: |
|
||||
This PR contains the auto-generated documentation audit for the week. It includes a new `audit-results-*.md` file with findings and any direct fixes applied by the agent.
|
||||
|
||||
Please review the suggestions and merge.
|
||||
branch: 'docs-audit-${{ github.run_id }}'
|
||||
base: 'main'
|
||||
team-reviewers: 'gemini-cli-docs, gemini-cli-maintainers'
|
||||
delete-branch: true
|
||||
@@ -0,0 +1,207 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { act } from 'react';
|
||||
import type { LegacyAgentProtocol } from '@google/gemini-cli-core';
|
||||
import { renderHookWithProviders } from '../../test-utils/render.js';
|
||||
|
||||
// --- MOCKS ---
|
||||
|
||||
const mockLegacyAgentProtocol = vi.hoisted(() => ({
|
||||
send: vi.fn().mockResolvedValue({ streamId: 'test-stream-id' }),
|
||||
subscribe: vi.fn().mockReturnValue(() => {}),
|
||||
abort: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
|
||||
vi.mock('../contexts/SessionContext.js', async (importOriginal) => {
|
||||
const actual = await importOriginal<Record<string, unknown>>();
|
||||
return {
|
||||
...actual,
|
||||
useSessionStats: vi.fn(() => ({
|
||||
startNewPrompt: vi.fn(),
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
// --- END MOCKS ---
|
||||
|
||||
import { useAgentStream } from './useAgentStream.js';
|
||||
import { MessageType, StreamingState } from '../types.js';
|
||||
|
||||
describe('useAgentStream', () => {
|
||||
const mockAddItem = vi.fn();
|
||||
const mockOnCancelSubmit = vi.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should initialize on mount', async () => {
|
||||
await renderHookWithProviders(() =>
|
||||
useAgentStream({
|
||||
agent: mockLegacyAgentProtocol as unknown as LegacyAgentProtocol,
|
||||
addItem: mockAddItem,
|
||||
onCancelSubmit: mockOnCancelSubmit,
|
||||
isShellFocused: false,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(mockLegacyAgentProtocol.subscribe).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call agent.send when submitQuery is called', async () => {
|
||||
const { result } = await renderHookWithProviders(() =>
|
||||
useAgentStream({
|
||||
agent: mockLegacyAgentProtocol as unknown as LegacyAgentProtocol,
|
||||
addItem: mockAddItem,
|
||||
onCancelSubmit: mockOnCancelSubmit,
|
||||
isShellFocused: false,
|
||||
}),
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await result.current.submitQuery('hello');
|
||||
});
|
||||
|
||||
expect(mockLegacyAgentProtocol.send).toHaveBeenCalledWith({
|
||||
message: { content: [{ type: 'text', text: 'hello' }] },
|
||||
});
|
||||
expect(mockAddItem).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ type: MessageType.USER, text: 'hello' }),
|
||||
expect.any(Number),
|
||||
);
|
||||
});
|
||||
|
||||
it('should update streamingState based on agent_start and agent_end events', async () => {
|
||||
const { result } = await renderHookWithProviders(() =>
|
||||
useAgentStream({
|
||||
agent: mockLegacyAgentProtocol as unknown as LegacyAgentProtocol,
|
||||
addItem: mockAddItem,
|
||||
onCancelSubmit: mockOnCancelSubmit,
|
||||
isShellFocused: false,
|
||||
}),
|
||||
);
|
||||
|
||||
const eventHandler = vi.mocked(mockLegacyAgentProtocol.subscribe).mock
|
||||
.calls[0][0];
|
||||
|
||||
expect(result.current.streamingState).toBe(StreamingState.Idle);
|
||||
|
||||
act(() => {
|
||||
eventHandler({
|
||||
type: 'agent_start',
|
||||
id: '1',
|
||||
timestamp: '',
|
||||
streamId: '',
|
||||
});
|
||||
});
|
||||
expect(result.current.streamingState).toBe(StreamingState.Responding);
|
||||
|
||||
act(() => {
|
||||
eventHandler({
|
||||
type: 'agent_end',
|
||||
reason: 'completed',
|
||||
id: '2',
|
||||
timestamp: '',
|
||||
streamId: '',
|
||||
});
|
||||
});
|
||||
expect(result.current.streamingState).toBe(StreamingState.Idle);
|
||||
});
|
||||
|
||||
it('should accumulate text content and update pendingHistoryItems', async () => {
|
||||
const { result } = await renderHookWithProviders(() =>
|
||||
useAgentStream({
|
||||
agent: mockLegacyAgentProtocol as unknown as LegacyAgentProtocol,
|
||||
addItem: mockAddItem,
|
||||
onCancelSubmit: mockOnCancelSubmit,
|
||||
isShellFocused: false,
|
||||
}),
|
||||
);
|
||||
|
||||
const eventHandler = vi.mocked(mockLegacyAgentProtocol.subscribe).mock
|
||||
.calls[0][0];
|
||||
|
||||
act(() => {
|
||||
eventHandler({
|
||||
type: 'message',
|
||||
role: 'agent',
|
||||
content: [{ type: 'text', text: 'Hello' }],
|
||||
id: '1',
|
||||
timestamp: '',
|
||||
streamId: '',
|
||||
});
|
||||
});
|
||||
|
||||
expect(result.current.pendingHistoryItems).toHaveLength(1);
|
||||
expect(result.current.pendingHistoryItems[0]).toMatchObject({
|
||||
type: 'gemini',
|
||||
text: 'Hello',
|
||||
});
|
||||
|
||||
act(() => {
|
||||
eventHandler({
|
||||
type: 'message',
|
||||
role: 'agent',
|
||||
content: [{ type: 'text', text: ' world' }],
|
||||
id: '2',
|
||||
timestamp: '',
|
||||
streamId: '',
|
||||
});
|
||||
});
|
||||
|
||||
expect(result.current.pendingHistoryItems[0].text).toBe('Hello world');
|
||||
});
|
||||
|
||||
it('should process thought events and update thought state', async () => {
|
||||
const { result } = await renderHookWithProviders(() =>
|
||||
useAgentStream({
|
||||
agent: mockLegacyAgentProtocol as unknown as LegacyAgentProtocol,
|
||||
addItem: mockAddItem,
|
||||
onCancelSubmit: mockOnCancelSubmit,
|
||||
isShellFocused: false,
|
||||
}),
|
||||
);
|
||||
|
||||
const eventHandler = vi.mocked(mockLegacyAgentProtocol.subscribe).mock
|
||||
.calls[0][0];
|
||||
|
||||
act(() => {
|
||||
eventHandler({
|
||||
type: 'message',
|
||||
role: 'agent',
|
||||
content: [{ type: 'thought', thought: '**Thinking** about tests' }],
|
||||
id: '1',
|
||||
timestamp: '',
|
||||
streamId: '',
|
||||
});
|
||||
});
|
||||
|
||||
expect(result.current.thought).toEqual({
|
||||
subject: 'Thinking',
|
||||
description: 'about tests',
|
||||
});
|
||||
});
|
||||
|
||||
it('should call agent.abort when cancelOngoingRequest is called', async () => {
|
||||
const { result } = await renderHookWithProviders(() =>
|
||||
useAgentStream({
|
||||
agent: mockLegacyAgentProtocol as unknown as LegacyAgentProtocol,
|
||||
addItem: mockAddItem,
|
||||
onCancelSubmit: mockOnCancelSubmit,
|
||||
isShellFocused: false,
|
||||
}),
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await result.current.cancelOngoingRequest();
|
||||
});
|
||||
|
||||
expect(mockLegacyAgentProtocol.abort).toHaveBeenCalled();
|
||||
expect(mockOnCancelSubmit).toHaveBeenCalledWith(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,528 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { useState, useRef, useCallback, useEffect, useMemo } from 'react';
|
||||
import {
|
||||
getErrorMessage,
|
||||
MessageSenderType,
|
||||
debugLogger,
|
||||
geminiPartsToContentParts,
|
||||
parseThought,
|
||||
CoreToolCallStatus,
|
||||
type ApprovalMode,
|
||||
Kind,
|
||||
type ThoughtSummary,
|
||||
type RetryAttemptPayload,
|
||||
type AgentEvent,
|
||||
type AgentProtocol,
|
||||
type Logger,
|
||||
type Part,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type {
|
||||
HistoryItemWithoutId,
|
||||
LoopDetectionConfirmationRequest,
|
||||
IndividualToolCallDisplay,
|
||||
HistoryItemToolGroup,
|
||||
} from '../types.js';
|
||||
import { StreamingState, MessageType } from '../types.js';
|
||||
import { findLastSafeSplitPoint } from '../utils/markdownUtilities.js';
|
||||
import { getToolGroupBorderAppearance } from '../utils/borderStyles.js';
|
||||
import { type BackgroundTask } from './useExecutionLifecycle.js';
|
||||
import type { UseHistoryManagerReturn } from './useHistoryManager.js';
|
||||
import { useSessionStats } from '../contexts/SessionContext.js';
|
||||
import { useStateAndRef } from './useStateAndRef.js';
|
||||
import { type MinimalTrackedToolCall } from './useTurnActivityMonitor.js';
|
||||
|
||||
export interface UseAgentStreamOptions {
|
||||
agent?: AgentProtocol;
|
||||
addItem: UseHistoryManagerReturn['addItem'];
|
||||
onCancelSubmit: (shouldRestorePrompt?: boolean) => void;
|
||||
isShellFocused?: boolean;
|
||||
logger?: Logger | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* useAgentStream implements the interactive agent loop using an AgentProtocol.
|
||||
* It is completely agnostic to the specific agent implementation.
|
||||
*/
|
||||
export const useAgentStream = ({
|
||||
agent,
|
||||
addItem,
|
||||
onCancelSubmit,
|
||||
isShellFocused,
|
||||
logger,
|
||||
}: UseAgentStreamOptions) => {
|
||||
const [initError] = useState<string | null>(null);
|
||||
const [retryStatus] = useState<RetryAttemptPayload | null>(null);
|
||||
const [streamingState, setStreamingState] = useState<StreamingState>(
|
||||
StreamingState.Idle,
|
||||
);
|
||||
const [thought, setThought] = useState<ThoughtSummary | null>(null);
|
||||
const [lastOutputTime, setLastOutputTime] = useState<number>(Date.now());
|
||||
|
||||
const currentStreamIdRef = useRef<string | null>(null);
|
||||
const userMessageTimestampRef = useRef<number>(0);
|
||||
const geminiMessageBufferRef = useRef<string>('');
|
||||
const [pendingHistoryItem, pendingHistoryItemRef, setPendingHistoryItem] =
|
||||
useStateAndRef<HistoryItemWithoutId | null>(null);
|
||||
|
||||
const [trackedTools, , setTrackedTools] = useStateAndRef<
|
||||
IndividualToolCallDisplay[]
|
||||
>([]);
|
||||
const [pushedToolCallIds, pushedToolCallIdsRef, setPushedToolCallIds] =
|
||||
useStateAndRef<Set<string>>(new Set());
|
||||
const [_isFirstToolInGroup, isFirstToolInGroupRef, setIsFirstToolInGroup] =
|
||||
useStateAndRef<boolean>(true);
|
||||
|
||||
const { startNewPrompt } = useSessionStats();
|
||||
|
||||
// TODO: Implement dynamic shell-related state derivation from trackedTools or dedicated refs.
|
||||
// This includes activePtyId, backgroundTasks, and related visibility states to restore
|
||||
// parity with legacy terminal focus detection and background task tracking.
|
||||
// Note: Avoid checking ITERM_SESSION_ID for terminal detection and ensure context is sanitized.
|
||||
const activePtyId = undefined;
|
||||
const backgroundTaskCount = 0;
|
||||
const isBackgroundTaskVisible = false;
|
||||
const toggleBackgroundTasks = useCallback(() => {}, []);
|
||||
const backgroundCurrentExecution = undefined;
|
||||
const backgroundTasks = useMemo(() => new Map<number, BackgroundTask>(), []);
|
||||
const dismissBackgroundTask = useCallback(async (_pid: number) => {}, []);
|
||||
|
||||
// Use the trackedTools to mock pendingToolCalls for inactivity monitors
|
||||
const pendingToolCalls = useMemo(
|
||||
(): MinimalTrackedToolCall[] =>
|
||||
trackedTools.map((t) => ({
|
||||
request: {
|
||||
name: t.originalRequestName || t.name,
|
||||
args: { command: t.description },
|
||||
callId: t.callId,
|
||||
isClientInitiated: t.isClientInitiated ?? false,
|
||||
prompt_id: '',
|
||||
},
|
||||
status: t.status,
|
||||
})),
|
||||
[trackedTools],
|
||||
);
|
||||
|
||||
// TODO: Support LoopDetection confirmation requests
|
||||
const [loopDetectionConfirmationRequest] =
|
||||
useState<LoopDetectionConfirmationRequest | null>(null);
|
||||
|
||||
const flushPendingText = useCallback(() => {
|
||||
if (pendingHistoryItemRef.current) {
|
||||
addItem(pendingHistoryItemRef.current, userMessageTimestampRef.current);
|
||||
setPendingHistoryItem(null);
|
||||
geminiMessageBufferRef.current = '';
|
||||
}
|
||||
}, [addItem, pendingHistoryItemRef, setPendingHistoryItem]);
|
||||
|
||||
const cancelOngoingRequest = useCallback(async () => {
|
||||
if (agent) {
|
||||
await agent.abort();
|
||||
setStreamingState(StreamingState.Idle);
|
||||
onCancelSubmit(false);
|
||||
}
|
||||
}, [agent, onCancelSubmit]);
|
||||
|
||||
// TODO: Support native handleApprovalModeChange for Plan Mode
|
||||
const handleApprovalModeChange = useCallback(
|
||||
async (newApprovalMode: ApprovalMode) => {
|
||||
debugLogger.debug(`Approval mode changed to ${newApprovalMode} (stub)`);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleEvent = useCallback(
|
||||
(event: AgentEvent) => {
|
||||
setLastOutputTime(Date.now());
|
||||
switch (event.type) {
|
||||
case 'agent_start':
|
||||
setStreamingState(StreamingState.Responding);
|
||||
break;
|
||||
case 'agent_end':
|
||||
setStreamingState(StreamingState.Idle);
|
||||
flushPendingText();
|
||||
break;
|
||||
case 'message':
|
||||
if (event.role === 'agent') {
|
||||
for (const part of event.content) {
|
||||
if (part.type === 'text') {
|
||||
geminiMessageBufferRef.current += part.text;
|
||||
// Update pending history item with incremental text
|
||||
const splitPoint = findLastSafeSplitPoint(
|
||||
geminiMessageBufferRef.current,
|
||||
);
|
||||
if (splitPoint === geminiMessageBufferRef.current.length) {
|
||||
setPendingHistoryItem({
|
||||
type: 'gemini',
|
||||
text: geminiMessageBufferRef.current,
|
||||
});
|
||||
} else {
|
||||
const before = geminiMessageBufferRef.current.substring(
|
||||
0,
|
||||
splitPoint,
|
||||
);
|
||||
const after =
|
||||
geminiMessageBufferRef.current.substring(splitPoint);
|
||||
addItem(
|
||||
{ type: 'gemini', text: before },
|
||||
userMessageTimestampRef.current,
|
||||
);
|
||||
geminiMessageBufferRef.current = after;
|
||||
setPendingHistoryItem({
|
||||
type: 'gemini_content',
|
||||
text: after,
|
||||
});
|
||||
}
|
||||
} else if (part.type === 'thought') {
|
||||
setThought(parseThought(part.thought));
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'tool_request': {
|
||||
flushPendingText();
|
||||
const legacyState = event._meta?.legacyState;
|
||||
const displayName = legacyState?.displayName ?? event.name;
|
||||
const isOutputMarkdown = legacyState?.isOutputMarkdown ?? false;
|
||||
const desc = legacyState?.description ?? '';
|
||||
|
||||
const fallbackKind = Kind.Other;
|
||||
|
||||
const newCall: IndividualToolCallDisplay = {
|
||||
callId: event.requestId,
|
||||
name: displayName,
|
||||
originalRequestName: event.name,
|
||||
description: desc,
|
||||
status: CoreToolCallStatus.Scheduled,
|
||||
isClientInitiated: false,
|
||||
renderOutputAsMarkdown: isOutputMarkdown,
|
||||
kind: legacyState?.kind ?? fallbackKind,
|
||||
confirmationDetails: undefined,
|
||||
resultDisplay: undefined,
|
||||
};
|
||||
setTrackedTools((prev) => [...prev, newCall]);
|
||||
break;
|
||||
}
|
||||
case 'tool_update': {
|
||||
setTrackedTools((prev) =>
|
||||
prev.map((tc): IndividualToolCallDisplay => {
|
||||
if (tc.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
|
||||
: tc.resultDisplay;
|
||||
const progressMessage =
|
||||
legacyState?.progressMessage ?? tc.progressMessage;
|
||||
const progress = legacyState?.progress ?? tc.progress;
|
||||
const progressTotal =
|
||||
legacyState?.progressTotal ?? tc.progressTotal;
|
||||
const ptyId = legacyState?.pid ?? tc.ptyId;
|
||||
const description = legacyState?.description ?? tc.description;
|
||||
|
||||
return {
|
||||
...tc,
|
||||
status,
|
||||
resultDisplay: liveOutput,
|
||||
progressMessage,
|
||||
progress,
|
||||
progressTotal,
|
||||
ptyId,
|
||||
description,
|
||||
};
|
||||
}),
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'tool_response': {
|
||||
setTrackedTools((prev) =>
|
||||
prev.map((tc): IndividualToolCallDisplay => {
|
||||
if (tc.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
|
||||
: tc.resultDisplay;
|
||||
|
||||
return {
|
||||
...tc,
|
||||
status: event.isError
|
||||
? CoreToolCallStatus.Error
|
||||
: CoreToolCallStatus.Success,
|
||||
resultDisplay,
|
||||
outputFile,
|
||||
};
|
||||
}),
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'error':
|
||||
addItem(
|
||||
{ type: MessageType.ERROR, text: event.message },
|
||||
userMessageTimestampRef.current,
|
||||
);
|
||||
break;
|
||||
|
||||
case 'initialize':
|
||||
case 'session_update':
|
||||
case 'elicitation_request':
|
||||
case 'elicitation_response':
|
||||
case 'usage':
|
||||
case 'custom':
|
||||
// These events are currently not handled in the UI
|
||||
break;
|
||||
|
||||
default:
|
||||
debugLogger.error('Unknown agent event type:', event);
|
||||
event satisfies never;
|
||||
break;
|
||||
}
|
||||
},
|
||||
[
|
||||
addItem,
|
||||
flushPendingText,
|
||||
setPendingHistoryItem,
|
||||
setTrackedTools,
|
||||
setStreamingState,
|
||||
setThought,
|
||||
setLastOutputTime,
|
||||
],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = agent?.subscribe(handleEvent);
|
||||
return () => unsubscribe?.();
|
||||
}, [agent, handleEvent]);
|
||||
|
||||
const submitQuery = useCallback(
|
||||
async (
|
||||
query: Part[] | string,
|
||||
options?: { isContinuation: boolean },
|
||||
_prompt_id?: string,
|
||||
) => {
|
||||
if (!agent) return;
|
||||
|
||||
const timestamp = Date.now();
|
||||
setLastOutputTime(timestamp);
|
||||
userMessageTimestampRef.current = timestamp;
|
||||
|
||||
geminiMessageBufferRef.current = '';
|
||||
|
||||
if (!options?.isContinuation) {
|
||||
if (typeof query === 'string') {
|
||||
addItem({ type: MessageType.USER, text: query }, timestamp);
|
||||
void logger?.logMessage(MessageSenderType.USER, query);
|
||||
}
|
||||
startNewPrompt();
|
||||
}
|
||||
|
||||
const parts = geminiPartsToContentParts(
|
||||
typeof query === 'string' ? [{ text: query }] : query,
|
||||
);
|
||||
|
||||
try {
|
||||
const { streamId } = await agent.send({
|
||||
message: { content: parts },
|
||||
});
|
||||
currentStreamIdRef.current = streamId;
|
||||
} catch (err) {
|
||||
addItem(
|
||||
{ type: MessageType.ERROR, text: getErrorMessage(err) },
|
||||
timestamp,
|
||||
);
|
||||
}
|
||||
},
|
||||
[agent, addItem, logger, startNewPrompt],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (trackedTools.length > 0) {
|
||||
const isNewBatch = !trackedTools.some((tc) =>
|
||||
pushedToolCallIdsRef.current.has(tc.callId),
|
||||
);
|
||||
if (isNewBatch) {
|
||||
setPushedToolCallIds(new Set());
|
||||
setIsFirstToolInGroup(true);
|
||||
}
|
||||
} else if (streamingState === StreamingState.Idle) {
|
||||
setPushedToolCallIds(new Set());
|
||||
setIsFirstToolInGroup(true);
|
||||
}
|
||||
}, [
|
||||
trackedTools,
|
||||
pushedToolCallIdsRef,
|
||||
setPushedToolCallIds,
|
||||
setIsFirstToolInGroup,
|
||||
streamingState,
|
||||
]);
|
||||
|
||||
// Push completed tools to history
|
||||
useEffect(() => {
|
||||
const toolsToPush: IndividualToolCallDisplay[] = [];
|
||||
for (let i = 0; i < trackedTools.length; i++) {
|
||||
const tc = trackedTools[i];
|
||||
if (pushedToolCallIdsRef.current.has(tc.callId)) continue;
|
||||
|
||||
if (
|
||||
tc.status === 'success' ||
|
||||
tc.status === 'error' ||
|
||||
tc.status === 'cancelled'
|
||||
) {
|
||||
toolsToPush.push(tc);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (toolsToPush.length > 0) {
|
||||
const newPushed = new Set(pushedToolCallIdsRef.current);
|
||||
for (const tc of toolsToPush) {
|
||||
newPushed.add(tc.callId);
|
||||
}
|
||||
|
||||
const isLastInBatch =
|
||||
toolsToPush[toolsToPush.length - 1] ===
|
||||
trackedTools[trackedTools.length - 1];
|
||||
|
||||
const appearance = getToolGroupBorderAppearance(
|
||||
{ type: 'tool_group', tools: trackedTools },
|
||||
activePtyId,
|
||||
!!isShellFocused,
|
||||
[],
|
||||
backgroundTasks,
|
||||
);
|
||||
|
||||
const historyItem: HistoryItemToolGroup = {
|
||||
type: 'tool_group',
|
||||
tools: toolsToPush,
|
||||
borderTop: isFirstToolInGroupRef.current,
|
||||
borderBottom: isLastInBatch,
|
||||
...appearance,
|
||||
};
|
||||
|
||||
addItem(historyItem);
|
||||
setPushedToolCallIds(newPushed);
|
||||
setIsFirstToolInGroup(false);
|
||||
}
|
||||
}, [
|
||||
trackedTools,
|
||||
pushedToolCallIdsRef,
|
||||
isFirstToolInGroupRef,
|
||||
setPushedToolCallIds,
|
||||
setIsFirstToolInGroup,
|
||||
addItem,
|
||||
activePtyId,
|
||||
isShellFocused,
|
||||
backgroundTasks,
|
||||
]);
|
||||
|
||||
const pendingToolGroupItems = useMemo((): HistoryItemWithoutId[] => {
|
||||
const remainingTools = trackedTools.filter(
|
||||
(tc) => !pushedToolCallIds.has(tc.callId),
|
||||
);
|
||||
|
||||
const items: HistoryItemWithoutId[] = [];
|
||||
|
||||
const appearance = getToolGroupBorderAppearance(
|
||||
{ type: 'tool_group', tools: trackedTools },
|
||||
activePtyId,
|
||||
!!isShellFocused,
|
||||
[],
|
||||
backgroundTasks,
|
||||
);
|
||||
|
||||
if (remainingTools.length > 0) {
|
||||
items.push({
|
||||
type: 'tool_group',
|
||||
tools: remainingTools,
|
||||
borderTop: pushedToolCallIds.size === 0,
|
||||
borderBottom: false,
|
||||
...appearance,
|
||||
});
|
||||
}
|
||||
|
||||
const allTerminal =
|
||||
trackedTools.length > 0 &&
|
||||
trackedTools.every(
|
||||
(tc) =>
|
||||
tc.status === 'success' ||
|
||||
tc.status === 'error' ||
|
||||
tc.status === 'cancelled',
|
||||
);
|
||||
|
||||
const allPushed =
|
||||
trackedTools.length > 0 &&
|
||||
trackedTools.every((tc) => pushedToolCallIds.has(tc.callId));
|
||||
|
||||
const anyVisibleInHistory = pushedToolCallIds.size > 0;
|
||||
const anyVisibleInPending = remainingTools.length > 0;
|
||||
|
||||
if (
|
||||
trackedTools.length > 0 &&
|
||||
!(allTerminal && allPushed) &&
|
||||
(anyVisibleInHistory || anyVisibleInPending)
|
||||
) {
|
||||
items.push({
|
||||
type: 'tool_group' as const,
|
||||
tools: [],
|
||||
borderTop: false,
|
||||
borderBottom: true,
|
||||
...appearance,
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
}, [
|
||||
trackedTools,
|
||||
pushedToolCallIds,
|
||||
activePtyId,
|
||||
isShellFocused,
|
||||
backgroundTasks,
|
||||
]);
|
||||
|
||||
const pendingHistoryItems = useMemo(
|
||||
() =>
|
||||
[pendingHistoryItem, ...pendingToolGroupItems].filter(
|
||||
(i): i is HistoryItemWithoutId => i !== undefined && i !== null,
|
||||
),
|
||||
[pendingHistoryItem, pendingToolGroupItems],
|
||||
);
|
||||
|
||||
return {
|
||||
streamingState,
|
||||
submitQuery,
|
||||
initError,
|
||||
pendingHistoryItems,
|
||||
thought,
|
||||
cancelOngoingRequest,
|
||||
pendingToolCalls,
|
||||
handleApprovalModeChange,
|
||||
activePtyId,
|
||||
loopDetectionConfirmationRequest,
|
||||
lastOutputTime,
|
||||
backgroundTaskCount,
|
||||
isBackgroundTaskVisible,
|
||||
toggleBackgroundTasks,
|
||||
backgroundCurrentExecution,
|
||||
backgroundTasks,
|
||||
retryStatus,
|
||||
dismissBackgroundTask,
|
||||
};
|
||||
};
|
||||
@@ -5,20 +5,22 @@
|
||||
*/
|
||||
|
||||
import { useInactivityTimer } from './useInactivityTimer.js';
|
||||
import { useTurnActivityMonitor } from './useTurnActivityMonitor.js';
|
||||
import {
|
||||
useTurnActivityMonitor,
|
||||
type MinimalTrackedToolCall,
|
||||
} from './useTurnActivityMonitor.js';
|
||||
import {
|
||||
SHELL_FOCUS_HINT_DELAY_MS,
|
||||
SHELL_ACTION_REQUIRED_TITLE_DELAY_MS,
|
||||
SHELL_SILENT_WORKING_TITLE_DELAY_MS,
|
||||
} from '../constants.js';
|
||||
import type { StreamingState } from '../types.js';
|
||||
import { type TrackedToolCall } from './useToolScheduler.js';
|
||||
|
||||
interface ShellInactivityStatusProps {
|
||||
activePtyId: number | string | null | undefined;
|
||||
lastOutputTime: number;
|
||||
streamingState: StreamingState;
|
||||
pendingToolCalls: TrackedToolCall[];
|
||||
pendingToolCalls: MinimalTrackedToolCall[];
|
||||
embeddedShellFocused: boolean;
|
||||
isInteractiveShellEnabled: boolean;
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ export function useToolScheduler(
|
||||
React.Dispatch<React.SetStateAction<TrackedToolCall[]>>,
|
||||
CancelAllFn,
|
||||
number,
|
||||
Scheduler,
|
||||
] {
|
||||
// State stores tool calls organized by their originating schedulerId
|
||||
const [toolCallsMap, setToolCallsMap] = useState<
|
||||
@@ -319,6 +320,7 @@ export function useToolScheduler(
|
||||
setToolCallsForDisplay,
|
||||
cancelAll,
|
||||
lastToolOutputTime,
|
||||
scheduler,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,16 @@
|
||||
|
||||
import { useState, useEffect, useRef, useMemo } from 'react';
|
||||
import { StreamingState } from '../types.js';
|
||||
import { hasRedirection } from '@google/gemini-cli-core';
|
||||
import { type TrackedToolCall } from './useToolScheduler.js';
|
||||
import {
|
||||
hasRedirection,
|
||||
type CoreToolCallStatus,
|
||||
type ToolCallRequestInfo,
|
||||
} from '@google/gemini-cli-core';
|
||||
|
||||
export interface MinimalTrackedToolCall {
|
||||
status: CoreToolCallStatus;
|
||||
request: ToolCallRequestInfo;
|
||||
}
|
||||
|
||||
export interface TurnActivityStatus {
|
||||
operationStartTime: number;
|
||||
@@ -21,7 +29,7 @@ export interface TurnActivityStatus {
|
||||
export const useTurnActivityMonitor = (
|
||||
streamingState: StreamingState,
|
||||
activePtyId: number | string | null | undefined,
|
||||
pendingToolCalls: TrackedToolCall[] = [],
|
||||
pendingToolCalls: MinimalTrackedToolCall[] = [],
|
||||
): TurnActivityStatus => {
|
||||
const [operationStartTime, setOperationStartTime] = useState(0);
|
||||
|
||||
|
||||
@@ -29,7 +29,10 @@ export function getToolGroupBorderAppearance(
|
||||
item:
|
||||
| HistoryItem
|
||||
| HistoryItemWithoutId
|
||||
| { type: 'tool_group'; tools: TrackedToolCall[] },
|
||||
| {
|
||||
type: 'tool_group';
|
||||
tools: Array<IndividualToolCallDisplay | TrackedToolCall>;
|
||||
},
|
||||
activeShellPtyId: number | null | undefined,
|
||||
embeddedShellFocused: boolean | undefined,
|
||||
allPendingItems: HistoryItemWithoutId[] = [],
|
||||
@@ -41,7 +44,7 @@ export function getToolGroupBorderAppearance(
|
||||
|
||||
// If this item has no tools, it's a closing slice for the current batch.
|
||||
// We need to look at the last pending item to determine the batch's appearance.
|
||||
const toolsToInspect: Array<IndividualToolCallDisplay | TrackedToolCall> =
|
||||
const toolsToInspect =
|
||||
item.tools.length > 0
|
||||
? item.tools
|
||||
: allPendingItems
|
||||
|
||||
@@ -76,7 +76,6 @@ export class LegacyAgentProtocol implements AgentProtocol {
|
||||
this._config = deps.config;
|
||||
this._client = deps.client ?? deps.config.getGeminiClient();
|
||||
this._promptId = deps.promptId ?? deps.config.promptId ?? '';
|
||||
|
||||
if (deps.scheduler) {
|
||||
this._scheduler = deps.scheduler;
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user