mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-13 23:51:16 -07:00
feat(cli): add experimental.useOSC52Copy setting (#19488)
This commit is contained in:
committed by
GitHub
parent
ba3e327ba1
commit
09b623fbd7
@@ -128,12 +128,13 @@ they appear in the UI.
|
||||
|
||||
### Experimental
|
||||
|
||||
| UI Label | Setting | Description | Default |
|
||||
| -------------------------- | ---------------------------------------- | ----------------------------------------------------------------------------------- | ------- |
|
||||
| Enable Tool Output Masking | `experimental.toolOutputMasking.enabled` | Enables tool output masking to save tokens. | `true` |
|
||||
| Use OSC 52 Paste | `experimental.useOSC52Paste` | Use OSC 52 sequence for pasting instead of clipboardy (useful for remote sessions). | `false` |
|
||||
| Plan | `experimental.plan` | Enable planning features (Plan Mode and tools). | `false` |
|
||||
| Model Steering | `experimental.modelSteering` | Enable model steering (user hints) to guide the model during tool execution. | `false` |
|
||||
| UI Label | Setting | Description | Default |
|
||||
| -------------------------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
|
||||
| Enable Tool Output Masking | `experimental.toolOutputMasking.enabled` | Enables tool output masking to save tokens. | `true` |
|
||||
| Use OSC 52 Paste | `experimental.useOSC52Paste` | Use OSC 52 for pasting. This may be more robust than the default system when using remote terminal sessions (if your terminal is configured to allow it). | `false` |
|
||||
| Use OSC 52 Copy | `experimental.useOSC52Copy` | Use OSC 52 for copying. This may be more robust than the default system when using remote terminal sessions (if your terminal is configured to allow it). | `false` |
|
||||
| Plan | `experimental.plan` | Enable planning features (Plan Mode and tools). | `false` |
|
||||
| Model Steering | `experimental.modelSteering` | Enable model steering (user hints) to guide the model during tool execution. | `false` |
|
||||
|
||||
### Skills
|
||||
|
||||
|
||||
@@ -941,8 +941,15 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`experimental.useOSC52Paste`** (boolean):
|
||||
- **Description:** Use OSC 52 sequence for pasting instead of clipboardy
|
||||
(useful for remote sessions).
|
||||
- **Description:** Use OSC 52 for pasting. This may be more robust than the
|
||||
default system when using remote terminal sessions (if your terminal is
|
||||
configured to allow it).
|
||||
- **Default:** `false`
|
||||
|
||||
- **`experimental.useOSC52Copy`** (boolean):
|
||||
- **Description:** Use OSC 52 for copying. This may be more robust than the
|
||||
default system when using remote terminal sessions (if your terminal is
|
||||
configured to allow it).
|
||||
- **Default:** `false`
|
||||
|
||||
- **`experimental.plan`** (boolean):
|
||||
|
||||
@@ -1632,7 +1632,17 @@ const SETTINGS_SCHEMA = {
|
||||
requiresRestart: false,
|
||||
default: false,
|
||||
description:
|
||||
'Use OSC 52 sequence for pasting instead of clipboardy (useful for remote sessions).',
|
||||
'Use OSC 52 for pasting. This may be more robust than the default system when using remote terminal sessions (if your terminal is configured to allow it).',
|
||||
showInDialog: true,
|
||||
},
|
||||
useOSC52Copy: {
|
||||
type: 'boolean',
|
||||
label: 'Use OSC 52 Copy',
|
||||
category: 'Experimental',
|
||||
requiresRestart: false,
|
||||
default: false,
|
||||
description:
|
||||
'Use OSC 52 for copying. This may be more robust than the default system when using remote terminal sessions (if your terminal is configured to allow it).',
|
||||
showInDialog: true,
|
||||
},
|
||||
plan: {
|
||||
|
||||
@@ -125,6 +125,7 @@ describe('copyCommand', () => {
|
||||
|
||||
expect(mockCopyToClipboard).toHaveBeenCalledWith(
|
||||
'Hi there! How can I help you?',
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -143,7 +144,10 @@ describe('copyCommand', () => {
|
||||
|
||||
const result = await copyCommand.action(mockContext, '');
|
||||
|
||||
expect(mockCopyToClipboard).toHaveBeenCalledWith('Part 1: Part 2: Part 3');
|
||||
expect(mockCopyToClipboard).toHaveBeenCalledWith(
|
||||
'Part 1: Part 2: Part 3',
|
||||
expect.anything(),
|
||||
);
|
||||
expect(result).toEqual({
|
||||
type: 'message',
|
||||
messageType: 'info',
|
||||
@@ -170,7 +174,10 @@ describe('copyCommand', () => {
|
||||
|
||||
const result = await copyCommand.action(mockContext, '');
|
||||
|
||||
expect(mockCopyToClipboard).toHaveBeenCalledWith('Text part more text');
|
||||
expect(mockCopyToClipboard).toHaveBeenCalledWith(
|
||||
'Text part more text',
|
||||
expect.anything(),
|
||||
);
|
||||
expect(result).toEqual({
|
||||
type: 'message',
|
||||
messageType: 'info',
|
||||
@@ -201,7 +208,10 @@ describe('copyCommand', () => {
|
||||
|
||||
const result = await copyCommand.action(mockContext, '');
|
||||
|
||||
expect(mockCopyToClipboard).toHaveBeenCalledWith('Second AI response');
|
||||
expect(mockCopyToClipboard).toHaveBeenCalledWith(
|
||||
'Second AI response',
|
||||
expect.anything(),
|
||||
);
|
||||
expect(result).toEqual({
|
||||
type: 'message',
|
||||
messageType: 'info',
|
||||
@@ -230,6 +240,11 @@ describe('copyCommand', () => {
|
||||
messageType: 'error',
|
||||
content: `Failed to copy to the clipboard. ${clipboardError.message}`,
|
||||
});
|
||||
|
||||
expect(mockCopyToClipboard).toHaveBeenCalledWith(
|
||||
'AI response',
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle non-Error clipboard errors', async () => {
|
||||
@@ -253,6 +268,11 @@ describe('copyCommand', () => {
|
||||
messageType: 'error',
|
||||
content: `Failed to copy to the clipboard. ${rejectedValue}`,
|
||||
});
|
||||
|
||||
expect(mockCopyToClipboard).toHaveBeenCalledWith(
|
||||
'AI response',
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return info message when no text parts found in AI message', async () => {
|
||||
|
||||
@@ -38,7 +38,8 @@ export const copyCommand: SlashCommand = {
|
||||
|
||||
if (lastAiOutput) {
|
||||
try {
|
||||
await copyToClipboard(lastAiOutput);
|
||||
const settings = context.services.settings.merged;
|
||||
await copyToClipboard(lastAiOutput, settings);
|
||||
|
||||
return {
|
||||
type: 'message',
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
copyToClipboard,
|
||||
getUrlOpenCommand,
|
||||
} from './commandUtils.js';
|
||||
import type { Settings } from '../../config/settingsSchema.js';
|
||||
|
||||
// Constants used by OSC-52 tests
|
||||
const ESC = '\u001B';
|
||||
@@ -257,6 +258,29 @@ describe('commandUtils', () => {
|
||||
expect(mockClipboardyWrite).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('uses OSC-52 when useOSC52Copy setting is enabled', async () => {
|
||||
const testText = 'forced-osc52';
|
||||
const tty = makeWritable({ isTTY: true });
|
||||
mockFs.createWriteStream.mockImplementation(() => {
|
||||
setTimeout(() => tty.emit('open'), 0);
|
||||
return tty;
|
||||
});
|
||||
|
||||
// NO environment signals for SSH/WSL/etc.
|
||||
const settings = {
|
||||
experimental: { useOSC52Copy: true },
|
||||
} as unknown as Settings;
|
||||
|
||||
await copyToClipboard(testText, settings);
|
||||
|
||||
const b64 = Buffer.from(testText, 'utf8').toString('base64');
|
||||
const expected = `${ESC}]52;c;${b64}${BEL}`;
|
||||
|
||||
expect(tty.write).toHaveBeenCalledTimes(1);
|
||||
expect(tty.write.mock.calls[0][0]).toBe(expected);
|
||||
expect(mockClipboardyWrite).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('wraps OSC-52 for tmux when in SSH', async () => {
|
||||
const testText = 'tmux-copy';
|
||||
const tty = makeWritable({ isTTY: true });
|
||||
|
||||
@@ -9,6 +9,7 @@ import clipboardy from 'clipboardy';
|
||||
import type { SlashCommand } from '../commands/types.js';
|
||||
import fs from 'node:fs';
|
||||
import type { Writable } from 'node:stream';
|
||||
import type { Settings } from '../../config/settingsSchema.js';
|
||||
|
||||
/**
|
||||
* Checks if a query string potentially represents an '@' command.
|
||||
@@ -157,8 +158,13 @@ const isWindowsTerminal = (): boolean =>
|
||||
|
||||
const isDumbTerm = (): boolean => (process.env['TERM'] ?? '') === 'dumb';
|
||||
|
||||
const shouldUseOsc52 = (tty: TtyTarget): boolean =>
|
||||
Boolean(tty) && !isDumbTerm() && (isSSH() || isWSL() || isWindowsTerminal());
|
||||
const shouldUseOsc52 = (tty: TtyTarget, settings?: Settings): boolean =>
|
||||
Boolean(tty) &&
|
||||
!isDumbTerm() &&
|
||||
(settings?.experimental?.useOSC52Copy ||
|
||||
isSSH() ||
|
||||
isWSL() ||
|
||||
isWindowsTerminal());
|
||||
|
||||
const safeUtf8Truncate = (buf: Buffer, maxBytes: number): Buffer => {
|
||||
if (buf.length <= maxBytes) return buf;
|
||||
@@ -237,12 +243,15 @@ const writeAll = (stream: Writable, data: string): Promise<void> =>
|
||||
});
|
||||
|
||||
// Copies a string snippet to the clipboard with robust OSC-52 support.
|
||||
export const copyToClipboard = async (text: string): Promise<void> => {
|
||||
export const copyToClipboard = async (
|
||||
text: string,
|
||||
settings?: Settings,
|
||||
): Promise<void> => {
|
||||
if (!text) return;
|
||||
|
||||
const tty = await pickTty();
|
||||
|
||||
if (shouldUseOsc52(tty)) {
|
||||
if (shouldUseOsc52(tty, settings)) {
|
||||
const osc = buildOsc52(text);
|
||||
const payload = inTmux()
|
||||
? wrapForTmux(osc)
|
||||
|
||||
@@ -1587,8 +1587,15 @@
|
||||
},
|
||||
"useOSC52Paste": {
|
||||
"title": "Use OSC 52 Paste",
|
||||
"description": "Use OSC 52 sequence for pasting instead of clipboardy (useful for remote sessions).",
|
||||
"markdownDescription": "Use OSC 52 sequence for pasting instead of clipboardy (useful for remote sessions).\n\n- Category: `Experimental`\n- Requires restart: `no`\n- Default: `false`",
|
||||
"description": "Use OSC 52 for pasting. This may be more robust than the default system when using remote terminal sessions (if your terminal is configured to allow it).",
|
||||
"markdownDescription": "Use OSC 52 for pasting. This may be more robust than the default system when using remote terminal sessions (if your terminal is configured to allow it).\n\n- Category: `Experimental`\n- Requires restart: `no`\n- Default: `false`",
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"useOSC52Copy": {
|
||||
"title": "Use OSC 52 Copy",
|
||||
"description": "Use OSC 52 for copying. This may be more robust than the default system when using remote terminal sessions (if your terminal is configured to allow it).",
|
||||
"markdownDescription": "Use OSC 52 for copying. This may be more robust than the default system when using remote terminal sessions (if your terminal is configured to allow it).\n\n- Category: `Experimental`\n- Requires restart: `no`\n- Default: `false`",
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user