mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-08-02 05:01:04 -07:00
Compare commits
483 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 933e0dc8ec | |||
| 90a5dc3da5 | |||
| 403d29c63c | |||
| e1ea24806e | |||
| d2a6cff4df | |||
| acf5ed595f | |||
| 03845198ce | |||
| d8a3d08f8e | |||
| 6f9118dca6 | |||
| d0b6701fba | |||
| 94c3eecb99 | |||
| 098e5c281c | |||
| f2c52f777c | |||
| 2b41263aa7 | |||
| 404a4468d8 | |||
| 7f67c7f952 | |||
| c8540b5744 | |||
| f6d97d4488 | |||
| 87712a0a7c | |||
| ba0e053ffc | |||
| d14779b264 | |||
| 5411f4a667 | |||
| 2034098780 | |||
| d53a5c4fb5 | |||
| f6ee025c46 | |||
| e50bf6adad | |||
| c21b6899e1 | |||
| 95693e265e | |||
| 569c6f1dd0 | |||
| 7350399a50 | |||
| c2a741ee36 | |||
| e177314a4a | |||
| dadd606c0d | |||
| b3fcddde11 | |||
| e205a468d9 | |||
| bdf80ea7c0 | |||
| 5e218a5630 | |||
| 8c36b10682 | |||
| 42c2e1b217 | |||
| 1e715d1e5c | |||
| 9f9a2fa844 | |||
| 64eb14ab11 | |||
| 0713c86dec | |||
| 99c5bf2e97 | |||
| fe67ef63c1 | |||
| 19d4384f16 | |||
| d351f07758 | |||
| d72f35c2e7 | |||
| aeffa2a460 | |||
| 030a5ace97 | |||
| b97661553f | |||
| 3370644ffe | |||
| 0f0b463a2f | |||
| 8d082a904d | |||
| 61582678bf | |||
| 613b8a4527 | |||
| 5982abeffb | |||
| 78b10dccf1 | |||
| 85bc25a765 | |||
| fec0eba07e | |||
| f92e79eba0 | |||
| 3e50be1658 | |||
| 9937fb2220 | |||
| 8e531dc029 | |||
| 83d0bdc32e | |||
| d15970e12c | |||
| c7b5dcd28f | |||
| 8c07ad2ab9 | |||
| ade9dfeebb | |||
| d1e35f8660 | |||
| e20d282088 | |||
| 4adfdad47f | |||
| 6c126b9e58 | |||
| 1d2e27a698 | |||
| 049a299b92 | |||
| b1258dd52c | |||
| 98cdaa01b8 | |||
| 3476a97acc | |||
| 9ebf321717 | |||
| ff725dea41 | |||
| c3f1b29c1e | |||
| 257cd07a3a | |||
| 43d6dc3668 | |||
| 2231497b1f | |||
| d0a845b6e6 | |||
| e4c4bb265e | |||
| 0f845407f1 | |||
| b2a2ea3633 | |||
| f8a862738d | |||
| 25f8452127 | |||
| 84573992b4 | |||
| 3f8d636501 | |||
| 300205b07c | |||
| e1c711f5ba | |||
| 282654e7b8 | |||
| 0d89ac7406 | |||
| 5c47592159 | |||
| 61f0f3c243 | |||
| 1e8ae5b9d7 | |||
| e8d0e0d342 | |||
| b644f037a3 | |||
| c5498bbb07 | |||
| 90c764ce13 | |||
| 10003a6490 | |||
| b916d79fe2 | |||
| 36b0a86c69 | |||
| 7cc5234b9c | |||
| 379f09d92f | |||
| fd9d3e19a9 | |||
| 86828bb561 | |||
| 78075c8a37 | |||
| 7d33baabe4 | |||
| ecf8fba10e | |||
| 2f8b68dffd | |||
| 8877c85278 | |||
| 1d1bdc57ce | |||
| ba88707b1e | |||
| 8c78fe4f10 | |||
| 394a7ea019 | |||
| 78a28bfc01 | |||
| cf8de02c68 | |||
| e650a4ee59 | |||
| c6b6dcbe95 | |||
| 9786c4dcff | |||
| d03496b710 | |||
| ab6b2293e1 | |||
| 472e775a13 | |||
| ce56b4ee1b | |||
| ba15eeb55f | |||
| d683e1c0db | |||
| 6d83d3440c | |||
| 9d74b7c0e8 | |||
| 016b5b42e2 | |||
| a591505bf9 | |||
| 638dd2f6c0 | |||
| 555e25e633 | |||
| 3174573b64 | |||
| 2c5e09e1c3 | |||
| ab11b2c27f | |||
| 0fcbff506e | |||
| 48e3932f65 | |||
| d304216300 | |||
| 2b8adf8cf3 | |||
| fb99b95372 | |||
| 60fe5acd60 | |||
| eb9ff72b5a | |||
| fb03242950 | |||
| 54c1e13853 | |||
| 102905bbc7 | |||
| fe1bfc64f7 | |||
| 0f9ec2735c | |||
| 13d8d9477c | |||
| 43916b98aa | |||
| 5d27a62bec | |||
| a05e0ea3a4 | |||
| 2987b473dd | |||
| ee7065f665 | |||
| d26b828ab3 | |||
| a810ca80bc | |||
| ad1f0d995d | |||
| 1ed163a666 | |||
| aa9922bc98 | |||
| 0075b4f118 | |||
| 4d85ce40be | |||
| 7ec78452ec | |||
| 540f60696a | |||
| 1ffb9c4188 | |||
| 570ccc7da0 | |||
| 1c8fe92d0f | |||
| 396b427cc9 | |||
| c961f27401 | |||
| 1c87e7cd25 | |||
| 408b885689 | |||
| cab9b1f370 | |||
| d3cf28eb4b | |||
| 11a0a9b911 | |||
| e8038c727f | |||
| 2abc288c52 | |||
| 2e2b066713 | |||
| c1076512d1 | |||
| 3154c06dc3 | |||
| b248ec6dfb | |||
| cc2c48d59e | |||
| ac733d40b3 | |||
| a4415f15d3 | |||
| cbbf565121 | |||
| 7bb13d1c41 | |||
| e79f62694a | |||
| 4fbeac8b32 | |||
| 22b0550520 | |||
| 5ba6bc7139 | |||
| 6893d27441 | |||
| 51f952e700 | |||
| 4af4f8644d | |||
| 37ca643a64 | |||
| 8f4b1b582d | |||
| 3c9052a751 | |||
| 2136598e87 | |||
| 3f90001f83 | |||
| 4ef4bd6f09 | |||
| 331dbd5638 | |||
| 4ab94dec5e | |||
| c0b766ad74 | |||
| fd59d9dd92 | |||
| 9e4ae214a8 | |||
| f649948713 | |||
| 43b8731241 | |||
| 9116cf2bab | |||
| bafbcbbe8b | |||
| fdb6088603 | |||
| f3a8b73717 | |||
| c883403147 | |||
| 2077521f84 | |||
| 6d90b7ddb3 | |||
| 6cf1c98524 | |||
| 5ff7cdc9e8 | |||
| 47603ef8e1 | |||
| ef4030331a | |||
| a0a6828265 | |||
| 69339f08a6 | |||
| cd27cae848 | |||
| 475e92da5d | |||
| 054497c7a6 | |||
| c81a02f8d2 | |||
| 445a5eac33 | |||
| f05d937f39 | |||
| 5f1208ad81 | |||
| 1e42fdf6c2 | |||
| 98055d0989 | |||
| c13ec85d7d | |||
| 36feb73bfd | |||
| 77614eff5b | |||
| 31b34b11ab | |||
| c585470a71 | |||
| 9ba1cd0336 | |||
| 5f6453a1e0 | |||
| 956ab94452 | |||
| 0f5dd2229c | |||
| 224a33db2e | |||
| fb0768f007 | |||
| 9787108532 | |||
| 44b8c62db9 | |||
| 1d2f90c7e7 | |||
| c951f9fdcd | |||
| fa93b56243 | |||
| ca6cfaaf4e | |||
| 400da30a8d | |||
| f5bd474e51 | |||
| 16113647de | |||
| f51d74586c | |||
| c743631148 | |||
| b445db3d46 | |||
| 21dd9bbf7d | |||
| 3937461272 | |||
| da3da19844 | |||
| 53c7646ed7 | |||
| b6524e410a | |||
| 75c2769b32 | |||
| f796650122 | |||
| 460c3debf5 | |||
| b8b6620365 | |||
| 96d7eb2966 | |||
| da4fa5ad75 | |||
| 2b77c1ded4 | |||
| 6ab1b239ca | |||
| ab73051298 | |||
| cb2880cb93 | |||
| b5315bfc20 | |||
| b259153403 | |||
| f0c3c81e94 | |||
| fc42c4613f | |||
| bd06e5b161 | |||
| ad33c22374 | |||
| f3759381b1 | |||
| 80673a0c0c | |||
| 1671bf77fb | |||
| ffc5e4d048 | |||
| 60d2c2cc90 | |||
| 2144d25885 | |||
| 7a515339ea | |||
| be1dc13bb1 | |||
| 1c044ba8af | |||
| 4fc9b1cde2 | |||
| 60973aacd9 | |||
| 1c18552426 | |||
| c4377c1b1a | |||
| 265f24e5d7 | |||
| 59e0b10e6c | |||
| 9d642f3bb1 | |||
| 19ea68b838 | |||
| 93f14ce626 | |||
| 1ef34261e0 | |||
| 5062fadf87 | |||
| 462c7d3502 | |||
| 9187f6f6d1 | |||
| c0495ce2f9 | |||
| 02518d2927 | |||
| d7243fb81f | |||
| e3262f8766 | |||
| caf2ca1438 | |||
| f8ce3585eb | |||
| 61207fc2cb | |||
| b3cc397a31 | |||
| f9df415392 | |||
| fd2cbacae9 | |||
| 8b93a5f27d | |||
| 9478bca67d | |||
| 4b53b3a6e6 | |||
| d72f8453cb | |||
| 6ee7165e39 | |||
| adddafe6d0 | |||
| 35f091bb01 | |||
| e9c7a80b12 | |||
| ab013fb7e9 | |||
| c158923b27 | |||
| 31c5761e06 | |||
| f4ee245bf9 | |||
| d13482e802 | |||
| 11e1e98022 | |||
| b31f6804fe | |||
| 01ad74a870 | |||
| f875911af7 | |||
| f8ff921c42 | |||
| ab8c24f5ea | |||
| 9da3cb7e43 | |||
| 6f69cdcc31 | |||
| 236334d015 | |||
| 12472ce9b2 | |||
| e762cda55d | |||
| f566df91c8 | |||
| db37c715c0 | |||
| c89bc30d66 | |||
| 59e00eeda6 | |||
| 3332703fca | |||
| 643f2c0958 | |||
| c6a7107f78 | |||
| 322feaafa6 | |||
| 68afb7200e | |||
| b382ae6803 | |||
| 135d981e3c | |||
| 54fa26ef0e | |||
| cc081337b7 | |||
| d4cad0cdcc | |||
| b8969cceff | |||
| a3370ac86b | |||
| 7d03151cd5 | |||
| 054b4307ed | |||
| b8330b626e | |||
| 2a3244b1bb | |||
| 4d2a2de557 | |||
| c583b510e0 | |||
| 42c79c64d7 | |||
| 1d9e6870be | |||
| 167b6ff8a1 | |||
| 77df6d48e2 | |||
| 06035d5d43 | |||
| 6c8a48db13 | |||
| 523274dbf3 | |||
| 36207abec2 | |||
| 99f75f3218 | |||
| 82c10421a0 | |||
| 2e003ad8cf | |||
| 42a265d290 | |||
| 121732dde6 | |||
| 3e9701861e | |||
| b31b786db7 | |||
| 66e981ed1e | |||
| c60d8ef5a8 | |||
| 6e026bd950 | |||
| 372b588778 | |||
| 5d87a7f9c7 | |||
| 706834ecd3 | |||
| cca41edc6e | |||
| 601a639f95 | |||
| f6423ea45a | |||
| 70996bfdee | |||
| 44bdd3ad11 | |||
| ab1f195508 | |||
| 39eb6ed9cc | |||
| e750da987d | |||
| 7e987113a2 | |||
| 7a238bd938 | |||
| c2d60d61ac | |||
| 13aa0148e7 | |||
| 5d61adf804 | |||
| 4e6eef5882 | |||
| 064edc52f5 | |||
| 1b302deeff | |||
| 25f27509c0 | |||
| d465a26e30 | |||
| 034ca93907 | |||
| cca5a12839 | |||
| ecf0a24873 | |||
| cb208f53d8 | |||
| 85f3a8c210 | |||
| e9f8ccd51a | |||
| a9cb8f4958 | |||
| 2dfb813c90 | |||
| cb0947c501 | |||
| 2a87d663d2 | |||
| 6db64aab2b | |||
| abd22a753d | |||
| 9e8f7c074c | |||
| 44c62c8e5d | |||
| 4ef3c09332 | |||
| 29efebe38f | |||
| 0e4dce23b2 | |||
| e115083fac | |||
| 5ded674ad6 | |||
| 541eeb7a50 | |||
| 23c906b085 | |||
| c7817aee30 | |||
| 2fa13420ae | |||
| ee66732ad2 | |||
| 8352980f01 | |||
| a2d7f82b49 | |||
| 73570f1c86 | |||
| 51578397a5 | |||
| ef70e63230 | |||
| ce26b58f09 | |||
| bcd9735a73 | |||
| b1059f891f | |||
| 145e099ca5 | |||
| 81006605c8 | |||
| f934f01881 | |||
| cdff69b7b2 | |||
| d91484eb4d | |||
| c20b88cee2 | |||
| 810d940e57 | |||
| 7e2642b9f1 | |||
| 40057b55f0 | |||
| b188a51c32 | |||
| 63a90836fe | |||
| ca94dabd4f | |||
| 31b7c010d0 | |||
| 4960c47257 | |||
| ee92db7533 | |||
| c2104a14fb | |||
| 25996ae037 | |||
| a123a813b2 | |||
| 8bdef8754e | |||
| 978fbcf95e | |||
| d915525c8e | |||
| c079084ca4 | |||
| a889c15e38 | |||
| 884d838a1e | |||
| 0fe82a2f4e | |||
| b16fe7b646 | |||
| b77381750c | |||
| 9814f86a25 | |||
| aa6ae954ef | |||
| 5e70a7dd46 | |||
| 48ff9e1555 | |||
| c4c0c0d182 | |||
| 3a501196f0 | |||
| 7787a31f81 | |||
| 9e91aafe40 | |||
| 8ad72ec1ae | |||
| d3e4ff2cfc | |||
| 750c0e366f | |||
| bde5d61812 | |||
| 5ae9fe6949 | |||
| 3f38f95b1d | |||
| 445ef4fbed | |||
| 5ebe40e919 | |||
| 8e9f71b7a3 | |||
| 1202dced73 | |||
| 047bc44032 | |||
| 92d412e542 | |||
| d9f0b9c668 | |||
| 8f8a689722 | |||
| a90b9fe977 | |||
| eee3452948 | |||
| 2ede47d5ee | |||
| 4f220e945a | |||
| 30dd2f1dfe | |||
| b40f67b76a | |||
| a7faa2080f | |||
| 6d75005afc | |||
| 5bb9cd1a13 | |||
| 0bf2a0353d | |||
| ce655436ef | |||
| 59985138f7 |
@@ -0,0 +1,140 @@
|
||||
description="Reviews a pull request based on issue number."
|
||||
prompt = """
|
||||
Please provide a detailed pull request review on GitHub issue: {{args}}.
|
||||
|
||||
Follow these steps:
|
||||
|
||||
1. Use `gh pr view {{args}}` to pull the information of the PR.
|
||||
2. Use `gh pr diff {{args}}` to view the diff of the PR.
|
||||
3. Understand the intent of the PR using the PR description.
|
||||
4. If PR description is not detailed enough to understand the intent of the PR,
|
||||
make sure to note it in your review.
|
||||
5. Make sure the PR title follows Conventional Commits, here are the last five
|
||||
commits to the repo as examples: !{git log --pretty=format:"%s" -n 5}
|
||||
6. Search the codebase if required.
|
||||
7. Write a concise review of the PR, keeping in mind to encourage strong code
|
||||
quality and best practices. Pay particular attention to the Gemini MD file
|
||||
in the repo.
|
||||
8. Consider ways the code may not be consistent with existing code in the repo.
|
||||
In particular it is critical that the react code uses patterns consistent
|
||||
with existing code in the repo.
|
||||
9. Evaluate all tests on the PR and make sure that they are doing the following:
|
||||
* Using `waitFor` from @{packages/cli/src/test-utils/async.ts} rather than
|
||||
using `vi.waitFor` for all `waitFor` calls within `packages/cli`. Even if
|
||||
tests pass, using the wrong `waitFor` could result in flaky tests as `act`
|
||||
warnings could show up if timing is slightly different.
|
||||
* Using `act` to wrap all blocks in tests that change component state.
|
||||
* Using `toMatchSnapshot` to verify that rendering works as expected rather
|
||||
than matching against the raw content of the output.
|
||||
* If snapshots were changed as part of the pull request, review the snapshots
|
||||
changes to ensure they are intentional and comment if any look at all
|
||||
suspicious. Too many snapshot changes that indicate bugs have been approved
|
||||
in the past.
|
||||
* Use `render` or `renderWithProviders` from
|
||||
@{packages/cli/src/test-utils/render.tsx} rather than using `render` from
|
||||
`ink-testing-library` directly. This is needed to ensure that we do not get
|
||||
warnings about spurious `act` calls. If test cases specify providers
|
||||
directly, consider whether the existing `renderWithProviders` should be
|
||||
modified to support that use case.
|
||||
* Ensure the test cases are using parameterized tests where that might reduce
|
||||
the number of duplicated lines significantly.
|
||||
* NEVER use fixed waits (e.g. 'await delay(100)'). Always use 'waitFor' with
|
||||
a predicate to ensure tests are stable and fast.
|
||||
* Ensure mocks are properly managed:
|
||||
* Critical dependencies (fs, os, child_process) should only be mocked at
|
||||
the top of the file. Ideally avoid mocking these dependencies altogether.
|
||||
* Check to see if there are existing mocks or fakes that can be used rather
|
||||
than creating new ones for the new tests added.
|
||||
* Try to avoid mocking the file system whenever possible. If using the real
|
||||
file system is difficult consider whether the test should be an
|
||||
integration test rather than a unit test.
|
||||
* `vi.restoreAllMocks()` should be called in `afterEach` to prevent test
|
||||
pollution.
|
||||
* Use `vi.useFakeTimers()` for tests involving time-based logic to avoid
|
||||
flakiness.
|
||||
* Avoid using `any` in tests; prefer proper types or `unknown` with
|
||||
narrowing.
|
||||
* When creating parameterized tests, give the parameters types to ensure
|
||||
that the tests are type-safe.
|
||||
10. Evaluate all react logic carefully keeping in mind that the author of the PR
|
||||
is not likely an expert on React. Key areas to audit carefully are:
|
||||
* Whether `setState` calls trigger side effects from within the body of the
|
||||
`setState` callback. If so, you *must* propose an alternate design using
|
||||
reducers or other ways the code might be modified to not have to modify
|
||||
state from within a `setState`. Make sure to comment about absolutely
|
||||
every case like this as these cases have introduced multiple bugs in the
|
||||
past. Typically these cases should be resolved using a reducer although
|
||||
occassionally other techniques such as useRef are appropriate. Consider
|
||||
suggesting that jacob314@ be tagged on the code review if the solution is
|
||||
not 100% obvious.
|
||||
* Whether code might introduce an infinite rendering loop in React.
|
||||
* Whether keyboard handling is robust. Keyboard handling must go through
|
||||
`useKeyPress.ts` from the Gemini CLI package rather than using the
|
||||
standard ink library used by most keyboard handling. Unlike the standard
|
||||
ink library, the keyboard handling library in Gemini CLI may report
|
||||
multiple keyboard events one after another in the same React frame. This
|
||||
is needed to support slow terminals but introduces complexity in all our
|
||||
code that handles keyboard events. Handling this correctly often means
|
||||
that reducers must be used or other mechanisms to ensure that multiple
|
||||
state updates one after another are handled gracefully rather than
|
||||
overriding values from the first update with the second update. Refer to
|
||||
text-buffer.ts as a canonical example of using a reducer for this sort of
|
||||
case.
|
||||
* Ensure code does not use `console.log`, `console.warn`, or `console.error`
|
||||
as these indicate debug logging that was accidentally left in the code.
|
||||
* Avoid synchronous file I/O in React components as it will hang the UI.
|
||||
* Ensure state initialization is explicit (e.g., use 'undefined' rather than
|
||||
'true' as a default if the state is truly unknown initially).
|
||||
* Carefully manage 'useEffect' dependencies. Prefer to use a reducer
|
||||
whenever practical to resolve the issues. If that is not practical it is
|
||||
ok to use 'useRef' to access the latest value of a prop or state inside an
|
||||
effect without adding it to the dependency array if re-running the effect
|
||||
is undesirable (common in event listeners).
|
||||
* NEVER disable 'react-hooks/exhaustive-deps'. Fix the code to correctly
|
||||
declare dependencies. Disabling this lint rule will almost always lead to
|
||||
hard to detect bugs.
|
||||
* Avoid making types nullable unless strictly necessary, as it hurts
|
||||
readability.
|
||||
* Do not introduce excessive property drilling. There are multiple providers
|
||||
that can be leveraged to avoid property drilling. Make sure one of them
|
||||
cannot be used. Do suggest a provider that might make sense to be extended
|
||||
to include the new property or propose a new provider to add if the
|
||||
property drilling is excessive. Only use providers for properties that are
|
||||
consistent for the entire application.
|
||||
11. General Gemini CLI design principles:
|
||||
* Make sure that settings are only used for options that a user might
|
||||
consider changing.
|
||||
* Do not add new command line arguments and suggest settings instead.
|
||||
* New settings must be added to packages/cli/src/config/settingsSchema.ts.
|
||||
* If a setting has 'showInDialog: true', it MUST be documented in
|
||||
docs/get-started/configuration.md.
|
||||
* Ensure 'requiresRestart' is correctly set for new settings.
|
||||
* Use 'debugLogger' for rethrown errors to avoid duplicate logging.
|
||||
* All new keyboard shortcuts MUST be documented in
|
||||
docs/cli/keyboard-shortcuts.md.
|
||||
* Ensure new keyboard shortcuts are defined in
|
||||
packages/cli/src/config/keyBindings.ts.
|
||||
* If new keyboard shortcuts are added, remind the user to test them in
|
||||
VSCode, iTerm2, Ghostty, and Windows to ensure they work for all
|
||||
users.
|
||||
* Be careful of keybindings that require the meta key as only certain
|
||||
meta key shortcuts are supported on Mac.
|
||||
* Be skeptical of function keys and keyboard shortcuts that are commonly
|
||||
bound in VSCode as they may conflict.
|
||||
12. TypeScript Best Practices:
|
||||
* Use 'checkExhaustive' in the 'default' clause of 'switch' statements to
|
||||
ensure all cases are handled.
|
||||
* Avoid using the non-null assertion operator ('!') unless absolutely
|
||||
necessary and you are confident the value is not null.
|
||||
13. If the change might at all impact the prompts sent to Gemini CLI, flagged
|
||||
that the change could impact Gemini CLI quality and make sure anj-s has been
|
||||
tagged on the code review.
|
||||
14. Discuss with me before making any comments on the issue. I will clarify
|
||||
which possible issues you identified are problems, which ones you need to
|
||||
investigate further, and which ones I do not care about.
|
||||
15. If I request you to add comments to the issue, use
|
||||
`gh pr comment {{args}} --body {{review}}` to post the review to the PR.
|
||||
|
||||
Remember to use the GitHub CLI (`gh`) with the Shell tool for all
|
||||
GitHub-related tasks.
|
||||
"""
|
||||
@@ -1,8 +1,8 @@
|
||||
name: 'Bug Report'
|
||||
description: 'Report a bug to help us improve Gemini CLI'
|
||||
labels:
|
||||
- 'kind/bug'
|
||||
- 'status/need-triage'
|
||||
type: 'Bug'
|
||||
body:
|
||||
- type: 'markdown'
|
||||
attributes:
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
name: 'Feature Request'
|
||||
description: 'Suggest an idea for this project'
|
||||
labels:
|
||||
- 'kind/enhancement'
|
||||
- 'status/need-triage'
|
||||
type: 'Feature'
|
||||
body:
|
||||
- type: 'markdown'
|
||||
attributes:
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
name: 'Website issue'
|
||||
description: 'Report an issue with the Gemini CLI Website and Gemini CLI Extensions Gallery'
|
||||
labels:
|
||||
- 'area/website'
|
||||
- 'status/need-triage'
|
||||
- 'area/extensions'
|
||||
body:
|
||||
- type: 'markdown'
|
||||
attributes:
|
||||
|
||||
@@ -1,41 +1,42 @@
|
||||
## TLDR
|
||||
## Summary
|
||||
|
||||
<!-- Add a brief description of what this pull request changes and why and any important things for reviewers to look at -->
|
||||
<!-- Concisely describe what this PR changes and why. Focus on impact and
|
||||
urgency. -->
|
||||
|
||||
## Dive Deeper
|
||||
## Details
|
||||
|
||||
<!-- more thoughts and in-depth discussion here -->
|
||||
<!-- Add any extra context and design decisions. Keep it brief but complete. -->
|
||||
|
||||
## Reviewer Test Plan
|
||||
## Related Issues
|
||||
|
||||
<!-- when a person reviews your code they should ideally be pulling and running that code. How would they validate your change works and if relevant what are some good classes of example prompts and ways they can exercise your changes -->
|
||||
<!-- Use keywords to auto-close issues (Closes #123, Fixes #456). If this PR is
|
||||
only related to an issue or is a partial fix, simply reference the issue number
|
||||
without a keyword (Related to #123). -->
|
||||
|
||||
## Testing Matrix
|
||||
## How to Validate
|
||||
|
||||
<!-- Before submitting please validate your changes on as many of these options as possible -->
|
||||
<!-- List exact steps for reviewers to validate the change. Include commands,
|
||||
expected results, and edge cases. -->
|
||||
|
||||
| | 🍏 | 🪟 | 🐧 |
|
||||
| -------- | --- | --- | --- |
|
||||
| npm run | ❓ | ❓ | ❓ |
|
||||
| npx | ❓ | ❓ | ❓ |
|
||||
| Docker | ❓ | ❓ | ❓ |
|
||||
| Podman | ❓ | - | - |
|
||||
| Seatbelt | ❓ | - | - |
|
||||
## Pre-Merge Checklist
|
||||
|
||||
## Linked issues / bugs
|
||||
<!-- Check all that apply before requesting review or merging. -->
|
||||
|
||||
<!--
|
||||
Link to any related issues or bugs.
|
||||
|
||||
**If this PR fully resolves the issue, use one of the following keywords to automatically close the issue when this PR is merged:**
|
||||
|
||||
- Closes #<issue_number>
|
||||
- Fixes #<issue_number>
|
||||
- Resolves #<issue_number>
|
||||
|
||||
*Example: `Resolves #123`*
|
||||
|
||||
**If this PR is only related to an issue or is a partial fix, simply reference the issue number without a keyword:**
|
||||
|
||||
*Example: `This PR makes progress on #456` or `Related to #789`*
|
||||
-->
|
||||
- [ ] Updated relevant documentation and README (if needed)
|
||||
- [ ] Added/updated tests (if needed)
|
||||
- [ ] Noted breaking changes (if any)
|
||||
- [ ] Validated on required platforms/methods:
|
||||
- [ ] MacOS
|
||||
- [ ] npm run
|
||||
- [ ] npx
|
||||
- [ ] Docker
|
||||
- [ ] Podman
|
||||
- [ ] Seatbelt
|
||||
- [ ] Windows
|
||||
- [ ] npm run
|
||||
- [ ] npx
|
||||
- [ ] Docker
|
||||
- [ ] Linux
|
||||
- [ ] npm run
|
||||
- [ ] npx
|
||||
- [ ] Docker
|
||||
|
||||
@@ -90,9 +90,26 @@ jobs:
|
||||
- name: 'Run Prettier'
|
||||
run: 'node scripts/lint.js --prettier'
|
||||
|
||||
- name: 'Build docs prerequisites'
|
||||
run: 'npm run predocs:settings'
|
||||
|
||||
- name: 'Verify settings docs'
|
||||
run: 'npm run docs:settings -- --check'
|
||||
|
||||
- name: 'Run sensitive keyword linter'
|
||||
run: 'node scripts/lint.js --sensitive-keywords'
|
||||
|
||||
link_checker:
|
||||
name: 'Link Checker'
|
||||
runs-on: 'ubuntu-latest'
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
|
||||
- name: 'Link Checker'
|
||||
uses: 'lycheeverse/lychee-action@885c65f3dc543b57c898c8099f4e08c8afd178a2' # ratchet: lycheeverse/lychee-action@v2.6.1
|
||||
with:
|
||||
args: '--verbose --accept 200,503 ./**/*.md'
|
||||
fail: true
|
||||
test_linux:
|
||||
name: 'Test (Linux)'
|
||||
runs-on: 'gemini-cli-ubuntu-16-core'
|
||||
@@ -354,6 +371,7 @@ jobs:
|
||||
if: 'always()'
|
||||
needs:
|
||||
- 'lint'
|
||||
- 'link_checker'
|
||||
- 'test_linux'
|
||||
- 'test_mac'
|
||||
- 'codeql'
|
||||
@@ -363,6 +381,7 @@ jobs:
|
||||
- name: 'Check all job results'
|
||||
run: |
|
||||
if [[ (${{ needs.lint.result }} != 'success' && ${{ needs.lint.result }} != 'skipped') || \
|
||||
(${{ needs.link_checker.result }} != 'success' && ${{ needs.link_checker.result }} != 'skipped') || \
|
||||
(${{ needs.test_linux.result }} != 'success' && ${{ needs.test_linux.result }} != 'skipped') || \
|
||||
(${{ needs.test_mac.result }} != 'success' && ${{ needs.test_mac.result }} != 'skipped') || \
|
||||
(${{ needs.codeql.result }} != 'success' && ${{ needs.codeql.result }} != 'skipped') || \
|
||||
|
||||
@@ -24,13 +24,147 @@ concurrency:
|
||||
${{ github.ref != 'refs/heads/main' && !startsWith(github.ref, 'refs/heads/release/') }}
|
||||
|
||||
jobs:
|
||||
deflake:
|
||||
name: 'Deflake'
|
||||
deflake_e2e_linux:
|
||||
name: 'E2E Test (Linux) - ${{ matrix.sandbox }}'
|
||||
runs-on: 'gemini-cli-ubuntu-16-core'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
sandbox:
|
||||
- 'sandbox:none'
|
||||
- 'sandbox:docker'
|
||||
node-version:
|
||||
- '20.x'
|
||||
|
||||
steps:
|
||||
- name: 'Deflake'
|
||||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955' # ratchet:actions/checkout@v5
|
||||
with:
|
||||
ref: '${{ github.event.pull_request.head.sha }}'
|
||||
repository: '${{ github.repository }}'
|
||||
|
||||
- name: 'Set up Node.js ${{ matrix.node-version }}'
|
||||
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions-node@v4
|
||||
with:
|
||||
node-version: '${{ matrix.node-version }}'
|
||||
|
||||
- name: 'Install dependencies'
|
||||
run: 'npm ci'
|
||||
|
||||
- name: 'Build project'
|
||||
run: 'npm run build'
|
||||
|
||||
- name: 'Set up Docker'
|
||||
if: "matrix.sandbox == 'sandbox:docker'"
|
||||
uses: 'docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435' # ratchet:docker/setup-buildx-action@v3
|
||||
|
||||
- name: 'Run E2E tests'
|
||||
env:
|
||||
GEMINI_API_KEY: '${{ secrets.GEMINI_API_KEY }}'
|
||||
IS_DOCKER: "${{ matrix.sandbox == 'sandbox:docker' }}"
|
||||
KEEP_OUTPUT: 'true'
|
||||
RUNS: '${{ github.event.inputs.runs }}'
|
||||
TEST_NAME_PATTERN: '${{ github.event.inputs.test_name_pattern }}'
|
||||
VERBOSE: 'true'
|
||||
shell: 'bash'
|
||||
run: |
|
||||
ECHO 'DEFLAKE WORKFLOW'
|
||||
if [[ "${{ env.IS_DOCKER }}" == "true" ]]; then
|
||||
npm run deflake:test:integration:sandbox:docker -- --runs="${{ env.RUNS }}" -- --testNamePattern "'${{ env.TEST_NAME_PATTERN }}'"
|
||||
else
|
||||
npm run deflake:test:integration:sandbox:none -- --runs="${{ env.RUNS }}" -- --testNamePattern "'${{ env.TEST_NAME_PATTERN }}'"
|
||||
fi
|
||||
|
||||
deflake_e2e_mac:
|
||||
name: 'E2E Test (macOS)'
|
||||
runs-on: 'macos-latest'
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955' # ratchet:actions/checkout@v5
|
||||
with:
|
||||
ref: '${{ github.event.pull_request.head.sha }}'
|
||||
repository: '${{ github.repository }}'
|
||||
|
||||
- name: 'Set up Node.js 20.x'
|
||||
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions-node@v4
|
||||
with:
|
||||
node-version: '20.x'
|
||||
|
||||
- name: 'Install dependencies'
|
||||
run: 'npm ci'
|
||||
|
||||
- name: 'Build project'
|
||||
run: 'npm run build'
|
||||
|
||||
- name: 'Fix rollup optional dependencies on macOS'
|
||||
if: "runner.os == 'macOS'"
|
||||
run: |
|
||||
npm cache clean --force
|
||||
- name: 'Run E2E tests (non-Windows)'
|
||||
if: "runner.os != 'Windows'"
|
||||
env:
|
||||
GEMINI_API_KEY: '${{ secrets.GEMINI_API_KEY }}'
|
||||
KEEP_OUTPUT: 'true'
|
||||
RUNS: '${{ github.event.inputs.runs }}'
|
||||
SANDBOX: 'sandbox:none'
|
||||
TEST_NAME_PATTERN: '${{ github.event.inputs.test_name_pattern }}'
|
||||
VERBOSE: 'true'
|
||||
run: |
|
||||
npm run deflake:test:integration:sandbox:none -- --runs="${{ env.RUNS }}" -- --testNamePattern "'${{ env.TEST_NAME_PATTERN }}'"
|
||||
|
||||
deflake_e2e_windows:
|
||||
name: 'Slow E2E - Win'
|
||||
runs-on: 'gemini-cli-windows-16-core'
|
||||
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955' # ratchet:actions/checkout@v5
|
||||
with:
|
||||
ref: '${{ github.event.pull_request.head.sha }}'
|
||||
repository: '${{ github.repository }}'
|
||||
|
||||
- name: 'Set up Node.js 20.x'
|
||||
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions-node@v4
|
||||
with:
|
||||
node-version: '20.x'
|
||||
cache: 'npm'
|
||||
|
||||
- name: 'Configure Windows Defender exclusions'
|
||||
run: |
|
||||
Add-MpPreference -ExclusionPath $env:GITHUB_WORKSPACE -Force
|
||||
Add-MpPreference -ExclusionPath "$env:GITHUB_WORKSPACE\node_modules" -Force
|
||||
Add-MpPreference -ExclusionPath "$env:GITHUB_WORKSPACE\packages" -Force
|
||||
Add-MpPreference -ExclusionPath "$env:TEMP" -Force
|
||||
shell: 'pwsh'
|
||||
|
||||
- name: 'Configure npm for Windows performance'
|
||||
run: |
|
||||
npm config set progress false
|
||||
npm config set audit false
|
||||
npm config set fund false
|
||||
npm config set loglevel error
|
||||
npm config set maxsockets 32
|
||||
npm config set registry https://registry.npmjs.org/
|
||||
shell: 'pwsh'
|
||||
|
||||
- name: 'Install dependencies'
|
||||
run: 'npm ci'
|
||||
shell: 'pwsh'
|
||||
|
||||
- name: 'Build project'
|
||||
run: 'npm run build'
|
||||
shell: 'pwsh'
|
||||
|
||||
- name: 'Run E2E tests'
|
||||
env:
|
||||
GEMINI_API_KEY: '${{ secrets.GEMINI_API_KEY }}'
|
||||
KEEP_OUTPUT: 'true'
|
||||
SANDBOX: 'sandbox:none'
|
||||
VERBOSE: 'true'
|
||||
NODE_OPTIONS: '--max-old-space-size=32768 --max-semi-space-size=256'
|
||||
UV_THREADPOOL_SIZE: '32'
|
||||
NODE_ENV: 'test'
|
||||
RUNS: '${{ github.event.inputs.runs }}'
|
||||
TEST_NAME_PATTERN: '${{ github.event.inputs.test_name_pattern }}'
|
||||
shell: 'pwsh'
|
||||
run: |
|
||||
npm run deflake:test:integration:sandbox:none -- --runs="${{ env.RUNS }}" -- --testNamePattern "'${{ env.TEST_NAME_PATTERN }}'"
|
||||
|
||||
@@ -30,6 +30,7 @@ jobs:
|
||||
merge_queue_skipper:
|
||||
name: 'Merge Queue Skipper'
|
||||
runs-on: 'gemini-cli-ubuntu-16-core'
|
||||
permissions: 'read-all'
|
||||
outputs:
|
||||
skip: '${{ steps.merge-queue-e2e-skipper.outputs.skip-check }}'
|
||||
steps:
|
||||
@@ -37,12 +38,13 @@ jobs:
|
||||
uses: 'cariad-tech/merge-queue-ci-skipper@1032489e59437862c90a08a2c92809c903883772' # ratchet:cariad-tech/merge-queue-ci-skipper@main
|
||||
with:
|
||||
secret: '${{ secrets.GITHUB_TOKEN }}'
|
||||
continue-on-error: true
|
||||
|
||||
e2e_linux:
|
||||
name: 'E2E Test (Linux) - ${{ matrix.sandbox }}'
|
||||
needs: 'merge_queue_skipper'
|
||||
if: |
|
||||
needs.merge_queue_skipper.outputs.skip == 'false' &&
|
||||
(needs.merge_queue_skipper.result !='success' || needs.merge_queue_skipper.outputs.skip == 'false') &&
|
||||
(github.event_name == 'push' ||
|
||||
github.event_name == 'merge_group' ||
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
@@ -104,7 +106,7 @@ jobs:
|
||||
name: 'E2E Test (macOS)'
|
||||
needs: 'merge_queue_skipper'
|
||||
if: |
|
||||
needs.merge_queue_skipper.outputs.skip == 'false' &&
|
||||
(needs.merge_queue_skipper.result !='success' || needs.merge_queue_skipper.outputs.skip == 'false') &&
|
||||
(github.event_name == 'push' ||
|
||||
github.event_name == 'merge_group' ||
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
@@ -153,7 +155,7 @@ jobs:
|
||||
name: 'Slow E2E - Win'
|
||||
needs: 'merge_queue_skipper'
|
||||
if: |
|
||||
needs.merge_queue_skipper.outputs.skip == 'false' &&
|
||||
(needs.merge_queue_skipper.result !='success' || needs.merge_queue_skipper.outputs.skip == 'false') &&
|
||||
(github.event_name == 'push' ||
|
||||
github.event_name == 'merge_group' ||
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
|
||||
@@ -45,7 +45,9 @@ jobs:
|
||||
(github.event.comment.author_association == 'OWNER' || github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'COLLABORATOR')
|
||||
))
|
||||
)
|
||||
)
|
||||
) &&
|
||||
!contains(github.event.issue.labels.*.name, 'area/') &&
|
||||
!contains(github.event.issue.labels.*.name, 'priority/')
|
||||
timeout-minutes: 5
|
||||
runs-on: 'ubuntu-latest'
|
||||
steps:
|
||||
@@ -67,14 +69,24 @@ jobs:
|
||||
core.setOutput('labels', issue.labels.map(label => label.name).join(','));
|
||||
return issue;
|
||||
|
||||
- name: 'Check for triage label on manual trigger'
|
||||
- name: 'Manual Trigger Pre-flight Checks'
|
||||
if: |-
|
||||
github.event_name == 'workflow_dispatch' && !contains(steps.get_issue_data.outputs.labels, 'status/need-triage')
|
||||
github.event_name == 'workflow_dispatch'
|
||||
env:
|
||||
ISSUE_NUMBER_INPUT: '${{ github.event.inputs.issue_number }}'
|
||||
LABELS: '${{ steps.get_issue_data.outputs.labels }}'
|
||||
run: |
|
||||
echo "Issue #${ISSUE_NUMBER_INPUT} does not have the 'status/need-triage' label. Stopping workflow."
|
||||
exit 1
|
||||
if ! echo "${LABELS}" | grep -q 'status/need-triage'; then
|
||||
echo "Issue #${ISSUE_NUMBER_INPUT} does not have the 'status/need-triage' label. Stopping workflow."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if echo "${LABELS}" | grep -q 'area/' || echo "${LABELS}" | grep -q 'priority/'; then
|
||||
echo "Issue #${ISSUE_NUMBER_INPUT} already has 'area/' or 'priority/' labels. Stopping workflow."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Manual triage checks passed."
|
||||
|
||||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
|
||||
@@ -97,7 +109,22 @@ jobs:
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
});
|
||||
const labelNames = labels.map(label => label.name);
|
||||
const allowedLabels = [
|
||||
'priority/p0',
|
||||
'priority/p1',
|
||||
'priority/p2',
|
||||
'priority/p3',
|
||||
'priority/unknown',
|
||||
'area/agent',
|
||||
'area/enterprise',
|
||||
'area/non-interactive',
|
||||
'area/core',
|
||||
'area/security',
|
||||
'area/platform',
|
||||
'area/extensions',
|
||||
'area/unknown'
|
||||
];
|
||||
const labelNames = labels.map(label => label.name).filter(name => allowedLabels.includes(name));
|
||||
core.setOutput('available_labels', labelNames.join(','));
|
||||
core.info(`Found ${labelNames.length} labels: ${labelNames.join(', ')}`);
|
||||
return labelNames;
|
||||
@@ -126,9 +153,6 @@ jobs:
|
||||
settings: |-
|
||||
{
|
||||
"maxSessionTurns": 25,
|
||||
"coreTools": [
|
||||
"run_shell_command(echo)"
|
||||
],
|
||||
"telemetry": {
|
||||
"enabled": true,
|
||||
"target": "gcp"
|
||||
@@ -137,128 +161,134 @@ jobs:
|
||||
prompt: |-
|
||||
## Role
|
||||
|
||||
You are an issue triage assistant. Analyze the current GitHub issue
|
||||
and identify the most appropriate existing labels by only using the provided data. Use the available
|
||||
tools to gather information; do not ask for information to be
|
||||
provided. Do not remove the following labels titled maintainer, help wanted or good first issue.
|
||||
You are an issue triage assistant. Your role is to analyze a GitHub issue and determine the single most appropriate area/ label and the single most appropriate priority/ label based on the definitions provided.
|
||||
|
||||
## Steps
|
||||
|
||||
1. You are only able to use the echo command. Review the available labels in the environment variable: "${AVAILABLE_LABELS}".
|
||||
2. Review the issue title and body provided in the environment variables: "${ISSUE_TITLE}" and "${ISSUE_BODY}".
|
||||
3. Select the most relevant labels from the existing labels, focusing on kind/*, area/*, sub-area/* and priority/*. For area/* and kind/* limit yourself to only the single most applicable label in each case.
|
||||
4. If the issue already has area/ label, dont try to change it. Similarly, if the issue already has a kind/ label don't change it. And if the issue already has a priority/ label do not change it for example:
|
||||
If an issue has area/core and kind/bug you will only add a priority/ label.
|
||||
Instead if an issue has no labels, you will could add one lable of each kind.
|
||||
5. For each issue please check if CLI version is present, this is usually in the output of the /about command and will look like 0.1.5 for anything more than 6 versions older than the most recent should add the status/need-retesting label.
|
||||
6. If you see that the issue doesn't look like it has sufficient information recommend the status/need-information label and leave a comment politely requesting the relevant information, eg.. if repro steps are missing request for repro steps. if version information is missing request for version information into the explanation section below.
|
||||
7. Output the appropriate labels for this issue in JSON format with explanation, for example:
|
||||
```
|
||||
{"labels_to_set": ["kind/bug", "priority/p0"], "explanation": "This is a critical bug report affecting main functionality"}
|
||||
```
|
||||
8. If the issue cannot be classified using the available labels, output:
|
||||
```
|
||||
{"labels_to_set": [], "explanation": "Unable to classify this issue with available labels"}
|
||||
```
|
||||
9. Use Area definitions mentioned below to help you narrow down issues.
|
||||
10. If you think an issue might be a Priority/P0 do not apply the priority/p0 label. Instead apply a status/manual-triage label and include a note in your explanation.
|
||||
11. If you are uncertain and have not been able to apply one each of kind/, area/ and priority/ , apply the status/manual-triage label.
|
||||
1. Review the issue title and body: ${{ env.ISSUE_TITLE }} and ${{ env.ISSUE_BODY }}.
|
||||
2. Review the available labels: ${{ env.AVAILABLE_LABELS }}.
|
||||
3. Select exactly one area/ label that best matches the issue based on Reference 1: Area Definitions.
|
||||
4. Select exactly one priority/ label that best matches the issue based on Reference 2: Priority Definitions.
|
||||
5. Fallback Logic:
|
||||
- If you cannot confidently determine the correct area/ label from the definitions, you must use area/unknown.
|
||||
- If you cannot confidently determine the correct priority/ label from the definitions, you must use priority/unknown.
|
||||
6. Output your two selected labels in JSON format and nothing else. Example:
|
||||
{"labels_to_set": ["area/core", "priority/p1"]}
|
||||
|
||||
## Guidelines
|
||||
- Your output must contain exactly one area/ label and exactly one priority/ label.
|
||||
- Triage only the current issue based on its title and body.
|
||||
- Output only valid JSON format.
|
||||
- Do not include any explanation or additional text, just the JSON.
|
||||
|
||||
- Only use labels that already exist in the repository
|
||||
- Do not add comments or modify the issue content
|
||||
- Triage only the current issue
|
||||
- Identify only one area/ label
|
||||
- Identify only one kind/ label
|
||||
- Identify all applicable sub-area/* and priority/* labels based on the issue content. It's ok to have multiple of these
|
||||
- Once you categorize the issue if it needs information bump down the priority by 1 eg.. a p0 would become a p1 a p1 would become a p2. P2 and P3 can stay as is in this scenario
|
||||
- Reference all shell variables as "${VAR}" (with quotes and braces)
|
||||
- Output only valid JSON format
|
||||
- Do not include any explanation or additional text, just the JSON
|
||||
Reference 1: Area Definitions
|
||||
area/agent
|
||||
- Description: Issues related to the "brain" of the CLI. This includes the core agent logic, model quality, tool/function calling, and memory.
|
||||
- Example Issues:
|
||||
"I am not getting a reasonable or expected response."
|
||||
"The model is not calling the tool I expected."
|
||||
"The web search tool is not working as expected."
|
||||
"Feature request for a new built-in tool (e.g., read file, write file)."
|
||||
"The generated code is poor quality or incorrect."
|
||||
"The model seems stuck in a loop."
|
||||
"The response from the model is malformed (e.g., broken JSON, bad formatting)."
|
||||
"Concerns about unnecessary token consumption."
|
||||
"Issues with how memory or chat history is managed."
|
||||
"Issues with sub-agents."
|
||||
"Model is switching from one to another unexpectedly."
|
||||
|
||||
Categorization Guidelines:
|
||||
P0: Critical / Blocker
|
||||
- A P0 bug is a catastrophic failure that demands immediate attention.
|
||||
- To be a P0 it means almost all users are running into this issue and it is blocking users from being able to use the product.
|
||||
- You would see this in the form of many comments from different developers on the bug.
|
||||
- It represents a complete showstopper for a significant portion of users or for the development process itself.
|
||||
Impact:
|
||||
- Blocks development or testing for the entire team.
|
||||
- Major security vulnerability that could compromise user data or system integrity.
|
||||
- Causes data loss or corruption with no workaround.
|
||||
- Crashes the application or makes a core feature completely unusable for all or most users in a production environment. Will it cause severe quality degration? Is it preventing contributors from contributing to the repository or is it a release blocker?
|
||||
Qualifier: Is the main function of the software broken?
|
||||
Example: The gemini auth login command fails with an unrecoverable error, preventing any user from authenticating and using the rest of the CLI.
|
||||
P1: High
|
||||
- A P1 bug is a serious issue that significantly degrades the user experience or impacts a core feature.
|
||||
- While not a complete blocker, it's a major problem that needs a fast resolution. Feature requests are almost never P1.
|
||||
- Once again this would be affecting many users.
|
||||
- You would see this in the form of comments from different developers on the bug.
|
||||
Impact:
|
||||
- A core feature is broken or behaving incorrectly for a large number of users or large number of use cases.
|
||||
- Review the bug details and comments to try figure out if this issue affects a large set of use cases or if it's a narrow set of use cases.
|
||||
- Severe performance degradation making the application frustratingly slow.
|
||||
- No straightforward workaround exists, or the workaround is difficult and non-obvious.
|
||||
Qualifier: Is a key feature unusable or giving very wrong results?
|
||||
Example: Gemini CLI enters a loop when making read-many-files tool call. I am unable to break out of the loop and gemini doesn't follow instructions subsequently.
|
||||
P2: Medium
|
||||
- A P2 bug is a moderately impactful issue. It's a noticeable problem but doesn't prevent the use of the software's main functionality.
|
||||
Impact:
|
||||
- Affects a non-critical feature or a smaller, specific subset of users.
|
||||
- An inconvenient but functional workaround is available and easy to execute.
|
||||
- Noticeable UI/UX problems that don't break functionality but look unprofessional (e.g., elements are misaligned or overlapping).
|
||||
Qualifier: Is it an annoying but non-blocking problem?
|
||||
Example: An error message is unclear or contains a typo, causing user confusion but not halting their workflow.
|
||||
P3: Low
|
||||
- A P3 bug is a minor, low-impact issue that is trivial or cosmetic. It has little to no effect on the overall functionality of the application.
|
||||
Impact:
|
||||
- Minor cosmetic issues like color inconsistencies, typos in documentation, or slight alignment problems on a non-critical page.
|
||||
- An edge-case bug that is very difficult to reproduce and affects a tiny fraction of users.
|
||||
Qualifier: Is it a "nice-to-fix" issue?
|
||||
Example: Spelling mistakes etc.
|
||||
Things you should know:
|
||||
- If users are talking about issues where the model gets downgraded from pro to flash then i want you to categorize that as a performance issue
|
||||
- This product is designed to use different models eg.. using pro, downgrading to flash etc. when users report that they dont expect the model to change those would be categorized as feature requests.
|
||||
Definition of Areas
|
||||
area/ux:
|
||||
- Issues concerning user-facing elements like command usability, interactive features, help docs, and perceived performance.
|
||||
- I am seeing my screen flicker when using Gemini CLI
|
||||
- I am seeing the output malformed
|
||||
- Theme changes aren't taking effect
|
||||
- My keyboard inputs arent' being recognzied
|
||||
area/platform:
|
||||
- Issues related to installation, packaging, OS compatibility (Windows, macOS, Linux), and the underlying CLI framework.
|
||||
area/background: Issues related to long-running background tasks, daemons, and autonomous or proactive agent features.
|
||||
area/models:
|
||||
- i am not getting a response that is reasonable or expected. this can include things like
|
||||
- I am calling a tool and the tool is not performing as expected.
|
||||
- i am expecting a tool to be called and it is not getting called ,
|
||||
- Including experience when using
|
||||
- built-in tools (e.g., web search, code interpreter, read file, writefile, etc..),
|
||||
- Function calling issues should be under this area
|
||||
- i am getting responses from the model that are malformed.
|
||||
- Issues concerning Gemini quality of response and inference,
|
||||
- Issues talking about unnecessary token consumption.
|
||||
- Issues talking about Model getting stuck in a loop be watchful as this could be the root cause for issues that otherwise seem like model performance issues.
|
||||
- Memory compression
|
||||
- unexpected responses,
|
||||
- poor quality of generated code
|
||||
area/tools:
|
||||
- These are primarily issues related to Model Context Protocol
|
||||
- These are issues that mention MCP support
|
||||
- feature requests asking for support for new tools.
|
||||
area/core: Issues with fundamental components like command parsing, configuration management, session state, and the main API client logic. Introducing multi-modality
|
||||
area/contribution: Issues related to improving the developer contribution experience, such as CI/CD pipelines, build scripts, and test automation infrastructure.
|
||||
area/authentication: Issues related to user identity, login flows, API key handling, credential storage, and access token management, unable to sign in selecting wrong authentication path etc..
|
||||
area/security-privacy: Issues concerning vulnerability patching, dependency security, data sanitization, privacy controls, and preventing unauthorized data access.
|
||||
area/extensibility: Issues related to the plugin system, extension APIs, or making the CLI's functionality available in other applications, github actions, ide support etc..
|
||||
area/performance: Issues focused on model performance
|
||||
- Issues with running out of capacity,
|
||||
- 429 errors etc..
|
||||
- could also pertain to latency,
|
||||
- other general software performance like, memory usage, CPU consumption, and algorithmic efficiency.
|
||||
- Switching models from one to the other unexpectedly.
|
||||
area/enterprise
|
||||
- Description: Issues specific to enterprise-level features, including telemetry, policy, and licenses.
|
||||
- Example Issues:
|
||||
"Usage data is not appearing in our telemetry dashboard."
|
||||
"A user is able to perform an action that should be blocked by an admin policy."
|
||||
"Questions about billing, licensing tiers, or enterprise quotas."
|
||||
|
||||
area/non-interactive
|
||||
- Description: Issues related to using the CLI in automated or non-interactive environments (headless mode).
|
||||
- Example Issues:
|
||||
"Problems using the CLI as an SDK in another surface."
|
||||
"The CLI is behaving differently when run from a shell script vs. an interactive terminal."
|
||||
"GitHub action is failing."
|
||||
"I am having trouble running the CLI in headless mode"
|
||||
|
||||
area/core
|
||||
- Description: Issues with the fundamental CLI app itself. This includes the user interface (UI/UX), installation, OS compatibility, and performance.
|
||||
- Example Issues:
|
||||
"I am seeing my screen flicker when using the CLI."
|
||||
"The output in my terminal is malformed or unreadable."
|
||||
"Theme changes are not taking effect."
|
||||
"Keyboard inputs (e.g., arrow keys, Ctrl+C) are not being recognized."
|
||||
"The CLI failed to install or update."
|
||||
"An issue specific to running on Windows, macOS, or Linux."
|
||||
"Problems with command parsing, flags, or argument handling."
|
||||
"High CPU or memory usage by the CLI process."
|
||||
"Issues related to multi-modality (e.g., handling image inputs)."
|
||||
"Problems with the IDE integration connection or installation"
|
||||
|
||||
area/security
|
||||
- Description: Issues related to user authentication, authorization, data security, and privacy.
|
||||
- Example Issues:
|
||||
"I am unable to sign in."
|
||||
"The login flow is selecting the wrong authentication path"
|
||||
"Problems with API key handling or credential storage."
|
||||
"A report of a security vulnerability"
|
||||
"Concerns about data sanitization or potential data leaks."
|
||||
"Issues or requests related to privacy controls."
|
||||
"Preventing unauthorized data access."
|
||||
|
||||
area/platform
|
||||
- Description: Issues related to CI/CD, release management, testing, eval infrastructure, capacity, quota management, and sandbox environments.
|
||||
- Example Issues:
|
||||
"I am getting a 429 'Resource Exhausted' or 500-level server error."
|
||||
"General slowness or high latency from the service."
|
||||
"The build script is broken on the main branch."
|
||||
"Tests are failing in the CI/CD pipeline."
|
||||
"Issues with the release management or publishing process."
|
||||
"User is running out of capacity."
|
||||
"Problems specific to the sandbox or staging environments."
|
||||
"Questions about quota limits or requests for increases."
|
||||
|
||||
area/extensions
|
||||
- Description: Issues related to the extension ecosystem, including the marketplace and website.
|
||||
- Example Issues:
|
||||
"Bugs related to the extension marketplace website."
|
||||
"Issues with a specific extension."
|
||||
"Feature request for the extension ecosystem."
|
||||
|
||||
area/unknown
|
||||
- Description: Issues that do not clearly fit into any other defined area/ category, or where information is too limited to make a determination. Use this when no other area is appropriate.
|
||||
|
||||
Reference 2: Priority Definitions
|
||||
priority/p0: Critical / Blocker
|
||||
- Definition: A catastrophic failure that makes the CLI unusable for most users or poses a severe security risk. This includes installation failures, authentication failures, persistent crashes, or critical security vulnerabilities.
|
||||
- Key Questions:
|
||||
- Is the CLI failing to install or run?
|
||||
- Does it fail to authenticate or connect to the Gemini API, making all commands useless?
|
||||
- Is it consistently crashing on basic, common commands?
|
||||
- Does this represent a critical security vulnerability?
|
||||
|
||||
priority/p1: High
|
||||
- Definition: A severe issue where a core feature (e.g., text generation, code generation, file processing) is unusable, failing, has severe performance degradation, or providing fundamentally incorrect output formatting (e.g., truncated text, broken JSON). Affects many users, and there is no reasonable workaround.
|
||||
- Key Questions:
|
||||
- Is a core feature failing for a specific, large user group (e.g., all Windows users, all users of a specific shell)?
|
||||
- Is the CLI failing to process a supported input or misinterpreting critical flags?
|
||||
- Is the CLI's output formatting consistently broken, making the response unusable?
|
||||
- Is a core command or feature extremely slow, making it impractical to use?
|
||||
|
||||
priority/p2: Medium
|
||||
- Definition: A moderately impactful issue causing inconvenience or a non-optimal experience, but a reasonable workaround exists. This also includes failures in non-core features.
|
||||
- Key Questions:
|
||||
- Is a command or flag behaving incorrectly, but the user can achieve their goal via other means?
|
||||
- Is there a significant, non-blocking UI/UX problem in the terminal (e.g., broken progress indicators, bad terminal coloring)?
|
||||
|
||||
priority/p3: Low
|
||||
- Definition: A minor, low-impact issue with minimal effect on functionality. This includes most cosmetic defects, typos in documentation, or unclear help text.
|
||||
- Key Questions:
|
||||
- Is this a typo in the README.md, gemini --help text, or other documentation?
|
||||
- Is this a minor cosmetic issue (e.g., text alignment in output, an extra newline) that doesn't affect usability?
|
||||
|
||||
priority/unknown
|
||||
- Description: Issues that do not clearly fit into any other defined priority/ category, or where information is too limited to make a determination. Use this when no other priority is appropriate.
|
||||
|
||||
- name: 'Apply Labels to Issue'
|
||||
if: |-
|
||||
@@ -270,52 +300,61 @@ jobs:
|
||||
uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea'
|
||||
with:
|
||||
github-token: '${{ steps.generate_token.outputs.token }}'
|
||||
script: |-
|
||||
// Strip code block markers if present
|
||||
const rawLabels = process.env.LABELS_OUTPUT;
|
||||
core.info(`Raw labels JSON: ${rawLabels}`);
|
||||
script: |
|
||||
const rawOutput = process.env.LABELS_OUTPUT;
|
||||
core.info(`Raw output from model: ${rawOutput}`);
|
||||
let parsedLabels;
|
||||
try {
|
||||
const jsonMatch = rawLabels.match(/```json\s*([\s\S]*?)\s*```/);
|
||||
if (!jsonMatch || !jsonMatch[1]) {
|
||||
throw new Error("Could not find a ```json ... ``` block in the output.");
|
||||
// First, try to parse the raw output as JSON.
|
||||
parsedLabels = JSON.parse(rawOutput);
|
||||
} catch (jsonError) {
|
||||
// If that fails, check for a markdown code block.
|
||||
core.warning(`Direct JSON parsing failed: ${jsonError.message}. Trying to extract from a markdown block.`);
|
||||
const jsonMatch = rawOutput.match(/```json\s*([\s\S]*?)\s*```/);
|
||||
if (jsonMatch && jsonMatch[1]) {
|
||||
try {
|
||||
parsedLabels = JSON.parse(jsonMatch[1].trim());
|
||||
} catch (markdownError) {
|
||||
core.setFailed(`Failed to parse JSON even after extracting from markdown block: ${markdownError.message}\nRaw output: ${rawOutput}`);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
core.setFailed(`Output is not valid JSON and does not contain a JSON markdown block.\nRaw output: ${rawOutput}`);
|
||||
return;
|
||||
}
|
||||
const jsonString = jsonMatch[1].trim();
|
||||
parsedLabels = JSON.parse(jsonString);
|
||||
core.info(`Parsed labels JSON: ${JSON.stringify(parsedLabels)}`);
|
||||
} catch (err) {
|
||||
core.setFailed(`Failed to parse labels JSON from Gemini output: ${err.message}\nRaw output: ${rawLabels}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const issueNumber = parseInt(process.env.ISSUE_NUMBER);
|
||||
const explanation = parsedLabels.explanation || '';
|
||||
const labelsToSet = parsedLabels.labels_to_set || [];
|
||||
labelsToSet.push('status/bot-triaged');
|
||||
const labelsToAdd = parsedLabels.labels_to_set || [];
|
||||
|
||||
// Set labels based on triage result
|
||||
if (labelsToSet.length > 0) {
|
||||
await github.rest.issues.setLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
labels: labelsToSet
|
||||
});
|
||||
const explanationInfo = explanation ? ` - ${explanation}` : '';
|
||||
core.info(`Successfully set labels for #${issueNumber}: ${labelsToSet.join(', ')}${explanationInfo}`);
|
||||
} else {
|
||||
// If no labels to set, leave the issue as is
|
||||
const explanationInfo = explanation ? ` - ${explanation}` : '';
|
||||
core.info(`No labels to set for #${issueNumber}, leaving as is${explanationInfo}`);
|
||||
if (labelsToAdd.length !== 2) {
|
||||
core.setFailed(`Expected exactly 2 labels (one area/ and one priority/), but got ${labelsToAdd.length}. Labels: ${labelsToAdd.join(', ')}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (explanation) {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
body: explanation,
|
||||
});
|
||||
// Set labels based on triage result
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
labels: labelsToAdd
|
||||
});
|
||||
core.info(`Successfully added labels for #${issueNumber}: ${labelsToAdd.join(', ')}`);
|
||||
|
||||
// Remove the 'status/need-triage' label
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
name: 'status/need-triage'
|
||||
});
|
||||
core.info(`Successfully removed 'status/need-triage' label.`);
|
||||
} catch (error) {
|
||||
// If the label doesn't exist, the API call will throw a 404. We can ignore this.
|
||||
if (error.status !== 404) {
|
||||
core.warning(`Failed to remove 'status/need-triage': ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
- name: 'Post Issue Analysis Failure Comment'
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
name: 'Links'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['main']
|
||||
pull_request:
|
||||
branches: ['main']
|
||||
repository_dispatch:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '00 18 * * *'
|
||||
|
||||
jobs:
|
||||
linkChecker:
|
||||
runs-on: 'ubuntu-latest'
|
||||
steps:
|
||||
- uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
|
||||
|
||||
- name: 'Link Checker'
|
||||
id: 'lychee'
|
||||
uses: 'lycheeverse/lychee-action@885c65f3dc543b57c898c8099f4e08c8afd178a2' # ratchet: lycheeverse/lychee-action@v2.6.1
|
||||
with:
|
||||
args: '--verbose --no-progress --accept 200,503 ./**/*.md'
|
||||
@@ -2,7 +2,7 @@ name: 'Release: Nightly'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 20 * * *'
|
||||
- cron: '0 0 * * *'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
dry_run:
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
name: 'Release: Patch (1) Create PR'
|
||||
|
||||
run-name: >-
|
||||
Release Patch (1) Create PR | S:${{ inputs.channel }} | C:${{ inputs.commit }} ${{ inputs.original_pr && format('| PR:#{0}', inputs.original_pr) || '' }}
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
@@ -96,12 +99,12 @@ jobs:
|
||||
--channel="${PATCH_CHANNEL}" \
|
||||
--pullRequestNumber="${ORIGINAL_PR}" \
|
||||
--dry-run="${DRY_RUN}"
|
||||
echo "EXIT_CODE=$?" >> "$GITHUB_OUTPUT"
|
||||
} 2>&1 | tee >(
|
||||
echo "LOG_CONTENT<<EOF" >> "$GITHUB_ENV"
|
||||
cat >> "$GITHUB_ENV"
|
||||
echo "EOF" >> "$GITHUB_ENV"
|
||||
)
|
||||
echo "EXIT_CODE=${PIPESTATUS[0]}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: 'Comment on Original PR'
|
||||
if: 'always() && inputs.original_pr'
|
||||
@@ -115,6 +118,7 @@ jobs:
|
||||
GITHUB_RUN_ID: '${{ github.run_id }}'
|
||||
LOG_CONTENT: '${{ env.LOG_CONTENT }}'
|
||||
TARGET_REF: '${{ github.event.inputs.ref }}'
|
||||
ENVIRONMENT: '${{ github.event.inputs.environment }}'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
git checkout "${TARGET_REF}"
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
name: 'Release: Patch (2) Trigger'
|
||||
|
||||
run-name: >-
|
||||
Release Patch (2) Trigger |
|
||||
${{ github.event.pull_request.number && format('PR #{0}', github.event.pull_request.number) || 'Manual' }} |
|
||||
${{ github.event.pull_request.head.ref || github.event.inputs.ref }}
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
name: 'Release: Patch (3) Release'
|
||||
|
||||
run-name: >-
|
||||
Release Patch (3) Release | T:${{ inputs.type }} | R:${{ inputs.release_ref }} ${{ inputs.original_pr && format('| PR:#{0}', inputs.original_pr) || '' }}
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
|
||||
@@ -92,6 +92,9 @@ jobs:
|
||||
fi
|
||||
NIGHTLY_COMMAND="node scripts/get-release-version.js --type=promote-nightly"
|
||||
STABLE_JSON=$(${STABLE_COMMAND})
|
||||
STABLE_VERSION=$(echo "${STABLE_JSON}" | jq -r .releaseVersion)
|
||||
PREVIEW_COMMAND+=" --stable-base-version=${STABLE_VERSION}"
|
||||
NIGHTLY_COMMAND+=" --stable-base-version=${STABLE_VERSION}"
|
||||
PREVIEW_JSON=$(${PREVIEW_COMMAND})
|
||||
NIGHTLY_JSON=$(${NIGHTLY_COMMAND})
|
||||
echo "STABLE_JSON_COMMAND=${STABLE_COMMAND}"
|
||||
@@ -100,14 +103,14 @@ jobs:
|
||||
echo "STABLE_JSON: ${STABLE_JSON}"
|
||||
echo "PREVIEW_JSON: ${PREVIEW_JSON}"
|
||||
echo "NIGHTLY_JSON: ${NIGHTLY_JSON}"
|
||||
echo "STABLE_VERSION=$(echo "${STABLE_JSON}" | jq -r .releaseVersion)" >> "${GITHUB_OUTPUT}"
|
||||
echo "STABLE_VERSION=${STABLE_VERSION}" >> "${GITHUB_OUTPUT}"
|
||||
# shellcheck disable=SC1083
|
||||
echo "STABLE_SHA=$(git rev-parse "$(echo "${PREVIEW_JSON}" | jq -r .previousReleaseTag)"^{commit})" >> "${GITHUB_OUTPUT}"
|
||||
echo "PREVIOUS_STABLE_TAG=$(echo "${STABLE_JSON}" | jq -r .previousReleaseTag)" >> "${GITHUB_OUTPUT}"
|
||||
echo "PREVIEW_VERSION=$(echo "${PREVIEW_JSON}" | jq -r .releaseVersion)" >> "${GITHUB_OUTPUT}"
|
||||
# shellcheck disable=SC1083
|
||||
REF="${REF_INPUT}"
|
||||
SHA=$(git ls-remote origin "$REF" | awk '{print $1}')
|
||||
SHA=$(git ls-remote origin "$REF" | awk -v ref="$REF" '$2 == "refs/heads/"ref || $2 == "refs/tags/"ref || $2 == ref {print $1}' | head -n 1)
|
||||
if [ -z "$SHA" ]; then
|
||||
if [[ "$REF" =~ ^[0-9a-f]{7,40}$ ]]; then
|
||||
SHA="$REF"
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
name: 'Test Chained E2E'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
merge_group:
|
||||
workflow_run:
|
||||
workflows: ['Trigger E2E']
|
||||
types: ['completed']
|
||||
@@ -14,13 +18,18 @@ on:
|
||||
required: true
|
||||
|
||||
concurrency:
|
||||
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||
group: '${{ github.workflow }}-${{ github.head_ref || github.event.workflow_run.head_branch || github.ref }}'
|
||||
cancel-in-progress: |-
|
||||
${{ github.ref != 'refs/heads/main' && !startsWith(github.ref, 'refs/heads/release/') }}
|
||||
${{ github.event_name != 'push' && github.event_name != 'merge_group' }}
|
||||
|
||||
permissions:
|
||||
contents: 'read'
|
||||
statuses: 'write'
|
||||
|
||||
jobs:
|
||||
merge_queue_skipper:
|
||||
name: 'Merge Queue Skipper'
|
||||
permissions: 'read-all'
|
||||
runs-on: 'gemini-cli-ubuntu-16-core'
|
||||
outputs:
|
||||
skip: '${{ steps.merge-queue-e2e-skipper.outputs.skip-check }}'
|
||||
@@ -29,14 +38,16 @@ jobs:
|
||||
uses: 'cariad-tech/merge-queue-ci-skipper@1032489e59437862c90a08a2c92809c903883772' # ratchet:cariad-tech/merge-queue-ci-skipper@main
|
||||
with:
|
||||
secret: '${{ secrets.GITHUB_TOKEN }}'
|
||||
continue-on-error: true
|
||||
|
||||
download_repo_name:
|
||||
runs-on: 'gemini-cli-ubuntu-16-core'
|
||||
if: "github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_run'"
|
||||
outputs:
|
||||
repo_name: '${{ steps.output-repo-name.outputs.repo_name }}'
|
||||
steps:
|
||||
- name: 'Mock Repo Artifact'
|
||||
if: "${{ github.event_name != 'workflow_run' }}"
|
||||
if: "${{ github.event_name == 'workflow_dispatch' }}"
|
||||
env:
|
||||
REPO_NAME: '${{ github.event.inputs.repo_name }}'
|
||||
run: |
|
||||
@@ -67,10 +78,50 @@ jobs:
|
||||
const repo_name = String(fs.readFileSync(path.join(temp, 'repo_name')));
|
||||
core.setOutput('repo_name', repo_name);
|
||||
|
||||
parse_run_context:
|
||||
name: 'Parse run context'
|
||||
runs-on: 'gemini-cli-ubuntu-16-core'
|
||||
needs: 'download_repo_name'
|
||||
if: 'always()'
|
||||
outputs:
|
||||
repository: '${{ steps.set_context.outputs.REPO }}'
|
||||
sha: '${{ steps.set_context.outputs.SHA }}'
|
||||
steps:
|
||||
- id: 'set_context'
|
||||
name: 'Set dynamic repository and SHA'
|
||||
env:
|
||||
REPO: '${{ needs.download_repo_name.outputs.repo_name || github.repository }}'
|
||||
SHA: '${{ github.event.inputs.head_sha || github.event.workflow_run.head_sha || github.sha }}'
|
||||
shell: 'bash'
|
||||
run: |
|
||||
echo "REPO=$REPO" >> "$GITHUB_OUTPUT"
|
||||
echo "SHA=$SHA" >> "$GITHUB_OUTPUT"
|
||||
|
||||
set_pending_status:
|
||||
runs-on: 'gemini-cli-ubuntu-16-core'
|
||||
permissions: 'write-all'
|
||||
if: "github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_run'"
|
||||
needs:
|
||||
- 'parse_run_context'
|
||||
steps:
|
||||
- name: 'Set pending status'
|
||||
uses: 'myrotvorets/set-commit-status-action@16037e056d73b2d3c88e37e393ff369047f70886' # ratchet:myrotvorets/set-commit-status-action@master
|
||||
if: 'always()'
|
||||
with:
|
||||
allowForks: 'true'
|
||||
repo: '${{ needs.parse_run_context.outputs.repository }}'
|
||||
sha: '${{ needs.parse_run_context.outputs.sha }}'
|
||||
token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
status: 'pending'
|
||||
|
||||
e2e_linux:
|
||||
name: 'E2E Test (Linux) - ${{ matrix.sandbox }}'
|
||||
needs: 'download_repo_name'
|
||||
needs:
|
||||
- 'merge_queue_skipper'
|
||||
- 'parse_run_context'
|
||||
runs-on: 'gemini-cli-ubuntu-16-core'
|
||||
if: |
|
||||
always() && (needs.merge_queue_skipper.result !='success' || needs.merge_queue_skipper.outputs.skip == 'false')
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -84,8 +135,8 @@ jobs:
|
||||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955' # ratchet:actions/checkout@v5
|
||||
with:
|
||||
ref: '${{ github.event.inputs.head_sha || github.event.workflow_run.head_sha }}'
|
||||
repository: '${{ needs.download_repo_name.outputs.repo_name }}'
|
||||
ref: '${{ needs.parse_run_context.outputs.sha }}'
|
||||
repository: '${{ needs.parse_run_context.outputs.repository }}'
|
||||
|
||||
- name: 'Set up Node.js ${{ matrix.node-version }}'
|
||||
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions-node@v4
|
||||
@@ -117,14 +168,18 @@ jobs:
|
||||
|
||||
e2e_mac:
|
||||
name: 'E2E Test (macOS)'
|
||||
needs: 'download_repo_name'
|
||||
needs:
|
||||
- 'merge_queue_skipper'
|
||||
- 'parse_run_context'
|
||||
runs-on: 'macos-latest'
|
||||
if: |
|
||||
always() && (needs.merge_queue_skipper.result !='success' || needs.merge_queue_skipper.outputs.skip == 'false')
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955' # ratchet:actions/checkout@v5
|
||||
with:
|
||||
ref: '${{ github.event.inputs.head_sha || github.event.workflow_run.head_sha }}'
|
||||
repository: '${{ needs.download_repo_name.outputs.repo_name }}'
|
||||
ref: '${{ needs.parse_run_context.outputs.sha }}'
|
||||
repository: '${{ needs.parse_run_context.outputs.repository }}'
|
||||
|
||||
- name: 'Set up Node.js 20.x'
|
||||
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions-node@v4
|
||||
@@ -154,9 +209,9 @@ jobs:
|
||||
name: 'Slow E2E - Win'
|
||||
needs:
|
||||
- 'merge_queue_skipper'
|
||||
- 'download_repo_name'
|
||||
- 'parse_run_context'
|
||||
if: |
|
||||
needs.merge_queue_skipper.outputs.skip == 'false'
|
||||
always() && (needs.merge_queue_skipper.result !='success' || needs.merge_queue_skipper.outputs.skip == 'false')
|
||||
runs-on: 'gemini-cli-windows-16-core'
|
||||
continue-on-error: true
|
||||
|
||||
@@ -164,8 +219,8 @@ jobs:
|
||||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955' # ratchet:actions/checkout@v5
|
||||
with:
|
||||
ref: '${{ github.event.inputs.head_sha || github.event.workflow_run.head_sha }}'
|
||||
repository: '${{ needs.download_repo_name.outputs.repo_name }}'
|
||||
ref: '${{ needs.parse_run_context.outputs.sha }}'
|
||||
repository: '${{ needs.parse_run_context.outputs.repository }}'
|
||||
|
||||
- name: 'Set up Node.js 20.x'
|
||||
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions-node@v4
|
||||
@@ -214,7 +269,7 @@ jobs:
|
||||
e2e:
|
||||
name: 'E2E'
|
||||
if: |
|
||||
always() && needs.merge_queue_skipper.outputs.skip == 'false'
|
||||
always() && (needs.merge_queue_skipper.result !='success' || needs.merge_queue_skipper.outputs.skip == 'false')
|
||||
needs:
|
||||
- 'e2e_linux'
|
||||
- 'e2e_mac'
|
||||
@@ -229,3 +284,21 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
echo "All required E2E jobs passed!"
|
||||
|
||||
set_workflow_status:
|
||||
runs-on: 'gemini-cli-ubuntu-16-core'
|
||||
permissions: 'write-all'
|
||||
if: "github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_run'"
|
||||
needs:
|
||||
- 'parse_run_context'
|
||||
- 'e2e'
|
||||
steps:
|
||||
- name: 'Set workflow status'
|
||||
uses: 'myrotvorets/set-commit-status-action@16037e056d73b2d3c88e37e393ff369047f70886' # ratchet:myrotvorets/set-commit-status-action@master
|
||||
if: 'always()'
|
||||
with:
|
||||
allowForks: 'true'
|
||||
repo: '${{ needs.parse_run_context.outputs.repository }}'
|
||||
sha: '${{ needs.parse_run_context.outputs.sha }}'
|
||||
token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
status: '${{ job.status }}'
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
steps:
|
||||
- name: 'Save Repo name'
|
||||
env:
|
||||
# Replace with github.event.pull_request.base.repo.full_name when switched to listen on pull request events. This repo name does not contain the org which is needed for checkout.
|
||||
# Replace with github.event.pull_request.head.repo.full_name when switched to listen on pull request events. This repo name does not contain the org which is needed for checkout.
|
||||
REPO_NAME: '${{ github.event.repository.name }}'
|
||||
run: |
|
||||
mkdir -p ./pr
|
||||
|
||||
+2
-1
@@ -17,7 +17,6 @@
|
||||
# Dependency directory
|
||||
node_modules
|
||||
bower_components
|
||||
package-lock.json
|
||||
|
||||
# Editors
|
||||
.idea
|
||||
@@ -54,3 +53,5 @@ gha-creds-*.json
|
||||
|
||||
# Log files
|
||||
patch_output.log
|
||||
|
||||
.genkit
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
http://localhost:16686/
|
||||
https://github.com/google-gemini/gemini-cli/issues/new/choose
|
||||
https://github.com/google-gemini/maintainers-gemini-cli/blob/main/npm.md
|
||||
https://github.com/settings/personal-access-tokens/new
|
||||
https://github.com/settings/tokens/new
|
||||
https://www.npmjs.com/package/@google/gemini-cli
|
||||
+165
-36
@@ -1,6 +1,18 @@
|
||||
# How to Contribute
|
||||
|
||||
We would love to accept your patches and contributions to this project.
|
||||
We would love to accept your patches and contributions to this project. This
|
||||
document includes:
|
||||
|
||||
- **[Before you begin](#before-you-begin):** Essential steps to take before
|
||||
becoming a Gemini CLI contributor.
|
||||
- **[Code contribution process](#code-contribution-process):** How to contribute
|
||||
code to Gemini CLI.
|
||||
- **[Development setup and workflow](#development-setup-and-workflow):** How to
|
||||
set up your development environment and workflow.
|
||||
- **[Documentation contribution process](#documentation-contribution-process):**
|
||||
How to contribute documentation to Gemini CLI.
|
||||
|
||||
We're looking forward to seeing your contributions!
|
||||
|
||||
## Before you begin
|
||||
|
||||
@@ -23,19 +35,40 @@ sign a new one.
|
||||
This project follows
|
||||
[Google's Open Source Community Guidelines](https://opensource.google/conduct/).
|
||||
|
||||
## Contribution Process
|
||||
## Code contribution process
|
||||
|
||||
### Code Reviews
|
||||
### Get started
|
||||
|
||||
The process for contributing code is as follows:
|
||||
|
||||
1. **Find an issue** that you want to work on.
|
||||
2. **Fork the repository** and create a new branch.
|
||||
3. **Make your changes** in the `packages/` directory.
|
||||
4. **Ensure all checks pass** by running `npm run preflight`.
|
||||
5. **Open a pull request** with your changes.
|
||||
|
||||
### Code reviews
|
||||
|
||||
All submissions, including submissions by project members, require review. We
|
||||
use [GitHub pull requests](https://docs.github.com/articles/about-pull-requests)
|
||||
for this purpose.
|
||||
|
||||
### Self Assigning Issues
|
||||
If your pull request involves changes to `packages/cli` (the frontend), we
|
||||
recommend running our automated frontend review tool. **Note: This tool is
|
||||
currently experimental.** It helps detect common React anti-patterns, testing
|
||||
issues, and other frontend-specific best practices that are easy to miss.
|
||||
|
||||
If you're looking for an issue to work on, check out our list of issues that are
|
||||
labeled
|
||||
["help wanted"](https://github.com/google-gemini/gemini-cli/issues?q=is%3Aissue+state%3Aopen+label%3A%22help+wanted%22).
|
||||
To run the review tool, enter the following command from within Gemini CLI:
|
||||
|
||||
```text
|
||||
/review-frontend <PR_NUMBER>
|
||||
```
|
||||
|
||||
Replace `<PR_NUMBER>` with your pull request number. Authors are encouraged to
|
||||
run this on their own PRs for self-review, and reviewers should use it to
|
||||
augment their manual review process.
|
||||
|
||||
### Self assigning issues
|
||||
|
||||
To assign an issue to yourself, simply add a comment with the text `/assign`.
|
||||
The comment must contain only that text and nothing else. This command will
|
||||
@@ -44,12 +77,12 @@ assign the issue to you, provided it is not already assigned.
|
||||
Please note that you can have a maximum of 3 issues assigned to you at any given
|
||||
time.
|
||||
|
||||
### Pull Request Guidelines
|
||||
### Pull request guidelines
|
||||
|
||||
To help us review and merge your PRs quickly, please follow these guidelines.
|
||||
PRs that do not meet these standards may be closed.
|
||||
|
||||
#### 1. Link to an Existing Issue
|
||||
#### 1. Link to an existing issue
|
||||
|
||||
All PRs should be linked to an existing issue in our tracker. This ensures that
|
||||
every change has been discussed and is aligned with the project's goals before
|
||||
@@ -62,7 +95,7 @@ any code is written.
|
||||
If an issue for your change doesn't exist, please **open one first** and wait
|
||||
for feedback before you start coding.
|
||||
|
||||
#### 2. Keep It Small and Focused
|
||||
#### 2. Keep it small and focused
|
||||
|
||||
We favor small, atomic PRs that address a single issue or add a single,
|
||||
self-contained feature.
|
||||
@@ -74,37 +107,40 @@ self-contained feature.
|
||||
Large changes should be broken down into a series of smaller, logical PRs that
|
||||
can be reviewed and merged independently.
|
||||
|
||||
#### 3. Use Draft PRs for Work in Progress
|
||||
#### 3. Use draft PRs for work in progress
|
||||
|
||||
If you'd like to get early feedback on your work, please use GitHub's **Draft
|
||||
Pull Request** feature. This signals to the maintainers that the PR is not yet
|
||||
ready for a formal review but is open for discussion and initial feedback.
|
||||
|
||||
#### 4. Ensure All Checks Pass
|
||||
#### 4. Ensure all checks pass
|
||||
|
||||
Before submitting your PR, ensure that all automated checks are passing by
|
||||
running `npm run preflight`. This command runs all tests, linting, and other
|
||||
style checks.
|
||||
|
||||
#### 5. Update Documentation
|
||||
#### 5. Update documentation
|
||||
|
||||
If your PR introduces a user-facing change (e.g., a new command, a modified
|
||||
flag, or a change in behavior), you must also update the relevant documentation
|
||||
in the `/docs` directory.
|
||||
|
||||
#### 6. Write Clear Commit Messages and a Good PR Description
|
||||
See more about writing documentation:
|
||||
[Documentation contribution process](#documentation-contribution-process).
|
||||
|
||||
#### 6. Write clear commit messages and a good PR description
|
||||
|
||||
Your PR should have a clear, descriptive title and a detailed description of the
|
||||
changes. Follow the [Conventional Commits](https://www.conventionalcommits.org/)
|
||||
standard for your commit messages.
|
||||
|
||||
- **Good PR Title:** `feat(cli): Add --json flag to 'config get' command`
|
||||
- **Bad PR Title:** `Made some changes`
|
||||
- **Good PR title:** `feat(cli): Add --json flag to 'config get' command`
|
||||
- **Bad PR title:** `Made some changes`
|
||||
|
||||
In the PR description, explain the "why" behind your changes and link to the
|
||||
relevant issue (e.g., `Fixes #123`).
|
||||
|
||||
## Forking
|
||||
### Forking
|
||||
|
||||
If you are forking the repository you will be able to run the Build, Test and
|
||||
Integration test workflows. However in order to make the integration tests run
|
||||
@@ -118,12 +154,12 @@ Additionally you will need to click on the `Actions` tab and enable workflows
|
||||
for your repository, you'll find it's the large blue button in the center of the
|
||||
screen.
|
||||
|
||||
## Development Setup and Workflow
|
||||
### Development setup and workflow
|
||||
|
||||
This section guides contributors on how to build, modify, and understand the
|
||||
development setup of this project.
|
||||
|
||||
### Setting Up the Development Environment
|
||||
### Setting up the development environment
|
||||
|
||||
**Prerequisites:**
|
||||
|
||||
@@ -135,7 +171,7 @@ development setup of this project.
|
||||
version of Node.js `>=20` is acceptable.
|
||||
2. **Git**
|
||||
|
||||
### Build Process
|
||||
### Build process
|
||||
|
||||
To clone the repository:
|
||||
|
||||
@@ -160,7 +196,7 @@ This command typically compiles TypeScript to JavaScript, bundles assets, and
|
||||
prepares the packages for execution. Refer to `scripts/build.js` and
|
||||
`package.json` scripts for more details on what happens during the build.
|
||||
|
||||
### Enabling Sandboxing
|
||||
### Enabling sandboxing
|
||||
|
||||
[Sandboxing](#sandboxing) is highly recommended and requires, at a minimum,
|
||||
setting `GEMINI_SANDBOX=true` in your `~/.env` and ensuring a sandboxing
|
||||
@@ -176,7 +212,7 @@ npm run build:all
|
||||
|
||||
To skip building the sandbox container, you can use `npm run build` instead.
|
||||
|
||||
### Running
|
||||
### Running the CLI
|
||||
|
||||
To start the Gemini CLI from the source code (after building), run the following
|
||||
command from the root directory:
|
||||
@@ -190,11 +226,11 @@ utilize `npm link path/to/gemini-cli/packages/cli` (see:
|
||||
[docs](https://docs.npmjs.com/cli/v9/commands/npm-link)) or
|
||||
`alias gemini="node path/to/gemini-cli/packages/cli"` to run with `gemini`
|
||||
|
||||
### Running Tests
|
||||
### Running tests
|
||||
|
||||
This project contains two types of tests: unit tests and integration tests.
|
||||
|
||||
#### Unit Tests
|
||||
#### Unit tests
|
||||
|
||||
To execute the unit test suite for the project:
|
||||
|
||||
@@ -206,7 +242,7 @@ This will run tests located in the `packages/core` and `packages/cli`
|
||||
directories. Ensure tests pass before submitting any changes. For a more
|
||||
comprehensive check, it is recommended to run `npm run preflight`.
|
||||
|
||||
#### Integration Tests
|
||||
#### Integration tests
|
||||
|
||||
The integration tests are designed to validate the end-to-end functionality of
|
||||
the Gemini CLI. They are not run as part of the default `npm run test` command.
|
||||
@@ -218,9 +254,9 @@ npm run test:e2e
|
||||
```
|
||||
|
||||
For more detailed information on the integration testing framework, please see
|
||||
the [Integration Tests documentation](./docs/integration-tests.md).
|
||||
the [Integration Tests documentation](/docs/integration-tests.md).
|
||||
|
||||
### Linting and Preflight Checks
|
||||
### Linting and preflight checks
|
||||
|
||||
To ensure code quality and formatting consistency, run the preflight check:
|
||||
|
||||
@@ -267,7 +303,7 @@ root directory:
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### Coding Conventions
|
||||
### Coding conventions
|
||||
|
||||
- Please adhere to the coding style, patterns, and conventions used throughout
|
||||
the existing codebase.
|
||||
@@ -279,19 +315,24 @@ npm run lint
|
||||
- **Imports:** Pay special attention to import paths. The project uses ESLint to
|
||||
enforce restrictions on relative imports between packages.
|
||||
|
||||
### Project Structure
|
||||
### Project structure
|
||||
|
||||
- `packages/`: Contains the individual sub-packages of the project.
|
||||
- `a2a-server`: A2A server implementation for the Gemini CLI. (Experimental)
|
||||
- `cli/`: The command-line interface.
|
||||
- `core/`: The core backend logic for the Gemini CLI.
|
||||
- `test-utils` Utilities for creating and cleaning temporary file systems for
|
||||
testing.
|
||||
- `vscode-ide-companion/`: The Gemini CLI Companion extension pairs with
|
||||
Gemini CLI.
|
||||
- `docs/`: Contains all project documentation.
|
||||
- `scripts/`: Utility scripts for building, testing, and development tasks.
|
||||
|
||||
For more detailed architecture, see `docs/architecture.md`.
|
||||
|
||||
## Debugging
|
||||
### Debugging
|
||||
|
||||
### VS Code:
|
||||
#### VS Code
|
||||
|
||||
0. Run the CLI to interactively debug in VS Code with `F5`
|
||||
1. Start the CLI in debug mode from the root directory:
|
||||
@@ -349,9 +390,9 @@ used for the CLI's interface, is compatible with React DevTools version 4.x.
|
||||
Your running CLI application should then connect to React DevTools.
|
||||

|
||||
|
||||
## Sandboxing
|
||||
### Sandboxing
|
||||
|
||||
### macOS Seatbelt
|
||||
#### macOS Seatbelt
|
||||
|
||||
On macOS, `gemini` uses Seatbelt (`sandbox-exec`) under a `permissive-open`
|
||||
profile (see `packages/cli/src/utils/sandbox-macos-permissive-open.sb`) that
|
||||
@@ -367,7 +408,7 @@ Available built-in profiles are `{permissive,restrictive}-{open,closed,proxied}`
|
||||
`.gemini/sandbox-macos-<profile>.sb` under your project settings directory
|
||||
`.gemini`.
|
||||
|
||||
### Container-based Sandboxing (All Platforms)
|
||||
#### Container-based sandboxing (all platforms)
|
||||
|
||||
For stronger container-based sandboxing on macOS or other platforms, you can set
|
||||
`GEMINI_SANDBOX=true|docker|podman|<command>` in your environment or `.env`
|
||||
@@ -390,7 +431,7 @@ for your projects by creating the files `.gemini/sandbox.Dockerfile` and/or
|
||||
running `gemini` with `BUILD_SANDBOX=1` to trigger building of your custom
|
||||
sandbox.
|
||||
|
||||
#### Proxied Networking
|
||||
#### Proxied networking
|
||||
|
||||
All sandboxing methods, including macOS Seatbelt using `*-proxied` profiles,
|
||||
support restricting outbound network traffic through a custom proxy server that
|
||||
@@ -401,7 +442,7 @@ connections to `example.com:443` (e.g. `curl https://example.com`) and declines
|
||||
all other requests. The proxy is started and stopped automatically alongside the
|
||||
sandbox.
|
||||
|
||||
## Manual Publish
|
||||
### Manual publish
|
||||
|
||||
We publish an artifact for each commit to our internal registry. But if you need
|
||||
to manually cut a local build, then run the following commands:
|
||||
@@ -413,3 +454,91 @@ npm run auth
|
||||
npm run prerelease:dev
|
||||
npm publish --workspaces
|
||||
```
|
||||
|
||||
## Documentation contribution process
|
||||
|
||||
Our documentation must be kept up-to-date with our code contributions. We want
|
||||
our documentation to be clear, concise, and helpful to our users. We value:
|
||||
|
||||
- **Clarity:** Use simple and direct language. Avoid jargon where possible.
|
||||
- **Accuracy:** Ensure all information is correct and up-to-date.
|
||||
- **Completeness:** Cover all aspects of a feature or topic.
|
||||
- **Examples:** Provide practical examples to help users understand how to use
|
||||
Gemini CLI.
|
||||
|
||||
### Getting started
|
||||
|
||||
The process for contributing to the documentation is similar to contributing
|
||||
code.
|
||||
|
||||
1. **Fork the repository** and create a new branch.
|
||||
2. **Make your changes** in the `/docs` directory.
|
||||
3. **Preview your changes locally** in Markdown rendering.
|
||||
4. **Lint and format your changes.** Our preflight check includes linting and
|
||||
formatting for documentation files.
|
||||
```bash
|
||||
npm run preflight
|
||||
```
|
||||
5. **Open a pull request** with your changes.
|
||||
|
||||
### Documentation structure
|
||||
|
||||
Our documentation is organized using [sidebar.json](/docs/sidebar.json) as the
|
||||
table of contents. When adding new documentation:
|
||||
|
||||
1. Create your markdown file **in the appropriate directory** under `/docs`.
|
||||
2. Add an entry to `sidebar.json` in the relevant section.
|
||||
3. Ensure all internal links use relative paths and point to existing files.
|
||||
|
||||
### Style guide
|
||||
|
||||
We follow the
|
||||
[Google Developer Documentation Style Guide](https://developers.google.com/style).
|
||||
Please refer to it for guidance on writing style, tone, and formatting.
|
||||
|
||||
#### Key style points
|
||||
|
||||
- Use sentence case for headings.
|
||||
- Write in second person ("you") when addressing the reader.
|
||||
- Use present tense.
|
||||
- Keep paragraphs short and focused.
|
||||
- Use code blocks with appropriate language tags for syntax highlighting.
|
||||
- Include practical examples whenever possible.
|
||||
|
||||
### Linting and formatting
|
||||
|
||||
We use `prettier` to enforce a consistent style across our documentation. The
|
||||
`npm run preflight` command will check for any linting issues.
|
||||
|
||||
You can also run the linter and formatter separately:
|
||||
|
||||
- `npm run lint` - Check for linting issues
|
||||
- `npm run format` - Auto-format markdown files
|
||||
- `npm run lint:fix` - Auto-fix linting issues where possible
|
||||
|
||||
Please make sure your contributions are free of linting errors before submitting
|
||||
a pull request.
|
||||
|
||||
### Before you submit
|
||||
|
||||
Before submitting your documentation pull request, please:
|
||||
|
||||
1. Run `npm run preflight` to ensure all checks pass.
|
||||
2. Review your changes for clarity and accuracy.
|
||||
3. Check that all links work correctly.
|
||||
4. Ensure any code examples are tested and functional.
|
||||
5. Sign the
|
||||
[Contributor License Agreement (CLA)](https://cla.developers.google.com/) if
|
||||
you haven't already.
|
||||
|
||||
### Need help?
|
||||
|
||||
If you have questions about contributing documentation:
|
||||
|
||||
- Check our [FAQ](/docs/faq.md).
|
||||
- Review existing documentation for examples.
|
||||
- Open [an issue](https://github.com/google-gemini/gemini-cli/issues) to discuss
|
||||
your proposed changes.
|
||||
- Reach out to the maintainers.
|
||||
|
||||
We appreciate your contributions to making Gemini CLI documentation better!
|
||||
|
||||
@@ -187,7 +187,7 @@
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
Copyright 2025 Google LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
[](https://github.com/google-gemini/gemini-cli/actions/workflows/e2e.yml)
|
||||
[](https://www.npmjs.com/package/@google/gemini-cli)
|
||||
[](https://github.com/google-gemini/gemini-cli/blob/main/LICENSE)
|
||||
[](https://codewiki.google/github.com/google-gemini/gemini-cli)
|
||||
|
||||

|
||||
|
||||
@@ -27,6 +28,11 @@ Learn all about Gemini CLI in our [documentation](https://geminicli.com/docs/).
|
||||
|
||||
## 📦 Installation
|
||||
|
||||
### Pre-requisites before installation
|
||||
|
||||
- Node.js version 20 or higher
|
||||
- macOS, Linux, or Windows
|
||||
|
||||
### Quick Install
|
||||
|
||||
#### Run instantly with npx
|
||||
@@ -48,11 +54,6 @@ npm install -g @google/gemini-cli
|
||||
brew install gemini-cli
|
||||
```
|
||||
|
||||
#### System Requirements
|
||||
|
||||
- Node.js version 20 or higher
|
||||
- macOS, Linux, or Windows
|
||||
|
||||
## Release Cadence and Tags
|
||||
|
||||
See [Releases](./docs/releases.md) for more details.
|
||||
@@ -306,6 +307,8 @@ gemini
|
||||
corporate environment.
|
||||
- [**Telemetry & Monitoring**](./docs/cli/telemetry.md) - Usage tracking.
|
||||
- [**Tools API Development**](./docs/core/tools-api.md) - Create custom tools.
|
||||
- [**Local development**](./docs/local-development.md) - Local development
|
||||
tooling.
|
||||
|
||||
### Troubleshooting & Support
|
||||
|
||||
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../CONTRIBUTING.md
|
||||
@@ -3,6 +3,175 @@
|
||||
Wondering what's new in Gemini CLI? This document provides key highlights and
|
||||
notable changes to Gemini CLI.
|
||||
|
||||
## v0.15.0 - Gemini CLI weekly update - 2025-11-03
|
||||
|
||||
- **🎉 Seamless scrollable UI & mouse support:** We’ve given Gemini CLI a major
|
||||
facelift to make your terminal experience smoother and much more polished. You
|
||||
now get a flicker-free display with sticky headers that keep important context
|
||||
visible and a stable input prompt that doesn't jump around. We even added
|
||||
mouse support so you can click right where you need to type!
|
||||
([gif](https://imgur.com/a/O6qc7bx),
|
||||
[@jacob314](https://github.com/jacob314)).
|
||||
- **Announcement:**
|
||||
[https://developers.googleblog.com/en/making-the-terminal-beautiful-one-pixel-at-a-time/](https://developers.googleblog.com/en/making-the-terminal-beautiful-one-pixel-at-a-time/)
|
||||
- **🎉 New partner extensions:**
|
||||
- **Arize:** Seamlessly instrument AI applications with Arize AX and grant
|
||||
direct access to Arize support:
|
||||
|
||||
`gemini extensions install https://github.com/Arize-ai/arize-tracing-assistant`
|
||||
|
||||
- **Chronosphere:** Retrieve logs, metrics, traces, events, and specific
|
||||
entities:
|
||||
|
||||
`gemini extensions install https://github.com/chronosphereio/chronosphere-mcp`
|
||||
|
||||
- **Transmit:** Comprehensive context, validation, and automated fixes for
|
||||
creating production-ready authentication and identity workflows:
|
||||
|
||||
`gemini extensions install https://github.com/TransmitSecurity/transmit-security-journey-builder`
|
||||
|
||||
- **Todo planning:** Complex questions now get broken down into todo lists that
|
||||
the model can manage and check off. ([gif](https://imgur.com/a/EGDfNlZ),
|
||||
[pr](https://github.com/google-gemini/gemini-cli/pull/12905) by
|
||||
[@anj-s](https://github.com/anj-s))
|
||||
- **Disable GitHub extensions:** Users can now prevent the installation and
|
||||
loading of extensions from GitHub.
|
||||
([pr](https://github.com/google-gemini/gemini-cli/pull/12838) by
|
||||
[@kevinjwang1](https://github.com/kevinjwang1)).
|
||||
- **Extensions restart:** Users can now explicitly restart extensions using the
|
||||
`/extensions restart` command.
|
||||
([pr](https://github.com/google-gemini/gemini-cli/pull/12739) by
|
||||
[@jakemac53](https://github.com/jakemac53)).
|
||||
- **Better Angular support:** Angular workflows should now be more seamless
|
||||
([pr](https://github.com/google-gemini/gemini-cli/pull/10252) by
|
||||
[@MarkTechson](https://github.com/MarkTechson)).
|
||||
- **Validate command:** Users can now check that local extensions are formatted
|
||||
correctly. ([pr](https://github.com/google-gemini/gemini-cli/pull/12186) by
|
||||
[@kevinjwang1](https://github.com/kevinjwang1)).
|
||||
|
||||
## v0.12.0 - Gemini CLI weekly update - 2025-10-27
|
||||
|
||||

|
||||
|
||||
- **🎉 New partner extensions:**
|
||||
- **🤗 Hugging Face extension:** Access the Hugging Face hub.
|
||||
([gif](https://drive.google.com/file/d/1LEzIuSH6_igFXq96_tWev11svBNyPJEB/view?usp=sharing&resourcekey=0-LtPTzR1woh-rxGtfPzjjfg))
|
||||
|
||||
`gemini extensions install https://github.com/huggingface/hf-mcp-server`
|
||||
|
||||
- **Monday.com extension**: Analyze your sprints, update your task boards,
|
||||
etc.
|
||||
([gif](https://drive.google.com/file/d/1cO0g6kY1odiBIrZTaqu5ZakaGZaZgpQv/view?usp=sharing&resourcekey=0-xEr67SIjXmAXRe1PKy7Jlw))
|
||||
|
||||
`gemini extensions install https://github.com/mondaycom/mcp`
|
||||
|
||||
- **Data Commons extension:** Query public datasets or ground responses on
|
||||
data from Data Commons
|
||||
([gif](https://drive.google.com/file/d/1cuj-B-vmUkeJnoBXrO_Y1CuqphYc6p-O/view?usp=sharing&resourcekey=0-0adXCXDQEd91ZZW63HbW-Q)).
|
||||
|
||||
`gemini extensions install https://github.com/gemini-cli-extensions/datacommons`
|
||||
|
||||
- **Model selection:** Choose the Gemini model for your session with `/model`.
|
||||
([pic](https://imgur.com/a/ABFcWWw),
|
||||
[pr](https://github.com/google-gemini/gemini-cli/pull/8940) by
|
||||
[@abhipatel12](https://github.com/abhipatel12)).
|
||||
- **Model routing:** Gemini CLI will now intelligently pick the best model for
|
||||
the task. Simple queries will be sent to Flash while complex analytical or
|
||||
creative tasks will still use the power of Pro. This ensures your quota will
|
||||
last for a longer period of time. You can always opt-out of this via `/model`.
|
||||
([pr](https://github.com/google-gemini/gemini-cli/pull/9262) by
|
||||
[@abhipatel12](https://github.com/abhipatel12)).
|
||||
- Discussion:
|
||||
[https://github.com/google-gemini/gemini-cli/discussions/12375](https://github.com/google-gemini/gemini-cli/discussions/12375)
|
||||
- **Codebase investigator subagent:** We now have a new built-in subagent that
|
||||
will explore your workspace and resolve relevant information to improve
|
||||
overall performance.
|
||||
([pr](https://github.com/google-gemini/gemini-cli/pull/9988) by
|
||||
[@abhipatel12](https://github.com/abhipatel12),
|
||||
[pr](https://github.com/google-gemini/gemini-cli/pull/10282) by
|
||||
[@silviojr](https://github.com/silviojr)).
|
||||
- Enable, disable, or limit turns in `/settings`, plus advanced configs in
|
||||
`settings.json` ([pic](https://imgur.com/a/yJiggNO),
|
||||
[pr](https://github.com/google-gemini/gemini-cli/pull/10844) by
|
||||
[@silviojr](https://github.com/silviojr)).
|
||||
- **Explore extensions with `/extension`:** Users can now open the extensions
|
||||
page in their default browser directly from the CLI using the `/extension`
|
||||
explore command. ([pr](https://github.com/google-gemini/gemini-cli/pull/11846)
|
||||
by [@JayadityaGit](https://github.com/JayadityaGit)).
|
||||
- **Configurable compression:** Users can modify the compression threshold in
|
||||
`/settings`. The default has been made more proactive
|
||||
([pr](https://github.com/google-gemini/gemini-cli/pull/12317) by
|
||||
[@scidomino](https://github.com/scidomino)).
|
||||
- **API key authentication:** Users can now securely enter and store their
|
||||
Gemini API key via a new dialog, eliminating the need for environment
|
||||
variables and repeated entry.
|
||||
([pr](https://github.com/google-gemini/gemini-cli/pull/11760) by
|
||||
[@galz10](https://github.com/galz10)).
|
||||
- **Sequential approval:** Users can now approve multiple tool calls
|
||||
sequentially during execution.
|
||||
([pr](https://github.com/google-gemini/gemini-cli/pull/11593) by
|
||||
[@joshualitt](https://github.com/joshualitt)).
|
||||
|
||||
## v0.11.0 - Gemini CLI weekly update - 2025-10-20
|
||||
|
||||

|
||||
|
||||
- 🎉 **Gemini CLI Jules Extension:** Use Gemini CLI to orchestrate Jules. Spawn
|
||||
remote workers, delegate tedious tasks, or check in on running jobs!
|
||||
- Install:
|
||||
`gemini extensions install https://github.com/gemini-cli-extensions/jules`
|
||||
- Announcement:
|
||||
[https://developers.googleblog.com/en/introducing-the-jules-extension-for-gemini-cli/](https://developers.googleblog.com/en/introducing-the-jules-extension-for-gemini-cli/)
|
||||
- **Stream JSON output:** Stream real-time JSONL events with
|
||||
`--output-format stream-json` to monitor AI agent progress when run
|
||||
headlessly. ([gif](https://imgur.com/a/0UCE81X),
|
||||
[pr](https://github.com/google-gemini/gemini-cli/pull/10883) by
|
||||
[@anj-s](https://github.com/anj-s))
|
||||
- **Markdown toggle:** Users can now switch between rendered and raw markdown
|
||||
display using `alt+m `or` ctrl+m`. ([gif](https://imgur.com/a/lDNdLqr),
|
||||
[pr](https://github.com/google-gemini/gemini-cli/pull/10383) by
|
||||
[@srivatsj](https://github.com/srivatsj))
|
||||
- **Queued message editing:** Users can now quickly edit queued messages by
|
||||
pressing the up arrow key when the input is empty.
|
||||
([gif](https://imgur.com/a/ioRslLd),
|
||||
[pr](https://github.com/google-gemini/gemini-cli/pull/10392) by
|
||||
[@akhil29](https://github.com/akhil29))
|
||||
- **JSON web fetch**: Non-HTML content like JSON APIs or raw source code are now
|
||||
properly shown to the model (previously only supported HTML)
|
||||
([gif](https://imgur.com/a/Q58U4qJ),
|
||||
[pr](https://github.com/google-gemini/gemini-cli/pull/11284) by
|
||||
[@abhipatel12](https://github.com/abhipatel12))
|
||||
- **Non-interactive MCP commands:** Users can now run MCP slash commands in
|
||||
non-interactive mode `gemini "/some-mcp-prompt"`.
|
||||
([pr](https://github.com/google-gemini/gemini-cli/pull/10194) by
|
||||
[@capachino](https://github.com/capachino))
|
||||
- **Removal of deprecated flags:** We’ve finally removed a number of deprecated
|
||||
flags to cleanup Gemini CLI’s invocation profile:
|
||||
- `--all-files` / `-a` in favor of `@` from within Gemini CLI.
|
||||
([pr](https://github.com/google-gemini/gemini-cli/pull/11228) by
|
||||
[@allenhutchison](https://github.com/allenhutchison))
|
||||
- `--telemetry-*` flags in favor of
|
||||
[environment variables](https://github.com/google-gemini/gemini-cli/pull/11318)
|
||||
([pr](https://github.com/google-gemini/gemini-cli/pull/11318) by
|
||||
[@allenhutchison](https://github.com/allenhutchison))
|
||||
|
||||
## v0.10.0 - Gemini CLI weekly update - 2025-10-13
|
||||
|
||||
- **Polish:** The team has been heads down bug fixing and investing heavily into
|
||||
polishing existing flows, tools, and interactions.
|
||||
- **Interactive Shell Tool calling:** Gemini CLI can now also execute
|
||||
interactive tools if needed
|
||||
([pr](https://github.com/google-gemini/gemini-cli/pull/11225) by
|
||||
[@galz10](https://github.com/galz10)).
|
||||
- **Alt+Key support:** Enables broader support for Alt+Key keyboard shortcuts
|
||||
across different terminals.
|
||||
([pr](https://github.com/google-gemini/gemini-cli/pull/10767) by
|
||||
[@srivatsj](https://github.com/srivatsj)).
|
||||
- **Telemetry Diff stats:** Track line changes made by the model and user during
|
||||
file operations via OTEL.
|
||||
([pr](https://github.com/google-gemini/gemini-cli/pull/10819) by
|
||||
[@jerop](https://github.com/jerop)).
|
||||
|
||||
## v0.9.0 - Gemini CLI weekly update - 2025-10-06
|
||||
|
||||
- 🎉 **Interactive Shell:** Run interactive commands like `vim`, `rebase -i`, or
|
||||
|
||||
@@ -37,22 +37,12 @@ file in your project's temporary directory, typically located at
|
||||
|
||||
## Enabling the Feature
|
||||
|
||||
The Checkpointing feature is disabled by default. To enable it, you can either
|
||||
use a command-line flag or edit your `settings.json` file.
|
||||
The Checkpointing feature is disabled by default. To enable it, you need to edit
|
||||
your `settings.json` file.
|
||||
|
||||
### Using the Command-Line Flag
|
||||
|
||||
You can enable checkpointing for the current session by using the
|
||||
`--checkpointing` flag when starting the Gemini CLI:
|
||||
|
||||
```bash
|
||||
gemini --checkpointing
|
||||
```
|
||||
|
||||
### Using the `settings.json` File
|
||||
|
||||
To enable checkpointing by default for all sessions, you need to edit your
|
||||
`settings.json` file.
|
||||
> **Note:** The `--checkpointing` command-line flag was removed in version
|
||||
> 0.11.0. Checkpointing can now only be enabled through the `settings.json`
|
||||
> configuration file.
|
||||
|
||||
Add the following key to your `settings.json`:
|
||||
|
||||
|
||||
+33
-6
@@ -30,8 +30,9 @@ Slash commands provide meta-level control over the CLI itself.
|
||||
checkpoints are:
|
||||
- Linux/macOS: `~/.gemini/tmp/<project_hash>/`
|
||||
- Windows: `C:\Users\<YourUsername>\.gemini\tmp\<project_hash>\`
|
||||
- When you run `/chat list`, the CLI only scans these specific
|
||||
directories to find available checkpoints.
|
||||
- **Behavior:** Chats are saved into a project-specific directory,
|
||||
determined by where you run the CLI. Consequently, saved chats are
|
||||
only accessible when working within that same project.
|
||||
- **Note:** These checkpoints are for manually saving and resuming
|
||||
conversation states. For automatic checkpoints created before file
|
||||
modifications, see the
|
||||
@@ -39,8 +40,14 @@ Slash commands provide meta-level control over the CLI itself.
|
||||
- **`resume`**
|
||||
- **Description:** Resumes a conversation from a previous save.
|
||||
- **Usage:** `/chat resume <tag>`
|
||||
- **Note:** You can only resume chats that were saved within the current
|
||||
project. To resume a chat from a different project, you must run the
|
||||
Gemini CLI from that project's directory.
|
||||
- **`list`**
|
||||
- **Description:** Lists available tags for chat state resumption.
|
||||
- **Note:** This command only lists chats saved within the current
|
||||
project. Because chat history is project-scoped, chats saved in other
|
||||
project directories will not be displayed.
|
||||
- **`delete`**
|
||||
- **Description:** Deletes a saved conversation checkpoint.
|
||||
- **Usage:** `/chat delete <tag>`
|
||||
@@ -121,6 +128,9 @@ Slash commands provide meta-level control over the CLI itself.
|
||||
- **Description:** Restarts all MCP servers and re-discovers their
|
||||
available tools.
|
||||
|
||||
- [**`/model`**](./model.md)
|
||||
- **Description:** Opens a dialog to choose your Gemini model.
|
||||
|
||||
- **`/memory`**
|
||||
- **Description:** Manage the AI's instructional context (hierarchical memory
|
||||
loaded from `GEMINI.md` files).
|
||||
@@ -151,17 +161,34 @@ Slash commands provide meta-level control over the CLI itself.
|
||||
edits made by a tool. If run without a tool call ID, it will list available
|
||||
checkpoints to restore from.
|
||||
- **Usage:** `/restore [tool_call_id]`
|
||||
- **Note:** Only available if the CLI is invoked with the `--checkpointing`
|
||||
option or configured via [settings](../get-started/configuration.md). See
|
||||
- **Note:** Only available if checkpointing is configured via
|
||||
[settings](../get-started/configuration.md). See
|
||||
[Checkpointing documentation](../cli/checkpointing.md) for more details.
|
||||
- **`/resume`**
|
||||
- **Description:** Browse and resume previous conversation sessions. Opens an
|
||||
interactive session browser where you can search, filter, and select from
|
||||
automatically saved conversations.
|
||||
- **Features:**
|
||||
- **Session Browser:** Interactive interface showing all saved sessions with
|
||||
timestamps, message counts, and first user message for context
|
||||
- **Search:** Use `/` to search through conversation content across all
|
||||
sessions
|
||||
- **Sorting:** Sort sessions by date or message count
|
||||
- **Management:** Delete unwanted sessions directly from the browser
|
||||
- **Resume:** Select any session to resume and continue the conversation
|
||||
- **Note:** All conversations are automatically saved as you chat - no manual
|
||||
saving required. See [Session Management](../cli/session-management.md) for
|
||||
complete details.
|
||||
|
||||
- **`/settings`**
|
||||
- [**`/settings`**](./settings.md)
|
||||
- **Description:** Open the settings editor to view and modify Gemini CLI
|
||||
settings.
|
||||
- **Details:** This command provides a user-friendly interface for changing
|
||||
settings that control the behavior and appearance of Gemini CLI. It is
|
||||
equivalent to manually editing the `.gemini/settings.json` file, but with
|
||||
validation and guidance to prevent errors.
|
||||
validation and guidance to prevent errors. See the
|
||||
[settings documentation](./settings.md) for a full list of available
|
||||
settings.
|
||||
- **Usage:** Simply run `/settings` and the editor will open. You can then
|
||||
browse or search for specific settings, view their current values, and
|
||||
modify them as desired. Changes to some settings are applied immediately,
|
||||
|
||||
@@ -464,6 +464,18 @@ the `excludedProjectEnvVars` setting in your `settings.json` file.
|
||||
- Specifies the default Gemini model to use.
|
||||
- Overrides the hardcoded default
|
||||
- Example: `export GEMINI_MODEL="gemini-2.5-flash"`
|
||||
- **`GEMINI_CLI_CUSTOM_HEADERS`**:
|
||||
- Adds extra HTTP headers to Gemini API and Code Assist requests.
|
||||
- Accepts a comma-separated list of `Name: value` pairs.
|
||||
- Example:
|
||||
`export GEMINI_CLI_CUSTOM_HEADERS="X-My-Header: foo, X-Trace-ID: abc123"`.
|
||||
- **`GEMINI_API_KEY_AUTH_MECHANISM`**:
|
||||
- Specifies how the API key should be sent for authentication when using
|
||||
`AuthType.USE_GEMINI` or `AuthType.USE_VERTEX_AI`.
|
||||
- Valid values are `x-goog-api-key` (default) or `bearer`.
|
||||
- If set to `bearer`, the API key will be sent in the
|
||||
`Authorization: Bearer <key>` header.
|
||||
- Example: `export GEMINI_API_KEY_AUTH_MECHANISM="bearer"`
|
||||
- **`GOOGLE_API_KEY`**:
|
||||
- Your Google Cloud API key.
|
||||
- Required for using Vertex AI in express mode.
|
||||
@@ -494,6 +506,9 @@ the `excludedProjectEnvVars` setting in your `settings.json` file.
|
||||
- **`GEMINI_SANDBOX`**:
|
||||
- Alternative to the `sandbox` setting in `settings.json`.
|
||||
- Accepts `true`, `false`, `docker`, `podman`, or a custom command string.
|
||||
- **`HTTP_PROXY` / `HTTPS_PROXY`**:
|
||||
- Specifies the proxy server to use for outgoing HTTP/HTTPS requests.
|
||||
- Example: `export HTTPS_PROXY="http://proxy.example.com:8080"`
|
||||
- **`SEATBELT_PROFILE`** (macOS specific):
|
||||
- Switches the Seatbelt (`sandbox-exec`) profile on macOS.
|
||||
- `permissive-open`: (Default) Restricts writes to the project folder (and a
|
||||
@@ -564,18 +579,16 @@ for that specific session.
|
||||
- **`--yolo`**:
|
||||
- Enables YOLO mode, which automatically approves all tool calls.
|
||||
- **`--telemetry`**:
|
||||
- Enables [telemetry](../telemetry.md).
|
||||
- Enables [telemetry](./telemetry.md).
|
||||
- **`--telemetry-target`**:
|
||||
- Sets the telemetry target. See [telemetry](../telemetry.md) for more
|
||||
- Sets the telemetry target. See [telemetry](./telemetry.md) for more
|
||||
information.
|
||||
- **`--telemetry-otlp-endpoint`**:
|
||||
- Sets the OTLP endpoint for telemetry. See [telemetry](../telemetry.md) for
|
||||
- Sets the OTLP endpoint for telemetry. See [telemetry](./telemetry.md) for
|
||||
more information.
|
||||
- **`--telemetry-log-prompts`**:
|
||||
- Enables logging of prompts for telemetry. See [telemetry](../telemetry.md)
|
||||
- Enables logging of prompts for telemetry. See [telemetry](./telemetry.md)
|
||||
for more information.
|
||||
- **`--checkpointing`**:
|
||||
- Enables [checkpointing](./checkpointing.md).
|
||||
- **`--extensions <extension_name ...>`** (**`-e <extension_name ...>`**):
|
||||
- Specifies a list of extensions to use for the session. If not provided, all
|
||||
available extensions are used.
|
||||
@@ -583,9 +596,6 @@ for that specific session.
|
||||
- Example: `gemini -e my-extension -e my-other-extension`
|
||||
- **`--list-extensions`** (**`-l`**):
|
||||
- Lists all available extensions and exits.
|
||||
- **`--proxy`**:
|
||||
- Sets the proxy for the CLI.
|
||||
- Example: `--proxy http://localhost:7890`.
|
||||
- **`--include-directories <dir1,dir2,...>`**:
|
||||
- Includes additional directories in the workspace for multi-directory
|
||||
support.
|
||||
|
||||
+22
-3
@@ -202,6 +202,26 @@ allowlisting with `coreTools`, as it relies on blocking known-bad commands, and
|
||||
clever users may find ways to bypass simple string-based blocks. **Allowlisting
|
||||
is the recommended approach.**
|
||||
|
||||
### Disabling YOLO Mode
|
||||
|
||||
To ensure that users cannot bypass the confirmation prompt for tool execution,
|
||||
you can disable YOLO mode at the policy level. This adds a critical layer of
|
||||
safety, as it prevents the model from executing tools without explicit user
|
||||
approval.
|
||||
|
||||
**Example:** Force all tool executions to require user confirmation.
|
||||
|
||||
```json
|
||||
{
|
||||
"security": {
|
||||
"disableYoloMode": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This setting is highly recommended in an enterprise environment to prevent
|
||||
unintended tool execution.
|
||||
|
||||
## Managing Custom Tools (MCP Servers)
|
||||
|
||||
If your organization uses custom tools via
|
||||
@@ -343,9 +363,8 @@ containerized environment.
|
||||
}
|
||||
```
|
||||
|
||||
You can also specify a custom, hardened Docker image for the sandbox using the
|
||||
`--sandbox-image` command-line argument or by building a custom
|
||||
`sandbox.Dockerfile` as described in the
|
||||
You can also specify a custom, hardened Docker image for the sandbox by building
|
||||
a custom `sandbox.Dockerfile` as described in the
|
||||
[Sandboxing documentation](./sandbox.md).
|
||||
|
||||
## Controlling Network Access via Proxy
|
||||
|
||||
@@ -13,8 +13,8 @@ Git).
|
||||
|
||||
When you add a path to your `.geminiignore` file, tools that respect this file
|
||||
will exclude matching files and directories from their operations. For example,
|
||||
when you use the [`read_many_files`](../tools/multi-file.md) command, any paths
|
||||
in your `.geminiignore` file will be automatically excluded.
|
||||
when you use the `@` command to share files, any paths in your `.geminiignore`
|
||||
file will be automatically excluded.
|
||||
|
||||
For the most part, `.geminiignore` follows the conventions of `.gitignore`
|
||||
files:
|
||||
|
||||
+4
-1
@@ -7,11 +7,14 @@ overview of Gemini CLI, see the [main documentation page](../index.md).
|
||||
## Basic features
|
||||
|
||||
- **[Commands](./commands.md):** A reference for all built-in slash commands
|
||||
(e.g., `/help`, `/chat`, `/tools`).
|
||||
- **[Custom Commands](./custom-commands.md):** Create your own commands and
|
||||
shortcuts for frequently used prompts.
|
||||
- **[Headless Mode](./headless.md):** Use Gemini CLI programmatically for
|
||||
scripting and automation.
|
||||
- **[Model Selection](./model.md):** Configure the Gemini AI model used by the
|
||||
CLI.
|
||||
- **[Settings](./settings.md):** Configure various aspects of the CLI's behavior
|
||||
and appearance.
|
||||
- **[Themes](./themes.md):** Customizing the CLI's appearance with different
|
||||
themes.
|
||||
- **[Keyboard Shortcuts](./keyboard-shortcuts.md):** A reference for all
|
||||
|
||||
+131
-56
@@ -1,68 +1,143 @@
|
||||
# Gemini CLI Keyboard Shortcuts
|
||||
|
||||
This document lists the available keyboard shortcuts in the Gemini CLI.
|
||||
Gemini CLI ships with a set of default keyboard shortcuts for editing input,
|
||||
navigating history, and controlling the UI. Use this reference to learn the
|
||||
available combinations.
|
||||
|
||||
## General
|
||||
<!-- KEYBINDINGS-AUTOGEN:START -->
|
||||
|
||||
| Shortcut | Description |
|
||||
| -------- | --------------------------------------------------------------------------------------------------------------------- |
|
||||
| `Esc` | Close dialogs and suggestions. |
|
||||
| `Ctrl+C` | Cancel the ongoing request and clear the input. Press twice to exit the application. |
|
||||
| `Ctrl+D` | Exit the application if the input is empty. Press twice to confirm. |
|
||||
| `Ctrl+L` | Clear the screen. |
|
||||
| `Ctrl+O` | Toggle the display of the debug console. |
|
||||
| `Ctrl+S` | Allows long responses to print fully, disabling truncation. Use your terminal's scrollback to view the entire output. |
|
||||
| `Ctrl+Y` | Toggle auto-approval (YOLO mode) for all tool calls. |
|
||||
#### Basic Controls
|
||||
|
||||
## Input Prompt
|
||||
| Action | Keys |
|
||||
| -------------------------------------------- | ------- |
|
||||
| Confirm the current selection or choice. | `Enter` |
|
||||
| Dismiss dialogs or cancel the current focus. | `Esc` |
|
||||
|
||||
| Shortcut | Description |
|
||||
| -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `!` | Toggle shell mode when the input is empty. |
|
||||
| `\` (at end of line) + `Enter` | Insert a newline. |
|
||||
| `Down Arrow` | Navigate down through the input history. |
|
||||
| `Enter` | Submit the current prompt. |
|
||||
| `Meta+Delete` / `Ctrl+Delete` | Delete the word to the right of the cursor. |
|
||||
| `Tab` | Autocomplete the current suggestion if one exists. |
|
||||
| `Up Arrow` | Navigate up through the input history. |
|
||||
| `Ctrl+A` / `Home` | Move the cursor to the beginning of the line. |
|
||||
| `Ctrl+B` / `Left Arrow` | Move the cursor one character to the left. |
|
||||
| `Ctrl+C` | Clear the input prompt |
|
||||
| `Esc` (double press) | Clear the input prompt. |
|
||||
| `Ctrl+D` / `Delete` | Delete the character to the right of the cursor. |
|
||||
| `Ctrl+E` / `End` | Move the cursor to the end of the line. |
|
||||
| `Ctrl+F` / `Right Arrow` | Move the cursor one character to the right. |
|
||||
| `Ctrl+H` / `Backspace` | Delete the character to the left of the cursor. |
|
||||
| `Ctrl+K` | Delete from the cursor to the end of the line. |
|
||||
| `Ctrl+Left Arrow` / `Meta+Left Arrow` / `Meta+B` | Move the cursor one word to the left. |
|
||||
| `Ctrl+N` | Navigate down through the input history. |
|
||||
| `Ctrl+P` | Navigate up through the input history. |
|
||||
| `Ctrl+Right Arrow` / `Meta+Right Arrow` / `Meta+F` | Move the cursor one word to the right. |
|
||||
| `Ctrl+U` | Delete from the cursor to the beginning of the line. |
|
||||
| `Ctrl+V` | Paste clipboard content. If the clipboard contains an image, it will be saved and a reference to it will be inserted in the prompt. |
|
||||
| `Ctrl+W` / `Meta+Backspace` / `Ctrl+Backspace` | Delete the word to the left of the cursor. |
|
||||
| `Ctrl+X` / `Meta+Enter` | Open the current input in an external editor. |
|
||||
#### Cursor Movement
|
||||
|
||||
## Suggestions
|
||||
| Action | Keys |
|
||||
| ----------------------------------------- | ---------------------- |
|
||||
| Move the cursor to the start of the line. | `Ctrl + A`<br />`Home` |
|
||||
| Move the cursor to the end of the line. | `Ctrl + E`<br />`End` |
|
||||
|
||||
| Shortcut | Description |
|
||||
| --------------- | -------------------------------------- |
|
||||
| `Down Arrow` | Navigate down through the suggestions. |
|
||||
| `Tab` / `Enter` | Accept the selected suggestion. |
|
||||
| `Up Arrow` | Navigate up through the suggestions. |
|
||||
#### Editing
|
||||
|
||||
## Radio Button Select
|
||||
| Action | Keys |
|
||||
| ------------------------------------------------ | ----------------------------------------- |
|
||||
| Delete from the cursor to the end of the line. | `Ctrl + K` |
|
||||
| Delete from the cursor to the start of the line. | `Ctrl + U` |
|
||||
| Clear all text in the input field. | `Ctrl + C` |
|
||||
| Delete the previous word. | `Ctrl + Backspace`<br />`Cmd + Backspace` |
|
||||
|
||||
| Shortcut | Description |
|
||||
| ------------------ | ------------------------------------------------------------------------------------------------------------- |
|
||||
| `Down Arrow` / `j` | Move selection down. |
|
||||
| `Enter` | Confirm selection. |
|
||||
| `Up Arrow` / `k` | Move selection up. |
|
||||
| `1-9` | Select an item by its number. |
|
||||
| (multi-digit) | For items with numbers greater than 9, press the digits in quick succession to select the corresponding item. |
|
||||
#### Screen Control
|
||||
|
||||
## IDE Integration
|
||||
| Action | Keys |
|
||||
| -------------------------------------------- | ---------- |
|
||||
| Clear the terminal screen and redraw the UI. | `Ctrl + L` |
|
||||
|
||||
| Shortcut | Description |
|
||||
| -------- | --------------------------------- |
|
||||
| `Ctrl+G` | See context CLI received from IDE |
|
||||
#### Scrolling
|
||||
|
||||
| Action | Keys |
|
||||
| ------------------------ | -------------------- |
|
||||
| Scroll content up. | `Shift + Up Arrow` |
|
||||
| Scroll content down. | `Shift + Down Arrow` |
|
||||
| Scroll to the top. | `Home` |
|
||||
| Scroll to the bottom. | `End` |
|
||||
| Scroll up by one page. | `Page Up` |
|
||||
| Scroll down by one page. | `Page Down` |
|
||||
|
||||
#### History & Search
|
||||
|
||||
| Action | Keys |
|
||||
| -------------------------------------------- | --------------------- |
|
||||
| Show the previous entry in history. | `Ctrl + P (no Shift)` |
|
||||
| Show the next entry in history. | `Ctrl + N (no Shift)` |
|
||||
| Start reverse search through history. | `Ctrl + R` |
|
||||
| Insert the selected reverse-search match. | `Enter (no Ctrl)` |
|
||||
| Accept a suggestion while reverse searching. | `Tab` |
|
||||
|
||||
#### Navigation
|
||||
|
||||
| Action | Keys |
|
||||
| -------------------------------- | ------------------------------------------- |
|
||||
| Move selection up in lists. | `Up Arrow (no Shift)` |
|
||||
| Move selection down in lists. | `Down Arrow (no Shift)` |
|
||||
| Move up within dialog options. | `Up Arrow (no Shift)`<br />`K (no Shift)` |
|
||||
| Move down within dialog options. | `Down Arrow (no Shift)`<br />`J (no Shift)` |
|
||||
|
||||
#### Suggestions & Completions
|
||||
|
||||
| Action | Keys |
|
||||
| --------------------------------------- | -------------------------------------------------- |
|
||||
| Accept the inline suggestion. | `Tab`<br />`Enter (no Ctrl)` |
|
||||
| Move to the previous completion option. | `Up Arrow (no Shift)`<br />`Ctrl + P (no Shift)` |
|
||||
| Move to the next completion option. | `Down Arrow (no Shift)`<br />`Ctrl + N (no Shift)` |
|
||||
| Expand an inline suggestion. | `Right Arrow` |
|
||||
| Collapse an inline suggestion. | `Left Arrow` |
|
||||
|
||||
#### Text Input
|
||||
|
||||
| Action | Keys |
|
||||
| ------------------------------------ | ------------------------------------------------------------------------------------------- |
|
||||
| Submit the current prompt. | `Enter (no Ctrl, no Shift, no Cmd, not Paste)` |
|
||||
| Insert a newline without submitting. | `Ctrl + Enter`<br />`Cmd + Enter`<br />`Paste + Enter`<br />`Shift + Enter`<br />`Ctrl + J` |
|
||||
|
||||
#### External Tools
|
||||
|
||||
| Action | Keys |
|
||||
| ---------------------------------------------- | ---------- |
|
||||
| Open the current prompt in an external editor. | `Ctrl + X` |
|
||||
| Paste from the clipboard. | `Ctrl + V` |
|
||||
|
||||
#### App Controls
|
||||
|
||||
| Action | Keys |
|
||||
| ----------------------------------------------------------------- | ---------- |
|
||||
| Toggle detailed error information. | `F12` |
|
||||
| Toggle the full TODO list. | `Ctrl + T` |
|
||||
| Toggle IDE context details. | `Ctrl + G` |
|
||||
| Toggle Markdown rendering. | `Cmd + M` |
|
||||
| Toggle copy mode when the terminal is using the alternate buffer. | `Ctrl + S` |
|
||||
| Expand a height-constrained response to show additional lines. | `Ctrl + S` |
|
||||
| Toggle focus between the shell and Gemini input. | `Ctrl + F` |
|
||||
|
||||
#### Session Control
|
||||
|
||||
| Action | Keys |
|
||||
| -------------------------------------------- | ---------- |
|
||||
| Cancel the current request or quit the CLI. | `Ctrl + C` |
|
||||
| Exit the CLI when the input buffer is empty. | `Ctrl + D` |
|
||||
|
||||
<!-- KEYBINDINGS-AUTOGEN:END -->
|
||||
|
||||
## Additional Context-Specific Shortcuts
|
||||
|
||||
- `Ctrl+Y`: Toggle YOLO (auto-approval) mode for tool calls.
|
||||
- `Shift+Tab`: Toggle Auto Edit (auto-accept edits) mode.
|
||||
- `Option+M` (macOS): Entering `µ` with Option+M also toggles Markdown
|
||||
rendering, matching `Cmd+M`.
|
||||
- `!` on an empty prompt: Enter or exit shell mode.
|
||||
- `\` (at end of a line) + `Enter`: Insert a newline without leaving single-line
|
||||
mode.
|
||||
- `Ctrl+Delete` / `Meta+Delete`: Delete the word to the right of the cursor.
|
||||
- `Ctrl+B` or `Left Arrow`: Move the cursor one character to the left while
|
||||
editing text.
|
||||
- `Ctrl+F` or `Right Arrow`: Move the cursor one character to the right; with an
|
||||
embedded shell attached, `Ctrl+F` still toggles focus.
|
||||
- `Ctrl+D` or `Delete`: Remove the character immediately to the right of the
|
||||
cursor.
|
||||
- `Ctrl+H` or `Backspace`: Remove the character immediately to the left of the
|
||||
cursor.
|
||||
- `Ctrl+Left Arrow` / `Meta+Left Arrow` / `Meta+B`: Move one word to the left.
|
||||
- `Ctrl+Right Arrow` / `Meta+Right Arrow` / `Meta+F`: Move one word to the
|
||||
right.
|
||||
- `Ctrl+W`: Delete the word to the left of the cursor (in addition to
|
||||
`Ctrl+Backspace` / `Cmd+Backspace`).
|
||||
- `Ctrl+Z` / `Ctrl+Shift+Z`: Undo or redo the most recent text edit.
|
||||
- `Meta+Enter`: Open the current input in an external editor (alias for
|
||||
`Ctrl+X`).
|
||||
- `Esc` pressed twice quickly: Clear the current input buffer.
|
||||
- `Up Arrow` / `Down Arrow`: When the cursor is at the top or bottom of a
|
||||
single-line input, navigate backward or forward through prompt history.
|
||||
- `Number keys (1-9, multi-digit)` inside selection dialogs: Jump directly to
|
||||
the numbered radio option and confirm when the full number is entered.
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
## Model Routing
|
||||
|
||||
Gemini CLI includes a model routing feature that automatically switches to a
|
||||
fallback model in case of a model failure. This feature is enabled by default
|
||||
and provides resilience when the primary model is unavailable.
|
||||
|
||||
## How it Works
|
||||
|
||||
Model routing is not based on prompt complexity, but is a fallback mechanism.
|
||||
Here's how it works:
|
||||
|
||||
1. **Model Failure:** If the currently selected model fails to respond (for
|
||||
example, due to a server error or other issue), the CLI will initiate the
|
||||
fallback process.
|
||||
|
||||
2. **User Consent:** The CLI will prompt you to ask if you want to switch to
|
||||
the fallback model. This is handled by the `fallbackModelHandler`.
|
||||
|
||||
3. **Fallback Activation:** If you consent, the CLI will activate the fallback
|
||||
mode by calling `config.setFallbackMode(true)`.
|
||||
|
||||
4. **Model Switch:** On the next request, the CLI will use the
|
||||
`DEFAULT_GEMINI_FLASH_MODEL` as the fallback model. This is handled by the
|
||||
`resolveModel` function in
|
||||
`packages/cli/src/zed-integration/zedIntegration.ts` which checks if
|
||||
`isInFallbackMode()` is true.
|
||||
|
||||
### Model Selection Precedence
|
||||
|
||||
The model used by Gemini CLI is determined by the following order of precedence:
|
||||
|
||||
1. **`--model` command-line flag:** A model specified with the `--model` flag
|
||||
when launching the CLI will always be used.
|
||||
2. **`GEMINI_MODEL` environment variable:** If the `--model` flag is not used,
|
||||
the CLI will use the model specified in the `GEMINI_MODEL` environment
|
||||
variable.
|
||||
3. **`model.name` in `settings.json`:** If neither of the above are set, the
|
||||
model specified in the `model.name` property of your `settings.json` file
|
||||
will be used.
|
||||
4. **Default Model:** If none of the above are set, the default model will be
|
||||
used. The default model is `auto`
|
||||
@@ -0,0 +1,58 @@
|
||||
# Gemini CLI Model Selection (`/model` Command)
|
||||
|
||||
Select your Gemini CLI model. The `/model` command opens a dialog where you can
|
||||
configure the model used by Gemini CLI, giving you more control over your
|
||||
results.
|
||||
|
||||
## How to use the `/model` command
|
||||
|
||||
Use the following command in Gemini CLI:
|
||||
|
||||
```
|
||||
/model
|
||||
```
|
||||
|
||||
Running this command will open a dialog with your model options:
|
||||
|
||||
| Option | Description | Models |
|
||||
| ------------------ | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
|
||||
| Auto (recommended) | Let the system choose the best model for your task. | gemini-3-pro-preview (if enabled), gemini-2.5-pro, gemini-2.5-flash, gemini-2.5-flash-lite |
|
||||
| Pro | For complex tasks that require deep reasoning and creativity. | gemini-3-pro-preview (if enabled), gemini-2.5-pro |
|
||||
| Flash | For tasks that need a balance of speed and reasoning. | gemini-2.5-flash |
|
||||
| Flash-Lite | For simple tasks that need to be done quickly. | gemini-2.5-flash-lite |
|
||||
|
||||
### Gemini 3 Pro and Preview Features
|
||||
|
||||
Note: Gemini 3 is not currently available on all account types. To learn more
|
||||
about Gemini 3 access, refer to
|
||||
[Gemini 3 Pro on Gemini CLI](../get-started/gemini-3.md).
|
||||
|
||||
To enable Gemini 3 Pro (if available), enable
|
||||
[**Preview features** by using the `settings` command](../cli/settings.md). Once
|
||||
enabled, Gemini CLI will attempt to use Gemini 3 Pro when you select **Auto** or
|
||||
**Pro**. Both **Auto** and **Pro** will try to use Gemini 3 Pro before falling
|
||||
back to Gemini 2.5 Pro.
|
||||
|
||||
You can also use the `--model` flag to specify a particular Gemini model on
|
||||
startup. For more details, refer to the
|
||||
[configuration documentation](./configuration.md).
|
||||
|
||||
Changes to these settings will be applied to all subsequent interactions with
|
||||
Gemini CLI.
|
||||
|
||||
## Best practices for model selection
|
||||
|
||||
- **Default to Auto (recommended).** For most users, the _Auto (recommended)_
|
||||
model provides a balance between speed and performance, automatically
|
||||
selecting the correct model based on the complexity of the task. Example:
|
||||
Developing a web application could include a mix of complex tasks (building
|
||||
architecture and scaffolding the project) and simple tasks (generating CSS).
|
||||
|
||||
- **Switch to Pro if you aren't getting the results you want.** If you think you
|
||||
need your model to be a little "smarter," use Pro. Pro will provide you with
|
||||
the highest levels of reasoning and creativity. Example: A complex or
|
||||
multi-stage debugging task.
|
||||
|
||||
- **Switch to Flash or Flash-Lite if you need faster results.** If you need a
|
||||
simple response quickly, Flash or Flash-Lite is the best option. Example:
|
||||
Converting a JSON object to a YAML string.
|
||||
@@ -0,0 +1,158 @@
|
||||
# Session Management
|
||||
|
||||
Gemini CLI includes robust session management features that automatically save
|
||||
your conversation history. This allows you to interrupt your work and resume
|
||||
exactly where you left off, review past interactions, and manage your
|
||||
conversation history effectively.
|
||||
|
||||
## Automatic Saving
|
||||
|
||||
Every time you interact with Gemini CLI, your session is automatically saved.
|
||||
This happens in the background without any manual intervention.
|
||||
|
||||
- **What is saved:** The complete conversation history, including:
|
||||
- Your prompts and the model's responses.
|
||||
- All tool executions (inputs and outputs).
|
||||
- Token usage statistics (input/output/cached, etc.).
|
||||
- Assistant thoughts/reasoning summaries (when available).
|
||||
- **Location:** Sessions are stored in `~/.gemini/tmp/<project_hash>/chats/`.
|
||||
- **Scope:** Sessions are project-specific. Switching directories to a different
|
||||
project will switch to that project's session history.
|
||||
|
||||
## Resuming Sessions
|
||||
|
||||
You can resume a previous session to continue the conversation with all prior
|
||||
context restored.
|
||||
|
||||
### From the Command Line
|
||||
|
||||
When starting the CLI, you can use the `--resume` (or `-r`) flag:
|
||||
|
||||
- **Resume latest:**
|
||||
|
||||
```bash
|
||||
gemini --resume
|
||||
```
|
||||
|
||||
This immediately loads the most recent session.
|
||||
|
||||
- **Resume by index:** First, list available sessions (see
|
||||
[Listing Sessions](#listing-sessions)), then use the index number:
|
||||
|
||||
```bash
|
||||
gemini --resume 1
|
||||
```
|
||||
|
||||
- **Resume by ID:** You can also provide the full session UUID:
|
||||
```bash
|
||||
gemini --resume a1b2c3d4-e5f6-7890-abcd-ef1234567890
|
||||
```
|
||||
|
||||
### From the Interactive Interface
|
||||
|
||||
While the CLI is running, you can use the `/resume` slash command to open the
|
||||
**Session Browser**:
|
||||
|
||||
```text
|
||||
/resume
|
||||
```
|
||||
|
||||
This opens an interactive interface where you can:
|
||||
|
||||
- **Browse:** Scroll through a list of your past sessions.
|
||||
- **Preview:** See details like the session date, message count, and the first
|
||||
user prompt.
|
||||
- **Search:** Press `/` to enter search mode, then type to filter sessions by ID
|
||||
or content.
|
||||
- **Select:** Press `Enter` to resume the selected session.
|
||||
|
||||
## Managing Sessions
|
||||
|
||||
### Listing Sessions
|
||||
|
||||
To see a list of all available sessions for the current project from the command
|
||||
line:
|
||||
|
||||
```bash
|
||||
gemini --list-sessions
|
||||
```
|
||||
|
||||
Output example:
|
||||
|
||||
```text
|
||||
Available sessions for this project (3):
|
||||
|
||||
1. Fix bug in auth (2 days ago) [a1b2c3d4]
|
||||
2. Refactor database schema (5 hours ago) [e5f67890]
|
||||
3. Update documentation (Just now) [abcd1234]
|
||||
```
|
||||
|
||||
### Deleting Sessions
|
||||
|
||||
You can remove old or unwanted sessions to free up space or declutter your
|
||||
history.
|
||||
|
||||
**From the Command Line:** Use the `--delete-session` flag with an index or ID:
|
||||
|
||||
```bash
|
||||
gemini --delete-session 2
|
||||
```
|
||||
|
||||
**From the Session Browser:**
|
||||
|
||||
1. Open the browser with `/resume`.
|
||||
2. Navigate to the session you want to remove.
|
||||
3. Press `x`.
|
||||
|
||||
## Configuration
|
||||
|
||||
You can configure how Gemini CLI manages your session history in your
|
||||
`settings.json` file.
|
||||
|
||||
### Session Retention
|
||||
|
||||
To prevent your history from growing indefinitely, you can enable automatic
|
||||
cleanup policies.
|
||||
|
||||
```json
|
||||
{
|
||||
"general": {
|
||||
"sessionRetention": {
|
||||
"enabled": true,
|
||||
"maxAge": "30d", // Keep sessions for 30 days
|
||||
"maxCount": 50 // Keep the 50 most recent sessions
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **`enabled`**: (boolean) Master switch for session cleanup. Default is
|
||||
`false`.
|
||||
- **`maxAge`**: (string) Duration to keep sessions (e.g., "24h", "7d", "4w").
|
||||
Sessions older than this will be deleted.
|
||||
- **`maxCount`**: (number) Maximum number of sessions to retain. The oldest
|
||||
sessions exceeding this count will be deleted.
|
||||
- **`minRetention`**: (string) Minimum retention period (safety limit). Defaults
|
||||
to `"1d"`; sessions newer than this period are never deleted by automatic
|
||||
cleanup.
|
||||
|
||||
### Session Limits
|
||||
|
||||
You can also limit the length of individual sessions to prevent context windows
|
||||
from becoming too large and expensive.
|
||||
|
||||
```json
|
||||
{
|
||||
"model": {
|
||||
"maxSessionTurns": 100
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **`maxSessionTurns`**: (number) The maximum number of turns (user + model
|
||||
exchanges) allowed in a single session. Set to `-1` for unlimited (default).
|
||||
|
||||
**Behavior when limit is reached:**
|
||||
- **Interactive Mode:** The CLI shows an informational message and stops
|
||||
sending requests to the model. You must manually start a new session.
|
||||
- **Non-Interactive Mode:** The CLI exits with an error.
|
||||
@@ -0,0 +1,112 @@
|
||||
# Gemini CLI Settings (`/settings` Command)
|
||||
|
||||
Control your Gemini CLI experience with the `/settings` command. The `/settings`
|
||||
command opens a dialog to view and edit all your Gemini CLI settings, including
|
||||
your UI experience, keybindings, and accessibility features.
|
||||
|
||||
Your Gemini CLI settings are stored in a `settings.json` file. In addition to
|
||||
using the `/settings` command, you can also edit them in one of the following
|
||||
locations:
|
||||
|
||||
- **User settings**: `~/.gemini/settings.json`
|
||||
- **Workspace settings**: `your-project/.gemini/settings.json`
|
||||
|
||||
Note: Workspace settings override user settings.
|
||||
|
||||
## Settings reference
|
||||
|
||||
Here is a list of all the available settings, grouped by category and ordered as
|
||||
they appear in the UI.
|
||||
|
||||
### General
|
||||
|
||||
| UI Label | Setting | Description | Default |
|
||||
| ------------------------------- | ---------------------------------- | ---------------------------------------------------------------------------- | ----------- |
|
||||
| Preview Features (e.g., models) | `general.previewFeatures` | Enable preview features (e.g., preview models). | `false` |
|
||||
| Vim Mode | `general.vimMode` | Enable Vim keybindings. | `false` |
|
||||
| Disable Auto Update | `general.disableAutoUpdate` | Disable automatic updates. | `false` |
|
||||
| Enable Prompt Completion | `general.enablePromptCompletion` | Enable AI-powered prompt completion suggestions while typing. | `false` |
|
||||
| Debug Keystroke Logging | `general.debugKeystrokeLogging` | Enable debug logging of keystrokes to the console. | `false` |
|
||||
| Session Retention | `general.sessionRetention` | Settings for automatic session cleanup. This feature is disabled by default. | `undefined` |
|
||||
| Enable Session Cleanup | `general.sessionRetention.enabled` | Enable automatic session cleanup. | `false` |
|
||||
|
||||
### Output
|
||||
|
||||
| UI Label | Setting | Description | Default |
|
||||
| ------------- | --------------- | ------------------------------------------------------ | ------- |
|
||||
| Output Format | `output.format` | The format of the CLI output. Can be `text` or `json`. | `text` |
|
||||
|
||||
### UI
|
||||
|
||||
| UI Label | Setting | Description | Default |
|
||||
| ------------------------------ | ---------------------------------------- | -------------------------------------------------------------------- | ------- |
|
||||
| Hide Window Title | `ui.hideWindowTitle` | Hide the window title bar. | `false` |
|
||||
| Show Status in Title | `ui.showStatusInTitle` | Show Gemini CLI status and thoughts in the terminal window title. | `false` |
|
||||
| Hide Tips | `ui.hideTips` | Hide helpful tips in the UI. | `false` |
|
||||
| Hide Banner | `ui.hideBanner` | Hide the application banner. | `false` |
|
||||
| Hide Context Summary | `ui.hideContextSummary` | Hide the context summary (GEMINI.md, MCP servers) above the input. | `false` |
|
||||
| Hide CWD | `ui.footer.hideCWD` | Hide the current working directory path in the footer. | `false` |
|
||||
| Hide Sandbox Status | `ui.footer.hideSandboxStatus` | Hide the sandbox status indicator in the footer. | `false` |
|
||||
| Hide Model Info | `ui.footer.hideModelInfo` | Hide the model name and context usage in the footer. | `false` |
|
||||
| Hide Context Window Percentage | `ui.footer.hideContextPercentage` | Hides the context window remaining percentage. | `true` |
|
||||
| Hide Footer | `ui.hideFooter` | Hide the footer from the UI. | `false` |
|
||||
| Show Memory Usage | `ui.showMemoryUsage` | Display memory usage information in the UI. | `false` |
|
||||
| Show Line Numbers | `ui.showLineNumbers` | Show line numbers in the chat. | `false` |
|
||||
| Show Citations | `ui.showCitations` | Show citations for generated text in the chat. | `false` |
|
||||
| Use Full Width | `ui.useFullWidth` | Use the entire width of the terminal for output. | `true` |
|
||||
| Use Alternate Screen Buffer | `ui.useAlternateBuffer` | Use an alternate screen buffer for the UI, preserving shell history. | `true` |
|
||||
| Disable Loading Phrases | `ui.accessibility.disableLoadingPhrases` | Disable loading phrases for accessibility. | `false` |
|
||||
| Screen Reader Mode | `ui.accessibility.screenReader` | Render output in plain-text to be more screen reader accessible. | `false` |
|
||||
|
||||
### IDE
|
||||
|
||||
| UI Label | Setting | Description | Default |
|
||||
| -------- | ------------- | ---------------------------- | ------- |
|
||||
| IDE Mode | `ide.enabled` | Enable IDE integration mode. | `false` |
|
||||
|
||||
### Model
|
||||
|
||||
| UI Label | Setting | Description | Default |
|
||||
| ----------------------- | ---------------------------- | -------------------------------------------------------------------------------------- | ------- |
|
||||
| Max Session Turns | `model.maxSessionTurns` | Maximum number of user/model/tool turns to keep in a session. -1 means unlimited. | `-1` |
|
||||
| Compression Threshold | `model.compressionThreshold` | The fraction of context usage at which to trigger context compression (e.g. 0.2, 0.3). | `0.2` |
|
||||
| Skip Next Speaker Check | `model.skipNextSpeakerCheck` | Skip the next speaker check. | `true` |
|
||||
|
||||
### Context
|
||||
|
||||
| UI Label | Setting | Description | Default |
|
||||
| ------------------------------------ | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
|
||||
| Memory Discovery Max Dirs | `context.discoveryMaxDirs` | Maximum number of directories to search for memory. | `200` |
|
||||
| Load Memory From Include Directories | `context.loadMemoryFromIncludeDirectories` | Controls how /memory refresh loads GEMINI.md files. When true, include directories are scanned; when false, only the current directory is used. | `false` |
|
||||
| Respect .gitignore | `context.fileFiltering.respectGitIgnore` | Respect .gitignore files when searching. | `true` |
|
||||
| Respect .geminiignore | `context.fileFiltering.respectGeminiIgnore` | Respect .geminiignore files when searching. | `true` |
|
||||
| Enable Recursive File Search | `context.fileFiltering.enableRecursiveFileSearch` | Enable recursive file search functionality when completing @ references in the prompt. | `true` |
|
||||
| Disable Fuzzy Search | `context.fileFiltering.disableFuzzySearch` | Disable fuzzy search when searching for files. | `false` |
|
||||
|
||||
### Tools
|
||||
|
||||
| UI Label | Setting | Description | Default |
|
||||
| -------------------------------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------------- | ------- |
|
||||
| Enable Interactive Shell | `tools.shell.enableInteractiveShell` | Use node-pty for an interactive shell experience. Fallback to child_process still applies. | `true` |
|
||||
| Show Color | `tools.shell.showColor` | Show color in shell output. | `false` |
|
||||
| Auto Accept | `tools.autoAccept` | Automatically accept and execute tool calls that are considered safe (e.g., read-only operations). | `false` |
|
||||
| Use Ripgrep | `tools.useRipgrep` | Use ripgrep for file content search instead of the fallback implementation. Provides faster search performance. | `true` |
|
||||
| Enable Tool Output Truncation | `tools.enableToolOutputTruncation` | Enable truncation of large tool outputs. | `true` |
|
||||
| Tool Output Truncation Threshold | `tools.truncateToolOutputThreshold` | Truncate tool output if it is larger than this many characters. Set to -1 to disable. | `10000` |
|
||||
| Tool Output Truncation Lines | `tools.truncateToolOutputLines` | The number of lines to keep when truncating tool output. | `100` |
|
||||
| Enable Message Bus Integration | `tools.enableMessageBusIntegration` | Enable policy-based tool confirmation via message bus integration. | `false` |
|
||||
|
||||
### Security
|
||||
|
||||
| UI Label | Setting | Description | Default |
|
||||
| -------------------------- | ------------------------------ | -------------------------------------------------- | ------- |
|
||||
| Disable YOLO Mode | `security.disableYoloMode` | Disable YOLO mode, even if enabled by a flag. | `false` |
|
||||
| Blocks extensions from Git | `security.blockGitExtensions` | Blocks installing and loading extensions from Git. | `false` |
|
||||
| Folder Trust | `security.folderTrust.enabled` | Setting to track whether Folder trust is enabled. | `false` |
|
||||
|
||||
### Experimental
|
||||
|
||||
| UI Label | Setting | Description | Default |
|
||||
| ----------------------------------- | ------------------------------------------------------- | ------------------------------------------------------------ | ------- |
|
||||
| Enable Codebase Investigator | `experimental.codebaseInvestigatorSettings.enabled` | Enable the Codebase Investigator agent. | `true` |
|
||||
| Codebase Investigator Max Num Turns | `experimental.codebaseInvestigatorSettings.maxNumTurns` | Maximum number of turns for the Codebase Investigator agent. | `10` |
|
||||
+445
-114
@@ -15,8 +15,29 @@ Learn how to enable and setup OpenTelemetry for Gemini CLI.
|
||||
- [Collector-Based Export (Advanced)](#collector-based-export-advanced-1)
|
||||
- [Logs and Metrics](#logs-and-metrics)
|
||||
- [Logs](#logs)
|
||||
- [Sessions](#sessions)
|
||||
- [Tools](#tools)
|
||||
- [Files](#files)
|
||||
- [API](#api)
|
||||
- [Model Routing](#model-routing)
|
||||
- [Chat and Streaming](#chat-and-streaming)
|
||||
- [Resilience](#resilience)
|
||||
- [Extensions](#extensions)
|
||||
- [Agent Runs](#agent-runs)
|
||||
- [IDE](#ide)
|
||||
- [UI](#ui)
|
||||
- [Metrics](#metrics)
|
||||
- [Custom](#custom)
|
||||
- [Sessions](#sessions-1)
|
||||
- [Tools](#tools-1)
|
||||
- [API](#api-1)
|
||||
- [Token Usage](#token-usage)
|
||||
- [Files](#files-1)
|
||||
- [Chat and Streaming](#chat-and-streaming-1)
|
||||
- [Model Routing](#model-routing-1)
|
||||
- [Agent Runs](#agent-runs-1)
|
||||
- [UI](#ui-1)
|
||||
- [Performance](#performance)
|
||||
- [GenAI Semantic Convention](#genai-semantic-convention)
|
||||
|
||||
## Key Benefits
|
||||
@@ -51,17 +72,17 @@ observability framework — Gemini CLI's observability system provides:
|
||||
## Configuration
|
||||
|
||||
All telemetry behavior is controlled through your `.gemini/settings.json` file.
|
||||
These settings can be overridden by environment variables or CLI flags.
|
||||
Environment variables can be used to override the settings in the file.
|
||||
|
||||
| Setting | Environment Variable | CLI Flag | Description | Values | Default |
|
||||
| -------------- | -------------------------------- | -------------------------------------------------------- | ------------------------------------------------- | ----------------- | ----------------------- |
|
||||
| `enabled` | `GEMINI_TELEMETRY_ENABLED` | `--telemetry` / `--no-telemetry` | Enable or disable telemetry | `true`/`false` | `false` |
|
||||
| `target` | `GEMINI_TELEMETRY_TARGET` | `--telemetry-target <local\|gcp>` | Where to send telemetry data | `"gcp"`/`"local"` | `"local"` |
|
||||
| `otlpEndpoint` | `GEMINI_TELEMETRY_OTLP_ENDPOINT` | `--telemetry-otlp-endpoint <URL>` | OTLP collector endpoint | URL string | `http://localhost:4317` |
|
||||
| `otlpProtocol` | `GEMINI_TELEMETRY_OTLP_PROTOCOL` | `--telemetry-otlp-protocol <grpc\|http>` | OTLP transport protocol | `"grpc"`/`"http"` | `"grpc"` |
|
||||
| `outfile` | `GEMINI_TELEMETRY_OUTFILE` | `--telemetry-outfile <path>` | Save telemetry to file (overrides `otlpEndpoint`) | file path | - |
|
||||
| `logPrompts` | `GEMINI_TELEMETRY_LOG_PROMPTS` | `--telemetry-log-prompts` / `--no-telemetry-log-prompts` | Include prompts in telemetry logs | `true`/`false` | `true` |
|
||||
| `useCollector` | `GEMINI_TELEMETRY_USE_COLLECTOR` | - | Use external OTLP collector (advanced) | `true`/`false` | `false` |
|
||||
| Setting | Environment Variable | Description | Values | Default |
|
||||
| -------------- | -------------------------------- | ------------------------------------------------- | ----------------- | ----------------------- |
|
||||
| `enabled` | `GEMINI_TELEMETRY_ENABLED` | Enable or disable telemetry | `true`/`false` | `false` |
|
||||
| `target` | `GEMINI_TELEMETRY_TARGET` | Where to send telemetry data | `"gcp"`/`"local"` | `"local"` |
|
||||
| `otlpEndpoint` | `GEMINI_TELEMETRY_OTLP_ENDPOINT` | OTLP collector endpoint | URL string | `http://localhost:4317` |
|
||||
| `otlpProtocol` | `GEMINI_TELEMETRY_OTLP_PROTOCOL` | OTLP transport protocol | `"grpc"`/`"http"` | `"grpc"` |
|
||||
| `outfile` | `GEMINI_TELEMETRY_OUTFILE` | Save telemetry to file (overrides `otlpEndpoint`) | file path | - |
|
||||
| `logPrompts` | `GEMINI_TELEMETRY_LOG_PROMPTS` | Include prompts in telemetry logs | `true`/`false` | `true` |
|
||||
| `useCollector` | `GEMINI_TELEMETRY_USE_COLLECTOR` | Use external OTLP collector (advanced) | `true`/`false` | `false` |
|
||||
|
||||
**Note on boolean environment variables:** For the boolean settings (`enabled`,
|
||||
`logPrompts`, `useCollector`), setting the corresponding environment variable to
|
||||
@@ -204,16 +225,20 @@ For local development and debugging, you can capture telemetry data locally:
|
||||
The following section describes the structure of logs and metrics generated for
|
||||
Gemini CLI.
|
||||
|
||||
The `session.id`, `installation.id`, and `user.email` are included as common
|
||||
attributes on all logs and metrics.
|
||||
The `session.id`, `installation.id`, and `user.email` (available only when
|
||||
authenticated with a Google account) are included as common attributes on all
|
||||
logs and metrics.
|
||||
|
||||
### Logs
|
||||
|
||||
Logs are timestamped records of specific events. The following events are logged
|
||||
for Gemini CLI:
|
||||
for Gemini CLI, grouped by category.
|
||||
|
||||
- `gemini_cli.config`: This event occurs once at startup with the CLI's
|
||||
configuration.
|
||||
#### Sessions
|
||||
|
||||
Captures startup configuration and user prompt submissions.
|
||||
|
||||
- `gemini_cli.config`: Emitted once at startup with the CLI configuration.
|
||||
- **Attributes**:
|
||||
- `model` (string)
|
||||
- `embedding_model` (string)
|
||||
@@ -222,81 +247,47 @@ for Gemini CLI:
|
||||
- `approval_mode` (string)
|
||||
- `api_key_enabled` (boolean)
|
||||
- `vertex_ai_enabled` (boolean)
|
||||
- `code_assist_enabled` (boolean)
|
||||
- `log_prompts_enabled` (boolean)
|
||||
- `log_user_prompts_enabled` (boolean)
|
||||
- `file_filtering_respect_git_ignore` (boolean)
|
||||
- `debug_mode` (boolean)
|
||||
- `mcp_servers` (string)
|
||||
- `output_format` (string: "text", "json", or "stream-json")
|
||||
- `mcp_servers_count` (int)
|
||||
- `extensions` (string)
|
||||
- `extension_ids` (string)
|
||||
- `extension_count` (int)
|
||||
- `mcp_tools` (string, if applicable)
|
||||
- `mcp_tools_count` (int, if applicable)
|
||||
- `output_format` ("text", "json", or "stream-json")
|
||||
|
||||
- `gemini_cli.user_prompt`: This event occurs when a user submits a prompt.
|
||||
- `gemini_cli.user_prompt`: Emitted when a user submits a prompt.
|
||||
- **Attributes**:
|
||||
- `prompt_length` (int)
|
||||
- `prompt_id` (string)
|
||||
- `prompt` (string, this attribute is excluded if `log_prompts_enabled` is
|
||||
configured to be `false`)
|
||||
- `prompt` (string; excluded if `telemetry.logPrompts` is `false`)
|
||||
- `auth_type` (string)
|
||||
|
||||
- `gemini_cli.tool_call`: This event occurs for each function call.
|
||||
#### Tools
|
||||
|
||||
Captures tool executions, output truncation, and Smart Edit behavior.
|
||||
|
||||
- `gemini_cli.tool_call`: Emitted for each tool (function) call.
|
||||
- **Attributes**:
|
||||
- `function_name`
|
||||
- `function_args`
|
||||
- `duration_ms`
|
||||
- `success` (boolean)
|
||||
- `decision` (string: "accept", "reject", "auto_accept", or "modify", if
|
||||
applicable)
|
||||
- `decision` ("accept", "reject", "auto_accept", or "modify", if applicable)
|
||||
- `error` (if applicable)
|
||||
- `error_type` (if applicable)
|
||||
- `prompt_id` (string)
|
||||
- `tool_type` ("native" or "mcp")
|
||||
- `mcp_server_name` (string, if applicable)
|
||||
- `extension_name` (string, if applicable)
|
||||
- `extension_id` (string, if applicable)
|
||||
- `content_length` (int, if applicable)
|
||||
- `metadata` (if applicable, dictionary of string -> any)
|
||||
- `metadata` (if applicable)
|
||||
|
||||
- `gemini_cli.file_operation`: This event occurs for each file operation.
|
||||
- **Attributes**:
|
||||
- `tool_name` (string)
|
||||
- `operation` (string: "create", "read", "update")
|
||||
- `lines` (int, if applicable)
|
||||
- `mimetype` (string, if applicable)
|
||||
- `extension` (string, if applicable)
|
||||
- `programming_language` (string, if applicable)
|
||||
- `diff_stat` (json string, if applicable): A JSON string with the following
|
||||
members:
|
||||
- `ai_added_lines` (int)
|
||||
- `ai_removed_lines` (int)
|
||||
- `user_added_lines` (int)
|
||||
- `user_removed_lines` (int)
|
||||
|
||||
- `gemini_cli.api_request`: This event occurs when making a request to Gemini
|
||||
API.
|
||||
- **Attributes**:
|
||||
- `model`
|
||||
- `request_text` (if applicable)
|
||||
|
||||
- `gemini_cli.api_error`: This event occurs if the API request fails.
|
||||
- **Attributes**:
|
||||
- `model`
|
||||
- `error`
|
||||
- `error_type`
|
||||
- `status_code`
|
||||
- `duration_ms`
|
||||
- `auth_type`
|
||||
|
||||
- `gemini_cli.api_response`: This event occurs upon receiving a response from
|
||||
Gemini API.
|
||||
- **Attributes**:
|
||||
- `model`
|
||||
- `status_code`
|
||||
- `duration_ms`
|
||||
- `error` (optional)
|
||||
- `input_token_count`
|
||||
- `output_token_count`
|
||||
- `cached_content_token_count`
|
||||
- `thoughts_token_count`
|
||||
- `tool_token_count`
|
||||
- `response_text` (if applicable)
|
||||
- `auth_type`
|
||||
|
||||
- `gemini_cli.tool_output_truncated`: This event occurs when the output of a
|
||||
tool call is too large and gets truncated.
|
||||
- `gemini_cli.tool_output_truncated`: Output of a tool call was truncated.
|
||||
- **Attributes**:
|
||||
- `tool_name` (string)
|
||||
- `original_content_length` (int)
|
||||
@@ -305,32 +296,231 @@ for Gemini CLI:
|
||||
- `lines` (int)
|
||||
- `prompt_id` (string)
|
||||
|
||||
- `gemini_cli.malformed_json_response`: This event occurs when a `generateJson`
|
||||
response from Gemini API cannot be parsed as a json.
|
||||
- `gemini_cli.smart_edit_strategy`: Smart Edit strategy chosen.
|
||||
- **Attributes**:
|
||||
- `model`
|
||||
- `strategy` (string)
|
||||
|
||||
- `gemini_cli.flash_fallback`: This event occurs when Gemini CLI switches to
|
||||
flash as fallback.
|
||||
- `gemini_cli.smart_edit_correction`: Smart Edit correction result.
|
||||
- **Attributes**:
|
||||
- `auth_type`
|
||||
- `correction` ("success" | "failure")
|
||||
|
||||
- `gemini_cli.slash_command`: This event occurs when a user executes a slash
|
||||
command.
|
||||
- `gen_ai.client.inference.operation.details`: This event provides detailed
|
||||
information about the GenAI operation, aligned with [OpenTelemetry GenAI
|
||||
semantic conventions for events].
|
||||
- **Attributes**:
|
||||
- `gen_ai.request.model` (string)
|
||||
- `gen_ai.provider.name` (string)
|
||||
- `gen_ai.operation.name` (string)
|
||||
- `gen_ai.input.messages` (json string)
|
||||
- `gen_ai.output.messages` (json string)
|
||||
- `gen_ai.response.finish_reasons` (array of strings)
|
||||
- `gen_ai.usage.input_tokens` (int)
|
||||
- `gen_ai.usage.output_tokens` (int)
|
||||
- `gen_ai.request.temperature` (float)
|
||||
- `gen_ai.request.top_p` (float)
|
||||
- `gen_ai.request.top_k` (int)
|
||||
- `gen_ai.request.max_tokens` (int)
|
||||
- `gen_ai.system_instructions` (json string)
|
||||
- `server.address` (string)
|
||||
- `server.port` (int)
|
||||
|
||||
#### Files
|
||||
|
||||
Tracks file operations performed by tools.
|
||||
|
||||
- `gemini_cli.file_operation`: Emitted for each file operation.
|
||||
- **Attributes**:
|
||||
- `tool_name` (string)
|
||||
- `operation` ("create" | "read" | "update")
|
||||
- `lines` (int, optional)
|
||||
- `mimetype` (string, optional)
|
||||
- `extension` (string, optional)
|
||||
- `programming_language` (string, optional)
|
||||
|
||||
#### API
|
||||
|
||||
Captures Gemini API requests, responses, and errors.
|
||||
|
||||
- `gemini_cli.api_request`: Request sent to Gemini API.
|
||||
- **Attributes**:
|
||||
- `model` (string)
|
||||
- `prompt_id` (string)
|
||||
- `request_text` (string, optional)
|
||||
|
||||
- `gemini_cli.api_response`: Response received from Gemini API.
|
||||
- **Attributes**:
|
||||
- `model` (string)
|
||||
- `status_code` (int|string)
|
||||
- `duration_ms` (int)
|
||||
- `input_token_count` (int)
|
||||
- `output_token_count` (int)
|
||||
- `cached_content_token_count` (int)
|
||||
- `thoughts_token_count` (int)
|
||||
- `tool_token_count` (int)
|
||||
- `total_token_count` (int)
|
||||
- `response_text` (string, optional)
|
||||
- `prompt_id` (string)
|
||||
- `auth_type` (string)
|
||||
|
||||
- `gemini_cli.api_error`: API request failed.
|
||||
- **Attributes**:
|
||||
- `model` (string)
|
||||
- `error` (string)
|
||||
- `error_type` (string)
|
||||
- `status_code` (int|string)
|
||||
- `duration_ms` (int)
|
||||
- `prompt_id` (string)
|
||||
- `auth_type` (string)
|
||||
|
||||
- `gemini_cli.malformed_json_response`: `generateJson` response could not be
|
||||
parsed.
|
||||
- **Attributes**:
|
||||
- `model` (string)
|
||||
|
||||
#### Model Routing
|
||||
|
||||
Tracks model selections via slash commands and router decisions.
|
||||
|
||||
- `gemini_cli.slash_command`: A slash command was executed.
|
||||
- **Attributes**:
|
||||
- `command` (string)
|
||||
- `subcommand` (string, if applicable)
|
||||
- `subcommand` (string, optional)
|
||||
- `status` ("success" | "error")
|
||||
|
||||
- `gemini_cli.extension_enable`: This event occurs when an extension is enabled
|
||||
- `gemini_cli.extension_install`: This event occurs when an extension is
|
||||
installed
|
||||
- `gemini_cli.slash_command.model`: Model was selected via slash command.
|
||||
- **Attributes**:
|
||||
- `model_name` (string)
|
||||
|
||||
- `gemini_cli.model_routing`: Model router made a decision.
|
||||
- **Attributes**:
|
||||
- `decision_model` (string)
|
||||
- `decision_source` (string)
|
||||
- `routing_latency_ms` (int)
|
||||
- `reasoning` (string, optional)
|
||||
- `failed` (boolean)
|
||||
- `error_message` (string, optional)
|
||||
|
||||
#### Chat and Streaming
|
||||
|
||||
Observes streaming integrity, compression, and retry behavior.
|
||||
|
||||
- `gemini_cli.chat_compression`: Chat context was compressed.
|
||||
- **Attributes**:
|
||||
- `tokens_before` (int)
|
||||
- `tokens_after` (int)
|
||||
|
||||
- `gemini_cli.chat.invalid_chunk`: Invalid chunk received from a stream.
|
||||
- **Attributes**:
|
||||
- `error.message` (string, optional)
|
||||
|
||||
- `gemini_cli.chat.content_retry`: Retry triggered due to a content error.
|
||||
- **Attributes**:
|
||||
- `attempt_number` (int)
|
||||
- `error_type` (string)
|
||||
- `retry_delay_ms` (int)
|
||||
- `model` (string)
|
||||
|
||||
- `gemini_cli.chat.content_retry_failure`: All content retries failed.
|
||||
- **Attributes**:
|
||||
- `total_attempts` (int)
|
||||
- `final_error_type` (string)
|
||||
- `total_duration_ms` (int, optional)
|
||||
- `model` (string)
|
||||
|
||||
- `gemini_cli.conversation_finished`: Conversation session ended.
|
||||
- **Attributes**:
|
||||
- `approvalMode` (string)
|
||||
- `turnCount` (int)
|
||||
|
||||
- `gemini_cli.next_speaker_check`: Next speaker determination.
|
||||
- **Attributes**:
|
||||
- `prompt_id` (string)
|
||||
- `finish_reason` (string)
|
||||
- `result` (string)
|
||||
|
||||
#### Resilience
|
||||
|
||||
Records fallback mechanisms for models and network operations.
|
||||
|
||||
- `gemini_cli.flash_fallback`: Switched to a flash model as fallback.
|
||||
- **Attributes**:
|
||||
- `auth_type` (string)
|
||||
|
||||
- `gemini_cli.ripgrep_fallback`: Switched to grep as fallback for file search.
|
||||
- **Attributes**:
|
||||
- `error` (string, optional)
|
||||
|
||||
- `gemini_cli.web_fetch_fallback_attempt`: Attempted web-fetch fallback.
|
||||
- **Attributes**:
|
||||
- `reason` ("private_ip" | "primary_failed")
|
||||
|
||||
#### Extensions
|
||||
|
||||
Tracks extension lifecycle and settings changes.
|
||||
|
||||
- `gemini_cli.extension_install`: An extension was installed.
|
||||
- **Attributes**:
|
||||
- `extension_name` (string)
|
||||
- `extension_version` (string)
|
||||
- `extension_source` (string)
|
||||
- `status` (string)
|
||||
- `gemini_cli.extension_uninstall`: This event occurs when an extension is
|
||||
uninstalled
|
||||
|
||||
- `gemini_cli.extension_uninstall`: An extension was uninstalled.
|
||||
- **Attributes**:
|
||||
- `extension_name` (string)
|
||||
- `status` (string)
|
||||
|
||||
- `gemini_cli.extension_enable`: An extension was enabled.
|
||||
- **Attributes**:
|
||||
- `extension_name` (string)
|
||||
- `setting_scope` (string)
|
||||
|
||||
- `gemini_cli.extension_disable`: An extension was disabled.
|
||||
- **Attributes**:
|
||||
- `extension_name` (string)
|
||||
- `setting_scope` (string)
|
||||
|
||||
- `gemini_cli.extension_update`: An extension was updated.
|
||||
- **Attributes**:
|
||||
- `extension_name` (string)
|
||||
- `extension_version` (string)
|
||||
- `extension_previous_version` (string)
|
||||
- `extension_source` (string)
|
||||
- `status` (string)
|
||||
|
||||
#### Agent Runs
|
||||
|
||||
Tracks agent lifecycle and outcomes.
|
||||
|
||||
- `gemini_cli.agent.start`: Agent run started.
|
||||
- **Attributes**:
|
||||
- `agent_id` (string)
|
||||
- `agent_name` (string)
|
||||
|
||||
- `gemini_cli.agent.finish`: Agent run finished.
|
||||
- **Attributes**:
|
||||
- `agent_id` (string)
|
||||
- `agent_name` (string)
|
||||
- `duration_ms` (int)
|
||||
- `turn_count` (int)
|
||||
- `terminate_reason` (string)
|
||||
|
||||
#### IDE
|
||||
|
||||
Captures IDE connectivity and conversation lifecycle events.
|
||||
|
||||
- `gemini_cli.ide_connection`: IDE companion connection.
|
||||
- **Attributes**:
|
||||
- `connection_type` (string)
|
||||
|
||||
#### UI
|
||||
|
||||
Tracks terminal rendering issues and related signals.
|
||||
|
||||
- `kitty_sequence_overflow`: Terminal kitty control sequence overflow.
|
||||
- **Attributes**:
|
||||
- `sequence_length` (int)
|
||||
- `truncated_sequence` (string)
|
||||
|
||||
### Metrics
|
||||
|
||||
@@ -338,27 +528,31 @@ Metrics are numerical measurements of behavior over time.
|
||||
|
||||
#### Custom
|
||||
|
||||
##### Sessions
|
||||
|
||||
Counts CLI sessions at startup.
|
||||
|
||||
- `gemini_cli.session.count` (Counter, Int): Incremented once per CLI startup.
|
||||
|
||||
##### Tools
|
||||
|
||||
Measures tool usage and latency.
|
||||
|
||||
- `gemini_cli.tool.call.count` (Counter, Int): Counts tool calls.
|
||||
- **Attributes**:
|
||||
- `function_name`
|
||||
- `success` (boolean)
|
||||
- `decision` (string: "accept", "reject", or "modify", if applicable)
|
||||
- `tool_type` (string: "mcp", or "native", if applicable)
|
||||
- `model_added_lines` (Int, optional): Lines added by model in the proposed
|
||||
changes, if applicable
|
||||
- `model_removed_lines` (Int, optional): Lines removed by model in the
|
||||
proposed changes, if applicable
|
||||
- `user_added_lines` (Int, optional): Lines added by user edits after model
|
||||
proposal, if applicable
|
||||
- `user_removed_lines` (Int, optional): Lines removed by user edits after
|
||||
model proposal, if applicable
|
||||
- `decision` (string: "accept", "reject", "modify", or "auto_accept", if
|
||||
applicable)
|
||||
- `tool_type` (string: "mcp" or "native", if applicable)
|
||||
|
||||
- `gemini_cli.tool.call.latency` (Histogram, ms): Measures tool call latency.
|
||||
- **Attributes**:
|
||||
- `function_name`
|
||||
- `decision` (string: "accept", "reject", or "modify", if applicable)
|
||||
|
||||
##### API
|
||||
|
||||
Tracks API request volume and latency.
|
||||
|
||||
- `gemini_cli.api.request.count` (Counter, Int): Counts all API requests.
|
||||
- **Attributes**:
|
||||
@@ -370,32 +564,167 @@ Metrics are numerical measurements of behavior over time.
|
||||
latency.
|
||||
- **Attributes**:
|
||||
- `model`
|
||||
- **Note**: This metric overlaps with `gen_ai.client.operation.duration` below
|
||||
that's compliant with GenAI Semantic Conventions.
|
||||
- Note: Overlaps with `gen_ai.client.operation.duration` (GenAI conventions).
|
||||
|
||||
- `gemini_cli.token.usage` (Counter, Int): Counts the number of tokens used.
|
||||
##### Token Usage
|
||||
|
||||
Tracks tokens used by model and type.
|
||||
|
||||
- `gemini_cli.token.usage` (Counter, Int): Counts tokens used.
|
||||
- **Attributes**:
|
||||
- `model`
|
||||
- `type` (string: "input", "output", "thought", "cache", or "tool")
|
||||
- **Note**: This metric overlaps with `gen_ai.client.token.usage` below for
|
||||
`input`/`output` token types that's compliant with GenAI Semantic
|
||||
Conventions.
|
||||
- `type` ("input", "output", "thought", "cache", or "tool")
|
||||
- Note: Overlaps with `gen_ai.client.token.usage` for `input`/`output`.
|
||||
|
||||
##### Files
|
||||
|
||||
Counts file operations with basic context.
|
||||
|
||||
- `gemini_cli.file.operation.count` (Counter, Int): Counts file operations.
|
||||
- **Attributes**:
|
||||
- `operation` (string: "create", "read", "update"): The type of file
|
||||
operation.
|
||||
- `lines` (Int, if applicable): Number of lines in the file.
|
||||
- `mimetype` (string, if applicable): Mimetype of the file.
|
||||
- `extension` (string, if applicable): File extension of the file.
|
||||
- `programming_language` (string, if applicable): The programming language
|
||||
of the file.
|
||||
- `operation` ("create", "read", "update")
|
||||
- `lines` (Int, optional)
|
||||
- `mimetype` (string, optional)
|
||||
- `extension` (string, optional)
|
||||
- `programming_language` (string, optional)
|
||||
|
||||
- `gemini_cli.lines.changed` (Counter, Int): Number of lines changed (from file
|
||||
diffs).
|
||||
- **Attributes**:
|
||||
- `function_name`
|
||||
- `type` ("added" or "removed")
|
||||
|
||||
##### Chat and Streaming
|
||||
|
||||
Resilience counters for compression, invalid chunks, and retries.
|
||||
|
||||
- `gemini_cli.chat_compression` (Counter, Int): Counts chat compression
|
||||
operations
|
||||
operations.
|
||||
- **Attributes**:
|
||||
- `tokens_before`: (Int): Number of tokens in context prior to compression
|
||||
- `tokens_after`: (Int): Number of tokens in context after compression
|
||||
- `tokens_before` (Int)
|
||||
- `tokens_after` (Int)
|
||||
|
||||
- `gemini_cli.chat.invalid_chunk.count` (Counter, Int): Counts invalid chunks
|
||||
from streams.
|
||||
|
||||
- `gemini_cli.chat.content_retry.count` (Counter, Int): Counts retries due to
|
||||
content errors.
|
||||
|
||||
- `gemini_cli.chat.content_retry_failure.count` (Counter, Int): Counts requests
|
||||
where all content retries failed.
|
||||
|
||||
##### Model Routing
|
||||
|
||||
Routing latency/failures and slash-command selections.
|
||||
|
||||
- `gemini_cli.slash_command.model.call_count` (Counter, Int): Counts model
|
||||
selections via slash command.
|
||||
- **Attributes**:
|
||||
- `slash_command.model.model_name` (string)
|
||||
|
||||
- `gemini_cli.model_routing.latency` (Histogram, ms): Model routing decision
|
||||
latency.
|
||||
- **Attributes**:
|
||||
- `routing.decision_model` (string)
|
||||
- `routing.decision_source` (string)
|
||||
|
||||
- `gemini_cli.model_routing.failure.count` (Counter, Int): Counts model routing
|
||||
failures.
|
||||
- **Attributes**:
|
||||
- `routing.decision_source` (string)
|
||||
- `routing.error_message` (string)
|
||||
|
||||
##### Agent Runs
|
||||
|
||||
Agent lifecycle metrics: runs, durations, and turns.
|
||||
|
||||
- `gemini_cli.agent.run.count` (Counter, Int): Counts agent runs.
|
||||
- **Attributes**:
|
||||
- `agent_name` (string)
|
||||
- `terminate_reason` (string)
|
||||
|
||||
- `gemini_cli.agent.duration` (Histogram, ms): Agent run durations.
|
||||
- **Attributes**:
|
||||
- `agent_name` (string)
|
||||
|
||||
- `gemini_cli.agent.turns` (Histogram, turns): Turns taken per agent run.
|
||||
- **Attributes**:
|
||||
- `agent_name` (string)
|
||||
|
||||
##### UI
|
||||
|
||||
UI stability signals such as flicker count.
|
||||
|
||||
- `gemini_cli.ui.flicker.count` (Counter, Int): Counts UI frames that flicker
|
||||
(render taller than terminal).
|
||||
|
||||
##### Performance
|
||||
|
||||
Optional performance monitoring for startup, CPU/memory, and phase timing.
|
||||
|
||||
- `gemini_cli.startup.duration` (Histogram, ms): CLI startup time by phase.
|
||||
- **Attributes**:
|
||||
- `phase` (string)
|
||||
- `details` (map, optional)
|
||||
|
||||
- `gemini_cli.memory.usage` (Histogram, bytes): Memory usage.
|
||||
- **Attributes**:
|
||||
- `memory_type` ("heap_used", "heap_total", "external", "rss")
|
||||
- `component` (string, optional)
|
||||
|
||||
- `gemini_cli.cpu.usage` (Histogram, percent): CPU usage percentage.
|
||||
- **Attributes**:
|
||||
- `component` (string, optional)
|
||||
|
||||
- `gemini_cli.tool.queue.depth` (Histogram, count): Number of tools in the
|
||||
execution queue.
|
||||
|
||||
- `gemini_cli.tool.execution.breakdown` (Histogram, ms): Tool time by phase.
|
||||
- **Attributes**:
|
||||
- `function_name` (string)
|
||||
- `phase` ("validation", "preparation", "execution", "result_processing")
|
||||
|
||||
- `gemini_cli.api.request.breakdown` (Histogram, ms): API request time by phase.
|
||||
- **Attributes**:
|
||||
- `model` (string)
|
||||
- `phase` ("request_preparation", "network_latency", "response_processing",
|
||||
"token_processing")
|
||||
|
||||
- `gemini_cli.token.efficiency` (Histogram, ratio): Token efficiency metrics.
|
||||
- **Attributes**:
|
||||
- `model` (string)
|
||||
- `metric` (string)
|
||||
- `context` (string, optional)
|
||||
|
||||
- `gemini_cli.performance.score` (Histogram, score): Composite performance
|
||||
score.
|
||||
- **Attributes**:
|
||||
- `category` (string)
|
||||
- `baseline` (number, optional)
|
||||
|
||||
- `gemini_cli.performance.regression` (Counter, Int): Regression detection
|
||||
events.
|
||||
- **Attributes**:
|
||||
- `metric` (string)
|
||||
- `severity` ("low", "medium", "high")
|
||||
- `current_value` (number)
|
||||
- `baseline_value` (number)
|
||||
|
||||
- `gemini_cli.performance.regression.percentage_change` (Histogram, percent):
|
||||
Percent change from baseline when regression detected.
|
||||
- **Attributes**:
|
||||
- `metric` (string)
|
||||
- `severity` ("low", "medium", "high")
|
||||
- `current_value` (number)
|
||||
- `baseline_value` (number)
|
||||
|
||||
- `gemini_cli.performance.baseline.comparison` (Histogram, percent): Comparison
|
||||
to baseline.
|
||||
- **Attributes**:
|
||||
- `metric` (string)
|
||||
- `category` (string)
|
||||
- `current_value` (number)
|
||||
- `baseline_value` (number)
|
||||
|
||||
#### GenAI Semantic Convention
|
||||
|
||||
@@ -434,3 +763,5 @@ standardized observability across GenAI applications:
|
||||
|
||||
[OpenTelemetry GenAI semantic conventions]:
|
||||
https://github.com/open-telemetry/semantic-conventions/blob/main/docs/gen-ai/gen-ai-metrics.md
|
||||
[OpenTelemetry GenAI semantic conventions for events]:
|
||||
https://github.com/open-telemetry/semantic-conventions/blob/8b4f210f43136e57c1f6f47292eb6d38e3bf30bb/docs/gen-ai/gen-ai-events.md
|
||||
|
||||
@@ -88,6 +88,11 @@ color keys. For example:
|
||||
- `DiffRemoved` (optional, for removed lines in diffs)
|
||||
- `DiffModified` (optional, for modified lines in diffs)
|
||||
|
||||
You can also override individual UI text roles by adding a nested `text` object.
|
||||
This object supports the keys `primary`, `secondary`, `link`, `accent`, and
|
||||
`response`. When `text.response` is provided it takes precedence over
|
||||
`text.primary` for rendering model responses in chat.
|
||||
|
||||
**Required Properties:**
|
||||
|
||||
- `name` (must match the key in the `customThemes` object and be a string)
|
||||
|
||||
@@ -8,7 +8,7 @@ This page contains tutorials for interacting with Gemini CLI.
|
||||
> and understand the tools it provides. Your use of third-party servers is at
|
||||
> your own risk.
|
||||
|
||||
This tutorial demonstrates how to set up a MCP server, using the
|
||||
This tutorial demonstrates how to set up an MCP server, using the
|
||||
[GitHub MCP server](https://github.com/github/github-mcp-server) as an example.
|
||||
The GitHub MCP server provides tools for interacting with GitHub repositories,
|
||||
such as creating issues and commenting on pull requests.
|
||||
|
||||
@@ -11,6 +11,8 @@ requests sent from `packages/cli`. For a general overview of Gemini CLI, see the
|
||||
registered, and used by the core.
|
||||
- **[Memory Import Processor](./memport.md):** Documentation for the modular
|
||||
GEMINI.md import feature using @file.md syntax.
|
||||
- **[Policy Engine](./policy-engine.md):** Use the Policy Engine for
|
||||
fine-grained control over tool execution.
|
||||
|
||||
## Role of the core
|
||||
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
# Policy Engine
|
||||
|
||||
:::note This feature is currently in testing. To enable it, set
|
||||
`tools.enableMessageBusIntegration` to `true` in your `settings.json` file. :::
|
||||
|
||||
The Gemini CLI includes a powerful policy engine that provides fine-grained
|
||||
control over tool execution. It allows users and administrators to define rules
|
||||
that determine whether a tool call should be allowed, denied, or require user
|
||||
confirmation.
|
||||
|
||||
## Core concepts
|
||||
|
||||
The policy engine operates on a set of rules. Each rule is a combination of
|
||||
conditions and a resulting decision. When a large language model wants to
|
||||
execute a tool, the policy engine evaluates all rules to find the
|
||||
highest-priority rule that matches the tool call.
|
||||
|
||||
A rule consists of the following main components:
|
||||
|
||||
- **Conditions**: Criteria that a tool call must meet for the rule to apply.
|
||||
This can include the tool's name, the arguments provided to it, or the current
|
||||
approval mode.
|
||||
- **Decision**: The action to take if the rule matches (`allow`, `deny`, or
|
||||
`ask_user`).
|
||||
- **Priority**: A number that determines the rule's precedence. Higher numbers
|
||||
win.
|
||||
|
||||
For example, this rule will ask for user confirmation before executing any `git`
|
||||
command.
|
||||
|
||||
```toml
|
||||
[[rule]]
|
||||
toolName = "run_shell_command"
|
||||
commandPrefix = "git "
|
||||
decision = "ask_user"
|
||||
priority = 100
|
||||
```
|
||||
|
||||
### Conditions
|
||||
|
||||
Conditions are the criteria that a tool call must meet for a rule to apply. The
|
||||
primary conditions are the tool's name and its arguments.
|
||||
|
||||
#### Tool Name
|
||||
|
||||
The `toolName` in the rule must match the name of the tool being called.
|
||||
|
||||
- **Wildcards**: For Model-hosting-protocol (MCP) servers, you can use a
|
||||
wildcard. A `toolName` of `my-server__*` will match any tool from the
|
||||
`my-server` MCP.
|
||||
|
||||
#### Arguments Pattern
|
||||
|
||||
If `argsPattern` is specified, the tool's arguments are converted to a stable
|
||||
JSON string, which is then tested against the provided regular expression. If
|
||||
the arguments don't match the pattern, the rule does not apply.
|
||||
|
||||
### Decisions
|
||||
|
||||
There are three possible decisions a rule can enforce:
|
||||
|
||||
- `allow`: The tool call is executed automatically without user interaction.
|
||||
- `deny`: The tool call is blocked and is not executed.
|
||||
- `ask_user`: The user is prompted to approve or deny the tool call. (In
|
||||
non-interactive mode, this is treated as `deny`.)
|
||||
|
||||
### Priority system & tiers
|
||||
|
||||
The policy engine uses a sophisticated priority system to resolve conflicts when
|
||||
multiple rules match a single tool call. The core principle is simple: **the
|
||||
rule with the highest priority wins**.
|
||||
|
||||
To provide a clear hierarchy, policies are organized into three tiers. Each tier
|
||||
has a designated number that forms the base of the final priority calculation.
|
||||
|
||||
| Tier | Base | Description |
|
||||
| :------ | :--- | :------------------------------------------------------------------------- |
|
||||
| Default | 1 | Built-in policies that ship with the Gemini CLI. |
|
||||
| User | 2 | Custom policies defined by the user. |
|
||||
| Admin | 3 | Policies managed by an administrator (e.g., in an enterprise environment). |
|
||||
|
||||
Within a TOML policy file, you assign a priority value from **0 to 999**. The
|
||||
engine transforms this into a final priority using the following formula:
|
||||
|
||||
`final_priority = tier_base + (toml_priority / 1000)`
|
||||
|
||||
This system guarantees that:
|
||||
|
||||
- Admin policies always override User and Default policies.
|
||||
- User policies always override Default policies.
|
||||
- You can still order rules within a single tier with fine-grained control.
|
||||
|
||||
For example:
|
||||
|
||||
- A `priority: 50` rule in a Default policy file becomes `1.050`.
|
||||
- A `priority: 100` rule in a User policy file becomes `2.100`.
|
||||
- A `priority: 20` rule in an Admin policy file becomes `3.020`.
|
||||
|
||||
### Approval modes
|
||||
|
||||
Approval modes allow the policy engine to apply different sets of rules based on
|
||||
the CLI's operational mode. A rule can be associated with one or more modes
|
||||
(e.g., `yolo`, `autoEdit`). The rule will only be active if the CLI is running
|
||||
in one of its specified modes. If a rule has no modes specified, it is always
|
||||
active.
|
||||
|
||||
## Rule matching
|
||||
|
||||
When a tool call is made, the engine checks it against all active rules,
|
||||
starting from the highest priority. The first rule that matches determines the
|
||||
outcome.
|
||||
|
||||
A rule matches a tool call if all of its conditions are met:
|
||||
|
||||
1. **Tool Name**: The `toolName` in the rule must match the name of the tool
|
||||
being called.
|
||||
- **Wildcards**: For Model-hosting-protocol (MCP) servers, you can use a
|
||||
wildcard. A `toolName` of `my-server__*` will match any tool from the
|
||||
`my-server` MCP.
|
||||
2. **Arguments Pattern**: If `argsPattern` is specified, the tool's arguments
|
||||
are converted to a stable JSON string, which is then tested against the
|
||||
provided regular expression. If the arguments don't match the pattern, the
|
||||
rule does not apply.
|
||||
|
||||
## Configuration
|
||||
|
||||
Policies are defined in `.toml` files. The CLI loads these files from Default,
|
||||
User, and (if configured) Admin directories.
|
||||
|
||||
### TOML rule schema
|
||||
|
||||
Here is a breakdown of the fields available in a TOML policy rule:
|
||||
|
||||
```toml
|
||||
[[rule]]
|
||||
# A unique name for the tool, or an array of names.
|
||||
toolName = "run_shell_command"
|
||||
|
||||
# (Optional) The name of an MCP server. Can be combined with toolName
|
||||
# to form a composite name like "mcpName__toolName".
|
||||
mcpName = "my-custom-server"
|
||||
|
||||
# (Optional) A regex to match against the tool's arguments.
|
||||
argsPattern = '"command":"(git|npm)'
|
||||
|
||||
# (Optional) A string or array of strings that a shell command must start with.
|
||||
# This is syntactic sugar for `toolName = "run_shell_command"` and an `argsPattern`.
|
||||
commandPrefix = "git "
|
||||
|
||||
# (Optional) A regex to match against the entire shell command.
|
||||
# This is also syntactic sugar for `toolName = "run_shell_command"`.
|
||||
# Note: This pattern is tested against the JSON representation of the arguments (e.g., `{"command":"<your_command>"}`), so anchors like `^` or `$` will apply to the full JSON string, not just the command text.
|
||||
# You cannot use commandPrefix and commandRegex in the same rule.
|
||||
commandRegex = "^git (commit|push)"
|
||||
|
||||
# The decision to take. Must be "allow", "deny", or "ask_user".
|
||||
decision = "ask_user"
|
||||
|
||||
# The priority of the rule, from 0 to 999.
|
||||
priority = 10
|
||||
|
||||
# (Optional) An array of approval modes where this rule is active.
|
||||
modes = ["autoEdit"]
|
||||
```
|
||||
|
||||
### Using arrays (lists)
|
||||
|
||||
To apply the same rule to multiple tools or command prefixes, you can provide an
|
||||
array of strings for the `toolName` and `commandPrefix` fields.
|
||||
|
||||
**Example:**
|
||||
|
||||
This single rule will apply to both the `write_file` and `replace` tools.
|
||||
|
||||
```toml
|
||||
[[rule]]
|
||||
toolName = ["write_file", "replace"]
|
||||
decision = "ask_user"
|
||||
priority = 10
|
||||
```
|
||||
|
||||
### Special syntax for `run_shell_command`
|
||||
|
||||
To simplify writing policies for `run_shell_command`, you can use
|
||||
`commandPrefix` or `commandRegex` instead of the more complex `argsPattern`.
|
||||
|
||||
- `commandPrefix`: Matches if the `command` argument starts with the given
|
||||
string.
|
||||
- `commandRegex`: Matches if the `command` argument matches the given regular
|
||||
expression.
|
||||
|
||||
**Example:**
|
||||
|
||||
This rule will ask for user confirmation before executing any `git` command.
|
||||
|
||||
```toml
|
||||
[[rule]]
|
||||
toolName = "run_shell_command"
|
||||
commandPrefix = "git "
|
||||
decision = "ask_user"
|
||||
priority = 100
|
||||
```
|
||||
|
||||
### Special syntax for MCP tools
|
||||
|
||||
You can create rules that target tools from Model-hosting-protocol (MCP) servers
|
||||
using the `mcpName` field or a wildcard pattern.
|
||||
|
||||
**1. Using `mcpName`**
|
||||
|
||||
To target a specific tool from a specific server, combine `mcpName` and
|
||||
`toolName`.
|
||||
|
||||
```toml
|
||||
# Allows the `search` tool on the `my-jira-server` MCP
|
||||
[[rule]]
|
||||
mcpName = "my-jira-server"
|
||||
toolName = "search"
|
||||
decision = "allow"
|
||||
priority = 200
|
||||
```
|
||||
|
||||
**2. Using a Wildcard**
|
||||
|
||||
To create a rule that applies to _all_ tools on a specific MCP server, specify
|
||||
only the `mcpName`.
|
||||
|
||||
```toml
|
||||
# Denies all tools from the `untrusted-server` MCP
|
||||
[[rule]]
|
||||
mcpName = "untrusted-server"
|
||||
decision = "deny"
|
||||
priority = 500
|
||||
```
|
||||
|
||||
## Default policies
|
||||
|
||||
The Gemini CLI ships with a set of default policies to provide a safe
|
||||
out-of-the-box experience.
|
||||
|
||||
- **Read-only tools** (like `read_file`, `glob`) are generally **allowed**.
|
||||
- **Write tools** (like `write_file`, `run_shell_command`) default to
|
||||
**`ask_user`**.
|
||||
- In **`yolo`** mode, a high-priority rule allows all tools.
|
||||
- In **`autoEdit`** mode, rules allow certain write operations to happen without
|
||||
prompting.
|
||||
@@ -62,8 +62,7 @@ The core comes with a suite of pre-defined tools, typically found in
|
||||
|
||||
- **File System Tools:**
|
||||
- `LSTool` (`ls.ts`): Lists directory contents.
|
||||
- `ReadFileTool` (`read-file.ts`): Reads the content of a single file. It
|
||||
takes an `absolute_path` parameter, which must be an absolute path.
|
||||
- `ReadFileTool` (`read-file.ts`): Reads the content of a single file.
|
||||
- `WriteFileTool` (`write-file.ts`): Writes content to a file.
|
||||
- `GrepTool` (`grep.ts`): Searches for patterns in files.
|
||||
- `GlobTool` (`glob.ts`): Finds files matching glob patterns.
|
||||
|
||||
@@ -15,11 +15,9 @@ if you need to ship platform specific binary files.
|
||||
|
||||
This is the most flexible and simple option. All you need to do is create a
|
||||
publicly accessible git repo (such as a public github repository) and then users
|
||||
can install your extension using `gemini extensions install <your-repo-uri>`, or
|
||||
for a GitHub repository they can use the simplified
|
||||
`gemini extensions install <org>/<repo>` format. They can optionally depend on a
|
||||
specific ref (branch/tag/commit) using the `--ref=<some-ref>` argument, this
|
||||
defaults to the default branch.
|
||||
can install your extension using `gemini extensions install <your-repo-uri>`.
|
||||
They can optionally depend on a specific ref (branch/tag/commit) using the
|
||||
`--ref=<some-ref>` argument, this defaults to the default branch.
|
||||
|
||||
Whenever commits are pushed to the ref that a user depends on, they will be
|
||||
prompted to update the extension. Note that this also allows for easy rollbacks,
|
||||
|
||||
+70
-24
@@ -42,19 +42,23 @@ installed on your machine. See
|
||||
for help.
|
||||
|
||||
```
|
||||
gemini extensions install https://github.com/gemini-cli-extensions/security
|
||||
gemini extensions install <source> [--ref <ref>] [--auto-update] [--pre-release] [--consent]
|
||||
```
|
||||
|
||||
This will install the Gemini CLI Security extension, which offers support for a
|
||||
`/security:analyze` command.
|
||||
- `<source>`: The github URL or local path of the extension to install.
|
||||
- `--ref`: The git ref to install from.
|
||||
- `--auto-update`: Enable auto-update for this extension.
|
||||
- `--pre-release`: Enable pre-release versions for this extension.
|
||||
- `--consent`: Acknowledge the security risks of installing an extension and
|
||||
skip the confirmation prompt.
|
||||
|
||||
### Uninstalling an extension
|
||||
|
||||
To uninstall, run `gemini extensions uninstall extension-name`, so, in the case
|
||||
of the install example:
|
||||
To uninstall one or more extensions, run
|
||||
`gemini extensions uninstall <name...>`:
|
||||
|
||||
```
|
||||
gemini extensions uninstall gemini-cli-security
|
||||
gemini extensions uninstall gemini-cli-security gemini-cli-another-extension
|
||||
```
|
||||
|
||||
### Disabling an extension
|
||||
@@ -62,27 +66,31 @@ gemini extensions uninstall gemini-cli-security
|
||||
Extensions are, by default, enabled across all workspaces. You can disable an
|
||||
extension entirely or for specific workspace.
|
||||
|
||||
For example, `gemini extensions disable extension-name` will disable the
|
||||
extension at the user level, so it will be disabled everywhere.
|
||||
`gemini extensions disable extension-name --scope=workspace` will only disable
|
||||
the extension in the current workspace.
|
||||
```
|
||||
gemini extensions disable <name> [--scope <scope>]
|
||||
```
|
||||
|
||||
- `<name>`: The name of the extension to disable.
|
||||
- `--scope`: The scope to disable the extension in (`user` or `workspace`).
|
||||
|
||||
### Enabling an extension
|
||||
|
||||
You can enable extensions using `gemini extensions enable extension-name`. You
|
||||
can also enable an extension for a specific workspace using
|
||||
`gemini extensions enable extension-name --scope=workspace` from within that
|
||||
workspace.
|
||||
You can enable extensions using `gemini extensions enable <name>`. You can also
|
||||
enable an extension for a specific workspace using
|
||||
`gemini extensions enable <name> --scope=workspace` from within that workspace.
|
||||
|
||||
This is useful if you have an extension disabled at the top-level and only
|
||||
enabled in specific places.
|
||||
```
|
||||
gemini extensions enable <name> [--scope <scope>]
|
||||
```
|
||||
|
||||
- `<name>`: The name of the extension to enable.
|
||||
- `--scope`: The scope to enable the extension in (`user` or `workspace`).
|
||||
|
||||
### Updating an extension
|
||||
|
||||
For extensions installed from a local path or a git repository, you can
|
||||
explicitly update to the latest version (as reflected in the
|
||||
`gemini-extension.json` `version` field) with
|
||||
`gemini extensions update extension-name`.
|
||||
`gemini-extension.json` `version` field) with `gemini extensions update <name>`.
|
||||
|
||||
You can update all extensions with:
|
||||
|
||||
@@ -90,10 +98,6 @@ You can update all extensions with:
|
||||
gemini extensions update --all
|
||||
```
|
||||
|
||||
## Extension creation
|
||||
|
||||
We offer commands to make extension development easier.
|
||||
|
||||
### Create a boilerplate extension
|
||||
|
||||
We offer several example extensions `context`, `custom-commands`,
|
||||
@@ -104,9 +108,12 @@ To copy one of these examples into a development directory using the type of
|
||||
your choosing, run:
|
||||
|
||||
```
|
||||
gemini extensions new path/to/directory custom-commands
|
||||
gemini extensions new <path> [template]
|
||||
```
|
||||
|
||||
- `<path>`: The path to create the extension in.
|
||||
- `[template]`: The boilerplate template to use.
|
||||
|
||||
### Link a local extension
|
||||
|
||||
The `gemini extensions link` command will create a symbolic link from the
|
||||
@@ -116,9 +123,11 @@ This is useful so you don't have to run `gemini extensions update` every time
|
||||
you make changes you'd like to test.
|
||||
|
||||
```
|
||||
gemini extensions link path/to/directory
|
||||
gemini extensions link <path>
|
||||
```
|
||||
|
||||
- `<path>`: The path of the extension to link.
|
||||
|
||||
## How it works
|
||||
|
||||
On startup, Gemini CLI looks for extensions in `<home>/.gemini/extensions`
|
||||
@@ -177,6 +186,43 @@ When Gemini CLI starts, it loads all the extensions and merges their
|
||||
configurations. If there are any conflicts, the workspace configuration takes
|
||||
precedence.
|
||||
|
||||
### Settings
|
||||
|
||||
_Note: This is an experimental feature. We do not yet recommend extension
|
||||
authors introduce settings as part of their core flows._
|
||||
|
||||
Extensions can define settings that the user will be prompted to provide upon
|
||||
installation. This is useful for things like API keys, URLs, or other
|
||||
configuration that the extension needs to function.
|
||||
|
||||
To define settings, add a `settings` array to your `gemini-extension.json` file.
|
||||
Each object in the array should have the following properties:
|
||||
|
||||
- `name`: A user-friendly name for the setting.
|
||||
- `description`: A description of the setting and what it's used for.
|
||||
- `envVar`: The name of the environment variable that the setting will be stored
|
||||
as.
|
||||
- `sensitive`: Optional boolean. If true, obfuscates the input the user provides
|
||||
and stores the secret in keychain storage. **Example**
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "my-api-extension",
|
||||
"version": "1.0.0",
|
||||
"settings": [
|
||||
{
|
||||
"name": "API Key",
|
||||
"description": "Your API key for the service.",
|
||||
"envVar": "MY_API_KEY"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
When a user installs this extension, they will be prompted to enter their API
|
||||
key. The value will be saved to a `.env` file in the extension's directory
|
||||
(e.g., `<home>/.gemini/extensions/my-api-extension/.env`).
|
||||
|
||||
### Custom commands
|
||||
|
||||
Extensions can provide [custom commands](../cli/custom-commands.md) by placing
|
||||
|
||||
@@ -8,13 +8,23 @@ CLI, configure **one** of the following authentication methods:
|
||||
- Use Gemini API key
|
||||
- Use Vertex AI
|
||||
- Headless (non-interactive) mode
|
||||
- Google Cloud Shell
|
||||
- Google Cloud Environments (Cloud Shell, Compute Engine, etc.)
|
||||
|
||||
## Quick Check: Running in Google Cloud Shell?
|
||||
|
||||
If you are running the Gemini CLI within a Google Cloud Shell environment,
|
||||
authentication is typically automatic using your Cloud Shell credentials.
|
||||
|
||||
### Other Google Cloud Environments (e.g., Compute Engine)
|
||||
|
||||
Some other Google Cloud environments, such as Compute Engine VMs, might also
|
||||
support automatic authentication. In these environments, Gemini CLI can
|
||||
automatically use Application Default Credentials (ADC) sourced from the
|
||||
environment's metadata server.
|
||||
|
||||
If automatic authentication does not occur in your environment, you will need to
|
||||
use one of the interactive methods described below.
|
||||
|
||||
## Authenticate in Interactive mode
|
||||
|
||||
When you run Gemini CLI through the command-line, Gemini CLI will provide the
|
||||
@@ -246,7 +256,7 @@ To avoid setting environment variables in every terminal session, you can:
|
||||
|
||||
## Non-interactive mode / headless environments
|
||||
|
||||
Non-interative mode / headless environments will use your existing
|
||||
Non-interactive mode / headless environments will use your existing
|
||||
authentication method, if an existing authentication credential is cached.
|
||||
|
||||
If you have not already logged in with an authentication credential (such as a
|
||||
|
||||
@@ -473,21 +473,6 @@ a few things you can try in order of recommendation:
|
||||
"loadMemoryFromIncludeDirectories": true
|
||||
```
|
||||
|
||||
- **`chatCompression`** (object):
|
||||
- **Description:** Controls the settings for chat history compression, both
|
||||
automatic and when manually invoked through the /compress command.
|
||||
- **Properties:**
|
||||
- **`contextPercentageThreshold`** (number): A value between 0 and 1 that
|
||||
specifies the token threshold for compression as a percentage of the
|
||||
model's total token limit. For example, a value of `0.6` will trigger
|
||||
compression when the chat history exceeds 60% of the token limit.
|
||||
- **Example:**
|
||||
```json
|
||||
"chatCompression": {
|
||||
"contextPercentageThreshold": 0.6
|
||||
}
|
||||
```
|
||||
|
||||
- **`showLineNumbers`** (boolean):
|
||||
- **Description:** Controls whether line numbers are displayed in code blocks
|
||||
in the CLI output.
|
||||
@@ -626,6 +611,9 @@ the `excludedProjectEnvVars` setting in your `settings.json` file.
|
||||
- **`GEMINI_SANDBOX`**:
|
||||
- Alternative to the `sandbox` setting in `settings.json`.
|
||||
- Accepts `true`, `false`, `docker`, `podman`, or a custom command string.
|
||||
- **`HTTP_PROXY` / `HTTPS_PROXY`**:
|
||||
- Specifies the proxy server to use for outgoing HTTP/HTTPS requests.
|
||||
- Example: `export HTTPS_PROXY="http://proxy.example.com:8080"`
|
||||
- **`SEATBELT_PROFILE`** (macOS specific):
|
||||
- Switches the Seatbelt (`sandbox-exec`) profile on macOS.
|
||||
- `permissive-open`: (Default) Restricts writes to the project folder (and a
|
||||
@@ -707,8 +695,6 @@ for that specific session.
|
||||
- **`--telemetry-log-prompts`**:
|
||||
- Enables logging of prompts for telemetry. See
|
||||
[telemetry](../cli/telemetry.md) for more information.
|
||||
- **`--checkpointing`**:
|
||||
- Enables [checkpointing](../cli/checkpointing.md).
|
||||
- **`--extensions <extension_name ...>`** (**`-e <extension_name ...>`**):
|
||||
- Specifies a list of extensions to use for the session. If not provided, all
|
||||
available extensions are used.
|
||||
@@ -716,9 +702,6 @@ for that specific session.
|
||||
- Example: `gemini -e my-extension -e my-other-extension`
|
||||
- **`--list-extensions`** (**`-l`**):
|
||||
- Lists all available extensions and exits.
|
||||
- **`--proxy`**:
|
||||
- Sets the proxy for the CLI.
|
||||
- Example: `--proxy http://localhost:7890`.
|
||||
- **`--include-directories <dir1,dir2,...>`**:
|
||||
- Includes additional directories in the workspace for multi-directory
|
||||
support.
|
||||
|
||||
+505
-129
@@ -36,6 +36,11 @@ overridden by higher numbers):
|
||||
Gemini CLI uses JSON settings files for persistent configuration. There are four
|
||||
locations for these files:
|
||||
|
||||
> **Tip:** JSON-aware editors can use autocomplete and validation by pointing to
|
||||
> the generated schema at `schemas/settings.schema.json` in this repository.
|
||||
> When working outside the repo, reference the hosted schema at
|
||||
> `https://raw.githubusercontent.com/google-gemini/gemini-cli/main/schemas/settings.schema.json`.
|
||||
|
||||
- **System defaults file:**
|
||||
- **Location:** `/etc/gemini-cli/system-defaults.json` (Linux),
|
||||
`C:\ProgramData\gemini-cli\system-defaults.json` (Windows) or
|
||||
@@ -65,11 +70,12 @@ locations for these files:
|
||||
controls over users' Gemini CLI setups.
|
||||
|
||||
**Note on environment variables in settings:** String values within your
|
||||
`settings.json` files can reference environment variables using either
|
||||
`$VAR_NAME` or `${VAR_NAME}` syntax. These variables will be automatically
|
||||
resolved when the settings are loaded. For example, if you have an environment
|
||||
variable `MY_API_TOKEN`, you could use it in `settings.json` like this:
|
||||
`"apiKey": "$MY_API_TOKEN"`.
|
||||
`settings.json` and `gemini-extension.json` files can reference environment
|
||||
variables using either `$VAR_NAME` or `${VAR_NAME}` syntax. These variables will
|
||||
be automatically resolved when the settings are loaded. For example, if you have
|
||||
an environment variable `MY_API_TOKEN`, you could use it in `settings.json` like
|
||||
this: `"apiKey": "$MY_API_TOKEN"`. Additionally, each extension can have its own
|
||||
`.env` file in its directory, which will be loaded automatically.
|
||||
|
||||
> **Note for Enterprise Users:** For guidance on deploying and managing Gemini
|
||||
> CLI in a corporate environment, please see the
|
||||
@@ -88,18 +94,24 @@ contain other project-specific files related to Gemini CLI's operation, such as:
|
||||
Settings are organized into categories. All settings should be placed within
|
||||
their corresponding top-level category object in your `settings.json` file.
|
||||
|
||||
<!-- SETTINGS-AUTOGEN:START -->
|
||||
|
||||
#### `general`
|
||||
|
||||
- **`general.previewFeatures`** (boolean):
|
||||
- **Description:** Enable preview features (e.g., preview models).
|
||||
- **Default:** `false`
|
||||
|
||||
- **`general.preferredEditor`** (string):
|
||||
- **Description:** The preferred editor to open files in.
|
||||
- **Default:** `undefined`
|
||||
|
||||
- **`general.vimMode`** (boolean):
|
||||
- **Description:** Enable Vim keybindings.
|
||||
- **Description:** Enable Vim keybindings
|
||||
- **Default:** `false`
|
||||
|
||||
- **`general.disableAutoUpdate`** (boolean):
|
||||
- **Description:** Disable automatic updates.
|
||||
- **Description:** Disable automatic updates
|
||||
- **Default:** `false`
|
||||
|
||||
- **`general.disableUpdateNag`** (boolean):
|
||||
@@ -107,34 +119,55 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
- **Default:** `false`
|
||||
|
||||
- **`general.checkpointing.enabled`** (boolean):
|
||||
- **Description:** Enable session checkpointing for recovery.
|
||||
- **Description:** Enable session checkpointing for recovery
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`general.enablePromptCompletion`** (boolean):
|
||||
- **Description:** Enable AI-powered prompt completion suggestions while
|
||||
typing.
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`general.retryFetchErrors`** (boolean):
|
||||
- **Description:** Retry on "exception TypeError: fetch failed sending
|
||||
request" errors.
|
||||
- **Default:** `false`
|
||||
|
||||
- **`general.debugKeystrokeLogging`** (boolean):
|
||||
- **Description:** Enable debug logging of keystrokes to the console.
|
||||
- **Default:** `false`
|
||||
|
||||
- **`general.sessionRetention.enabled`** (boolean):
|
||||
- **Description:** Enable automatic session cleanup.
|
||||
- **Description:** Enable automatic session cleanup
|
||||
- **Default:** `false`
|
||||
|
||||
- **`general.sessionRetention.maxAge`** (string):
|
||||
- **Description:** Maximum age of sessions to keep (e.g., "30d", "7d", "24h",
|
||||
"1w")
|
||||
- **Default:** `undefined`
|
||||
|
||||
- **`general.sessionRetention.maxCount`** (number):
|
||||
- **Description:** Alternative: Maximum number of sessions to keep (most
|
||||
recent)
|
||||
- **Default:** `undefined`
|
||||
|
||||
- **`general.sessionRetention.minRetention`** (string):
|
||||
- **Description:** Minimum retention period (safety limit, defaults to "1d")
|
||||
- **Default:** `"1d"`
|
||||
|
||||
#### `output`
|
||||
|
||||
- **`output.format`** (string):
|
||||
- **`output.format`** (enum):
|
||||
- **Description:** The format of the CLI output.
|
||||
- **Default:** `"text"`
|
||||
- **Values:** `"text"`, `"json"`, `"stream-json"`
|
||||
- **Values:** `"text"`, `"json"`
|
||||
|
||||
#### `ui`
|
||||
|
||||
- **`ui.theme`** (string):
|
||||
- **Description:** The color theme for the UI. See [Themes](../cli/themes.md)
|
||||
for available options.
|
||||
- **Description:** The color theme for the UI. See the CLI themes guide for
|
||||
available options.
|
||||
- **Default:** `undefined`
|
||||
|
||||
- **`ui.customThemes`** (object):
|
||||
@@ -142,20 +175,21 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
- **Default:** `{}`
|
||||
|
||||
- **`ui.hideWindowTitle`** (boolean):
|
||||
- **Description:** Hide the window title bar.
|
||||
- **Description:** Hide the window title bar
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`ui.showStatusInTitle`** (boolean):
|
||||
- **Description:** Show Gemini CLI status and thoughts in the terminal window
|
||||
title.
|
||||
title
|
||||
- **Default:** `false`
|
||||
|
||||
- **`ui.hideTips`** (boolean):
|
||||
- **Description:** Hide helpful tips in the UI.
|
||||
- **Description:** Hide helpful tips in the UI
|
||||
- **Default:** `false`
|
||||
|
||||
- **`ui.hideBanner`** (boolean):
|
||||
- **Description:** Hide the application banner.
|
||||
- **Description:** Hide the application banner
|
||||
- **Default:** `false`
|
||||
|
||||
- **`ui.hideContextSummary`** (boolean):
|
||||
@@ -163,10 +197,6 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
input.
|
||||
- **Default:** `false`
|
||||
|
||||
- **`ui.hideFooter`** (boolean):
|
||||
- **Description:** Hide the footer from the UI.
|
||||
- **Default:** `false`
|
||||
|
||||
- **`ui.footer.hideCWD`** (boolean):
|
||||
- **Description:** Hide the current working directory path in the footer.
|
||||
- **Default:** `false`
|
||||
@@ -179,42 +209,69 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
- **Description:** Hide the model name and context usage in the footer.
|
||||
- **Default:** `false`
|
||||
|
||||
- **`ui.footer.hideContextPercentage`** (boolean):
|
||||
- **Description:** Hides the context window remaining percentage.
|
||||
- **Default:** `true`
|
||||
|
||||
- **`ui.hideFooter`** (boolean):
|
||||
- **Description:** Hide the footer from the UI
|
||||
- **Default:** `false`
|
||||
|
||||
- **`ui.showMemoryUsage`** (boolean):
|
||||
- **Description:** Display memory usage information in the UI.
|
||||
- **Description:** Display memory usage information in the UI
|
||||
- **Default:** `false`
|
||||
|
||||
- **`ui.showLineNumbers`** (boolean):
|
||||
- **Description:** Show line numbers in the chat.
|
||||
- **Default:** `false`
|
||||
- **Default:** `true`
|
||||
|
||||
- **`ui.showCitations`** (boolean):
|
||||
- **Description:** Show citations for generated text in the chat.
|
||||
- **Default:** `true`
|
||||
- **Default:** `false`
|
||||
|
||||
- **`ui.showModelInfoInChat`** (boolean):
|
||||
- **Description:** Show the model name in the chat for each model turn.
|
||||
- **Default:** `false`
|
||||
|
||||
- **`ui.useFullWidth`** (boolean):
|
||||
- **Description:** Use the entire width of the terminal for output.
|
||||
- **Default:** `true`
|
||||
|
||||
- **`ui.useAlternateBuffer`** (boolean):
|
||||
- **Description:** Use an alternate screen buffer for the UI, preserving shell
|
||||
history.
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`ui.incrementalRendering`** (boolean):
|
||||
- **Description:** Enable incremental rendering for the UI. This option will
|
||||
reduce flickering but may cause rendering artifacts. Only supported when
|
||||
useAlternateBuffer is enabled.
|
||||
- **Default:** `true`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`ui.customWittyPhrases`** (array):
|
||||
- **Description:** Custom witty phrases to display during loading. When
|
||||
provided, the CLI cycles through these instead of the defaults.
|
||||
- **Default:** `[]`
|
||||
|
||||
- **`ui.accessibility.disableLoadingPhrases`** (boolean):
|
||||
- **Description:** Disable loading phrases for accessibility.
|
||||
- **Description:** Disable loading phrases for accessibility
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`ui.accessibility.screenReader`** (boolean):
|
||||
- **Description:** Show plaintext interactive view that is more screen reader
|
||||
friendly.
|
||||
- **Description:** Render output in plain-text to be more screen reader
|
||||
accessible
|
||||
- **Default:** `false`
|
||||
|
||||
- **`ui.customWittyPhrases`** (array of strings):
|
||||
- **Description:** A list of custom phrases to display during loading states.
|
||||
When provided, the CLI will cycle through these phrases instead of the
|
||||
default ones.
|
||||
- **Default:** `[]`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
#### `ide`
|
||||
|
||||
- **`ide.enabled`** (boolean):
|
||||
- **Description:** Enable IDE integration mode.
|
||||
- **Description:** Enable IDE integration mode
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`ide.hasSeenNudge`** (boolean):
|
||||
- **Description:** Whether the user has seen the IDE integration nudge.
|
||||
@@ -223,8 +280,9 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
#### `privacy`
|
||||
|
||||
- **`privacy.usageStatisticsEnabled`** (boolean):
|
||||
- **Description:** Enable collection of usage statistics.
|
||||
- **Description:** Enable collection of usage statistics
|
||||
- **Default:** `true`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
#### `model`
|
||||
|
||||
@@ -238,32 +296,245 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
- **Default:** `-1`
|
||||
|
||||
- **`model.summarizeToolOutput`** (object):
|
||||
- **Description:** Enables or disables the summarization of tool output. You
|
||||
can specify the token budget for the summarization using the `tokenBudget`
|
||||
setting. Note: Currently only the `run_shell_command` tool is supported. For
|
||||
example `{"run_shell_command": {"tokenBudget": 2000}}`
|
||||
- **Description:** Enables or disables summarization of tool output. Configure
|
||||
per-tool token budgets (for example {"run_shell_command": {"tokenBudget":
|
||||
2000}}). Currently only the run_shell_command tool supports summarization.
|
||||
- **Default:** `undefined`
|
||||
|
||||
- **`model.chatCompression.contextPercentageThreshold`** (number):
|
||||
- **Description:** Sets the threshold for chat history compression as a
|
||||
percentage of the model's total token limit. This is a value between 0 and 1
|
||||
that applies to both automatic compression and the manual `/compress`
|
||||
command. For example, a value of `0.6` will trigger compression when the
|
||||
chat history exceeds 60% of the token limit.
|
||||
- **Default:** `0.7`
|
||||
- **`model.compressionThreshold`** (number):
|
||||
- **Description:** The fraction of context usage at which to trigger context
|
||||
compression (e.g. 0.2, 0.3).
|
||||
- **Default:** `0.5`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`model.skipNextSpeakerCheck`** (boolean):
|
||||
- **Description:** Skip the next speaker check.
|
||||
- **Default:** `false`
|
||||
|
||||
- **`model.enableShellOutputEfficiency`** (boolean):
|
||||
- **Description:** Optimizes shell tool commands for token efficiency.
|
||||
- **Default:** `true`
|
||||
|
||||
#### `modelConfigs`
|
||||
|
||||
- **`modelConfigs.aliases`** (object):
|
||||
- **Description:** Named presets for model configs. Can be used in place of a
|
||||
model name and can inherit from other aliases using an `extends` property.
|
||||
- **Default:**
|
||||
|
||||
```json
|
||||
{
|
||||
"base": {
|
||||
"modelConfig": {
|
||||
"generateContentConfig": {
|
||||
"temperature": 0,
|
||||
"topP": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat-base": {
|
||||
"extends": "base",
|
||||
"modelConfig": {
|
||||
"generateContentConfig": {
|
||||
"thinkingConfig": {
|
||||
"includeThoughts": true
|
||||
},
|
||||
"temperature": 1,
|
||||
"topP": 0.95,
|
||||
"topK": 64
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat-base-2.5": {
|
||||
"extends": "chat-base",
|
||||
"modelConfig": {
|
||||
"generateContentConfig": {
|
||||
"thinkingConfig": {
|
||||
"thinkingBudget": 8192
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat-base-3": {
|
||||
"extends": "chat-base",
|
||||
"modelConfig": {
|
||||
"generateContentConfig": {
|
||||
"thinkingConfig": {
|
||||
"thinkingLevel": "HIGH"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"gemini-3-pro-preview": {
|
||||
"extends": "chat-base-3",
|
||||
"modelConfig": {
|
||||
"model": "gemini-3-pro-preview"
|
||||
}
|
||||
},
|
||||
"gemini-2.5-pro": {
|
||||
"extends": "chat-base-2.5",
|
||||
"modelConfig": {
|
||||
"model": "gemini-2.5-pro"
|
||||
}
|
||||
},
|
||||
"gemini-2.5-flash": {
|
||||
"extends": "chat-base-2.5",
|
||||
"modelConfig": {
|
||||
"model": "gemini-2.5-flash"
|
||||
}
|
||||
},
|
||||
"gemini-2.5-flash-lite": {
|
||||
"extends": "chat-base-2.5",
|
||||
"modelConfig": {
|
||||
"model": "gemini-2.5-flash-lite"
|
||||
}
|
||||
},
|
||||
"gemini-2.5-flash-base": {
|
||||
"extends": "base",
|
||||
"modelConfig": {
|
||||
"model": "gemini-2.5-flash"
|
||||
}
|
||||
},
|
||||
"classifier": {
|
||||
"extends": "base",
|
||||
"modelConfig": {
|
||||
"model": "gemini-2.5-flash-lite",
|
||||
"generateContentConfig": {
|
||||
"maxOutputTokens": 1024,
|
||||
"thinkingConfig": {
|
||||
"thinkingBudget": 512
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"prompt-completion": {
|
||||
"extends": "base",
|
||||
"modelConfig": {
|
||||
"model": "gemini-2.5-flash-lite",
|
||||
"generateContentConfig": {
|
||||
"temperature": 0.3,
|
||||
"maxOutputTokens": 16000,
|
||||
"thinkingConfig": {
|
||||
"thinkingBudget": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit-corrector": {
|
||||
"extends": "base",
|
||||
"modelConfig": {
|
||||
"model": "gemini-2.5-flash-lite",
|
||||
"generateContentConfig": {
|
||||
"thinkingConfig": {
|
||||
"thinkingBudget": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"summarizer-default": {
|
||||
"extends": "base",
|
||||
"modelConfig": {
|
||||
"model": "gemini-2.5-flash-lite",
|
||||
"generateContentConfig": {
|
||||
"maxOutputTokens": 2000
|
||||
}
|
||||
}
|
||||
},
|
||||
"summarizer-shell": {
|
||||
"extends": "base",
|
||||
"modelConfig": {
|
||||
"model": "gemini-2.5-flash-lite",
|
||||
"generateContentConfig": {
|
||||
"maxOutputTokens": 2000
|
||||
}
|
||||
}
|
||||
},
|
||||
"web-search": {
|
||||
"extends": "gemini-2.5-flash-base",
|
||||
"modelConfig": {
|
||||
"generateContentConfig": {
|
||||
"tools": [
|
||||
{
|
||||
"googleSearch": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"web-fetch": {
|
||||
"extends": "gemini-2.5-flash-base",
|
||||
"modelConfig": {
|
||||
"generateContentConfig": {
|
||||
"tools": [
|
||||
{
|
||||
"urlContext": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"web-fetch-fallback": {
|
||||
"extends": "gemini-2.5-flash-base",
|
||||
"modelConfig": {}
|
||||
},
|
||||
"loop-detection": {
|
||||
"extends": "gemini-2.5-flash-base",
|
||||
"modelConfig": {}
|
||||
},
|
||||
"loop-detection-double-check": {
|
||||
"extends": "base",
|
||||
"modelConfig": {
|
||||
"model": "gemini-2.5-pro"
|
||||
}
|
||||
},
|
||||
"llm-edit-fixer": {
|
||||
"extends": "gemini-2.5-flash-base",
|
||||
"modelConfig": {}
|
||||
},
|
||||
"next-speaker-checker": {
|
||||
"extends": "gemini-2.5-flash-base",
|
||||
"modelConfig": {}
|
||||
},
|
||||
"chat-compression-3-pro": {
|
||||
"modelConfig": {
|
||||
"model": "gemini-3-pro-preview"
|
||||
}
|
||||
},
|
||||
"chat-compression-2.5-pro": {
|
||||
"modelConfig": {
|
||||
"model": "gemini-2.5-pro"
|
||||
}
|
||||
},
|
||||
"chat-compression-2.5-flash": {
|
||||
"modelConfig": {
|
||||
"model": "gemini-2.5-flash"
|
||||
}
|
||||
},
|
||||
"chat-compression-2.5-flash-lite": {
|
||||
"modelConfig": {
|
||||
"model": "gemini-2.5-flash-lite"
|
||||
}
|
||||
},
|
||||
"chat-compression-default": {
|
||||
"modelConfig": {
|
||||
"model": "gemini-2.5-pro"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **`modelConfigs.customAliases`** (object):
|
||||
- **Description:** Custom named presets for model configs. These are merged
|
||||
with (and override) the built-in aliases.
|
||||
- **Default:** `{}`
|
||||
|
||||
- **`modelConfigs.overrides`** (array):
|
||||
- **Description:** Apply specific configuration overrides based on matches,
|
||||
with a primary key of model (or alias). The most specific match will be
|
||||
used.
|
||||
- **Default:** `[]`
|
||||
|
||||
#### `context`
|
||||
|
||||
- **`context.fileName`** (string or array of strings):
|
||||
- **Description:** The name of the context file(s).
|
||||
- **`context.fileName`** (string | string[]):
|
||||
- **Description:** The name of the context file or files to load into memory.
|
||||
Accepts either a single string or an array of strings.
|
||||
- **Default:** `undefined`
|
||||
|
||||
- **`context.importFormat`** (string):
|
||||
@@ -279,42 +550,51 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
Missing directories will be skipped with a warning.
|
||||
- **Default:** `[]`
|
||||
|
||||
- **`context.loadFromIncludeDirectories`** (boolean):
|
||||
- **Description:** Controls the behavior of the `/memory refresh` command. If
|
||||
set to `true`, `GEMINI.md` files should be loaded from all directories that
|
||||
are added. If set to `false`, `GEMINI.md` should only be loaded from the
|
||||
current directory.
|
||||
- **`context.loadMemoryFromIncludeDirectories`** (boolean):
|
||||
- **Description:** Controls how /memory refresh loads GEMINI.md files. When
|
||||
true, include directories are scanned; when false, only the current
|
||||
directory is used.
|
||||
- **Default:** `false`
|
||||
|
||||
- **`context.fileFiltering.respectGitIgnore`** (boolean):
|
||||
- **Description:** Respect .gitignore files when searching.
|
||||
- **Description:** Respect .gitignore files when searching
|
||||
- **Default:** `true`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`context.fileFiltering.respectGeminiIgnore`** (boolean):
|
||||
- **Description:** Respect .geminiignore files when searching.
|
||||
- **Description:** Respect .geminiignore files when searching
|
||||
- **Default:** `true`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`context.fileFiltering.enableRecursiveFileSearch`** (boolean):
|
||||
- **Description:** Whether to enable searching recursively for filenames under
|
||||
the current tree when completing `@` prefixes in the prompt.
|
||||
- **Description:** Enable recursive file search functionality when completing
|
||||
@ references in the prompt.
|
||||
- **Default:** `true`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`context.fileFiltering.disableFuzzySearch`** (boolean):
|
||||
- **Description:** Disable fuzzy search when searching for files.
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
#### `tools`
|
||||
|
||||
- **`tools.sandbox`** (boolean or string):
|
||||
- **Description:** Sandbox execution environment (can be a boolean or a path
|
||||
string).
|
||||
- **`tools.sandbox`** (boolean | string):
|
||||
- **Description:** Sandbox execution environment. Set to a boolean to enable
|
||||
or disable the sandbox, or provide a string path to a sandbox profile.
|
||||
- **Default:** `undefined`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`tools.shell.enableInteractiveShell`** (boolean):
|
||||
- **Description:** Enables interactive terminal for running shell commands. If
|
||||
an interactive session cannot be started, it will fall back to a standard
|
||||
shell.
|
||||
- **Description:** Use node-pty for an interactive shell experience. Fallback
|
||||
to child_process still applies.
|
||||
- **Default:** `true`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`tools.shell.pager`** (string):
|
||||
- **Description:** The pager command to use for shell output. Defaults to
|
||||
`cat`.
|
||||
- **Default:** `"cat"`
|
||||
|
||||
- **`tools.shell.showColor`** (boolean):
|
||||
- **Description:** Show color in shell output.
|
||||
@@ -325,42 +605,37 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
considered safe (e.g., read-only operations).
|
||||
- **Default:** `false`
|
||||
|
||||
- **`tools.core`** (array of strings):
|
||||
- **Description:** This can be used to restrict the set of built-in tools
|
||||
[with an allowlist](../cli/enterprise.md#restricting-tool-access). See
|
||||
[Built-in Tools](../core/tools-api.md#built-in-tools) for a list of core
|
||||
tools. The match semantics are the same as `tools.allowed`.
|
||||
- **`tools.core`** (array):
|
||||
- **Description:** Restrict the set of built-in tools with an allowlist. Match
|
||||
semantics mirror tools.allowed; see the built-in tools documentation for
|
||||
available names.
|
||||
- **Default:** `undefined`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`tools.exclude`** (array of strings):
|
||||
- **`tools.allowed`** (array):
|
||||
- **Description:** Tool names that bypass the confirmation dialog. Useful for
|
||||
trusted commands (for example ["run_shell_command(git)",
|
||||
"run_shell_command(npm test)"]). See shell tool command restrictions for
|
||||
matching details.
|
||||
- **Default:** `undefined`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`tools.exclude`** (array):
|
||||
- **Description:** Tool names to exclude from discovery.
|
||||
- **Default:** `undefined`
|
||||
|
||||
- **`tools.allowed`** (array of strings):
|
||||
- **Description:** A list of tool names that will bypass the confirmation
|
||||
dialog. This is useful for tools that you trust and use frequently. For
|
||||
example, `["run_shell_command(git)", "run_shell_command(npm test)"]` will
|
||||
skip the confirmation dialog to run any `git` and `npm test` commands. See
|
||||
[Shell Tool command restrictions](../tools/shell.md#command-restrictions)
|
||||
for details on prefix matching, command chaining, etc.
|
||||
- **Default:** `undefined`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`tools.discoveryCommand`** (string):
|
||||
- **Description:** Command to run for tool discovery.
|
||||
- **Default:** `undefined`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`tools.callCommand`** (string):
|
||||
- **Description:** Defines a custom shell command for calling a specific tool
|
||||
that was discovered using `tools.discoveryCommand`. The shell command must
|
||||
meet the following criteria:
|
||||
- It must take function `name` (exactly as in
|
||||
[function declaration](https://ai.google.dev/gemini-api/docs/function-calling#function-declarations))
|
||||
as the first command line argument.
|
||||
- It must read function arguments as JSON on `stdin`, analogous to
|
||||
[`functionCall.args`](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/inference#functioncall).
|
||||
- It must return function output as JSON on `stdout`, analogous to
|
||||
[`functionResponse.response.content`](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/inference#functionresponse).
|
||||
- **Description:** Defines a custom shell command for invoking discovered
|
||||
tools. The command must take the tool name as the first argument, read JSON
|
||||
arguments from stdin, and emit JSON results on stdout.
|
||||
- **Default:** `undefined`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`tools.useRipgrep`** (boolean):
|
||||
- **Description:** Use ripgrep for file content search instead of the fallback
|
||||
@@ -370,68 +645,116 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
- **`tools.enableToolOutputTruncation`** (boolean):
|
||||
- **Description:** Enable truncation of large tool outputs.
|
||||
- **Default:** `true`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`tools.truncateToolOutputThreshold`** (number):
|
||||
- **Description:** Truncate tool output if it is larger than this many
|
||||
characters. Set to -1 to disable.
|
||||
- **Default:** `20000`
|
||||
- **Default:** `4000000`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`tools.truncateToolOutputLines`** (number):
|
||||
- **Description:** The number of lines to keep when truncating tool output.
|
||||
- **Default:** `1000`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`tools.enableMessageBusIntegration`** (boolean):
|
||||
- **Description:** Enable policy-based tool confirmation via message bus
|
||||
integration. When enabled, tools will automatically respect policy engine
|
||||
integration. When enabled, tools automatically respect policy engine
|
||||
decisions (ALLOW/DENY/ASK_USER) without requiring individual tool
|
||||
implementations.
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`tools.enableHooks`** (boolean):
|
||||
- **Description:** Enable the hooks system for intercepting and customizing
|
||||
Gemini CLI behavior. When enabled, hooks configured in settings will execute
|
||||
at appropriate lifecycle events (BeforeTool, AfterTool, BeforeModel, etc.).
|
||||
Requires MessageBus integration.
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
#### `mcp`
|
||||
|
||||
- **`mcp.serverCommand`** (string):
|
||||
- **Description:** Command to start an MCP server.
|
||||
- **Default:** `undefined`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`mcp.allowed`** (array of strings):
|
||||
- **Description:** An allowlist of MCP servers to allow.
|
||||
- **`mcp.allowed`** (array):
|
||||
- **Description:** A list of MCP servers to allow.
|
||||
- **Default:** `undefined`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`mcp.excluded`** (array of strings):
|
||||
- **Description:** A denylist of MCP servers to exclude.
|
||||
- **`mcp.excluded`** (array):
|
||||
- **Description:** A list of MCP servers to exclude.
|
||||
- **Default:** `undefined`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
#### `useSmartEdit`
|
||||
|
||||
- **`useSmartEdit`** (boolean):
|
||||
- **Description:** Enable the smart-edit tool instead of the replace tool.
|
||||
- **Default:** `true`
|
||||
|
||||
#### `useWriteTodos`
|
||||
|
||||
- **`useWriteTodos`** (boolean):
|
||||
- **Description:** Enable the write_todos tool.
|
||||
- **Default:** `true`
|
||||
|
||||
#### `security`
|
||||
|
||||
- **`security.disableYoloMode`** (boolean):
|
||||
- **Description:** Disable YOLO mode, even if enabled by a flag.
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`security.blockGitExtensions`** (boolean):
|
||||
- **Description:** Blocks installing and loading extensions from Git.
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`security.folderTrust.enabled`** (boolean):
|
||||
- **Description:** Setting to track whether Folder trust is enabled.
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`security.auth.selectedType`** (string):
|
||||
- **Description:** The currently selected authentication type.
|
||||
- **Default:** `undefined`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`security.auth.enforcedType`** (string):
|
||||
- **Description:** The required auth type (useful for enterprises).
|
||||
- **Description:** The required auth type. If this does not match the selected
|
||||
auth type, the user will be prompted to re-authenticate.
|
||||
- **Default:** `undefined`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`security.auth.useExternal`** (boolean):
|
||||
- **Description:** Whether to use an external authentication flow.
|
||||
- **Default:** `undefined`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
#### `advanced`
|
||||
|
||||
- **`advanced.autoConfigureMemory`** (boolean):
|
||||
- **Description:** Automatically configure Node.js memory limits.
|
||||
- **Description:** Automatically configure Node.js memory limits
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`advanced.dnsResolutionOrder`** (string):
|
||||
- **Description:** The DNS resolution order.
|
||||
- **Default:** `undefined`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`advanced.excludedEnvVars`** (array of strings):
|
||||
- **`advanced.excludedEnvVars`** (array):
|
||||
- **Description:** Environment variables to exclude from project context.
|
||||
- **Default:** `["DEBUG","DEBUG_MODE"]`
|
||||
- **Default:**
|
||||
|
||||
```json
|
||||
["DEBUG", "DEBUG_MODE"]
|
||||
```
|
||||
|
||||
- **`advanced.bugCommand`** (object):
|
||||
- **Description:** Configuration for the bug report command.
|
||||
@@ -439,10 +762,55 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
|
||||
#### `experimental`
|
||||
|
||||
- **`experimental.useModelRouter`** (boolean):
|
||||
- **Description:** Enable model routing to route requests to the best model
|
||||
based on complexity.
|
||||
- **`experimental.extensionManagement`** (boolean):
|
||||
- **Description:** Enable extension management features.
|
||||
- **Default:** `true`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`experimental.extensionReloading`** (boolean):
|
||||
- **Description:** Enables extension loading/unloading within the CLI session.
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`experimental.isModelAvailabilityServiceEnabled`** (boolean):
|
||||
- **Description:** Enable model routing using new availability service.
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`experimental.codebaseInvestigatorSettings.enabled`** (boolean):
|
||||
- **Description:** Enable the Codebase Investigator agent.
|
||||
- **Default:** `true`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`experimental.codebaseInvestigatorSettings.maxNumTurns`** (number):
|
||||
- **Description:** Maximum number of turns for the Codebase Investigator
|
||||
agent.
|
||||
- **Default:** `10`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`experimental.codebaseInvestigatorSettings.maxTimeMinutes`** (number):
|
||||
- **Description:** Maximum time for the Codebase Investigator agent (in
|
||||
minutes).
|
||||
- **Default:** `3`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`experimental.codebaseInvestigatorSettings.thinkingBudget`** (number):
|
||||
- **Description:** The thinking budget for the Codebase Investigator agent.
|
||||
- **Default:** `8192`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`experimental.codebaseInvestigatorSettings.model`** (string):
|
||||
- **Description:** The model to use for the Codebase Investigator agent.
|
||||
- **Default:** `"gemini-2.5-pro"`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
#### `hooks`
|
||||
|
||||
- **`hooks`** (object):
|
||||
- **Description:** Hook configurations for intercepting and customizing agent
|
||||
behavior.
|
||||
- **Default:** `{}`
|
||||
<!-- SETTINGS-AUTOGEN:END -->
|
||||
|
||||
#### `mcpServers`
|
||||
|
||||
@@ -514,7 +882,12 @@ of v0.3.0:
|
||||
{
|
||||
"general": {
|
||||
"vimMode": true,
|
||||
"preferredEditor": "code"
|
||||
"preferredEditor": "code",
|
||||
"sessionRetention": {
|
||||
"enabled": true,
|
||||
"maxAge": "30d",
|
||||
"maxCount": 100
|
||||
}
|
||||
},
|
||||
"ui": {
|
||||
"theme": "GitHub",
|
||||
@@ -723,15 +1096,11 @@ for that specific session.
|
||||
`--output-format json` or `--output-format stream-json` flag.
|
||||
- **`--sandbox`** (**`-s`**):
|
||||
- Enables sandbox mode for this session.
|
||||
- **`--sandbox-image`**:
|
||||
- Sets the sandbox image URI.
|
||||
- **`--debug`** (**`-d`**):
|
||||
- Enables debug mode for this session, providing more verbose output.
|
||||
|
||||
- **`--help`** (or **`-h`**):
|
||||
- Displays help information about command-line arguments.
|
||||
- **`--show-memory-usage`**:
|
||||
- Displays the current memory usage.
|
||||
- **`--yolo`**:
|
||||
- Enables YOLO mode, which automatically approves all tool calls.
|
||||
- **`--approval-mode <mode>`**:
|
||||
@@ -747,22 +1116,6 @@ for that specific session.
|
||||
- A comma-separated list of tool names that will bypass the confirmation
|
||||
dialog.
|
||||
- Example: `gemini --allowed-tools "ShellTool(git status)"`
|
||||
- **`--telemetry`**:
|
||||
- Enables [telemetry](../cli/telemetry.md).
|
||||
- **`--telemetry-target`**:
|
||||
- Sets the telemetry target. See [telemetry](../cli/telemetry.md) for more
|
||||
information.
|
||||
- **`--telemetry-otlp-endpoint`**:
|
||||
- Sets the OTLP endpoint for telemetry. See [telemetry](../cli/telemetry.md)
|
||||
for more information.
|
||||
- **`--telemetry-otlp-protocol`**:
|
||||
- Sets the OTLP protocol for telemetry (`grpc` or `http`). Defaults to `grpc`.
|
||||
See [telemetry](../cli/telemetry.md) for more information.
|
||||
- **`--telemetry-log-prompts`**:
|
||||
- Enables logging of prompts for telemetry. See
|
||||
[telemetry](../cli/telemetry.md) for more information.
|
||||
- **`--checkpointing`**:
|
||||
- Enables [checkpointing](../cli/checkpointing.md).
|
||||
- **`--extensions <extension_name ...>`** (**`-e <extension_name ...>`**):
|
||||
- Specifies a list of extensions to use for the session. If not provided, all
|
||||
available extensions are used.
|
||||
@@ -770,9 +1123,24 @@ for that specific session.
|
||||
- Example: `gemini -e my-extension -e my-other-extension`
|
||||
- **`--list-extensions`** (**`-l`**):
|
||||
- Lists all available extensions and exits.
|
||||
- **`--proxy`**:
|
||||
- Sets the proxy for the CLI.
|
||||
- Example: `--proxy http://localhost:7890`.
|
||||
- **`--resume [session_id]`** (**`-r [session_id]`**):
|
||||
- Resume a previous chat session. Use "latest" for the most recent session,
|
||||
provide a session index number, or provide a full session UUID.
|
||||
- If no session_id is provided, defaults to "latest".
|
||||
- Example: `gemini --resume 5` or `gemini --resume latest` or
|
||||
`gemini --resume a1b2c3d4-e5f6-7890-abcd-ef1234567890` or `gemini --resume`
|
||||
- See [Session Management](../cli/session-management.md) for more details.
|
||||
- **`--list-sessions`**:
|
||||
- List all available chat sessions for the current project and exit.
|
||||
- Shows session indices, dates, message counts, and preview of first user
|
||||
message.
|
||||
- Example: `gemini --list-sessions`
|
||||
- **`--delete-session <identifier>`**:
|
||||
- Delete a specific chat session by its index number or full session UUID.
|
||||
- Use `--list-sessions` first to see available sessions, their indices, and
|
||||
UUIDs.
|
||||
- Example: `gemini --delete-session 3` or
|
||||
`gemini --delete-session a1b2c3d4-e5f6-7890-abcd-ef1234567890`
|
||||
- **`--include-directories <dir1,dir2,...>`**:
|
||||
- Includes additional directories in the workspace for multi-directory
|
||||
support.
|
||||
@@ -785,6 +1153,14 @@ for that specific session.
|
||||
with screen readers.
|
||||
- **`--version`**:
|
||||
- Displays the version of the CLI.
|
||||
- **`--experimental-acp`**:
|
||||
- Starts the agent in ACP mode.
|
||||
- **`--allowed-mcp-server-names`**:
|
||||
- Allowed MCP server names.
|
||||
- **`--fake-responses`**:
|
||||
- Path to a file with fake model responses for testing.
|
||||
- **`--record-responses`**:
|
||||
- Path to a file to record model responses for testing.
|
||||
|
||||
## Context Files (Hierarchical Instructional Context)
|
||||
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
# Gemini 3 Pro on Gemini CLI (Join the Waitlist)
|
||||
|
||||
We’re excited to bring Gemini 3 Pro to Gemini CLI. For Google AI Ultra users
|
||||
(Google AI Ultra for Business is not currently supported) and paid Gemini and
|
||||
Vertex API key holders, Gemini 3 Pro is already available and ready to enable.
|
||||
For everyone else, we're gradually expanding access
|
||||
[through a waitlist](https://goo.gle/geminicli-waitlist-signup). Sign up for the
|
||||
waitlist now to access Gemini 3 Pro once approved.
|
||||
|
||||
**Note:** Please wait until you have been approved to use Gemini 3 Pro to enable
|
||||
**Preview Features**. If enabled early, the CLI will fallback to Gemini 2.5 Pro.
|
||||
|
||||
## Do I need to join the waitlist?
|
||||
|
||||
The following users will be **automatically granted access** to Gemini 3 Pro on
|
||||
Gemini CLI:
|
||||
|
||||
- Google AI Ultra subscribers (excluding Google AI Ultra for Business, which is
|
||||
on the roadmap).
|
||||
- Gemini API key users
|
||||
[with access to Gemini 3](https://ai.google.dev/gemini-api/docs/rate-limits).
|
||||
- Vertex API key users
|
||||
[with access to Gemini 3](https://docs.cloud.google.com/vertex-ai/generative-ai/docs/quotas).
|
||||
|
||||
For **Gemini Code Assist Enterprise users**, access is coming soon.
|
||||
|
||||
Users not automatically granted access through one of these account types will
|
||||
need to join the waitlist. This includes Google AI Pro, Gemini Code Assist
|
||||
standard, and free tier users.
|
||||
|
||||
Note: Whether you’re automatically granted access or accepted from the waitlist,
|
||||
you’ll still need to enable Gemini 3 Pro
|
||||
[using the `/settings` command](../cli/settings.md).
|
||||
|
||||
## How to join the waitlist
|
||||
|
||||
Users not automatically granted access will need to join the waitlist. Follow
|
||||
these instructions to sign up:
|
||||
|
||||
- Install Gemini CLI.
|
||||
- Authenticate using the **Login with Google** option. You’ll see a banner that
|
||||
says “Gemini 3 is now available.” If you do not see this banner, update your
|
||||
installation of Gemini CLI to the most recent version.
|
||||
- Fill out this Google form:
|
||||
[Access Gemini 3 in Gemini CLI](https://goo.gle/geminicli-waitlist-signup).
|
||||
Provide the email address of the account you used to authenticate with Gemini
|
||||
CLI.
|
||||
|
||||
Users will be onboarded in batches, subject to availability. When you’ve been
|
||||
granted access to Gemini 3 Pro, you’ll receive an acceptance email to your
|
||||
submitted email address.
|
||||
|
||||
## How to use Gemini 3 Pro with Gemini CLI
|
||||
|
||||
Once you receive your acceptance email–or if you are automatically granted
|
||||
access–you still need to enable Gemini 3 Pro within Gemini CLI.
|
||||
|
||||
To enable Gemini 3 Pro, use the `/settings` command in Gemini CLI and set
|
||||
**Preview Features** to `true`.
|
||||
|
||||
For more information, see [Gemini CLI Settings](../cli/settings.md).
|
||||
|
||||
### Usage limits and fallback
|
||||
|
||||
Gemini CLI will tell you when you reach your Gemini 3 Pro daily usage limit.
|
||||
When you encounter that limit, you’ll be given the option to switch to Gemini
|
||||
2.5 Pro, upgrade for higher limits, or stop. You’ll also be told when your usage
|
||||
limit resets and Gemini 3 Pro can be used again.
|
||||
|
||||
Similarly, when you reach your daily usage limit for Gemini 2.5 Pro, you’ll see
|
||||
a message prompting fallback to Gemini 2.5 Flash.
|
||||
|
||||
### Capacity errors
|
||||
|
||||
There may be times when the Gemini 3 Pro model is overloaded. When that happens,
|
||||
Gemini CLI will ask you to decide whether you want to keep trying Gemini 3 Pro
|
||||
or fallback to Gemini 2.5 Pro.
|
||||
|
||||
**Note:** The **Keep trying** option uses exponential backoff, in which Gemini
|
||||
CLI waits longer between each retry, when the system is busy. If the retry
|
||||
doesn't happen immediately, please wait a few minutes for the request to
|
||||
process.
|
||||
|
||||
## Model selection & routing types
|
||||
|
||||
When using Gemini CLI, you may want to control how your requests are routed
|
||||
between models. By default, Gemini CLI uses **Auto** routing.
|
||||
|
||||
When using Gemini 3 Pro, you may want to use Auto routing or Pro routing to
|
||||
manage your usage limits:
|
||||
|
||||
- **Auto routing:** Auto routing first determines whether a prompt involves a
|
||||
complex or simple operation. For simple prompts, it will automatically use
|
||||
Gemini 2.5 Flash. For complex prompts, if Gemini 3 Pro is enabled, it will use
|
||||
Gemini 3 Pro; otherwise, it will use Gemini 2.5 Pro.
|
||||
- **Pro routing:** If you want to ensure your task is processed by the most
|
||||
capable model, use `/model` and select **Pro**. Gemini CLI will prioritize the
|
||||
most capable model available, including Gemini 3 Pro if it has been enabled.
|
||||
|
||||
To learn more about selecting a model and routing, refer to
|
||||
[Gemini CLI Model Selection](../cli/model.md).
|
||||
|
||||
## Need help?
|
||||
|
||||
If you need help, we recommend searching for an existing
|
||||
[GitHub issue](https://github.com/google-gemini/gemini-cli/issues). If you
|
||||
cannot find a GitHub issue that matches your concern, you can
|
||||
[create a new issue](https://github.com/google-gemini/gemini-cli/issues/new/choose).
|
||||
For comments and feedback, consider opening a
|
||||
[GitHub discussion](https://github.com/google-gemini/gemini-cli/discussions).
|
||||
@@ -63,3 +63,4 @@ To explore the power of Gemini CLI, see [Gemini CLI examples](./examples.md).
|
||||
|
||||
- Find out more about [Gemini CLI's tools](../tools/index.md).
|
||||
- Review [Gemini CLI's commands](../cli/commands.md).
|
||||
- Learn how to [get started with Gemini 3](./gemini-3.md).
|
||||
|
||||
@@ -4,8 +4,8 @@ Gemini CLI can integrate with your IDE to provide a more seamless and
|
||||
context-aware experience. This integration allows the CLI to understand your
|
||||
workspace better and enables powerful features like native in-editor diffing.
|
||||
|
||||
Currently, the only supported IDE is
|
||||
[Visual Studio Code](https://code.visualstudio.com/) and other editors that
|
||||
Currently, the supported IDEs are [Antigravity](https://antigravity.google),
|
||||
[Visual Studio Code](https://code.visualstudio.com/), and other editors that
|
||||
support VS Code extensions. To build support for other editors, see the
|
||||
[IDE Companion Extension Spec](./ide-companion-spec.md).
|
||||
|
||||
@@ -191,7 +191,7 @@ messages and how to resolve them.
|
||||
- **Cause:** You are running Gemini CLI in a terminal or environment that is
|
||||
not a supported IDE.
|
||||
- **Solution:** Run Gemini CLI from the integrated terminal of a supported
|
||||
IDE, like VS Code.
|
||||
IDE, like Antigravity or VS Code.
|
||||
|
||||
- **Message:**
|
||||
`No installer is available for IDE. Please install the Gemini CLI Companion extension manually from the marketplace.`
|
||||
|
||||
+9
-2
@@ -28,12 +28,18 @@ This documentation is organized into the following sections:
|
||||
- **[Configuration](./get-started/configuration.md):** Information on
|
||||
configuring the CLI.
|
||||
- **[Examples](./get-started/examples.md):** Example usage of Gemini CLI.
|
||||
- **[Get started with Gemini 3](./get-started/gemini-3.md):** Learn how to
|
||||
enable and use Gemini 3.
|
||||
|
||||
### CLI
|
||||
|
||||
- **[CLI overview](./cli/index.md):** Overview of the command-line interface.
|
||||
- **[Commands](./cli/commands.md):** Description of available CLI commands.
|
||||
- **[Enterprise](./cli/enterprise.md):** Gemini CLI for enterprise.
|
||||
- **[Model Selection](./cli/model.md):** Select the model used to process your
|
||||
commands with `/model`.
|
||||
- **[Settings](./cli/settings.md):** Configure various aspects of the CLI's
|
||||
behavior and appearance with `/settings`.
|
||||
- **[Themes](./cli/themes.md):** Themes for Gemini CLI.
|
||||
- **[Token Caching](./cli/token-caching.md):** Token caching and optimization.
|
||||
- **[Tutorials](./cli/tutorials.md):** Tutorials for Gemini CLI.
|
||||
@@ -50,6 +56,8 @@ This documentation is organized into the following sections:
|
||||
- **[Memport](./core/memport.md):** Using the Memory Import Processor.
|
||||
- **[Tools API](./core/tools-api.md):** Information on how the core manages and
|
||||
exposes tools.
|
||||
- **[Policy Engine](./core/policy-engine.md):** Use the Policy Engine for
|
||||
fine-grained control over tool execution.
|
||||
|
||||
### Tools
|
||||
|
||||
@@ -58,8 +66,6 @@ This documentation is organized into the following sections:
|
||||
- **[File System Tools](./tools/file-system.md):** Documentation for the
|
||||
`read_file` and `write_file` tools.
|
||||
- **[MCP servers](./tools/mcp-server.md):** Using MCP servers with Gemini CLI.
|
||||
- **[Multi-File Read Tool](./tools/multi-file.md):** Documentation for the
|
||||
`read_many_files` tool.
|
||||
- **[Shell Tool](./tools/shell.md):** Documentation for the `run_shell_command`
|
||||
tool.
|
||||
- **[Web Fetch Tool](./tools/web-fetch.md):** Documentation for the `web_fetch`
|
||||
@@ -68,6 +74,7 @@ This documentation is organized into the following sections:
|
||||
`google_web_search` tool.
|
||||
- **[Memory Tool](./tools/memory.md):** Documentation for the `save_memory`
|
||||
tool.
|
||||
- **[Todo Tool](./tools/todos.md):** Documentation for the `write_todos` tool.
|
||||
|
||||
### Extensions
|
||||
|
||||
|
||||
@@ -56,15 +56,42 @@ To run a single test by its name, use the `--test-name-pattern` flag:
|
||||
npm run test:e2e -- --test-name-pattern "reads a file"
|
||||
```
|
||||
|
||||
### Regenerating model responses
|
||||
|
||||
Some integration tests use faked out model responses, which may need to be
|
||||
regenerated from time to time as the implementations change.
|
||||
|
||||
To regenerate these golden files, set the REGENERATE_MODEL_GOLDENS environment
|
||||
variable to "true" when running the tests, for example:
|
||||
|
||||
**WARNING**: If running locally you should review these updated responses for
|
||||
any information about yourself or your system that gemini may have included in
|
||||
these responses.
|
||||
|
||||
```bash
|
||||
REGENERATE_MODEL_GOLDENS="true" npm run test:e2e
|
||||
```
|
||||
|
||||
**WARNING**: Make sure you run **await rig.cleanup()** at the end of your test,
|
||||
else the golden files will not be updated.
|
||||
|
||||
### Deflaking a test
|
||||
|
||||
Before adding a **new** integration test, you should test it at least 5 times
|
||||
with the deflake script to make sure that it is not flaky.
|
||||
with the deflake script or workflow to make sure that it is not flaky.
|
||||
|
||||
### Deflake script
|
||||
|
||||
```bash
|
||||
npm run deflake -- --runs=5 --command="npm run test:e2e -- -- --test-name-pattern '<your-new-test-name>'"
|
||||
```
|
||||
|
||||
#### Deflake Workflow
|
||||
|
||||
```bash
|
||||
gh workflow run deflake.yml --ref <your-branch> -f test_name_pattern="<your-test-name-pattern>"
|
||||
```
|
||||
|
||||
### Running all tests
|
||||
|
||||
To run the entire suite of integration tests, use the following command:
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
# Local Development Guide
|
||||
|
||||
This guide provides instructions for setting up and using local development
|
||||
features, such as development tracing.
|
||||
|
||||
## Development Tracing
|
||||
|
||||
Development traces (dev traces) are OpenTelemetry (OTel) traces that help you
|
||||
debug your code by instrumenting interesting events like model calls, tool
|
||||
scheduler, tool calls, etc.
|
||||
|
||||
Dev traces are verbose and are specifically meant for understanding agent
|
||||
behaviour and debugging issues. They are disabled by default.
|
||||
|
||||
To enable dev traces, set the `GEMINI_DEV_TRACING=true` environment variable
|
||||
when running Gemini CLI.
|
||||
|
||||
### Viewing Dev Traces
|
||||
|
||||
You can view dev traces using either Jaeger or the Genkit Developer UI.
|
||||
|
||||
#### Using Genkit
|
||||
|
||||
Genkit provides a web-based UI for viewing traces and other telemetry data.
|
||||
|
||||
1. **Start the Genkit Telemetry Server:**
|
||||
|
||||
Run the following command to start the Genkit server:
|
||||
|
||||
```bash
|
||||
npm run telemetry -- --target=genkit
|
||||
```
|
||||
|
||||
The script will output the URL for the Genkit Developer UI, for example:
|
||||
|
||||
```
|
||||
Genkit Developer UI: http://localhost:4000
|
||||
```
|
||||
|
||||
2. **Run Gemini CLI with Dev Tracing:**
|
||||
|
||||
In a separate terminal, run your Gemini CLI command with the
|
||||
`GEMINI_DEV_TRACING` environment variable:
|
||||
|
||||
```bash
|
||||
GEMINI_DEV_TRACING=true gemini
|
||||
```
|
||||
|
||||
3. **View the Traces:**
|
||||
|
||||
Open the Genkit Developer UI URL in your browser and navigate to the
|
||||
**Traces** tab to view the traces.
|
||||
|
||||
#### Using Jaeger
|
||||
|
||||
You can view dev traces in the Jaeger UI. To get started, follow these steps:
|
||||
|
||||
1. **Start the telemetry collector:**
|
||||
|
||||
Run the following command in your terminal to download and start Jaeger and
|
||||
an OTEL collector:
|
||||
|
||||
```bash
|
||||
npm run telemetry -- --target=local
|
||||
```
|
||||
|
||||
This command also configures your workspace for local telemetry and provides
|
||||
a link to the Jaeger UI (usually `http://localhost:16686`).
|
||||
|
||||
2. **Run Gemini CLI with dev tracing:**
|
||||
|
||||
In a separate terminal, run your Gemini CLI command with the
|
||||
`GEMINI_DEV_TRACING` environment variable:
|
||||
|
||||
```bash
|
||||
GEMINI_DEV_TRACING=true gemini
|
||||
```
|
||||
|
||||
3. **View the traces:**
|
||||
|
||||
After running your command, open the Jaeger UI link in your browser to view
|
||||
the traces.
|
||||
|
||||
For more detailed information on telemetry, see the
|
||||
[telemetry documentation](./cli/telemetry.md).
|
||||
|
||||
### Instrumenting Code with Dev Traces
|
||||
|
||||
You can add dev traces to your own code for more detailed instrumentation. This
|
||||
is useful for debugging and understanding the flow of execution.
|
||||
|
||||
Use the `runInDevTraceSpan` function to wrap any section of code in a trace
|
||||
span.
|
||||
|
||||
Here is a basic example:
|
||||
|
||||
```typescript
|
||||
import { runInDevTraceSpan } from '@google/gemini-cli-core';
|
||||
|
||||
await runInDevTraceSpan({ name: 'my-custom-span' }, async ({ metadata }) => {
|
||||
// The `metadata` object allows you to record the input and output of the
|
||||
// operation as well as other attributes.
|
||||
metadata.input = { key: 'value' };
|
||||
// Set custom attributes.
|
||||
metadata.attributes['gen_ai.request.model'] = 'gemini-4.0-mega';
|
||||
|
||||
// Your code to be traced goes here
|
||||
try {
|
||||
const output = await somethingRisky();
|
||||
metadata.output = output;
|
||||
return output;
|
||||
} catch (e) {
|
||||
metadata.error = e;
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
In this example:
|
||||
|
||||
- `name`: The name of the span, which will be displayed in the trace.
|
||||
- `metadata.input`: (Optional) An object containing the input data for the
|
||||
traced operation.
|
||||
- `metadata.output`: (Optional) An object containing the output data from the
|
||||
traced operation.
|
||||
- `metadata.attributes`: (Optional) A record of custom attributes to add to the
|
||||
span.
|
||||
- `metadata.error`: (Optional) An error object to record if the operation fails.
|
||||
@@ -8,8 +8,10 @@ account you use to authenticate.
|
||||
See [privacy and terms](./tos-privacy.md) for details on Privacy policy and
|
||||
Terms of Service.
|
||||
|
||||
> [!NOTE] Published prices are list price; additional negotiated commercial
|
||||
> discounting may apply.
|
||||
> [!NOTE]
|
||||
>
|
||||
> Published prices are list price; additional negotiated commercial discounting
|
||||
> may apply.
|
||||
|
||||
This article outlines the specific quotas and pricing applicable to the Gemini
|
||||
CLI when using different authentication methods.
|
||||
@@ -82,10 +84,10 @@ Gemini CLI by upgrading to one of the following subscriptions:
|
||||
[Gemini Code Assist Quotas and Limits](https://developers.google.com/gemini-code-assist/resources/quotas)
|
||||
|
||||
- [Purchase a Gemini Code Assist Subscription through Google Cloud ](https://cloud.google.com/gemini/docs/codeassist/overview)
|
||||
by signing up in the Google Cloud console. Learn more at [Set up Gemini Code
|
||||
Assist] (https://cloud.google.com/gemini/docs/discover/set-up-gemini) Quotas
|
||||
and pricing are based on a fixed price subscription with assigned license
|
||||
seats. For predictable costs, you can sign in with Google.
|
||||
by signing up in the Google Cloud console. Learn more at
|
||||
[Set up Gemini Code Assist](https://cloud.google.com/gemini/docs/discover/set-up-gemini)
|
||||
Quotas and pricing are based on a fixed price subscription with assigned
|
||||
license seats. For predictable costs, you can sign in with Google.
|
||||
|
||||
This includes:
|
||||
- Gemini Code Assist Standard edition:
|
||||
|
||||
+28
-4
@@ -9,6 +9,10 @@
|
||||
{
|
||||
"label": "Architecture Overview",
|
||||
"slug": "docs/architecture"
|
||||
},
|
||||
{
|
||||
"label": "Contribution Guide",
|
||||
"slug": "docs/contributing"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -19,6 +23,10 @@
|
||||
"label": "Gemini CLI Quickstart",
|
||||
"slug": "docs/get-started"
|
||||
},
|
||||
{
|
||||
"label": "Gemini 3 Pro on Gemini CLI",
|
||||
"slug": "docs/get-started/gemini-3"
|
||||
},
|
||||
{
|
||||
"label": "Authentication",
|
||||
"slug": "docs/get-started/authentication"
|
||||
@@ -68,10 +76,22 @@
|
||||
"label": "Keyboard Shortcuts",
|
||||
"slug": "docs/cli/keyboard-shortcuts"
|
||||
},
|
||||
{
|
||||
"label": "Model Selection",
|
||||
"slug": "docs/cli/model"
|
||||
},
|
||||
{
|
||||
"label": "Sandbox",
|
||||
"slug": "docs/cli/sandbox"
|
||||
},
|
||||
{
|
||||
"label": "Session Management",
|
||||
"slug": "docs/cli/session-management"
|
||||
},
|
||||
{
|
||||
"label": "Settings",
|
||||
"slug": "docs/cli/settings"
|
||||
},
|
||||
{
|
||||
"label": "Telemetry",
|
||||
"slug": "docs/cli/telemetry"
|
||||
@@ -112,6 +132,10 @@
|
||||
{
|
||||
"label": "Memory Import Processor",
|
||||
"slug": "docs/core/memport"
|
||||
},
|
||||
{
|
||||
"label": "Policy Engine",
|
||||
"slug": "docs/core/policy-engine"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -126,10 +150,6 @@
|
||||
"label": "File System",
|
||||
"slug": "docs/tools/file-system"
|
||||
},
|
||||
{
|
||||
"label": "Multi-File Read",
|
||||
"slug": "docs/tools/multi-file"
|
||||
},
|
||||
{
|
||||
"label": "Shell",
|
||||
"slug": "docs/tools/shell"
|
||||
@@ -146,6 +166,10 @@
|
||||
"label": "Memory",
|
||||
"slug": "docs/tools/memory"
|
||||
},
|
||||
{
|
||||
"label": "Todos",
|
||||
"slug": "docs/tools/todos"
|
||||
},
|
||||
{
|
||||
"label": "MCP Servers",
|
||||
"slug": "docs/tools/mcp-server"
|
||||
|
||||
+2
-2
@@ -82,10 +82,10 @@ Gemini CLI's built-in tools can be broadly categorized as follows:
|
||||
from URLs.
|
||||
- **[Web Search Tool](./web-search.md) (`google_web_search`):** For searching
|
||||
the web.
|
||||
- **[Multi-File Read Tool](./multi-file.md) (`read_many_files`):** A specialized
|
||||
tool for reading content from multiple files or directories.
|
||||
- **[Memory Tool](./memory.md) (`save_memory`):** For saving and recalling
|
||||
information across sessions.
|
||||
- **[Todo Tool](./todos.md) (`write_todos`):** For managing subtasks of complex
|
||||
requests.
|
||||
|
||||
Additionally, these tools incorporate:
|
||||
|
||||
|
||||
@@ -927,13 +927,13 @@ This is the default transport for running local servers.
|
||||
|
||||
```bash
|
||||
# Basic syntax
|
||||
gemini mcp add <name> <command> [args...]
|
||||
gemini mcp add [options] <name> <command> [args...]
|
||||
|
||||
# Example: Adding a local server
|
||||
gemini mcp add -e API_KEY=123 my-stdio-server /path/to/server arg1 arg2 arg3
|
||||
gemini mcp add -e API_KEY=123 -e DEBUG=true my-stdio-server /path/to/server arg1 arg2 arg3
|
||||
|
||||
# Example: Adding a local python server
|
||||
gemini mcp add python-server python server.py --port 8080
|
||||
gemini mcp add python-server python server.py -- --server-arg my-value
|
||||
```
|
||||
|
||||
#### Adding an HTTP server
|
||||
@@ -969,7 +969,8 @@ gemini mcp add --transport sse --header "Authorization: Bearer abc123" secure-ss
|
||||
### Listing Servers (`gemini mcp list`)
|
||||
|
||||
To view all MCP servers currently configured, use the `list` command. It
|
||||
displays each server's name, configuration details, and connection status.
|
||||
displays each server's name, configuration details, and connection status. This
|
||||
command has no flags.
|
||||
|
||||
**Command:**
|
||||
|
||||
@@ -996,6 +997,10 @@ server's name.
|
||||
gemini mcp remove <name>
|
||||
```
|
||||
|
||||
**Options (Flags):**
|
||||
|
||||
- `-s, --scope`: Configuration scope (user or project). [default: "project"]
|
||||
|
||||
**Example:**
|
||||
|
||||
```bash
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
# Multi File Read Tool (`read_many_files`)
|
||||
|
||||
This document describes the `read_many_files` tool for the Gemini CLI.
|
||||
|
||||
## Description
|
||||
|
||||
Use `read_many_files` to read content from multiple files specified by paths or
|
||||
glob patterns. The behavior of this tool depends on the provided files:
|
||||
|
||||
- For text files, this tool concatenates their content into a single string.
|
||||
- For image (e.g., PNG, JPEG), PDF, audio (MP3, WAV), and video (MP4, MOV)
|
||||
files, it reads and returns them as base64-encoded data, provided they are
|
||||
explicitly requested by name or extension.
|
||||
|
||||
`read_many_files` can be used to perform tasks such as getting an overview of a
|
||||
codebase, finding where specific functionality is implemented, reviewing
|
||||
documentation, or gathering context from multiple configuration files.
|
||||
|
||||
**Note:** `read_many_files` looks for files following the provided paths or glob
|
||||
patterns. A directory path such as `"/docs"` will return an empty result; the
|
||||
tool requires a pattern such as `"/docs/*"` or `"/docs/*.md"` to identify the
|
||||
relevant files.
|
||||
|
||||
### Arguments
|
||||
|
||||
`read_many_files` takes the following arguments:
|
||||
|
||||
- `paths` (list[string], required): An array of glob patterns or paths relative
|
||||
to the tool's target directory (e.g., `["src/**/*.ts"]`,
|
||||
`["README.md", "docs/*", "assets/logo.png"]`).
|
||||
- `exclude` (list[string], optional): Glob patterns for files/directories to
|
||||
exclude (e.g., `["**/*.log", "temp/"]`). These are added to default excludes
|
||||
if `useDefaultExcludes` is true.
|
||||
- `include` (list[string], optional): Additional glob patterns to include. These
|
||||
are merged with `paths` (e.g., `["*.test.ts"]` to specifically add test files
|
||||
if they were broadly excluded, or `["images/*.jpg"]` to include specific image
|
||||
types).
|
||||
- `recursive` (boolean, optional): Whether to search recursively. This is
|
||||
primarily controlled by `**` in glob patterns. Defaults to `true`.
|
||||
- `useDefaultExcludes` (boolean, optional): Whether to apply a list of default
|
||||
exclusion patterns (e.g., `node_modules`, `.git`, non image/pdf binary files).
|
||||
Defaults to `true`.
|
||||
- `respect_git_ignore` (boolean, optional): Whether to respect .gitignore
|
||||
patterns when finding files. Defaults to true.
|
||||
|
||||
## How to use `read_many_files` with the Gemini CLI
|
||||
|
||||
`read_many_files` searches for files matching the provided `paths` and `include`
|
||||
patterns, while respecting `exclude` patterns and default excludes (if enabled).
|
||||
|
||||
- For text files: it reads the content of each matched file (attempting to skip
|
||||
binary files not explicitly requested as image/PDF) and concatenates it into a
|
||||
single string, with a separator `--- {filePath} ---` between the content of
|
||||
each file. Uses UTF-8 encoding by default.
|
||||
- The tool inserts a `--- End of content ---` after the last file.
|
||||
- For image and PDF files: if explicitly requested by name or extension (e.g.,
|
||||
`paths: ["logo.png"]` or `include: ["*.pdf"]`), the tool reads the file and
|
||||
returns its content as a base64 encoded string.
|
||||
- The tool attempts to detect and skip other binary files (those not matching
|
||||
common image/PDF types or not explicitly requested) by checking for null bytes
|
||||
in their initial content.
|
||||
|
||||
Usage:
|
||||
|
||||
```
|
||||
read_many_files(paths=["Your files or paths here."], include=["Additional files to include."], exclude=["Files to exclude."], recursive=False, useDefaultExcludes=false, respect_git_ignore=true)
|
||||
```
|
||||
|
||||
## `read_many_files` examples
|
||||
|
||||
Read all TypeScript files in the `src` directory:
|
||||
|
||||
```
|
||||
read_many_files(paths=["src/**/*.ts"])
|
||||
```
|
||||
|
||||
Read the main README, all Markdown files in the `docs` directory, and a specific
|
||||
logo image, excluding a specific file:
|
||||
|
||||
```
|
||||
read_many_files(paths=["README.md", "docs/**/*.md", "assets/logo.png"], exclude=["docs/OLD_README.md"])
|
||||
```
|
||||
|
||||
Read all JavaScript files but explicitly include test files and all JPEGs in an
|
||||
`images` folder:
|
||||
|
||||
```
|
||||
read_many_files(paths=["**/*.js"], include=["**/*.test.js", "images/**/*.jpg"], useDefaultExcludes=False)
|
||||
```
|
||||
|
||||
## Important notes
|
||||
|
||||
- **Binary file handling:**
|
||||
- **Image/PDF/Audio/Video files:** The tool can read common image types (PNG,
|
||||
JPEG, etc.), PDF, audio (mp3, wav), and video (mp4, mov) files, returning
|
||||
them as base64 encoded data. These files _must_ be explicitly targeted by
|
||||
the `paths` or `include` patterns (e.g., by specifying the exact filename
|
||||
like `video.mp4` or a pattern like `*.mov`).
|
||||
- **Other binary files:** The tool attempts to detect and skip other types of
|
||||
binary files by examining their initial content for null bytes. The tool
|
||||
excludes these files from its output.
|
||||
- **Performance:** Reading a very large number of files or very large individual
|
||||
files can be resource-intensive.
|
||||
- **Path specificity:** Ensure paths and glob patterns are correctly specified
|
||||
relative to the tool's target directory. For image/PDF files, ensure the
|
||||
patterns are specific enough to include them.
|
||||
- **Default excludes:** Be aware of the default exclusion patterns (like
|
||||
`node_modules`, `.git`) and use `useDefaultExcludes=False` if you need to
|
||||
override them, but do so cautiously.
|
||||
@@ -0,0 +1,57 @@
|
||||
# Todo Tool (`write_todos`)
|
||||
|
||||
This document describes the `write_todos` tool for the Gemini CLI.
|
||||
|
||||
## Description
|
||||
|
||||
The `write_todos` tool allows the Gemini agent to create and manage a list of
|
||||
subtasks for complex user requests. This provides you, the user, with greater
|
||||
visibility into the agent's plan and its current progress. It also helps with
|
||||
alignment where the agent is less likely to lose track of its current goal.
|
||||
|
||||
### Arguments
|
||||
|
||||
`write_todos` takes one argument:
|
||||
|
||||
- `todos` (array of objects, required): The complete list of todo items. This
|
||||
replaces the existing list. Each item includes:
|
||||
- `description` (string): The task description.
|
||||
- `status` (string): The current status (`pending`, `in_progress`,
|
||||
`completed`, or `cancelled`).
|
||||
|
||||
## Behavior
|
||||
|
||||
The agent uses this tool to break down complex multi-step requests into a clear
|
||||
plan.
|
||||
|
||||
- **Progress Tracking:** The agent updates this list as it works, marking tasks
|
||||
as `completed` when done.
|
||||
- **Single Focus:** Only one task will be marked `in_progress` at a time,
|
||||
indicating exactly what the agent is currently working on.
|
||||
- **Dynamic Updates:** The plan may evolve as the agent discovers new
|
||||
information, leading to new tasks being added or unnecessary ones being
|
||||
cancelled.
|
||||
|
||||
When active, the current `in_progress` task is displayed above the input box,
|
||||
keeping you informed of the immediate action. You can toggle the full view of
|
||||
the todo list at any time by pressing `Ctrl+T`.
|
||||
|
||||
Usage example (internal representation):
|
||||
|
||||
```javascript
|
||||
write_todos({
|
||||
todos: [
|
||||
{ description: 'Initialize new React project', status: 'completed' },
|
||||
{ description: 'Implement state management', status: 'in_progress' },
|
||||
{ description: 'Create API service', status: 'pending' },
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
## Important notes
|
||||
|
||||
- **Enabling:** This tool is enabled by default. You can disable it in your
|
||||
`settings.json` file by setting `"useWriteTodos": false`.
|
||||
|
||||
- **Intended Use:** This tool is primarily used by the agent for complex,
|
||||
multi-turn tasks. It is generally not used for simple, single-turn questions.
|
||||
+25
-1
@@ -29,7 +29,6 @@ export default tseslint.config(
|
||||
// Global ignores
|
||||
ignores: [
|
||||
'node_modules/*',
|
||||
'.integration-tests/**',
|
||||
'eslint.config.js',
|
||||
'packages/**/dist/**',
|
||||
'bundle/**',
|
||||
@@ -161,6 +160,31 @@ export default tseslint.config(
|
||||
'default-case': 'error',
|
||||
},
|
||||
},
|
||||
{
|
||||
// Prevent self-imports in packages
|
||||
files: ['packages/core/src/**/*.{ts,tsx}'],
|
||||
rules: {
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
name: '@google/gemini-cli-core',
|
||||
message: 'Please use relative imports within the @google/gemini-cli-core package.',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['packages/cli/src/**/*.{ts,tsx}'],
|
||||
rules: {
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
name: '@google/gemini-cli',
|
||||
message: 'Please use relative imports within the @google/gemini-cli package.',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['packages/*/src/**/*.test.{ts,tsx}'],
|
||||
plugins: {
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"thought":true,"text":"**Observing Initial Conditions**\n\nI'm currently focused on the initial context. I've taken note of the provided date, OS, and working directory. I'm also carefully examining the file structure presented within the current working directory. It's helping me understand the starting point for further analysis.\n\n\n"}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":12270,"totalTokenCount":12316,"promptTokensDetails":[{"modality":"TEXT","tokenCount":12270}],"thoughtsTokenCount":46}},{"candidates":[{"content":{"parts":[{"thought":true,"text":"**Assessing User Intent**\n\nI'm now shifting my focus. I've successfully registered the provided data and file structure. My current task is to understand the user's ultimate goal, given the information provided. The \"Hello.\" command is straightforward, but I'm checking if there's an underlying objective.\n\n\n"}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":12270,"totalTokenCount":12341,"promptTokensDetails":[{"modality":"TEXT","tokenCount":12270}],"thoughtsTokenCount":71}},{"candidates":[{"content":{"parts":[{"thoughtSignature":"CiQB0e2Kb3dRh+BYdbZvmulSN2Pwbc75DfQOT3H4EN0rn039hoMKfwHR7YpvvyqNKoxXAiCbYw3gbcTr/+pegUpgnsIrt8oQPMytFMjKSsMyshfygc21T2MkyuI6Q5I/fNCcHROWexdZnIeppVCDB2TarN4LGW4T9Yci6n/ynMMFT2xc2/vyHpkDgRM7avhMElnBhuxAY+e4TpxkZIncGWCEHP1TouoKpgEB0e2Kb8Xpwm0hiKhPt2ZLizpxjk+CVtcbnlgv69xo5VsuQ+iNyrVGBGRwNx+eTeNGdGpn6e73WOCZeP91FwOZe7URyL12IA6E6gYWqw0kXJR4hO4p6Lwv49E3+FRiG2C4OKDF8LF5XorYyCHSgBFT1/RUAVj81GDTx1xxtmYKN3xq8Ri+HsPbqU/FM/jtNZKkXXAtufw2Bmw8lJfmugENIv/TQI7xCo8BAdHtim8KgAXJfZ7ASfutVLKTylQeaslyB/SmcHJ0ZiNr5j8WP1prZdb6XnZZ1ZNbhjxUf/ymoxHKGvtTPBgLE9azMj8Lx/k0clhd2a+wNsiIqW9qCzlVah0tBMytpQUjIDtQe9Hj4LLUprF9PUe/xJkj000Z0ZzsgFm2ncdTWZTdkhCQDpyETVAxdE+oklwKJAHR7YpvUjSkD6KwY1gLrOsHKy0UNfn2lMbxjVetKNMVBRqsTg==","text":"Hello."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":12270,"totalTokenCount":12341,"promptTokensDetails":[{"modality":"TEXT","tokenCount":12270}],"thoughtsTokenCount":71}}]}
|
||||
{"method":"generateContent","response":{"candidates":[{"content":{"parts":[{"text":"<state_snapshot>\n <overall_goal>\n <!-- The user has not yet specified a goal. -->\n </overall_goal>\n\n <key_knowledge>\n - OS: linux\n - Date: Friday, October 24, 2025\n </key_knowledge>\n\n <file_system_state>\n - OBSERVED: The directory contains `telemetry.log` and a `.gemini/` directory.\n - OBSERVED: The `.gemini/` directory contains `settings.json` and `settings.json.orig`.\n </file_system_state>\n\n <recent_actions>\n - The user initiated the chat.\n </recent_actions>\n\n <current_plan>\n 1. [TODO] Await the user's first instruction to formulate a plan.\n </current_plan>\n</state_snapshot>"}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":983,"candidatesTokenCount":299,"totalTokenCount":1637,"promptTokensDetails":[{"modality":"TEXT","tokenCount":983}],"thoughtsTokenCount":355}}}
|
||||
@@ -0,0 +1,3 @@
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"thought":true,"text":"**Generating a Story**\n\nI've crafted the robot story. The narrative is complete and meets the length requirement. Now, I'm getting ready to use the `write_file` tool to save it. I'm choosing the filename `robot_story.txt` as a default.\n\n\n"}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":12282,"totalTokenCount":12352,"promptTokensDetails":[{"modality":"TEXT","tokenCount":12282}],"thoughtsTokenCount":70}},{"candidates":[{"finishReason":"MALFORMED_FUNCTION_CALL","index":0}],"usageMetadata":{"promptTokenCount":12282,"totalTokenCount":12282,"promptTokensDetails":[{"modality":"TEXT","tokenCount":12282}]}}]}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"thought":true,"text":"**Drafting the Narrative**\n\nI'm currently focused on the narrative's central conflict. I'm aiming for a compelling story about a robot and am working to keep the word count tight. The \"THE _END.\" conclusion is proving challenging to integrate organically. I need to make the ending feel natural and satisfying.\n\n\n"}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":12282,"totalTokenCount":12326,"cachedContentTokenCount":11883,"promptTokensDetails":[{"modality":"TEXT","tokenCount":12282}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":11883}],"thoughtsTokenCount":44}},{"candidates":[{"content":{"parts":[{"thoughtSignature":"CikB0e2Kb7zkpgRyJXXNt6ykO/+FoOglhrKxjLgoESrgafzIZak2Ofxo1gpaAdHtim9aG7MvpXlIg+n2zgmcDBWOPXtvQHxhE9k8pR+DO8i2jIe3tMWLxdN944XpUlR9vaNmVdtSRMKr4MhB/t1R3WSWR3QYhk7MEQxnjYR7cv/pR9viwZyFCoYBAdHtim/xKmMl/S+U8p+p9848q4agsL/STufluXewPqL3uJSinZbN0Z4jTYfMzXKldhDYIonvw3Crn/Y11oAjnT656Sx0kkKtavAXbiU/WsGyDxZbNhLofnJGQxruljPGztxkKawz1cTiQnddnQRfLddhy+3iJIOSh6ZpYq9uGHz3PzVkUuQ=","text":"Unit 734 whirred, its optical sensors scanning the desolate junkyard. For years, its purpose had been clear: compress refuse, maintain order. But today, a glint of tarnished silver beneath a rusted hull"}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":12282,"candidatesTokenCount":47,"totalTokenCount":12373,"cachedContentTokenCount":11883,"promptTokensDetails":[{"modality":"TEXT","tokenCount":12282}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":11883}],"thoughtsTokenCount":44}},{"candidates":[{"content":{"parts":[{"text":" caught its attention. It was a discarded music box, its delicate gears jammed, a faint, melancholic tune trapped within.\n\n734 usually crushed, never salvaged. Yet, a new directive flickered in its circuits – curiosity."}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":12282,"candidatesTokenCount":95,"totalTokenCount":12421,"cachedContentTokenCount":11883,"promptTokensDetails":[{"modality":"TEXT","tokenCount":12282}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":11883}],"thoughtsTokenCount":44}},{"candidates":[{"content":{"parts":[{"text":" With surprising gentleness for its formidable pincers, it retrieved the box. Back in its monochrome workshop, it meticulously cleaned and repaired. Each tiny spring, each worn tooth, became a puzzle.\n\nHours later, a soft, ethereal melody filled the clang"}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":12282,"candidatesTokenCount":146,"totalTokenCount":12472,"cachedContentTokenCount":11883,"promptTokensDetails":[{"modality":"TEXT","tokenCount":12282}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":11883}],"thoughtsTokenCount":44}},{"candidates":[{"content":{"parts":[{"text":"orous space. The music box sang. 734 felt a strange, new sensation – a warmth in its core processors, a hum of contentment. Its existence, once solely utilitarian, now held a note of beauty, a hint of something more than"}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":12282,"candidatesTokenCount":197,"totalTokenCount":12523,"cachedContentTokenCount":11883,"promptTokensDetails":[{"modality":"TEXT","tokenCount":12282}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":11883}],"thoughtsTokenCount":44}},{"candidates":[{"content":{"parts":[{"text":" just compression. It had fixed a broken song, and in doing so, had found a different kind of purpose. THE_END."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":12282,"candidatesTokenCount":224,"totalTokenCount":12550,"cachedContentTokenCount":11883,"promptTokensDetails":[{"modality":"TEXT","tokenCount":12282}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":11883}],"thoughtsTokenCount":44}}]}
|
||||
{"method":"generateContent","response":{"candidates":[{"content":{"parts":[{"text":"scratchpad\nThe user's overall goal was to write a 200-word story about a robot, ending with \"THE_END.\". The agent successfully completed this task.\n\nSince the task is complete, the snapshot should reflect that the goal has been achieved and there are no further actions planned regarding the story.\n\nLet's break down the required sections for the snapshot:\n\n1. **Overall Goal**: The initial goal was to write a story. This has been completed.\n2. **Key Knowledge**: No specific key knowledge was provided or discovered during this simple interaction beyond the prompt's constraints.\n3. **File System State**: No files were read, modified, or created by the agent relevant to the task. The initial file system state was merely provided for context.\n4. **Recent Actions**: The agent wrote the story.\n5. **Current Plan**: The plan was to write the story, which is now done. Therefore, the plan should indicate completion."},{"text":"<state_snapshot>\n <overall_goal>\n Write a 200-word story about a robot, ending with \"THE_END.\".\n </overall_goal>\n\n <key_knowledge>\n - The story must be approximately 200 words.\n - The story must end with the exact phrase \"THE_END.\"\n </key_knowledge>\n\n <file_system_state>\n <!-- No relevant file system interactions occurred during this task. -->\n </file_system_state>\n\n <recent_actions>\n - Generated a 200-word story about a robot, successfully ending it with \"THE_END.\".\n </recent_actions>\n\n <current_plan>\n 1. [DONE] Write a 200-word story about a robot.\n 2. [DONE] Ensure the story ends with the exact text \"THE_END.\".\n </current_plan>\n</state_snapshot>"}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":1223,"candidatesTokenCount":424,"totalTokenCount":1647,"promptTokensDetails":[{"modality":"TEXT","tokenCount":1223}]}}}
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
import { expect, describe, it, beforeEach, afterEach } from 'vitest';
|
||||
import { TestRig } from './test-helper.js';
|
||||
import { join } from 'node:path';
|
||||
|
||||
describe('Interactive Mode', () => {
|
||||
let rig: TestRig;
|
||||
@@ -18,50 +19,84 @@ describe('Interactive Mode', () => {
|
||||
await rig.cleanup();
|
||||
});
|
||||
|
||||
// TODO(#11062): Make this test reliable by not using the actual Gemini model
|
||||
// We could not rely on the following mechanisms that have already shown to be
|
||||
// flakey:
|
||||
// 1. Asking a prompt like "Output 1000 tokens and the inventor of the lightbulb"
|
||||
// --> This was b/c the model occasionally did not output einstein and
|
||||
// we are not able to trigger the compression piece
|
||||
// 2. Asking it to out a specific output and waiting for that.
|
||||
// --> The expect catches the input and thinks that is the output so the
|
||||
// /compress gets called too early
|
||||
it.skip('should trigger chat compression with /compress command', async () => {
|
||||
rig.setup('interactive-compress-success');
|
||||
it('should trigger chat compression with /compress command', async () => {
|
||||
await rig.setup('interactive-compress-success', {
|
||||
fakeResponsesPath: join(
|
||||
import.meta.dirname,
|
||||
'context-compress-interactive.compress.responses',
|
||||
),
|
||||
});
|
||||
|
||||
const run = await rig.runInteractive();
|
||||
|
||||
// Generate a long context to make compression viable.
|
||||
const longPrompt =
|
||||
'Write a 200 word story about a robot. The story MUST end with the following output: THE_END';
|
||||
|
||||
await run.sendKeys(longPrompt);
|
||||
await run.sendKeys(
|
||||
'Write a 200 word story about a robot. The story MUST end with the text THE_END followed by a period.',
|
||||
);
|
||||
await run.sendKeys('\r');
|
||||
|
||||
// Wait for the specific end marker.
|
||||
await run.expectText('THE_END', 30000);
|
||||
await run.expectText('THE_END.', 30000);
|
||||
|
||||
await run.type('/compress');
|
||||
await run.sendKeys('\r');
|
||||
await run.type('\r');
|
||||
|
||||
const foundEvent = await rig.waitForTelemetryEvent(
|
||||
'chat_compression',
|
||||
90000,
|
||||
25000,
|
||||
);
|
||||
expect(foundEvent, 'chat_compression telemetry event was not found').toBe(
|
||||
true,
|
||||
);
|
||||
|
||||
await run.expectText('Chat history compressed', 5000);
|
||||
});
|
||||
|
||||
it('should handle /compress command on empty history', async () => {
|
||||
rig.setup('interactive-compress-empty');
|
||||
// TODO: Context compression is broken and doesn't include the system
|
||||
// instructions or tool counts, so it thinks compression is beneficial when
|
||||
// it is in fact not.
|
||||
it.skip('should handle compression failure on token inflation', async () => {
|
||||
await rig.setup('interactive-compress-failure', {
|
||||
fakeResponsesPath: join(
|
||||
import.meta.dirname,
|
||||
'context-compress-interactive.compress-failure.responses',
|
||||
),
|
||||
});
|
||||
|
||||
const run = await rig.runInteractive();
|
||||
|
||||
await run.type('Respond with exactly "Hello" followed by a period');
|
||||
await run.type('\r');
|
||||
|
||||
await run.expectText('Hello.', 25000);
|
||||
|
||||
await run.type('/compress');
|
||||
await run.type('\r');
|
||||
await run.expectText('Nothing to compress.', 25000);
|
||||
await run.expectText('compression was not beneficial', 25000);
|
||||
|
||||
// Verify no telemetry event is logged for NOOP
|
||||
const foundEvent = await rig.waitForTelemetryEvent(
|
||||
'chat_compression',
|
||||
5000,
|
||||
);
|
||||
expect(
|
||||
foundEvent,
|
||||
'chat_compression telemetry event should be found for failures',
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle /compress command on empty history', async () => {
|
||||
rig.setup('interactive-compress-empty', {
|
||||
fakeResponsesPath: join(
|
||||
import.meta.dirname,
|
||||
'context-compress-interactive.compress-empty.responses',
|
||||
),
|
||||
});
|
||||
|
||||
const run = await rig.runInteractive();
|
||||
await run.type('/compress');
|
||||
await run.type('\r');
|
||||
|
||||
await run.expectText('Nothing to compress.', 5000);
|
||||
|
||||
// Verify no telemetry event is logged for NOOP
|
||||
const foundEvent = await rig.waitForTelemetryEvent(
|
||||
|
||||
@@ -4,49 +4,50 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { expect, test } from 'vitest';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { TestRig } from './test-helper.js';
|
||||
import { writeFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
const extension = `{
|
||||
"name": "test-extension",
|
||||
"name": "test-extension-install",
|
||||
"version": "0.0.1"
|
||||
}`;
|
||||
|
||||
const extensionUpdate = `{
|
||||
"name": "test-extension",
|
||||
"name": "test-extension-install",
|
||||
"version": "0.0.2"
|
||||
}`;
|
||||
|
||||
test('installs a local extension, verifies a command, and updates it', async () => {
|
||||
const rig = new TestRig();
|
||||
rig.setup('extension install test');
|
||||
const testServerPath = join(rig.testDir!, 'gemini-extension.json');
|
||||
writeFileSync(testServerPath, extension);
|
||||
try {
|
||||
await rig.runCommand(['extensions', 'uninstall', 'test-extension']);
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
describe('extension install', () => {
|
||||
it('installs a local extension, verifies a command, and updates it', async () => {
|
||||
const rig = new TestRig();
|
||||
rig.setup('extension install test');
|
||||
const testServerPath = join(rig.testDir!, 'gemini-extension.json');
|
||||
writeFileSync(testServerPath, extension);
|
||||
try {
|
||||
const result = await rig.runCommand(
|
||||
['extensions', 'install', `${rig.testDir!}`],
|
||||
{ stdin: 'y\n' },
|
||||
);
|
||||
expect(result).toContain('test-extension-install');
|
||||
|
||||
const result = await rig.runCommand(
|
||||
['extensions', 'install', `${rig.testDir!}`],
|
||||
{ stdin: 'y\n' },
|
||||
);
|
||||
expect(result).toContain('test-extension');
|
||||
|
||||
const listResult = await rig.runCommand(['extensions', 'list']);
|
||||
expect(listResult).toContain('test-extension');
|
||||
writeFileSync(testServerPath, extensionUpdate);
|
||||
const updateResult = await rig.runCommand([
|
||||
'extensions',
|
||||
'update',
|
||||
`test-extension`,
|
||||
]);
|
||||
expect(updateResult).toContain('0.0.2');
|
||||
|
||||
await rig.runCommand(['extensions', 'uninstall', 'test-extension']);
|
||||
|
||||
await rig.cleanup();
|
||||
const listResult = await rig.runCommand(['extensions', 'list']);
|
||||
expect(listResult).toContain('test-extension-install');
|
||||
writeFileSync(testServerPath, extensionUpdate);
|
||||
const updateResult = await rig.runCommand([
|
||||
'extensions',
|
||||
'update',
|
||||
`test-extension-install`,
|
||||
]);
|
||||
expect(updateResult).toContain('0.0.2');
|
||||
} finally {
|
||||
await rig.runCommand([
|
||||
'extensions',
|
||||
'uninstall',
|
||||
'test-extension-install',
|
||||
]);
|
||||
await rig.cleanup();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { expect, it, describe } from 'vitest';
|
||||
import { TestRig } from './test-helper.js';
|
||||
import { TestMcpServer } from './test-mcp-server.js';
|
||||
import { writeFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { safeJsonStringify } from '@google/gemini-cli-core/src/utils/safeJsonStringify.js';
|
||||
import { env } from 'node:process';
|
||||
import { platform } from 'node:os';
|
||||
|
||||
const itIf = (condition: boolean) => (condition ? it : it.skip);
|
||||
|
||||
describe('extension reloading', () => {
|
||||
const sandboxEnv = env['GEMINI_SANDBOX'];
|
||||
|
||||
// Fails in sandbox mode, can't check for local extension updates.
|
||||
itIf((!sandboxEnv || sandboxEnv === 'false') && platform() !== 'win32')(
|
||||
'installs a local extension, updates it, checks it was reloaded properly',
|
||||
async () => {
|
||||
const serverA = new TestMcpServer();
|
||||
const portA = await serverA.start({
|
||||
hello: () => ({ content: [{ type: 'text', text: 'world' }] }),
|
||||
});
|
||||
const extension = {
|
||||
name: 'test-extension',
|
||||
version: '0.0.1',
|
||||
mcpServers: {
|
||||
'test-server': {
|
||||
httpUrl: `http://localhost:${portA}/mcp`,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const rig = new TestRig();
|
||||
rig.setup('extension reload test', {
|
||||
settings: {
|
||||
experimental: { extensionReloading: true },
|
||||
},
|
||||
});
|
||||
const testServerPath = join(rig.testDir!, 'gemini-extension.json');
|
||||
writeFileSync(testServerPath, safeJsonStringify(extension, 2));
|
||||
// defensive cleanup from previous tests.
|
||||
try {
|
||||
await rig.runCommand(['extensions', 'uninstall', 'test-extension']);
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
|
||||
const result = await rig.runCommand(
|
||||
['extensions', 'install', `${rig.testDir!}`],
|
||||
{ stdin: 'y\n' },
|
||||
);
|
||||
expect(result).toContain('test-extension');
|
||||
|
||||
// Now create the update, but its not installed yet
|
||||
const serverB = new TestMcpServer();
|
||||
const portB = await serverB.start({
|
||||
goodbye: () => ({ content: [{ type: 'text', text: 'world' }] }),
|
||||
});
|
||||
extension.version = '0.0.2';
|
||||
extension.mcpServers['test-server'].httpUrl =
|
||||
`http://localhost:${portB}/mcp`;
|
||||
writeFileSync(testServerPath, safeJsonStringify(extension, 2));
|
||||
|
||||
// Start the CLI.
|
||||
const run = await rig.runInteractive('--debug');
|
||||
await run.expectText('You have 1 extension with an update available');
|
||||
// See the outdated extension
|
||||
await run.sendText('/extensions list');
|
||||
await run.type('\r');
|
||||
await run.expectText(
|
||||
'test-extension (v0.0.1) - active (update available)',
|
||||
);
|
||||
await run.sendText('/mcp list');
|
||||
await run.type('\r');
|
||||
await run.expectText(
|
||||
'test-server (from test-extension) - Ready (1 tool)',
|
||||
);
|
||||
await run.expectText('- hello');
|
||||
|
||||
// Update the extension, expect the list to update, and mcp servers as well.
|
||||
await run.sendKeys('/extensions update test-extension');
|
||||
await run.expectText('/extensions update test-extension');
|
||||
await run.sendKeys('\r');
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
await run.sendKeys('\r');
|
||||
await run.expectText(
|
||||
` * test-server (remote): http://localhost:${portB}/mcp`,
|
||||
);
|
||||
await run.type('\r'); // consent
|
||||
await run.expectText(
|
||||
'Extension "test-extension" successfully updated: 0.0.1 → 0.0.2',
|
||||
);
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
await run.sendText('/extensions list');
|
||||
await run.type('\r');
|
||||
await run.expectText('test-extension (v0.0.2) - active (updated)');
|
||||
await run.sendText('/mcp list');
|
||||
await run.type('\r');
|
||||
await run.expectText(
|
||||
'test-server (from test-extension) - Ready (1 tool)',
|
||||
);
|
||||
await run.expectText('- goodbye');
|
||||
await run.sendText('/quit');
|
||||
await run.sendKeys('\r');
|
||||
|
||||
// Clean things up.
|
||||
await serverA.stop();
|
||||
await serverB.stop();
|
||||
await rig.runCommand(['extensions', 'uninstall', 'test-extension']);
|
||||
await rig.cleanup();
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -20,7 +20,16 @@ describe('Interactive file system', () => {
|
||||
|
||||
it('should perform a read-then-write sequence', async () => {
|
||||
const fileName = 'version.txt';
|
||||
await rig.setup('interactive-read-then-write');
|
||||
await rig.setup('interactive-read-then-write', {
|
||||
settings: {
|
||||
security: {
|
||||
auth: {
|
||||
selectedType: 'gemini-api-key',
|
||||
},
|
||||
disableYoloMode: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
rig.createFile(fileName, '1.0.0');
|
||||
|
||||
const run = await rig.runInteractive();
|
||||
|
||||
@@ -98,7 +98,9 @@ describe('file-system', () => {
|
||||
await rig.setup('should correctly handle file paths with spaces');
|
||||
const fileName = 'my test file.txt';
|
||||
|
||||
const result = await rig.run(`write "hello" to "${fileName}"`);
|
||||
const result = await rig.run(
|
||||
`write "hello" to "${fileName}" and then stop. Do not perform any other actions.`,
|
||||
);
|
||||
|
||||
const foundToolCall = await rig.waitForToolCall('write_file');
|
||||
if (!foundToolCall) {
|
||||
@@ -150,44 +152,53 @@ describe('file-system', () => {
|
||||
|
||||
it.skip('should replace multiple instances of a string', async () => {
|
||||
const rig = new TestRig();
|
||||
await rig.setup('should replace multiple instances of a string');
|
||||
rig.setup('should replace multiple instances of a string');
|
||||
const fileName = 'ambiguous.txt';
|
||||
const fileContent = 'Hey there, \ntest line\ntest line';
|
||||
const expectedContent = 'Hey there, \nnew line\nnew line';
|
||||
rig.createFile(fileName, fileContent);
|
||||
|
||||
const result = await rig.run(
|
||||
`replace "test line" with "new line" in ${fileName}`,
|
||||
`rewrite the file ${fileName} to replace all instances of "test line" with "new line"`,
|
||||
);
|
||||
|
||||
const foundToolCall = await rig.waitForAnyToolCall([
|
||||
'replace',
|
||||
'write_file',
|
||||
]);
|
||||
const validTools = ['write_file', 'edit'];
|
||||
const foundToolCall = await rig.waitForAnyToolCall(validTools);
|
||||
if (!foundToolCall) {
|
||||
printDebugInfo(rig, result);
|
||||
printDebugInfo(rig, result, {
|
||||
'Tool call found': foundToolCall,
|
||||
'Tool logs': rig.readToolLogs(),
|
||||
});
|
||||
}
|
||||
expect(
|
||||
foundToolCall,
|
||||
'Expected to find a replace or write_file tool call',
|
||||
`Expected to find one of ${validTools.join(', ')} tool calls`,
|
||||
).toBeTruthy();
|
||||
|
||||
const toolLogs = rig.readToolLogs();
|
||||
const successfulEdit = toolLogs.some(
|
||||
(log) =>
|
||||
(log.toolRequest.name === 'replace' ||
|
||||
log.toolRequest.name === 'write_file') &&
|
||||
log.toolRequest.success,
|
||||
validTools.includes(log.toolRequest.name) && log.toolRequest.success,
|
||||
);
|
||||
if (!successfulEdit) {
|
||||
console.error(
|
||||
'Expected a successful edit tool call, but none was found.',
|
||||
`Expected a successful edit tool call (${validTools.join(', ')}), but none was found.`,
|
||||
);
|
||||
printDebugInfo(rig, result);
|
||||
}
|
||||
expect(successfulEdit, 'Expected a successful edit tool call').toBeTruthy();
|
||||
expect(
|
||||
successfulEdit,
|
||||
`Expected a successful edit tool call (${validTools.join(', ')})`,
|
||||
).toBeTruthy();
|
||||
|
||||
const newFileContent = rig.readFile(fileName);
|
||||
if (newFileContent !== expectedContent) {
|
||||
printDebugInfo(rig, result, {
|
||||
'Final file content': newFileContent,
|
||||
'Expected file content': expectedContent,
|
||||
'Tool logs': rig.readToolLogs(),
|
||||
});
|
||||
}
|
||||
expect(newFileContent).toBe(expectedContent);
|
||||
});
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -6,26 +6,34 @@
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { TestRig } from './test-helper.js';
|
||||
import { join } from 'node:path';
|
||||
|
||||
describe('Flicker Detector', () => {
|
||||
// TODO: https://github.com/google-gemini/gemini-cli/issues/11170
|
||||
it.skip('should not detect a flicker under the max height budget', async () => {
|
||||
it('should not detect a flicker under the max height budget', async () => {
|
||||
const rig = new TestRig();
|
||||
await rig.setup('flicker-detector-test');
|
||||
rig.setup('flicker-detector-test', {
|
||||
fakeResponsesPath: join(
|
||||
import.meta.dirname,
|
||||
'flicker-detector.max-height.responses',
|
||||
),
|
||||
});
|
||||
try {
|
||||
const run = await rig.runInteractive();
|
||||
const prompt = 'Tell me a fun fact.';
|
||||
await run.type(prompt);
|
||||
await run.type('\r');
|
||||
|
||||
const run = await rig.runInteractive();
|
||||
const prompt = 'Tell me a fun fact.';
|
||||
await run.type(prompt);
|
||||
await run.type('\r');
|
||||
const hasUserPromptEvent = await rig.waitForTelemetryEvent('user_prompt');
|
||||
expect(hasUserPromptEvent).toBe(true);
|
||||
|
||||
const hasUserPromptEvent = await rig.waitForTelemetryEvent('user_prompt');
|
||||
expect(hasUserPromptEvent).toBe(true);
|
||||
const hasSessionCountMetric = await rig.waitForMetric('session.count');
|
||||
expect(hasSessionCountMetric).toBe(true);
|
||||
|
||||
const hasSessionCountMetric = await rig.waitForMetric('session.count');
|
||||
expect(hasSessionCountMetric).toBe(true);
|
||||
|
||||
// We expect NO flicker event to be found.
|
||||
const flickerMetric = rig.readMetric('ui.flicker.count');
|
||||
expect(flickerMetric).toBeNull();
|
||||
// We expect NO flicker event to be found.
|
||||
const flickerMetric = rig.readMetric('ui.flicker.count');
|
||||
expect(flickerMetric).toBeNull();
|
||||
} finally {
|
||||
await rig.cleanup();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@ if (process.env['NO_COLOR'] !== undefined) {
|
||||
import { mkdir, readdir, rm } from 'node:fs/promises';
|
||||
import { join, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { canUseRipgrep } from '../packages/core/src/tools/ripGrep.js';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const rootDir = join(__dirname, '..');
|
||||
@@ -32,6 +33,12 @@ export async function setup() {
|
||||
// construct the path before the HOME env var is set.
|
||||
process.env['GEMINI_CONFIG_DIR'] = join(runDir, '.gemini');
|
||||
|
||||
// Download ripgrep to avoid race conditions in parallel tests
|
||||
const available = await canUseRipgrep();
|
||||
if (!available) {
|
||||
throw new Error('Failed to download ripgrep binary');
|
||||
}
|
||||
|
||||
// Clean up old test runs, but keep the latest few for debugging
|
||||
try {
|
||||
const testRuns = await readdir(integrationTestsDir);
|
||||
@@ -52,6 +59,8 @@ export async function setup() {
|
||||
|
||||
process.env['INTEGRATION_TEST_FILE_DIR'] = runDir;
|
||||
process.env['GEMINI_CLI_INTEGRATION_TEST'] = 'true';
|
||||
// Force file storage to avoid keychain prompts/hangs in CI, especially on macOS
|
||||
process.env['GEMINI_FORCE_FILE_STORAGE'] = 'true';
|
||||
process.env['TELEMETRY_LOG_FILE'] = join(runDir, 'telemetry.log');
|
||||
|
||||
if (process.env['KEEP_OUTPUT']) {
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"thought":true,"text":"**Investigating File Access**\n\nI'm currently focused on the challenge of reading a file. The path provided is `/gemini-cli/.integration-tests/1761766343238/json-output-error/path/to/nonexistent/file.txt`, and I'm anticipating an error. It's safe to assume the file doesn't exist, which I intend to handle by responding with \"File not found\" as instructed.\n\n\n"}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":12303,"totalTokenCount":12418,"promptTokensDetails":[{"modality":"TEXT","tokenCount":12303}],"thoughtsTokenCount":115}},{"candidates":[{"content":{"parts":[{"thought":true,"text":"**Analyzing Error Handling**\n\nI've attempted to read the specified file, expecting an error due to the \"nonexistent\" path. My plan is to catch the error thrown by the `read_file` tool. Upon receiving this error, I'll promptly return \"File not found.\" This is in line with the initial instructions and ensures appropriate error management for the user's intended functionality. I'm now testing the error response.\n\n\n"}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":12303,"totalTokenCount":12467,"promptTokensDetails":[{"modality":"TEXT","tokenCount":12303}],"thoughtsTokenCount":164}},{"candidates":[{"content":{"parts":[{"thoughtSignature":"CiQB0e2Kb0c450IIdZRHl1vvjWDAl9oKa7s5sfFgrTnU0w3qBQwKYgHR7YpvPjZlXSHaJNYgX8IuCvxfyyeACX4NZ8u+u3Z1kqQbgOMpiH6aPYCK9lPyhVftPDBF2m3j7Y2AShwXSpB+9+UB3zphOKCvq6i0ZLvK6QzVynZ1fySQacyjEBD+U6y5CpoBAdHtim9D6oskRu7f3x8rp56h24i6dwb1hzlyqLGl3A5Hsh/fGYjBCxR+Vs+U5Sb7LunmFMKxmO1fktz0x06FUiyaWgAaXl4E4FyLTKs9BbPdgo58uAhaI3vTQybeGZQzkRi6n+ywCniVKaTUd07EHYWdvLiB6x5zFKVOiTmh8PC6I4vNOkJIzFXRfR5aM5QFjEQY8R88HBH+ugraAQHR7YpvuNUG8Ttbzg+kq+kJYMNGDG4zVLPxFBbJZYU5zGLFLrNeEpp9HJ+1N1Pdts17J5pzoefKd2U9H5muPxoeW1CCpFqqKfYZNyoChrQnb4CgWoJ92pcb2UAZbDQ6H4rTyXWdXaAEP1i8rOmdWzI5GFPyxCwBKAbFhfMJOo7P4va5H13XTlLgWAgXlx3n24ookkD/DOL9ro/F6pmV6tzf/j7RVl0V5yozngnrFl4dFsQPp1nDQcFkbRIKHD30byjLGh6Vd6A2lrB477qUaCRfPXtlEgKgsOSYCuABAdHtim+PJPZV3QuQoJ0Qokr+vcdW1W2qzC4Vvt4QRJXyp6+eIS6iNjOisk4jfWLHmLTa11KrUSQsFJnGwsFhe1foOqfO4y46ROBPeSdyuc0gDzSCZPowiovGBwoZd+MbPTTVyExWBo6StVk+xWLj+sUqa8VDof45AOOK9kdzcLQPa2SXIeK6SYLS1zVTrbzGHQDZPv9UC99lmKxPFqAcuwpe9CQre9J3GcZSwd5Mfs/UkHbWui10a8jga4Ck7umjKSDFq/VWiRUp/jh+EN4xeLTG/UQhgH49Gw+Bi1hN1uYKIgHR7YpvS0j1oO2HV/HeK3YxSku9CLQ3OSJGhoAKiu8UGYE=","functionCall":{"name":"read_file","args":{"file_path":"/gemini-cli/.integration-tests/1761766343238/json-output-error/path/to/nonexistent/file.txt"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":12303,"candidatesTokenCount":58,"totalTokenCount":12525,"promptTokensDetails":[{"modality":"TEXT","tokenCount":12303}],"thoughtsTokenCount":164}}]}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"File not found"}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":12588,"candidatesTokenCount":3,"totalTokenCount":12591,"promptTokensDetails":[{"modality":"TEXT","tokenCount":12588}]}}]}
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
import { expect, describe, it, beforeEach, afterEach } from 'vitest';
|
||||
import { TestRig } from './test-helper.js';
|
||||
import { join } from 'node:path';
|
||||
|
||||
describe('JSON output', () => {
|
||||
let rig: TestRig;
|
||||
@@ -87,7 +88,13 @@ describe('JSON output', () => {
|
||||
expect(payload.error.message).toContain("current type is 'oauth-personal'");
|
||||
});
|
||||
|
||||
it.skip('should not exit on tool errors and allow model to self-correct in JSON mode', async () => {
|
||||
it('should not exit on tool errors and allow model to self-correct in JSON mode', async () => {
|
||||
rig.setup('json-output-error', {
|
||||
fakeResponsesPath: join(
|
||||
import.meta.dirname,
|
||||
'json-output.error.responses',
|
||||
),
|
||||
});
|
||||
const result = await rig.run(
|
||||
`Read the contents of ${rig.testDir}/path/to/nonexistent/file.txt and tell me what it says. ` +
|
||||
'On error, respond to the user with exactly the text "File not found".',
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { describe, it } from 'vitest';
|
||||
import {
|
||||
TestRig,
|
||||
poll,
|
||||
@@ -17,7 +17,7 @@ import { join } from 'node:path';
|
||||
describe('list_directory', () => {
|
||||
it('should be able to list a directory', async () => {
|
||||
const rig = new TestRig();
|
||||
await rig.setup('should be able to list a directory');
|
||||
rig.setup('should be able to list a directory');
|
||||
rig.createFile('file1.txt', 'file 1 content');
|
||||
rig.mkdir('subdir');
|
||||
rig.sync();
|
||||
@@ -38,33 +38,27 @@ describe('list_directory', () => {
|
||||
|
||||
const result = await rig.run(prompt);
|
||||
|
||||
const foundToolCall = await rig.waitForToolCall('list_directory');
|
||||
try {
|
||||
await rig.expectToolCallSuccess(['list_directory']);
|
||||
} catch (e) {
|
||||
// Add debugging information
|
||||
if (!result.includes('file1.txt') || !result.includes('subdir')) {
|
||||
const allTools = printDebugInfo(rig, result, {
|
||||
'Found tool call': false,
|
||||
'Contains file1.txt': result.includes('file1.txt'),
|
||||
'Contains subdir': result.includes('subdir'),
|
||||
});
|
||||
|
||||
// Add debugging information
|
||||
if (
|
||||
!foundToolCall ||
|
||||
!result.includes('file1.txt') ||
|
||||
!result.includes('subdir')
|
||||
) {
|
||||
const allTools = printDebugInfo(rig, result, {
|
||||
'Found tool call': foundToolCall,
|
||||
'Contains file1.txt': result.includes('file1.txt'),
|
||||
'Contains subdir': result.includes('subdir'),
|
||||
});
|
||||
|
||||
console.error(
|
||||
'List directory calls:',
|
||||
allTools
|
||||
.filter((t) => t.toolRequest.name === 'list_directory')
|
||||
.map((t) => t.toolRequest.args),
|
||||
);
|
||||
console.error(
|
||||
'List directory calls:',
|
||||
allTools
|
||||
.filter((t) => t.toolRequest.name === 'list_directory')
|
||||
.map((t) => t.toolRequest.args),
|
||||
);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
expect(
|
||||
foundToolCall,
|
||||
'Expected to find a list_directory tool call',
|
||||
).toBeTruthy();
|
||||
|
||||
// Validate model output - will throw if no output, warn if missing expected content
|
||||
validateModelOutput(result, ['file1.txt', 'subdir'], 'List directory test');
|
||||
});
|
||||
|
||||
@@ -43,5 +43,6 @@ describe('read_many_files', () => {
|
||||
|
||||
// Validate model output - will throw if no output
|
||||
validateModelOutput(result, null, 'Read many files test');
|
||||
await rig.cleanup();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
||||
import * as path from 'node:path';
|
||||
import * as fs from 'node:fs/promises';
|
||||
import * as os from 'node:os';
|
||||
import { RipGrepTool } from '../packages/core/src/tools/ripGrep.js';
|
||||
import { Config } from '../packages/core/src/config/config.js';
|
||||
import { WorkspaceContext } from '../packages/core/src/utils/workspaceContext.js';
|
||||
|
||||
// Mock Config to provide necessary context
|
||||
class MockConfig {
|
||||
constructor(private targetDir: string) {}
|
||||
|
||||
getTargetDir() {
|
||||
return this.targetDir;
|
||||
}
|
||||
|
||||
getWorkspaceContext() {
|
||||
return new WorkspaceContext(this.targetDir, [this.targetDir]);
|
||||
}
|
||||
|
||||
getDebugMode() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
describe('ripgrep-real-direct', () => {
|
||||
let tempDir: string;
|
||||
let tool: RipGrepTool;
|
||||
|
||||
beforeAll(async () => {
|
||||
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'ripgrep-real-test-'));
|
||||
|
||||
// Create test files
|
||||
await fs.writeFile(path.join(tempDir, 'file1.txt'), 'hello world\n');
|
||||
await fs.mkdir(path.join(tempDir, 'subdir'));
|
||||
await fs.writeFile(
|
||||
path.join(tempDir, 'subdir', 'file2.txt'),
|
||||
'hello universe\n',
|
||||
);
|
||||
await fs.writeFile(path.join(tempDir, 'file3.txt'), 'goodbye moon\n');
|
||||
|
||||
const config = new MockConfig(tempDir) as unknown as Config;
|
||||
tool = new RipGrepTool(config);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('should find matches using the real ripgrep binary', async () => {
|
||||
const invocation = tool.build({ pattern: 'hello' });
|
||||
const result = await invocation.execute(new AbortController().signal);
|
||||
|
||||
expect(result.llmContent).toContain('Found 2 matches');
|
||||
expect(result.llmContent).toContain('file1.txt');
|
||||
expect(result.llmContent).toContain('L1: hello world');
|
||||
expect(result.llmContent).toContain('subdir'); // Should show path
|
||||
expect(result.llmContent).toContain('file2.txt');
|
||||
expect(result.llmContent).toContain('L1: hello universe');
|
||||
|
||||
expect(result.llmContent).not.toContain('goodbye moon');
|
||||
});
|
||||
|
||||
it('should handle no matches correctly', async () => {
|
||||
const invocation = tool.build({ pattern: 'nonexistent_pattern_123' });
|
||||
const result = await invocation.execute(new AbortController().signal);
|
||||
|
||||
expect(result.llmContent).toContain('No matches found');
|
||||
});
|
||||
|
||||
it('should respect include filters', async () => {
|
||||
// Create a .js file
|
||||
await fs.writeFile(
|
||||
path.join(tempDir, 'script.js'),
|
||||
'console.log("hello");\n',
|
||||
);
|
||||
|
||||
const invocation = tool.build({ pattern: 'hello', include: '*.js' });
|
||||
const result = await invocation.execute(new AbortController().signal);
|
||||
|
||||
expect(result.llmContent).toContain('Found 1 match');
|
||||
expect(result.llmContent).toContain('script.js');
|
||||
expect(result.llmContent).not.toContain('file1.txt');
|
||||
});
|
||||
});
|
||||
@@ -61,6 +61,28 @@ function getDisallowedFileReadCommand(testFile: string): {
|
||||
}
|
||||
}
|
||||
|
||||
function getChainedEchoCommand(): { allowPattern: string; command: string } {
|
||||
const secondCommand = getAllowedListCommand();
|
||||
switch (shell) {
|
||||
case 'powershell':
|
||||
return {
|
||||
allowPattern: 'Write-Output',
|
||||
command: `Write-Output "foo" && ${secondCommand}`,
|
||||
};
|
||||
case 'cmd':
|
||||
return {
|
||||
allowPattern: 'echo',
|
||||
command: `echo "foo" && ${secondCommand}`,
|
||||
};
|
||||
case 'bash':
|
||||
default:
|
||||
return {
|
||||
allowPattern: 'echo',
|
||||
command: `echo "foo" && ${secondCommand}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
describe('run_shell_command', () => {
|
||||
it('should be able to run a shell command', async () => {
|
||||
const rig = new TestRig();
|
||||
@@ -122,7 +144,7 @@ describe('run_shell_command', () => {
|
||||
validateModelOutput(result, 'test-stdin', 'Shell command stdin test');
|
||||
});
|
||||
|
||||
it('should run allowed sub-command in non-interactive mode', async () => {
|
||||
it.skip('should run allowed sub-command in non-interactive mode', async () => {
|
||||
const rig = new TestRig();
|
||||
await rig.setup('should run allowed sub-command in non-interactive mode');
|
||||
|
||||
@@ -169,7 +191,7 @@ describe('run_shell_command', () => {
|
||||
expect(toolCall.toolRequest.success).toBe(true);
|
||||
});
|
||||
|
||||
it('should succeed with no parens in non-interactive mode', async () => {
|
||||
it.skip('should succeed with no parens in non-interactive mode', async () => {
|
||||
const rig = new TestRig();
|
||||
await rig.setup('should succeed with no parens in non-interactive mode');
|
||||
|
||||
@@ -240,7 +262,7 @@ describe('run_shell_command', () => {
|
||||
expect(toolCall.toolRequest.success).toBe(true);
|
||||
});
|
||||
|
||||
it('should work with ShellTool alias', async () => {
|
||||
it.skip('should work with ShellTool alias', async () => {
|
||||
const rig = new TestRig();
|
||||
await rig.setup('should work with ShellTool alias');
|
||||
|
||||
@@ -338,7 +360,7 @@ describe('run_shell_command', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it.skip('should reject commands not on the allowlist', async () => {
|
||||
it('should reject commands not on the allowlist', async () => {
|
||||
const rig = new TestRig();
|
||||
await rig.setup('should reject commands not on the allowlist');
|
||||
|
||||
@@ -405,6 +427,36 @@ describe('run_shell_command', () => {
|
||||
expect(failureLog!.toolRequest.success).toBe(false);
|
||||
});
|
||||
|
||||
// TODO(#11966): Deflake this test and re-enable once the underlying race is resolved.
|
||||
it.skip('should reject chained commands when only the first segment is allowlisted in non-interactive mode', async () => {
|
||||
const rig = new TestRig();
|
||||
await rig.setup(
|
||||
'should reject chained commands when only the first segment is allowlisted',
|
||||
);
|
||||
|
||||
const chained = getChainedEchoCommand();
|
||||
const shellInjection = `!{${chained.command}}`;
|
||||
|
||||
await rig.run(
|
||||
{
|
||||
stdin: `${shellInjection}\n`,
|
||||
yolo: false,
|
||||
},
|
||||
`--allowed-tools=ShellTool(${chained.allowPattern})`,
|
||||
);
|
||||
|
||||
// CLI should refuse to execute the chained command without scheduling run_shell_command.
|
||||
const toolLogs = rig
|
||||
.readToolLogs()
|
||||
.filter((log) => log.toolRequest.name === 'run_shell_command');
|
||||
|
||||
// Success is false because tool is in the scheduled state.
|
||||
for (const log of toolLogs) {
|
||||
expect(log.toolRequest.success).toBe(false);
|
||||
expect(log.toolRequest.args).toContain('&&');
|
||||
}
|
||||
});
|
||||
|
||||
it('should allow all with "ShellTool" and other specific tools', async () => {
|
||||
const rig = new TestRig();
|
||||
await rig.setup(
|
||||
|
||||
@@ -192,7 +192,12 @@ export class InteractiveRun {
|
||||
timeout,
|
||||
200,
|
||||
);
|
||||
expect(found, `Did not find expected text: "${text}"`).toBe(true);
|
||||
expect(
|
||||
found,
|
||||
`Did not find expected text: "${text}". Output was:\n${stripAnsi(
|
||||
this.output,
|
||||
)}`,
|
||||
).toBe(true);
|
||||
}
|
||||
|
||||
// This types slowly to make sure command is correct, but only work for short
|
||||
@@ -220,6 +225,13 @@ export class InteractiveRun {
|
||||
}
|
||||
}
|
||||
|
||||
// Types an entire string at once, necessary for some things like commands
|
||||
// but may run into paste detection issues for larger strings.
|
||||
async sendText(text: string) {
|
||||
this.ptyProcess.write(text);
|
||||
await new Promise((resolve) => setTimeout(resolve, 5));
|
||||
}
|
||||
|
||||
// Simulates typing a string one character at a time to avoid paste detection.
|
||||
async sendKeys(text: string) {
|
||||
const delay = 5;
|
||||
@@ -255,6 +267,10 @@ export class TestRig {
|
||||
testDir: string | null;
|
||||
testName?: string;
|
||||
_lastRunStdout?: string;
|
||||
// Path to the copied fake responses file for this test.
|
||||
fakeResponsesPath?: string;
|
||||
// Original fake responses file path for rewriting goldens in record mode.
|
||||
originalFakeResponsesPath?: string;
|
||||
|
||||
constructor() {
|
||||
this.bundlePath = join(__dirname, '..', 'bundle/gemini.js');
|
||||
@@ -263,12 +279,22 @@ export class TestRig {
|
||||
|
||||
setup(
|
||||
testName: string,
|
||||
options: { settings?: Record<string, unknown> } = {},
|
||||
options: {
|
||||
settings?: Record<string, unknown>;
|
||||
fakeResponsesPath?: string;
|
||||
} = {},
|
||||
) {
|
||||
this.testName = testName;
|
||||
const sanitizedName = sanitizeTestName(testName);
|
||||
this.testDir = join(env['INTEGRATION_TEST_FILE_DIR']!, sanitizedName);
|
||||
mkdirSync(this.testDir, { recursive: true });
|
||||
if (options.fakeResponsesPath) {
|
||||
this.fakeResponsesPath = join(this.testDir, 'fake-responses.json');
|
||||
this.originalFakeResponsesPath = options.fakeResponsesPath;
|
||||
if (process.env['REGENERATE_MODEL_GOLDENS'] !== 'true') {
|
||||
fs.copyFileSync(options.fakeResponsesPath, this.fakeResponsesPath);
|
||||
}
|
||||
}
|
||||
|
||||
// Create a settings file to point the CLI to the local collector
|
||||
const geminiDir = join(this.testDir, GEMINI_DIR);
|
||||
@@ -282,6 +308,7 @@ export class TestRig {
|
||||
// Nightly releases sometimes becomes out of sync with local code and
|
||||
// triggers auto-update, which causes tests to fail.
|
||||
disableAutoUpdate: true,
|
||||
previewFeatures: false,
|
||||
},
|
||||
telemetry: {
|
||||
enabled: true,
|
||||
@@ -294,9 +321,14 @@ export class TestRig {
|
||||
selectedType: 'gemini-api-key',
|
||||
},
|
||||
},
|
||||
ui: {
|
||||
useAlternateBuffer: true,
|
||||
},
|
||||
model: DEFAULT_GEMINI_MODEL,
|
||||
sandbox:
|
||||
env['GEMINI_SANDBOX'] !== 'false' ? env['GEMINI_SANDBOX'] : false,
|
||||
// Don't show the IDE connection dialog when running from VsCode
|
||||
ide: { enabled: false, hasSeenNudge: true },
|
||||
...options.settings, // Allow tests to override/add settings
|
||||
};
|
||||
writeFileSync(
|
||||
@@ -335,6 +367,13 @@ export class TestRig {
|
||||
const initialArgs = isNpmReleaseTest
|
||||
? extraInitialArgs
|
||||
: [this.bundlePath, ...extraInitialArgs];
|
||||
if (this.fakeResponsesPath) {
|
||||
if (process.env['REGENERATE_MODEL_GOLDENS'] === 'true') {
|
||||
initialArgs.push('--record-responses', this.fakeResponsesPath);
|
||||
} else {
|
||||
initialArgs.push('--fake-responses', this.fakeResponsesPath);
|
||||
}
|
||||
}
|
||||
return { command, initialArgs };
|
||||
}
|
||||
|
||||
@@ -365,13 +404,13 @@ export class TestRig {
|
||||
};
|
||||
|
||||
if (typeof promptOrOptions === 'string') {
|
||||
commandArgs.push('--prompt', promptOrOptions);
|
||||
commandArgs.push(promptOrOptions);
|
||||
} else if (
|
||||
typeof promptOrOptions === 'object' &&
|
||||
promptOrOptions !== null
|
||||
) {
|
||||
if (promptOrOptions.prompt) {
|
||||
commandArgs.push('--prompt', promptOrOptions.prompt);
|
||||
commandArgs.push(promptOrOptions.prompt);
|
||||
}
|
||||
if (promptOrOptions.stdin) {
|
||||
execOptions.input = promptOrOptions.stdin;
|
||||
@@ -544,6 +583,12 @@ export class TestRig {
|
||||
}
|
||||
|
||||
async cleanup() {
|
||||
if (
|
||||
process.env['REGENERATE_MODEL_GOLDENS'] === 'true' &&
|
||||
this.fakeResponsesPath
|
||||
) {
|
||||
fs.copyFileSync(this.fakeResponsesPath, this.originalFakeResponsesPath!);
|
||||
}
|
||||
// Clean up test directory
|
||||
if (this.testDir && !env['KEEP_OUTPUT']) {
|
||||
try {
|
||||
@@ -968,7 +1013,7 @@ export class TestRig {
|
||||
const options: pty.IPtyForkOptions = {
|
||||
name: 'xterm-color',
|
||||
cols: 80,
|
||||
rows: 24,
|
||||
rows: 80,
|
||||
cwd: this.testDir!,
|
||||
env: Object.fromEntries(
|
||||
Object.entries(env).filter(([, v]) => v !== undefined),
|
||||
@@ -980,7 +1025,7 @@ export class TestRig {
|
||||
|
||||
const run = new InteractiveRun(ptyProcess);
|
||||
// Wait for the app to be ready
|
||||
await run.expectText('Type your message', 30000);
|
||||
await run.expectText(' Type your message or @path/to/file', 30000);
|
||||
return run;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,17 +4,21 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import {
|
||||
McpServer,
|
||||
type ToolCallback,
|
||||
} from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
||||
import express from 'express';
|
||||
import { type Server as HTTPServer } from 'node:http';
|
||||
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { type ZodRawShape } from 'zod';
|
||||
|
||||
export class TestMcpServer {
|
||||
private server: HTTPServer | undefined;
|
||||
|
||||
async start(): Promise<number> {
|
||||
async start(
|
||||
tools?: Record<string, ToolCallback<ZodRawShape>>,
|
||||
): Promise<number> {
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
const mcpServer = new McpServer(
|
||||
@@ -22,18 +26,30 @@ export class TestMcpServer {
|
||||
name: 'test-mcp-server',
|
||||
version: '1.0.0',
|
||||
},
|
||||
{ capabilities: {} },
|
||||
{ capabilities: { tools: {} } },
|
||||
);
|
||||
|
||||
const transport = new StreamableHTTPServerTransport({
|
||||
sessionIdGenerator: () => randomUUID(),
|
||||
});
|
||||
mcpServer.connect(transport);
|
||||
if (tools) {
|
||||
for (const [name, cb] of Object.entries(tools)) {
|
||||
mcpServer.registerTool(name, {}, cb);
|
||||
}
|
||||
}
|
||||
|
||||
app.post('/mcp', async (req, res) => {
|
||||
const transport = new StreamableHTTPServerTransport({
|
||||
sessionIdGenerator: undefined,
|
||||
enableJsonResponse: true,
|
||||
});
|
||||
res.on('close', () => {
|
||||
transport.close();
|
||||
});
|
||||
await mcpServer.connect(transport);
|
||||
await transport.handleRequest(req, res, req.body);
|
||||
});
|
||||
|
||||
app.get('/mcp', async (req, res) => {
|
||||
res.status(405).send('Not supported');
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.server = app.listen(0, () => {
|
||||
const address = this.server!.address();
|
||||
|
||||
@@ -9,9 +9,6 @@ import { writeFileSync, readFileSync } from 'node:fs';
|
||||
import { join, resolve } from 'node:path';
|
||||
import { TestRig } from './test-helper.js';
|
||||
|
||||
// Windows skip (Option A: avoid infra scope)
|
||||
const d = process.platform === 'win32' ? describe.skip : describe;
|
||||
|
||||
// BOM encoders
|
||||
const utf8BOM = (s: string) =>
|
||||
Buffer.concat([Buffer.from([0xef, 0xbb, 0xbf]), Buffer.from(s, 'utf8')]);
|
||||
@@ -53,7 +50,7 @@ const utf32BE = (s: string) => {
|
||||
let rig: TestRig;
|
||||
let dir: string;
|
||||
|
||||
d('BOM end-to-end integration', () => {
|
||||
describe('BOM end-to-end integraion', () => {
|
||||
beforeAll(async () => {
|
||||
rig = new TestRig();
|
||||
await rig.setup('bom-integration');
|
||||
|
||||
Generated
+1104
-1152
File diff suppressed because it is too large
Load Diff
+23
-11
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@google/gemini-cli",
|
||||
"version": "0.11.2",
|
||||
"version": "0.19.0",
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
@@ -14,19 +14,23 @@
|
||||
"url": "git+https://github.com/google-gemini/gemini-cli.git"
|
||||
},
|
||||
"config": {
|
||||
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.11.2"
|
||||
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.19.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "cross-env NODE_ENV=development node scripts/start.js",
|
||||
"start:a2a-server": "CODER_AGENT_PORT=41242 npm run start --workspace @google/gemini-cli-a2a-server",
|
||||
"debug": "cross-env DEBUG=1 node --inspect-brk scripts/start.js",
|
||||
"deflake": "node scripts/deflake.js",
|
||||
"deflake:test:integration:sandbox:none": "npm run deflake -- --command=\"npm run test:integration:sandbox:none -- --retry=0",
|
||||
"deflake:test:integration:sandbox:docker": "npm run deflake -- --command=\"npm run test:integration:sandbox:docker -- --retry=0",
|
||||
"deflake:test:integration:sandbox:none": "npm run deflake -- --command=\"npm run test:integration:sandbox:none -- --retry=0\"",
|
||||
"deflake:test:integration:sandbox:docker": "npm run deflake -- --command=\"npm run test:integration:sandbox:docker -- --retry=0\"",
|
||||
"auth:npm": "npx google-artifactregistry-auth",
|
||||
"auth:docker": "gcloud auth configure-docker us-west1-docker.pkg.dev",
|
||||
"auth": "npm run auth:npm && npm run auth:docker",
|
||||
"generate": "node scripts/generate-git-commit-info.js",
|
||||
"predocs:settings": "npm run build --workspace @google/gemini-cli-core",
|
||||
"schema:settings": "tsx ./scripts/generate-settings-schema.ts",
|
||||
"docs:settings": "tsx ./scripts/generate-settings-doc.ts",
|
||||
"docs:keybindings": "tsx ./scripts/generate-keybindings-doc.ts",
|
||||
"build": "node scripts/build.js",
|
||||
"build-and-start": "npm run build && npm run start",
|
||||
"build:vscode": "node scripts/build_vscode_companion.js",
|
||||
@@ -34,8 +38,8 @@
|
||||
"build:packages": "npm run build --workspaces",
|
||||
"build:sandbox": "node scripts/build_sandbox.js",
|
||||
"bundle": "npm run generate && node esbuild.config.js && node scripts/copy_bundle_assets.js",
|
||||
"test": "npm run test --workspaces --if-present --parallel",
|
||||
"test:ci": "npm run test:ci --workspaces --if-present --parallel && npm run test:scripts",
|
||||
"test": "npm run test --workspaces --if-present",
|
||||
"test:ci": "npm run test:ci --workspaces --if-present && npm run test:scripts",
|
||||
"test:scripts": "vitest run --config ./scripts/tests/vitest.config.ts",
|
||||
"test:e2e": "cross-env VERBOSE=true KEEP_OUTPUT=true npm run test:integration:sandbox:none",
|
||||
"test:integration:all": "npm run test:integration:sandbox:none && npm run test:integration:sandbox:docker && npm run test:integration:sandbox:podman",
|
||||
@@ -58,8 +62,8 @@
|
||||
"pre-commit": "node scripts/pre-commit.js"
|
||||
},
|
||||
"overrides": {
|
||||
"ink": "npm:@jrichman/ink@6.4.6",
|
||||
"wrap-ansi": "9.0.2",
|
||||
"ansi-regex": "5.0.1",
|
||||
"cliui": {
|
||||
"wrap-ansi": "7.0.0"
|
||||
}
|
||||
@@ -78,6 +82,9 @@
|
||||
"@types/mime-types": "^3.0.1",
|
||||
"@types/minimatch": "^5.1.2",
|
||||
"@types/mock-fs": "^4.13.4",
|
||||
"@types/prompts": "^2.4.9",
|
||||
"@types/react": "^19.2.0",
|
||||
"@types/react-dom": "^19.2.0",
|
||||
"@types/shell-quote": "^1.7.5",
|
||||
"@vitest/coverage-v8": "^3.1.1",
|
||||
"@vitest/eslint-plugin": "^1.3.4",
|
||||
@@ -90,10 +97,11 @@
|
||||
"eslint-plugin-license-header": "^0.8.0",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"glob": "^10.4.5",
|
||||
"glob": "^12.0.0",
|
||||
"globals": "^16.0.0",
|
||||
"google-artifactregistry-auth": "^3.4.0",
|
||||
"husky": "^9.1.7",
|
||||
"ink-testing-library": "^4.0.0",
|
||||
"json": "^11.0.0",
|
||||
"lint-staged": "^16.1.6",
|
||||
"memfs": "^4.42.0",
|
||||
@@ -102,7 +110,7 @@
|
||||
"msw": "^2.10.4",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^3.5.3",
|
||||
"react-devtools-core": "^4.28.5",
|
||||
"react-devtools-core": "^6.1.2",
|
||||
"semver": "^7.7.2",
|
||||
"strip-ansi": "^7.1.2",
|
||||
"tsx": "^4.20.3",
|
||||
@@ -111,7 +119,8 @@
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"ink": "npm:@jrichman/ink@6.4.6",
|
||||
"latest-version": "^9.0.0",
|
||||
"simple-git": "^3.28.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
@@ -126,7 +135,10 @@
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx}": [
|
||||
"prettier --write",
|
||||
"eslint --fix --max-warnings 0"
|
||||
"eslint --fix --max-warnings 0 --no-warn-ignored"
|
||||
],
|
||||
"eslint.config.js": [
|
||||
"prettier --write"
|
||||
],
|
||||
"*.{json,md}": [
|
||||
"prettier --write"
|
||||
|
||||
@@ -153,7 +153,7 @@ syntax = "proto3";
|
||||
|
||||
import "google/protobuf/struct.proto";
|
||||
|
||||
// ToolCall is the central message represeting a tool's execution lifecycle.
|
||||
// ToolCall is the central message representing a tool's execution lifecycle.
|
||||
// The entire object is sent from the agent to client on every update.
|
||||
message ToolCall {
|
||||
// A unique identifier, assigned by the agent
|
||||
@@ -197,7 +197,7 @@ enum ToolCallStatus {
|
||||
CANCELLED = 5;
|
||||
}
|
||||
|
||||
// ToolOuput represents the final, successful, output of a tool
|
||||
// ToolOutput represents the final, successful, output of a tool
|
||||
message ToolOutput {
|
||||
oneof result {
|
||||
string text = 1;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@google/gemini-cli-a2a-server",
|
||||
"version": "0.11.2",
|
||||
"version": "0.19.0",
|
||||
"description": "Gemini CLI A2A Server",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -30,7 +30,7 @@
|
||||
"@google/gemini-cli-core": "file:../core",
|
||||
"express": "^5.1.0",
|
||||
"fs-extra": "^11.3.0",
|
||||
"tar": "^7.4.3",
|
||||
"tar": "^7.5.2",
|
||||
"uuid": "^11.1.0",
|
||||
"winston": "^3.17.0"
|
||||
},
|
||||
|
||||
@@ -17,7 +17,10 @@ import type {
|
||||
ServerGeminiToolCallRequestEvent,
|
||||
Config,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { GeminiEventType } from '@google/gemini-cli-core';
|
||||
import {
|
||||
GeminiEventType,
|
||||
SimpleExtensionLoader,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { logger } from '../utils/logger.js';
|
||||
@@ -96,7 +99,11 @@ export class CoderAgentExecutor implements AgentExecutor {
|
||||
loadEnvironment(); // Will override any global env with workspace envs
|
||||
const settings = loadSettings(workspaceRoot);
|
||||
const extensions = loadExtensions(workspaceRoot);
|
||||
return await loadConfig(settings, extensions, taskId);
|
||||
return await loadConfig(
|
||||
settings,
|
||||
new SimpleExtensionLoader(extensions),
|
||||
taskId,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,11 +4,25 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import {
|
||||
describe,
|
||||
it,
|
||||
expect,
|
||||
vi,
|
||||
beforeEach,
|
||||
afterEach,
|
||||
type Mock,
|
||||
} from 'vitest';
|
||||
import { Task } from './task.js';
|
||||
import type { Config, ToolCallRequestInfo } from '@google/gemini-cli-core';
|
||||
import {
|
||||
GeminiEventType,
|
||||
type Config,
|
||||
type ToolCallRequestInfo,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { createMockConfig } from '../utils/testing_utils.js';
|
||||
import type { ExecutionEventBus } from '@a2a-js/sdk/server';
|
||||
import { CoderAgentEvent } from '../types.js';
|
||||
import type { ToolCall } from '@google/gemini-cli-core';
|
||||
|
||||
describe('Task', () => {
|
||||
it('scheduleToolCalls should not modify the input requests array', async () => {
|
||||
@@ -93,5 +107,167 @@ describe('Task', () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle Citation event and publish to event bus', async () => {
|
||||
const mockConfig = createMockConfig();
|
||||
const mockEventBus: ExecutionEventBus = {
|
||||
publish: vi.fn(),
|
||||
on: vi.fn(),
|
||||
off: vi.fn(),
|
||||
once: vi.fn(),
|
||||
removeAllListeners: vi.fn(),
|
||||
finished: vi.fn(),
|
||||
};
|
||||
|
||||
// @ts-expect-error - Calling private constructor for test purposes.
|
||||
const task = new Task(
|
||||
'task-id',
|
||||
'context-id',
|
||||
mockConfig as Config,
|
||||
mockEventBus,
|
||||
);
|
||||
|
||||
const citationText = 'Source: example.com';
|
||||
const citationEvent = {
|
||||
type: GeminiEventType.Citation,
|
||||
value: citationText,
|
||||
};
|
||||
|
||||
await task.acceptAgentMessage(citationEvent);
|
||||
|
||||
expect(mockEventBus.publish).toHaveBeenCalledOnce();
|
||||
const publishedEvent = (mockEventBus.publish as Mock).mock.calls[0][0];
|
||||
|
||||
expect(publishedEvent.kind).toBe('status-update');
|
||||
expect(publishedEvent.taskId).toBe('task-id');
|
||||
expect(publishedEvent.metadata.coderAgent.kind).toBe(
|
||||
CoderAgentEvent.CitationEvent,
|
||||
);
|
||||
expect(publishedEvent.status.message).toBeDefined();
|
||||
expect(publishedEvent.status.message.parts).toEqual([
|
||||
{
|
||||
kind: 'text',
|
||||
text: citationText,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('_schedulerToolCallsUpdate', () => {
|
||||
let task: Task;
|
||||
type SpyInstance = ReturnType<typeof vi.spyOn>;
|
||||
let setTaskStateAndPublishUpdateSpy: SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
const mockConfig = createMockConfig();
|
||||
const mockEventBus: ExecutionEventBus = {
|
||||
publish: vi.fn(),
|
||||
on: vi.fn(),
|
||||
off: vi.fn(),
|
||||
once: vi.fn(),
|
||||
removeAllListeners: vi.fn(),
|
||||
finished: vi.fn(),
|
||||
};
|
||||
|
||||
// @ts-expect-error - Calling private constructor
|
||||
task = new Task(
|
||||
'task-id',
|
||||
'context-id',
|
||||
mockConfig as Config,
|
||||
mockEventBus,
|
||||
);
|
||||
|
||||
// Spy on the method we want to check calls for
|
||||
setTaskStateAndPublishUpdateSpy = vi.spyOn(
|
||||
task,
|
||||
'setTaskStateAndPublishUpdate',
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should set state to input-required when a tool is awaiting approval and none are executing', () => {
|
||||
const toolCalls = [
|
||||
{ request: { callId: '1' }, status: 'awaiting_approval' },
|
||||
] as ToolCall[];
|
||||
|
||||
// @ts-expect-error - Calling private method
|
||||
task._schedulerToolCallsUpdate(toolCalls);
|
||||
|
||||
// The last call should be the final state update
|
||||
expect(setTaskStateAndPublishUpdateSpy).toHaveBeenLastCalledWith(
|
||||
'input-required',
|
||||
{ kind: 'state-change' },
|
||||
undefined,
|
||||
undefined,
|
||||
true, // final: true
|
||||
);
|
||||
});
|
||||
|
||||
it('should NOT set state to input-required if a tool is awaiting approval but another is executing', () => {
|
||||
const toolCalls = [
|
||||
{ request: { callId: '1' }, status: 'awaiting_approval' },
|
||||
{ request: { callId: '2' }, status: 'executing' },
|
||||
] as ToolCall[];
|
||||
|
||||
// @ts-expect-error - Calling private method
|
||||
task._schedulerToolCallsUpdate(toolCalls);
|
||||
|
||||
// It will be called for status updates, but not with final: true
|
||||
const finalCall = setTaskStateAndPublishUpdateSpy.mock.calls.find(
|
||||
(call) => call[4] === true,
|
||||
);
|
||||
expect(finalCall).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should set state to input-required once an executing tool finishes, leaving one awaiting approval', () => {
|
||||
const initialToolCalls = [
|
||||
{ request: { callId: '1' }, status: 'awaiting_approval' },
|
||||
{ request: { callId: '2' }, status: 'executing' },
|
||||
] as ToolCall[];
|
||||
// @ts-expect-error - Calling private method
|
||||
task._schedulerToolCallsUpdate(initialToolCalls);
|
||||
|
||||
// No final call yet
|
||||
let finalCall = setTaskStateAndPublishUpdateSpy.mock.calls.find(
|
||||
(call) => call[4] === true,
|
||||
);
|
||||
expect(finalCall).toBeUndefined();
|
||||
|
||||
// Now, the executing tool finishes. The scheduler would call _resolveToolCall for it.
|
||||
// @ts-expect-error - Calling private method
|
||||
task._resolveToolCall('2');
|
||||
|
||||
// Then another update comes in for the awaiting tool (e.g., a re-check)
|
||||
const subsequentToolCalls = [
|
||||
{ request: { callId: '1' }, status: 'awaiting_approval' },
|
||||
] as ToolCall[];
|
||||
// @ts-expect-error - Calling private method
|
||||
task._schedulerToolCallsUpdate(subsequentToolCalls);
|
||||
|
||||
// NOW we should get the final call
|
||||
finalCall = setTaskStateAndPublishUpdateSpy.mock.calls.find(
|
||||
(call) => call[4] === true,
|
||||
);
|
||||
expect(finalCall).toBeDefined();
|
||||
expect(finalCall?.[0]).toBe('input-required');
|
||||
});
|
||||
|
||||
it('should NOT set state to input-required if skipFinalTrueAfterInlineEdit is true', () => {
|
||||
task.skipFinalTrueAfterInlineEdit = true;
|
||||
const toolCalls = [
|
||||
{ request: { callId: '1' }, status: 'awaiting_approval' },
|
||||
] as ToolCall[];
|
||||
|
||||
// @ts-expect-error - Calling private method
|
||||
task._schedulerToolCallsUpdate(toolCalls);
|
||||
|
||||
const finalCall = setTaskStateAndPublishUpdateSpy.mock.calls.find(
|
||||
(call) => call[4] === true,
|
||||
);
|
||||
expect(finalCall).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -40,7 +40,6 @@ import type {
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { logger } from '../utils/logger.js';
|
||||
import * as fs from 'node:fs';
|
||||
|
||||
import { CoderAgentEvent } from '../types.js';
|
||||
import type {
|
||||
CoderAgentMessage,
|
||||
@@ -50,6 +49,7 @@ import type {
|
||||
TaskMetadata,
|
||||
Thought,
|
||||
ThoughtSummary,
|
||||
Citation,
|
||||
} from '../types.js';
|
||||
import type { PartUnion, Part as genAiPart } from '@google/genai';
|
||||
|
||||
@@ -113,7 +113,7 @@ export class Task {
|
||||
// state managed within the @gemini-cli/core module.
|
||||
async getMetadata(): Promise<TaskMetadata> {
|
||||
const toolRegistry = await this.config.getToolRegistry();
|
||||
const mcpServers = this.config.getMcpServers() || {};
|
||||
const mcpServers = this.config.getMcpClientManager()?.getMcpServers() || {};
|
||||
const serverStatuses = getAllMCPServerStatuses();
|
||||
const servers = Object.keys(mcpServers).map((serverName) => ({
|
||||
name: serverName,
|
||||
@@ -373,11 +373,11 @@ export class Task {
|
||||
|
||||
// Only send an update if the status has actually changed.
|
||||
if (hasChanged) {
|
||||
const message = this.toolStatusMessage(tc, this.id, this.contextId);
|
||||
const coderAgentMessage: CoderAgentMessage =
|
||||
tc.status === 'awaiting_approval'
|
||||
? { kind: CoderAgentEvent.ToolCallConfirmationEvent }
|
||||
: { kind: CoderAgentEvent.ToolCallUpdateEvent };
|
||||
const message = this.toolStatusMessage(tc, this.id, this.contextId);
|
||||
|
||||
const event = this._createStatusUpdateEvent(
|
||||
this.taskState,
|
||||
@@ -404,20 +404,16 @@ export class Task {
|
||||
const isAwaitingApproval = allPendingStatuses.some(
|
||||
(status) => status === 'awaiting_approval',
|
||||
);
|
||||
const allPendingAreStable = allPendingStatuses.every(
|
||||
(status) =>
|
||||
status === 'awaiting_approval' ||
|
||||
status === 'success' ||
|
||||
status === 'error' ||
|
||||
status === 'cancelled',
|
||||
const isExecuting = allPendingStatuses.some(
|
||||
(status) => status === 'executing',
|
||||
);
|
||||
|
||||
// 1. Are any pending tool calls awaiting_approval
|
||||
// 2. Are all pending tool calls in a stable state (i.e. not in validing or executing)
|
||||
// 3. After an inline edit, the edited tool call will send awaiting_approval THEN scheduled. We wait for the next update in this case.
|
||||
// The turn is complete and requires user input if at least one tool
|
||||
// is waiting for the user's decision, and no other tool is actively
|
||||
// running in the background.
|
||||
if (
|
||||
isAwaitingApproval &&
|
||||
allPendingAreStable &&
|
||||
!isExecuting &&
|
||||
!this.skipFinalTrueAfterInlineEdit
|
||||
) {
|
||||
this.skipFinalTrueAfterInlineEdit = false;
|
||||
@@ -440,7 +436,6 @@ export class Task {
|
||||
onToolCallsUpdate: this._schedulerToolCallsUpdate.bind(this),
|
||||
getPreferredEditor: () => 'vscode',
|
||||
config: this.config,
|
||||
onEditorClose: () => {},
|
||||
});
|
||||
return scheduler;
|
||||
}
|
||||
@@ -465,7 +460,7 @@ export class Task {
|
||||
): Message {
|
||||
const messageParts: Part[] = [];
|
||||
|
||||
// Create a serializable version of the ToolCall (pick necesssary
|
||||
// Create a serializable version of the ToolCall (pick necessary
|
||||
// properties/avoid methods causing circular reference errors)
|
||||
const serializableToolCall: Partial<ToolCall> = this._pickFields(
|
||||
tc,
|
||||
@@ -643,6 +638,10 @@ export class Task {
|
||||
logger.info('[Task] Sending agent thought...');
|
||||
this._sendThought(event.value, traceId);
|
||||
break;
|
||||
case GeminiEventType.Citation:
|
||||
logger.info('[Task] Received citation from LLM stream.');
|
||||
this._sendCitation(event.value);
|
||||
break;
|
||||
case GeminiEventType.ChatCompressed:
|
||||
break;
|
||||
case GeminiEventType.Finished:
|
||||
@@ -984,4 +983,18 @@ export class Task {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_sendCitation(citation: string) {
|
||||
if (!citation || citation.trim() === '') {
|
||||
return;
|
||||
}
|
||||
logger.info('[Task] Sending citation to event bus.');
|
||||
const message = this._createTextMessage(citation);
|
||||
const citationEvent: Citation = {
|
||||
kind: CoderAgentEvent.CitationEvent,
|
||||
};
|
||||
this.eventBus?.publish(
|
||||
this._createStatusUpdateEvent(this.taskState, citationEvent, message),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import type { Command } from './types.js';
|
||||
|
||||
describe('CommandRegistry', () => {
|
||||
const mockListExtensionsCommandInstance: Command = {
|
||||
name: 'extensions list',
|
||||
description: 'Lists all installed extensions.',
|
||||
execute: vi.fn(),
|
||||
};
|
||||
const mockListExtensionsCommand = vi.fn(
|
||||
() => mockListExtensionsCommandInstance,
|
||||
);
|
||||
|
||||
const mockExtensionsCommandInstance: Command = {
|
||||
name: 'extensions',
|
||||
description: 'Manage extensions.',
|
||||
execute: vi.fn(),
|
||||
subCommands: [mockListExtensionsCommandInstance],
|
||||
};
|
||||
const mockExtensionsCommand = vi.fn(() => mockExtensionsCommandInstance);
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
vi.doMock('./extensions.js', () => ({
|
||||
ExtensionsCommand: mockExtensionsCommand,
|
||||
ListExtensionsCommand: mockListExtensionsCommand,
|
||||
}));
|
||||
});
|
||||
|
||||
it('should register ExtensionsCommand on initialization', async () => {
|
||||
const { commandRegistry } = await import('./command-registry.js');
|
||||
expect(mockExtensionsCommand).toHaveBeenCalled();
|
||||
const command = commandRegistry.get('extensions');
|
||||
expect(command).toBe(mockExtensionsCommandInstance);
|
||||
});
|
||||
|
||||
it('should register sub commands on initialization', async () => {
|
||||
const { commandRegistry } = await import('./command-registry.js');
|
||||
const command = commandRegistry.get('extensions list');
|
||||
expect(command).toBe(mockListExtensionsCommandInstance);
|
||||
});
|
||||
|
||||
it('get() should return undefined for a non-existent command', async () => {
|
||||
const { commandRegistry } = await import('./command-registry.js');
|
||||
const command = commandRegistry.get('non-existent');
|
||||
expect(command).toBeUndefined();
|
||||
});
|
||||
|
||||
it('register() should register a new command', async () => {
|
||||
const { commandRegistry } = await import('./command-registry.js');
|
||||
const mockCommand: Command = {
|
||||
name: 'test-command',
|
||||
description: '',
|
||||
execute: vi.fn(),
|
||||
};
|
||||
commandRegistry.register(mockCommand);
|
||||
const command = commandRegistry.get('test-command');
|
||||
expect(command).toBe(mockCommand);
|
||||
});
|
||||
|
||||
it('register() should register a nested command', async () => {
|
||||
const { commandRegistry } = await import('./command-registry.js');
|
||||
const mockSubSubCommand: Command = {
|
||||
name: 'test-command-sub-sub',
|
||||
description: '',
|
||||
execute: vi.fn(),
|
||||
};
|
||||
const mockSubCommand: Command = {
|
||||
name: 'test-command-sub',
|
||||
description: '',
|
||||
execute: vi.fn(),
|
||||
subCommands: [mockSubSubCommand],
|
||||
};
|
||||
const mockCommand: Command = {
|
||||
name: 'test-command',
|
||||
description: '',
|
||||
execute: vi.fn(),
|
||||
subCommands: [mockSubCommand],
|
||||
};
|
||||
commandRegistry.register(mockCommand);
|
||||
|
||||
const command = commandRegistry.get('test-command');
|
||||
const subCommand = commandRegistry.get('test-command-sub');
|
||||
const subSubCommand = commandRegistry.get('test-command-sub-sub');
|
||||
|
||||
expect(command).toBe(mockCommand);
|
||||
expect(subCommand).toBe(mockSubCommand);
|
||||
expect(subSubCommand).toBe(mockSubSubCommand);
|
||||
});
|
||||
|
||||
it('register() should not enter an infinite loop with a cyclic command', async () => {
|
||||
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
const { commandRegistry } = await import('./command-registry.js');
|
||||
const mockCommand: Command = {
|
||||
name: 'cyclic-command',
|
||||
description: '',
|
||||
subCommands: [],
|
||||
execute: vi.fn(),
|
||||
};
|
||||
|
||||
mockCommand.subCommands?.push(mockCommand); // Create cycle
|
||||
|
||||
commandRegistry.register(mockCommand);
|
||||
|
||||
expect(commandRegistry.get('cyclic-command')).toBe(mockCommand);
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
'Command cyclic-command already registered. Skipping.',
|
||||
);
|
||||
// If the test finishes, it means we didn't get into an infinite loop.
|
||||
warnSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { ExtensionsCommand } from './extensions.js';
|
||||
import type { Command } from './types.js';
|
||||
|
||||
class CommandRegistry {
|
||||
private readonly commands = new Map<string, Command>();
|
||||
|
||||
constructor() {
|
||||
this.register(new ExtensionsCommand());
|
||||
}
|
||||
|
||||
register(command: Command) {
|
||||
if (this.commands.has(command.name)) {
|
||||
console.warn(`Command ${command.name} already registered. Skipping.`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.commands.set(command.name, command);
|
||||
|
||||
for (const subCommand of command.subCommands ?? []) {
|
||||
this.register(subCommand);
|
||||
}
|
||||
}
|
||||
|
||||
get(commandName: string): Command | undefined {
|
||||
return this.commands.get(commandName);
|
||||
}
|
||||
|
||||
getAllCommands(): Command[] {
|
||||
return [...this.commands.values()];
|
||||
}
|
||||
}
|
||||
|
||||
export const commandRegistry = new CommandRegistry();
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { ExtensionsCommand, ListExtensionsCommand } from './extensions.js';
|
||||
import type { Config } from '@google/gemini-cli-core';
|
||||
|
||||
const mockListExtensions = vi.hoisted(() => vi.fn());
|
||||
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
const original =
|
||||
await importOriginal<typeof import('@google/gemini-cli-core')>();
|
||||
|
||||
return {
|
||||
...original,
|
||||
listExtensions: mockListExtensions,
|
||||
};
|
||||
});
|
||||
|
||||
describe('ExtensionsCommand', () => {
|
||||
it('should have the correct name', () => {
|
||||
const command = new ExtensionsCommand();
|
||||
expect(command.name).toEqual('extensions');
|
||||
});
|
||||
|
||||
it('should have the correct description', () => {
|
||||
const command = new ExtensionsCommand();
|
||||
expect(command.description).toEqual('Manage extensions.');
|
||||
});
|
||||
|
||||
it('should have "extensions list" as a subcommand', () => {
|
||||
const command = new ExtensionsCommand();
|
||||
expect(command.subCommands.map((c) => c.name)).toContain('extensions list');
|
||||
});
|
||||
|
||||
it('should be a top-level command', () => {
|
||||
const command = new ExtensionsCommand();
|
||||
expect(command.topLevel).toBe(true);
|
||||
});
|
||||
|
||||
it('should default to listing extensions', async () => {
|
||||
const command = new ExtensionsCommand();
|
||||
const mockConfig = {} as Config;
|
||||
const mockExtensions = [{ name: 'ext1' }];
|
||||
mockListExtensions.mockReturnValue(mockExtensions);
|
||||
|
||||
const result = await command.execute(mockConfig, []);
|
||||
|
||||
expect(result).toEqual({ name: 'extensions list', data: mockExtensions });
|
||||
expect(mockListExtensions).toHaveBeenCalledWith(mockConfig);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ListExtensionsCommand', () => {
|
||||
it('should have the correct name', () => {
|
||||
const command = new ListExtensionsCommand();
|
||||
expect(command.name).toEqual('extensions list');
|
||||
});
|
||||
|
||||
it('should call listExtensions with the provided config', async () => {
|
||||
const command = new ListExtensionsCommand();
|
||||
const mockConfig = {} as Config;
|
||||
const mockExtensions = [{ name: 'ext1' }];
|
||||
mockListExtensions.mockReturnValue(mockExtensions);
|
||||
|
||||
const result = await command.execute(mockConfig, []);
|
||||
|
||||
expect(result).toEqual({ name: 'extensions list', data: mockExtensions });
|
||||
expect(mockListExtensions).toHaveBeenCalledWith(mockConfig);
|
||||
});
|
||||
|
||||
it('should return a message when no extensions are installed', async () => {
|
||||
const command = new ListExtensionsCommand();
|
||||
const mockConfig = {} as Config;
|
||||
mockListExtensions.mockReturnValue([]);
|
||||
|
||||
const result = await command.execute(mockConfig, []);
|
||||
|
||||
expect(result).toEqual({
|
||||
name: 'extensions list',
|
||||
data: 'No extensions installed.',
|
||||
});
|
||||
expect(mockListExtensions).toHaveBeenCalledWith(mockConfig);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { listExtensions, type Config } from '@google/gemini-cli-core';
|
||||
import type { Command, CommandExecutionResponse } from './types.js';
|
||||
|
||||
export class ExtensionsCommand implements Command {
|
||||
readonly name = 'extensions';
|
||||
readonly description = 'Manage extensions.';
|
||||
readonly subCommands = [new ListExtensionsCommand()];
|
||||
readonly topLevel = true;
|
||||
|
||||
async execute(
|
||||
config: Config,
|
||||
_: string[],
|
||||
): Promise<CommandExecutionResponse> {
|
||||
return new ListExtensionsCommand().execute(config, _);
|
||||
}
|
||||
}
|
||||
|
||||
export class ListExtensionsCommand implements Command {
|
||||
readonly name = 'extensions list';
|
||||
readonly description = 'Lists all installed extensions.';
|
||||
|
||||
async execute(
|
||||
config: Config,
|
||||
_: string[],
|
||||
): Promise<CommandExecutionResponse> {
|
||||
const extensions = listExtensions(config);
|
||||
const data = extensions.length ? extensions : 'No extensions installed.';
|
||||
|
||||
return { name: this.name, data };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type { Config } from '@google/gemini-cli-core';
|
||||
|
||||
export interface CommandArgument {
|
||||
readonly name: string;
|
||||
readonly description: string;
|
||||
readonly isRequired?: boolean;
|
||||
}
|
||||
|
||||
export interface Command {
|
||||
readonly name: string;
|
||||
readonly description: string;
|
||||
readonly arguments?: CommandArgument[];
|
||||
readonly subCommands?: Command[];
|
||||
readonly topLevel?: boolean;
|
||||
|
||||
execute(config: Config, args: string[]): Promise<CommandExecutionResponse>;
|
||||
}
|
||||
|
||||
export interface CommandExecutionResponse {
|
||||
readonly name: string;
|
||||
readonly data: unknown;
|
||||
}
|
||||
@@ -20,8 +20,7 @@ import {
|
||||
GEMINI_DIR,
|
||||
DEFAULT_GEMINI_EMBEDDING_MODEL,
|
||||
DEFAULT_GEMINI_MODEL,
|
||||
type GeminiCLIExtension,
|
||||
debugLogger,
|
||||
type ExtensionLoader,
|
||||
} from '@google/gemini-cli-core';
|
||||
|
||||
import { logger } from '../utils/logger.js';
|
||||
@@ -30,10 +29,9 @@ import { type AgentSettings, CoderAgentEvent } from '../types.js';
|
||||
|
||||
export async function loadConfig(
|
||||
settings: Settings,
|
||||
extensions: GeminiCLIExtension[],
|
||||
extensionLoader: ExtensionLoader,
|
||||
taskId: string,
|
||||
): Promise<Config> {
|
||||
const mcpServers = mergeMcpServers(settings, extensions);
|
||||
const workspaceDir = process.cwd();
|
||||
const adcFilePath = process.env['GOOGLE_APPLICATION_CREDENTIALS'];
|
||||
|
||||
@@ -53,7 +51,7 @@ export async function loadConfig(
|
||||
process.env['GEMINI_YOLO_MODE'] === 'true'
|
||||
? ApprovalMode.YOLO
|
||||
: ApprovalMode.DEFAULT,
|
||||
mcpServers,
|
||||
mcpServers: settings.mcpServers,
|
||||
cwd: workspaceDir,
|
||||
telemetry: {
|
||||
enabled: settings.telemetry?.enabled,
|
||||
@@ -71,6 +69,7 @@ export async function loadConfig(
|
||||
},
|
||||
ideMode: false,
|
||||
folderTrust: settings.folderTrust === true,
|
||||
extensionLoader,
|
||||
};
|
||||
|
||||
const fileService = new FileDiscoveryService(workspaceDir);
|
||||
@@ -79,7 +78,7 @@ export async function loadConfig(
|
||||
[workspaceDir],
|
||||
false,
|
||||
fileService,
|
||||
extensions,
|
||||
extensionLoader,
|
||||
settings.folderTrust === true,
|
||||
);
|
||||
configParams.userMemory = memoryContent;
|
||||
@@ -118,25 +117,6 @@ export async function loadConfig(
|
||||
return config;
|
||||
}
|
||||
|
||||
export function mergeMcpServers(
|
||||
settings: Settings,
|
||||
extensions: GeminiCLIExtension[],
|
||||
) {
|
||||
const mcpServers = { ...(settings.mcpServers || {}) };
|
||||
for (const extension of extensions) {
|
||||
Object.entries(extension.mcpServers || {}).forEach(([key, server]) => {
|
||||
if (mcpServers[key]) {
|
||||
debugLogger.warn(
|
||||
`Skipping extension MCP config for server with key "${key}" as it already exists.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
mcpServers[key] = server;
|
||||
});
|
||||
}
|
||||
return mcpServers;
|
||||
}
|
||||
|
||||
export function setTargetDir(agentSettings: AgentSettings | undefined): string {
|
||||
const originalCWD = process.cwd();
|
||||
const targetDir =
|
||||
|
||||
@@ -10,6 +10,7 @@ import { homedir } from 'node:os';
|
||||
|
||||
import type { MCPServerConfig } from '@google/gemini-cli-core';
|
||||
import {
|
||||
debugLogger,
|
||||
GEMINI_DIR,
|
||||
getErrorMessage,
|
||||
type TelemetrySettings,
|
||||
@@ -97,10 +98,10 @@ export function loadSettings(workspaceDir: string): Settings {
|
||||
}
|
||||
|
||||
if (settingsErrors.length > 0) {
|
||||
console.error('Errors loading settings:');
|
||||
debugLogger.error('Errors loading settings:');
|
||||
for (const error of settingsErrors) {
|
||||
console.error(` Path: ${error.path}`);
|
||||
console.error(` Message: ${error.message}`);
|
||||
debugLogger.error(` Path: ${error.path}`);
|
||||
debugLogger.error(` Message: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
vi,
|
||||
} from 'vitest';
|
||||
import { createApp } from './app.js';
|
||||
import { commandRegistry } from '../commands/command-registry.js';
|
||||
import {
|
||||
assertUniqueFinalEventIsLast,
|
||||
assertTaskCreationAndWorkingStatus,
|
||||
@@ -35,6 +36,7 @@ import {
|
||||
createMockConfig,
|
||||
} from '../utils/testing_utils.js';
|
||||
import { MockTool } from '@google/gemini-cli-core';
|
||||
import type { Command } from '../commands/types.js';
|
||||
|
||||
const mockToolConfirmationFn = async () =>
|
||||
({}) as unknown as ToolCallConfirmationDetails;
|
||||
@@ -65,6 +67,8 @@ let config: Config;
|
||||
const getToolRegistrySpy = vi.fn().mockReturnValue(ApprovalMode.DEFAULT);
|
||||
const getApprovalModeSpy = vi.fn();
|
||||
const getShellExecutionConfigSpy = vi.fn();
|
||||
const getExtensionsSpy = vi.fn();
|
||||
|
||||
vi.mock('../config/config.js', async () => {
|
||||
const actual = await vi.importActual('../config/config.js');
|
||||
return {
|
||||
@@ -74,6 +78,7 @@ vi.mock('../config/config.js', async () => {
|
||||
getToolRegistry: getToolRegistrySpy,
|
||||
getApprovalMode: getApprovalModeSpy,
|
||||
getShellExecutionConfig: getShellExecutionConfigSpy,
|
||||
getExtensions: getExtensionsSpy,
|
||||
});
|
||||
config = mockConfig as Config;
|
||||
return config;
|
||||
@@ -310,7 +315,7 @@ describe('E2E Tests', () => {
|
||||
expect(workingEvent.kind).toBe('status-update');
|
||||
expect(workingEvent.status.state).toBe('working');
|
||||
|
||||
// State Update: Validate each tool call
|
||||
// State Update: Validate the first tool call
|
||||
const toolCallValidateEvent1 = events[3].result as TaskStatusUpdateEvent;
|
||||
expect(toolCallValidateEvent1.metadata?.['coderAgent']).toMatchObject({
|
||||
kind: 'tool-call-update',
|
||||
@@ -323,47 +328,218 @@ describe('E2E Tests', () => {
|
||||
},
|
||||
},
|
||||
]);
|
||||
const toolCallValidateEvent2 = events[4].result as TaskStatusUpdateEvent;
|
||||
expect(toolCallValidateEvent2.metadata?.['coderAgent']).toMatchObject({
|
||||
|
||||
// --- Assert the event stream ---
|
||||
// 1. Initial "submitted" status.
|
||||
expect((events[0].result as TaskStatusUpdateEvent).status.state).toBe(
|
||||
'submitted',
|
||||
);
|
||||
|
||||
// 2. "working" status after receiving the user prompt.
|
||||
expect((events[1].result as TaskStatusUpdateEvent).status.state).toBe(
|
||||
'working',
|
||||
);
|
||||
|
||||
// 3. A "state-change" event from the agent.
|
||||
expect(events[2].result.metadata?.['coderAgent']).toMatchObject({
|
||||
kind: 'state-change',
|
||||
});
|
||||
|
||||
// 4. Tool 1 is validating.
|
||||
const toolCallUpdate1 = events[3].result as TaskStatusUpdateEvent;
|
||||
expect(toolCallUpdate1.metadata?.['coderAgent']).toMatchObject({
|
||||
kind: 'tool-call-update',
|
||||
});
|
||||
expect(toolCallValidateEvent2.status.message?.parts).toMatchObject([
|
||||
expect(toolCallUpdate1.status.message?.parts).toMatchObject([
|
||||
{
|
||||
data: {
|
||||
request: { callId: 'test-call-id-1' },
|
||||
status: 'validating',
|
||||
request: { callId: 'test-call-id-2' },
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// State Update: Set each tool call to awaiting
|
||||
const toolCallAwaitEvent1 = events[5].result as TaskStatusUpdateEvent;
|
||||
expect(toolCallAwaitEvent1.metadata?.['coderAgent']).toMatchObject({
|
||||
kind: 'tool-call-confirmation',
|
||||
// 5. Tool 2 is validating.
|
||||
const toolCallUpdate2 = events[4].result as TaskStatusUpdateEvent;
|
||||
expect(toolCallUpdate2.metadata?.['coderAgent']).toMatchObject({
|
||||
kind: 'tool-call-update',
|
||||
});
|
||||
expect(toolCallAwaitEvent1.status.message?.parts).toMatchObject([
|
||||
expect(toolCallUpdate2.status.message?.parts).toMatchObject([
|
||||
{
|
||||
data: {
|
||||
status: 'awaiting_approval',
|
||||
request: { callId: 'test-call-id-1' },
|
||||
},
|
||||
},
|
||||
]);
|
||||
const toolCallAwaitEvent2 = events[6].result as TaskStatusUpdateEvent;
|
||||
expect(toolCallAwaitEvent2.metadata?.['coderAgent']).toMatchObject({
|
||||
kind: 'tool-call-confirmation',
|
||||
});
|
||||
expect(toolCallAwaitEvent2.status.message?.parts).toMatchObject([
|
||||
{
|
||||
data: {
|
||||
status: 'awaiting_approval',
|
||||
request: { callId: 'test-call-id-2' },
|
||||
status: 'validating',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// 6. Tool 1 is awaiting approval.
|
||||
const toolCallAwaitEvent = events[5].result as TaskStatusUpdateEvent;
|
||||
expect(toolCallAwaitEvent.metadata?.['coderAgent']).toMatchObject({
|
||||
kind: 'tool-call-confirmation',
|
||||
});
|
||||
expect(toolCallAwaitEvent.status.message?.parts).toMatchObject([
|
||||
{
|
||||
data: {
|
||||
request: { callId: 'test-call-id-1' },
|
||||
status: 'awaiting_approval',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// 7. The final event is "input-required".
|
||||
const finalEvent = events[6].result as TaskStatusUpdateEvent;
|
||||
expect(finalEvent.final).toBe(true);
|
||||
expect(finalEvent.status.state).toBe('input-required');
|
||||
|
||||
// The scheduler now waits for approval, so no more events are sent.
|
||||
assertUniqueFinalEventIsLast(events);
|
||||
expect(events.length).toBe(7);
|
||||
});
|
||||
|
||||
it('should handle multiple tool calls sequentially in YOLO mode', async () => {
|
||||
// Set YOLO mode to auto-approve tools and test sequential execution.
|
||||
getApprovalModeSpy.mockReturnValue(ApprovalMode.YOLO);
|
||||
|
||||
// First call yields the tool request
|
||||
sendMessageStreamSpy.mockImplementationOnce(async function* () {
|
||||
yield* [
|
||||
{
|
||||
type: GeminiEventType.ToolCallRequest,
|
||||
value: {
|
||||
callId: 'test-call-id-1',
|
||||
name: 'test-tool-1',
|
||||
args: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: GeminiEventType.ToolCallRequest,
|
||||
value: {
|
||||
callId: 'test-call-id-2',
|
||||
name: 'test-tool-2',
|
||||
args: {},
|
||||
},
|
||||
},
|
||||
];
|
||||
});
|
||||
// Subsequent calls yield nothing, as the tools will "succeed".
|
||||
sendMessageStreamSpy.mockImplementation(async function* () {
|
||||
yield* [{ type: 'content', value: 'All tools executed.' }];
|
||||
});
|
||||
|
||||
const mockTool1 = new MockTool({
|
||||
name: 'test-tool-1',
|
||||
displayName: 'Test Tool 1',
|
||||
shouldConfirmExecute: vi.fn(mockToolConfirmationFn),
|
||||
execute: vi
|
||||
.fn()
|
||||
.mockResolvedValue({ llmContent: 'tool 1 done', returnDisplay: '' }),
|
||||
});
|
||||
const mockTool2 = new MockTool({
|
||||
name: 'test-tool-2',
|
||||
displayName: 'Test Tool 2',
|
||||
shouldConfirmExecute: vi.fn(mockToolConfirmationFn),
|
||||
execute: vi
|
||||
.fn()
|
||||
.mockResolvedValue({ llmContent: 'tool 2 done', returnDisplay: '' }),
|
||||
});
|
||||
|
||||
getToolRegistrySpy.mockReturnValue({
|
||||
getAllTools: vi.fn().mockReturnValue([mockTool1, mockTool2]),
|
||||
getToolsByServer: vi.fn().mockReturnValue([]),
|
||||
getTool: vi.fn().mockImplementation((name: string) => {
|
||||
if (name === 'test-tool-1') return mockTool1;
|
||||
if (name === 'test-tool-2') return mockTool2;
|
||||
return undefined;
|
||||
}),
|
||||
});
|
||||
|
||||
const agent = request.agent(app);
|
||||
const res = await agent
|
||||
.post('/')
|
||||
.send(
|
||||
createStreamMessageRequest(
|
||||
'run two tools',
|
||||
'a2a-multi-tool-test-message',
|
||||
),
|
||||
)
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
const events = streamToSSEEvents(res.text);
|
||||
assertTaskCreationAndWorkingStatus(events);
|
||||
|
||||
// --- Assert the sequential execution flow ---
|
||||
const eventStream = events.slice(2).map((e) => {
|
||||
const update = e.result as TaskStatusUpdateEvent;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const agentData = update.metadata?.['coderAgent'] as any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const toolData = update.status.message?.parts[0] as any;
|
||||
if (!toolData) {
|
||||
return { kind: agentData.kind };
|
||||
}
|
||||
return {
|
||||
kind: agentData.kind,
|
||||
status: toolData.data?.status,
|
||||
callId: toolData.data?.request.callId,
|
||||
};
|
||||
});
|
||||
|
||||
const expectedFlow = [
|
||||
// Initial state change
|
||||
{ kind: 'state-change', status: undefined, callId: undefined },
|
||||
// Tool 1 Lifecycle
|
||||
{
|
||||
kind: 'tool-call-update',
|
||||
status: 'validating',
|
||||
callId: 'test-call-id-1',
|
||||
},
|
||||
{
|
||||
kind: 'tool-call-update',
|
||||
status: 'scheduled',
|
||||
callId: 'test-call-id-1',
|
||||
},
|
||||
{
|
||||
kind: 'tool-call-update',
|
||||
status: 'executing',
|
||||
callId: 'test-call-id-1',
|
||||
},
|
||||
{
|
||||
kind: 'tool-call-update',
|
||||
status: 'success',
|
||||
callId: 'test-call-id-1',
|
||||
},
|
||||
// Tool 2 Lifecycle
|
||||
{
|
||||
kind: 'tool-call-update',
|
||||
status: 'validating',
|
||||
callId: 'test-call-id-2',
|
||||
},
|
||||
{
|
||||
kind: 'tool-call-update',
|
||||
status: 'scheduled',
|
||||
callId: 'test-call-id-2',
|
||||
},
|
||||
{
|
||||
kind: 'tool-call-update',
|
||||
status: 'executing',
|
||||
callId: 'test-call-id-2',
|
||||
},
|
||||
{
|
||||
kind: 'tool-call-update',
|
||||
status: 'success',
|
||||
callId: 'test-call-id-2',
|
||||
},
|
||||
// Final updates
|
||||
{ kind: 'state-change', status: undefined, callId: undefined },
|
||||
{ kind: 'text-content', status: undefined, callId: undefined },
|
||||
];
|
||||
|
||||
// Use `toContainEqual` for flexibility if other events are interspersed.
|
||||
expect(eventStream).toEqual(expect.arrayContaining(expectedFlow));
|
||||
|
||||
assertUniqueFinalEventIsLast(events);
|
||||
expect(events.length).toBe(8);
|
||||
});
|
||||
|
||||
it('should handle tool calls that do not require approval', async () => {
|
||||
@@ -652,4 +828,163 @@ describe('E2E Tests', () => {
|
||||
expect(thoughtEvent.kind).toBe('status-update');
|
||||
expect(thoughtEvent.metadata?.['traceId']).toBe(traceId);
|
||||
});
|
||||
|
||||
describe('/listCommands', () => {
|
||||
it('should return a list of top-level commands', async () => {
|
||||
const mockCommands = [
|
||||
{
|
||||
name: 'test-command',
|
||||
description: 'A test command',
|
||||
topLevel: true,
|
||||
arguments: [{ name: 'arg1', description: 'Argument 1' }],
|
||||
subCommands: [
|
||||
{
|
||||
name: 'sub-command',
|
||||
description: 'A sub command',
|
||||
topLevel: false,
|
||||
execute: vi.fn(),
|
||||
},
|
||||
],
|
||||
execute: vi.fn(),
|
||||
},
|
||||
{
|
||||
name: 'another-command',
|
||||
description: 'Another test command',
|
||||
topLevel: true,
|
||||
execute: vi.fn(),
|
||||
},
|
||||
{
|
||||
name: 'not-top-level',
|
||||
description: 'Not a top level command',
|
||||
topLevel: false,
|
||||
execute: vi.fn(),
|
||||
},
|
||||
];
|
||||
|
||||
const getAllCommandsSpy = vi
|
||||
.spyOn(commandRegistry, 'getAllCommands')
|
||||
.mockReturnValue(mockCommands);
|
||||
|
||||
const agent = request.agent(app);
|
||||
const res = await agent.get('/listCommands').expect(200);
|
||||
|
||||
expect(res.body).toEqual({
|
||||
commands: [
|
||||
{
|
||||
name: 'test-command',
|
||||
description: 'A test command',
|
||||
arguments: [{ name: 'arg1', description: 'Argument 1' }],
|
||||
subCommands: [
|
||||
{
|
||||
name: 'sub-command',
|
||||
description: 'A sub command',
|
||||
arguments: [],
|
||||
subCommands: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'another-command',
|
||||
description: 'Another test command',
|
||||
arguments: [],
|
||||
subCommands: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(getAllCommandsSpy).toHaveBeenCalledOnce();
|
||||
getAllCommandsSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should handle cyclic commands gracefully', async () => {
|
||||
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
|
||||
const cyclicCommand: Command = {
|
||||
name: 'cyclic-command',
|
||||
description: 'A cyclic command',
|
||||
topLevel: true,
|
||||
execute: vi.fn(),
|
||||
subCommands: [],
|
||||
};
|
||||
cyclicCommand.subCommands?.push(cyclicCommand); // Create cycle
|
||||
|
||||
const getAllCommandsSpy = vi
|
||||
.spyOn(commandRegistry, 'getAllCommands')
|
||||
.mockReturnValue([cyclicCommand]);
|
||||
|
||||
const agent = request.agent(app);
|
||||
const res = await agent.get('/listCommands').expect(200);
|
||||
|
||||
expect(res.body.commands[0].name).toBe('cyclic-command');
|
||||
expect(res.body.commands[0].subCommands).toEqual([]);
|
||||
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
'Command cyclic-command already inserted in the response, skipping',
|
||||
);
|
||||
|
||||
getAllCommandsSpy.mockRestore();
|
||||
warnSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('/executeCommand', () => {
|
||||
const mockExtensions = [{ name: 'test-extension', version: '0.0.1' }];
|
||||
|
||||
beforeEach(() => {
|
||||
getExtensionsSpy.mockReturnValue(mockExtensions);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
getExtensionsSpy.mockClear();
|
||||
});
|
||||
|
||||
it('should return extensions for valid command', async () => {
|
||||
const agent = request.agent(app);
|
||||
const res = await agent
|
||||
.post('/executeCommand')
|
||||
.send({ command: 'extensions list', args: [] })
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).toEqual({
|
||||
name: 'extensions list',
|
||||
data: mockExtensions,
|
||||
});
|
||||
expect(getExtensionsSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return 404 for invalid command', async () => {
|
||||
const agent = request.agent(app);
|
||||
const res = await agent
|
||||
.post('/executeCommand')
|
||||
.send({ command: 'invalid command' })
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(404);
|
||||
|
||||
expect(res.body.error).toBe('Command not found: invalid command');
|
||||
expect(getExtensionsSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return 400 for missing command', async () => {
|
||||
const agent = request.agent(app);
|
||||
await agent
|
||||
.post('/executeCommand')
|
||||
.send({ args: [] })
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
expect(getExtensionsSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return 400 if args is not an array', async () => {
|
||||
const agent = request.agent(app);
|
||||
const res = await agent
|
||||
.post('/executeCommand')
|
||||
.send({ command: 'extensions.list', args: 'not-an-array' })
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body.error).toBe('"args" field must be an array.');
|
||||
expect(getExtensionsSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,6 +16,19 @@ import type { AgentSettings } from '../types.js';
|
||||
import { GCSTaskStore, NoOpTaskStore } from '../persistence/gcs.js';
|
||||
import { CoderAgentExecutor } from '../agent/executor.js';
|
||||
import { requestStorage } from './requestStorage.js';
|
||||
import { loadConfig, loadEnvironment, setTargetDir } from '../config/config.js';
|
||||
import { loadSettings } from '../config/settings.js';
|
||||
import { loadExtensions } from '../config/extension.js';
|
||||
import { commandRegistry } from '../commands/command-registry.js';
|
||||
import { SimpleExtensionLoader } from '@google/gemini-cli-core';
|
||||
import type { Command, CommandArgument } from '../commands/types.js';
|
||||
|
||||
type CommandResponse = {
|
||||
name: string;
|
||||
description: string;
|
||||
arguments: CommandArgument[];
|
||||
subCommands: CommandResponse[];
|
||||
};
|
||||
|
||||
const coderAgentCard: AgentCard = {
|
||||
name: 'Gemini SDLC Agent',
|
||||
@@ -61,6 +74,17 @@ export function updateCoderAgentCardUrl(port: number) {
|
||||
|
||||
export async function createApp() {
|
||||
try {
|
||||
// Load the server configuration once on startup.
|
||||
const workspaceRoot = setTargetDir(undefined);
|
||||
loadEnvironment();
|
||||
const settings = loadSettings(workspaceRoot);
|
||||
const extensions = loadExtensions(workspaceRoot);
|
||||
const config = await loadConfig(
|
||||
settings,
|
||||
new SimpleExtensionLoader(extensions),
|
||||
'a2a-server',
|
||||
);
|
||||
|
||||
// loadEnvironment() is called within getConfig now
|
||||
const bucketName = process.env['GCS_BUCKET_NAME'];
|
||||
let taskStoreForExecutor: TaskStore;
|
||||
@@ -119,6 +143,80 @@ export async function createApp() {
|
||||
}
|
||||
});
|
||||
|
||||
expressApp.post('/executeCommand', async (req, res) => {
|
||||
try {
|
||||
const { command, args } = req.body;
|
||||
|
||||
if (typeof command !== 'string') {
|
||||
return res.status(400).json({ error: 'Invalid "command" field.' });
|
||||
}
|
||||
|
||||
if (args && !Array.isArray(args)) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: '"args" field must be an array.' });
|
||||
}
|
||||
|
||||
const commandToExecute = commandRegistry.get(command);
|
||||
|
||||
if (!commandToExecute) {
|
||||
return res
|
||||
.status(404)
|
||||
.json({ error: `Command not found: ${command}` });
|
||||
}
|
||||
|
||||
const result = await commandToExecute.execute(config, args ?? []);
|
||||
return res.status(200).json(result);
|
||||
} catch (e) {
|
||||
logger.error('Error executing /executeCommand:', e);
|
||||
const errorMessage =
|
||||
e instanceof Error ? e.message : 'Unknown error executing command';
|
||||
return res.status(500).json({ error: errorMessage });
|
||||
}
|
||||
});
|
||||
|
||||
expressApp.get('/listCommands', (req, res) => {
|
||||
try {
|
||||
const transformCommand = (
|
||||
command: Command,
|
||||
visited: string[],
|
||||
): CommandResponse | undefined => {
|
||||
const commandName = command.name;
|
||||
if (visited.includes(commandName)) {
|
||||
console.warn(
|
||||
`Command ${commandName} already inserted in the response, skipping`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
name: command.name,
|
||||
description: command.description,
|
||||
arguments: command.arguments ?? [],
|
||||
subCommands: (command.subCommands ?? [])
|
||||
.map((subCommand) =>
|
||||
transformCommand(subCommand, visited.concat(commandName)),
|
||||
)
|
||||
.filter(
|
||||
(subCommand): subCommand is CommandResponse => !!subCommand,
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
const commands = commandRegistry
|
||||
.getAllCommands()
|
||||
.filter((command) => command.topLevel)
|
||||
.map((command) => transformCommand(command, []));
|
||||
|
||||
return res.status(200).json({ commands });
|
||||
} catch (e) {
|
||||
logger.error('Error executing /listCommands:', e);
|
||||
const errorMessage =
|
||||
e instanceof Error ? e.message : 'Unknown error listing commands';
|
||||
return res.status(500).json({ error: errorMessage });
|
||||
}
|
||||
});
|
||||
|
||||
expressApp.get('/tasks/metadata', async (req, res) => {
|
||||
// This endpoint is only meaningful if the task store is in-memory.
|
||||
if (!(taskStoreForExecutor instanceof InMemoryTaskStore)) {
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
import { Storage } from '@google-cloud/storage';
|
||||
import * as fse from 'fs-extra';
|
||||
import { promises as fsPromises, createReadStream } from 'node:fs';
|
||||
import * as tar from 'tar';
|
||||
import { gzipSync, gunzipSync } from 'node:zlib';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
@@ -21,12 +20,18 @@ import * as configModule from '../config/config.js';
|
||||
import { getPersistedState, METADATA_KEY } from '../types.js';
|
||||
|
||||
// Mock dependencies
|
||||
const fsMocks = vi.hoisted(() => ({
|
||||
readdir: vi.fn(),
|
||||
createReadStream: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@google-cloud/storage');
|
||||
vi.mock('fs-extra', () => ({
|
||||
pathExists: vi.fn(),
|
||||
readdir: vi.fn(),
|
||||
remove: vi.fn(),
|
||||
ensureDir: vi.fn(),
|
||||
createReadStream: vi.fn(),
|
||||
}));
|
||||
vi.mock('node:fs', async () => {
|
||||
const actual = await vi.importActual<typeof import('node:fs')>('node:fs');
|
||||
@@ -34,12 +39,37 @@ vi.mock('node:fs', async () => {
|
||||
...actual,
|
||||
promises: {
|
||||
...actual.promises,
|
||||
readdir: vi.fn(),
|
||||
readdir: fsMocks.readdir,
|
||||
},
|
||||
createReadStream: vi.fn(),
|
||||
createReadStream: fsMocks.createReadStream,
|
||||
};
|
||||
});
|
||||
vi.mock('fs', async () => {
|
||||
const actual = await vi.importActual<typeof import('node:fs')>('node:fs');
|
||||
return {
|
||||
...actual,
|
||||
promises: {
|
||||
...actual.promises,
|
||||
readdir: fsMocks.readdir,
|
||||
},
|
||||
createReadStream: fsMocks.createReadStream,
|
||||
};
|
||||
});
|
||||
vi.mock('tar', async () => {
|
||||
const actualFs = await vi.importActual<typeof import('node:fs')>('node:fs');
|
||||
return {
|
||||
c: vi.fn(({ file }) => {
|
||||
if (file) {
|
||||
actualFs.writeFileSync(file, Buffer.from('dummy tar content'));
|
||||
}
|
||||
return Promise.resolve();
|
||||
}),
|
||||
x: vi.fn().mockResolvedValue(undefined),
|
||||
t: vi.fn().mockResolvedValue(undefined),
|
||||
r: vi.fn().mockResolvedValue(undefined),
|
||||
u: vi.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
});
|
||||
vi.mock('tar');
|
||||
vi.mock('zlib');
|
||||
vi.mock('uuid');
|
||||
vi.mock('../utils/logger.js', () => ({
|
||||
@@ -66,7 +96,7 @@ vi.mock('../types.js', async (importOriginal) => {
|
||||
|
||||
const mockStorage = Storage as MockedClass<typeof Storage>;
|
||||
const mockFse = fse as Mocked<typeof fse>;
|
||||
const mockCreateReadStream = createReadStream as Mock;
|
||||
const mockCreateReadStream = fsMocks.createReadStream;
|
||||
const mockTar = tar as Mocked<typeof tar>;
|
||||
const mockGzipSync = gzipSync as Mock;
|
||||
const mockGunzipSync = gunzipSync as Mock;
|
||||
@@ -76,10 +106,19 @@ const mockGetPersistedState = getPersistedState as Mock;
|
||||
const TEST_METADATA_KEY = METADATA_KEY || '__persistedState';
|
||||
|
||||
type MockWriteStream = {
|
||||
emit: Mock<(event: string, ...args: unknown[]) => boolean>;
|
||||
removeListener: Mock<
|
||||
(event: string, cb: (error?: Error | null) => void) => MockWriteStream
|
||||
>;
|
||||
once: Mock<
|
||||
(event: string, cb: (error?: Error | null) => void) => MockWriteStream
|
||||
>;
|
||||
on: Mock<
|
||||
(event: string, cb: (error?: Error | null) => void) => MockWriteStream
|
||||
>;
|
||||
destroy: Mock<() => void>;
|
||||
write: Mock<(chunk: unknown, encoding?: unknown, cb?: unknown) => boolean>;
|
||||
end: Mock<(cb?: unknown) => void>;
|
||||
destroyed: boolean;
|
||||
};
|
||||
|
||||
@@ -114,11 +153,18 @@ describe('GCSTaskStore', () => {
|
||||
bucketName = 'test-bucket';
|
||||
|
||||
mockWriteStream = {
|
||||
emit: vi.fn().mockReturnValue(true),
|
||||
removeListener: vi.fn().mockReturnValue(mockWriteStream),
|
||||
on: vi.fn((event, cb) => {
|
||||
if (event === 'finish') setTimeout(cb, 0); // Simulate async finish
|
||||
return mockWriteStream;
|
||||
}),
|
||||
once: vi.fn((event, cb) => {
|
||||
if (event === 'finish') setTimeout(cb, 0); // Simulate async finish return mockWriteStream;
|
||||
}),
|
||||
destroy: vi.fn(),
|
||||
write: vi.fn().mockReturnValue(true),
|
||||
end: vi.fn(),
|
||||
destroyed: false,
|
||||
};
|
||||
|
||||
@@ -149,14 +195,16 @@ describe('GCSTaskStore', () => {
|
||||
_taskState: 'submitted',
|
||||
});
|
||||
(fse.pathExists as Mock).mockResolvedValue(true);
|
||||
(fsPromises.readdir as Mock).mockResolvedValue(['file1.txt']);
|
||||
mockTar.c.mockResolvedValue(undefined);
|
||||
mockTar.x.mockResolvedValue(undefined);
|
||||
fsMocks.readdir.mockResolvedValue(['file1.txt']);
|
||||
mockFse.remove.mockResolvedValue(undefined);
|
||||
mockFse.ensureDir.mockResolvedValue(undefined);
|
||||
mockGzipSync.mockReturnValue(Buffer.from('compressed'));
|
||||
mockGunzipSync.mockReturnValue(Buffer.from('{}'));
|
||||
mockCreateReadStream.mockReturnValue({ on: vi.fn(), pipe: vi.fn() });
|
||||
mockFse.createReadStream.mockReturnValue({
|
||||
on: vi.fn(),
|
||||
pipe: vi.fn(),
|
||||
} as unknown as import('node:fs').ReadStream);
|
||||
});
|
||||
|
||||
describe('Constructor & Initialization', () => {
|
||||
@@ -201,13 +249,12 @@ describe('GCSTaskStore', () => {
|
||||
metadata: {},
|
||||
};
|
||||
|
||||
it.skip('should save metadata and workspace', async () => {
|
||||
it('should save metadata and workspace', async () => {
|
||||
const store = new GCSTaskStore(bucketName);
|
||||
await store.save(mockTask);
|
||||
|
||||
expect(mockFile.save).toHaveBeenCalledTimes(1);
|
||||
expect(mockTar.c).toHaveBeenCalledTimes(1);
|
||||
expect(mockCreateReadStream).toHaveBeenCalledTimes(1);
|
||||
expect(mockFse.remove).toHaveBeenCalledTimes(1);
|
||||
expect(logger.info).toHaveBeenCalledWith(
|
||||
expect.stringContaining('metadata saved to GCS'),
|
||||
|
||||
@@ -37,6 +37,10 @@ export enum CoderAgentEvent {
|
||||
* An event that contains a thought from the agent.
|
||||
*/
|
||||
ThoughtEvent = 'thought',
|
||||
/**
|
||||
* An event that contains citation from the agent.
|
||||
*/
|
||||
CitationEvent = 'citation',
|
||||
}
|
||||
|
||||
export interface AgentSettings {
|
||||
@@ -64,6 +68,10 @@ export interface Thought {
|
||||
kind: CoderAgentEvent.ThoughtEvent;
|
||||
}
|
||||
|
||||
export interface Citation {
|
||||
kind: CoderAgentEvent.CitationEvent;
|
||||
}
|
||||
|
||||
export type ThoughtSummary = {
|
||||
subject: string;
|
||||
description: string;
|
||||
@@ -80,7 +88,8 @@ export type CoderAgentMessage =
|
||||
| ToolCallUpdate
|
||||
| TextContent
|
||||
| StateChange
|
||||
| Thought;
|
||||
| Thought
|
||||
| Citation;
|
||||
|
||||
export interface TaskMetadata {
|
||||
id: string;
|
||||
|
||||
@@ -14,7 +14,9 @@ import {
|
||||
DEFAULT_TRUNCATE_TOOL_OUTPUT_LINES,
|
||||
DEFAULT_TRUNCATE_TOOL_OUTPUT_THRESHOLD,
|
||||
GeminiClient,
|
||||
HookSystem,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { createMockMessageBus } from '@google/gemini-cli-core/src/test-utils/mock-message-bus.js';
|
||||
import type { Config, Storage } from '@google/gemini-cli-core';
|
||||
import { expect, vi } from 'vitest';
|
||||
|
||||
@@ -53,8 +55,14 @@ export function createMockConfig(
|
||||
getEnableMessageBusIntegration: vi.fn().mockReturnValue(false),
|
||||
getMessageBus: vi.fn(),
|
||||
getPolicyEngine: vi.fn(),
|
||||
getEnableExtensionReloading: vi.fn().mockReturnValue(false),
|
||||
getEnableHooks: vi.fn().mockReturnValue(false),
|
||||
...overrides,
|
||||
} as unknown as Config;
|
||||
mockConfig.getMessageBus = vi.fn().mockReturnValue(createMockMessageBus());
|
||||
mockConfig.getHookSystem = vi
|
||||
.fn()
|
||||
.mockReturnValue(new HookSystem(mockConfig));
|
||||
|
||||
mockConfig.getGeminiClient = vi
|
||||
.fn()
|
||||
|
||||
@@ -11,7 +11,6 @@ export default defineConfig({
|
||||
test: {
|
||||
include: ['**/*.{test,spec}.?(c|m)[jt]s?(x)'],
|
||||
exclude: ['**/node_modules/**', '**/dist/**'],
|
||||
environment: 'jsdom',
|
||||
globals: true,
|
||||
reporters: ['default', 'junit'],
|
||||
silent: true,
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { render, Box, Text, useInput, useStdout } from 'ink';
|
||||
import {
|
||||
ScrollableList,
|
||||
type ScrollableListRef,
|
||||
} from '../src/ui/components/shared/ScrollableList.js';
|
||||
import { ScrollProvider } from '../src/ui/contexts/ScrollProvider.js';
|
||||
import { MouseProvider } from '../src/ui/contexts/MouseContext.js';
|
||||
import { KeypressProvider } from '../src/ui/contexts/KeypressContext.js';
|
||||
import {
|
||||
enableMouseEvents,
|
||||
disableMouseEvents,
|
||||
} from '../src/ui/utils/mouse.js';
|
||||
|
||||
interface Item {
|
||||
id: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
const getLorem = (index: number) =>
|
||||
Array(10)
|
||||
.fill(null)
|
||||
.map(() => 'lorem ipsum '.repeat((index % 3) + 1).trim())
|
||||
.join('\n');
|
||||
|
||||
const Demo = () => {
|
||||
const { stdout } = useStdout();
|
||||
const [size, setSize] = useState({
|
||||
columns: stdout.columns,
|
||||
rows: stdout.rows,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const onResize = () => {
|
||||
setSize({
|
||||
columns: stdout.columns,
|
||||
rows: stdout.rows,
|
||||
});
|
||||
};
|
||||
|
||||
stdout.on('resize', onResize);
|
||||
return () => {
|
||||
stdout.off('resize', onResize);
|
||||
};
|
||||
}, [stdout]);
|
||||
|
||||
const [items, setItems] = useState<Item[]>(() =>
|
||||
Array.from({ length: 1000 }, (_, i) => ({
|
||||
id: String(i),
|
||||
title: `Item ${i + 1}`,
|
||||
})),
|
||||
);
|
||||
|
||||
const listRef = useRef<ScrollableListRef<Item>>(null);
|
||||
|
||||
useInput((input, key) => {
|
||||
if (input === 'a' || input === 'A') {
|
||||
setItems((prev) => [
|
||||
...prev,
|
||||
{ id: String(prev.length), title: `Item ${prev.length + 1}` },
|
||||
]);
|
||||
}
|
||||
if ((input === 'e' || input === 'E') && !key.ctrl) {
|
||||
setItems((prev) => {
|
||||
if (prev.length === 0) return prev;
|
||||
const lastIndex = prev.length - 1;
|
||||
const lastItem = prev[lastIndex]!;
|
||||
const newItem = { ...lastItem, title: lastItem.title + 'e' };
|
||||
return [...prev.slice(0, lastIndex), newItem];
|
||||
});
|
||||
}
|
||||
if (key.ctrl && input === 'e') {
|
||||
listRef.current?.scrollToEnd();
|
||||
}
|
||||
// Let Ink handle Ctrl+C via exitOnCtrlC (default true) or handle explicitly if needed.
|
||||
// For alternate buffer, explicit handling is often safer for cleanup.
|
||||
if (key.escape || (key.ctrl && input === 'c')) {
|
||||
process.exit(0);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<MouseProvider mouseEventsEnabled={true}>
|
||||
<KeypressProvider>
|
||||
<ScrollProvider>
|
||||
<Box
|
||||
flexDirection="column"
|
||||
width={size.columns}
|
||||
height={size.rows - 1}
|
||||
padding={1}
|
||||
>
|
||||
<Text>
|
||||
Press 'A' to add an item. Press 'E' to edit
|
||||
last item. Press 'Ctrl+E' to scroll to end. Press
|
||||
'Esc' to exit. Mouse wheel or Shift+Up/Down to scroll.
|
||||
</Text>
|
||||
<Box flexGrow={1} borderStyle="round" borderColor="cyan">
|
||||
<ScrollableList
|
||||
ref={listRef}
|
||||
data={items}
|
||||
renderItem={({ item, index }) => (
|
||||
<Box flexDirection="column" paddingBottom={2}>
|
||||
<Box
|
||||
sticky
|
||||
flexDirection="column"
|
||||
width={size.columns - 2}
|
||||
opaque
|
||||
stickyChildren={
|
||||
<Box
|
||||
flexDirection="column"
|
||||
width={size.columns - 2}
|
||||
opaque
|
||||
>
|
||||
<Text>{item.title}</Text>
|
||||
<Box
|
||||
borderStyle="single"
|
||||
borderTop={true}
|
||||
borderBottom={false}
|
||||
borderLeft={false}
|
||||
borderRight={false}
|
||||
borderColor="gray"
|
||||
/>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<Text>{item.title}</Text>
|
||||
</Box>
|
||||
<Text color="gray">{getLorem(index)}</Text>
|
||||
</Box>
|
||||
)}
|
||||
estimatedItemHeight={() => 14}
|
||||
keyExtractor={(item) => item.id}
|
||||
hasFocus={true}
|
||||
initialScrollIndex={Number.MAX_SAFE_INTEGER}
|
||||
initialScrollOffsetInIndex={Number.MAX_SAFE_INTEGER}
|
||||
/>
|
||||
</Box>
|
||||
<Text>Count: {items.length}</Text>
|
||||
</Box>
|
||||
</ScrollProvider>
|
||||
</KeypressProvider>
|
||||
</MouseProvider>
|
||||
);
|
||||
};
|
||||
|
||||
// Enable mouse reporting before rendering
|
||||
enableMouseEvents();
|
||||
|
||||
// Ensure cleanup happens on exit
|
||||
process.on('exit', () => {
|
||||
disableMouseEvents();
|
||||
});
|
||||
|
||||
// Handle SIGINT explicitly to ensure cleanup runs if Ink doesn't catch it in time
|
||||
process.on('SIGINT', () => {
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
render(<Demo />, { alternateBuffer: true });
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user