Skip to content

Commit e952464

Browse files
Add clipboard management capabilities with read functionality for multiple OS
1 parent 2b846ac commit e952464

4 files changed

Lines changed: 138 additions & 59 deletions

File tree

src/config/constants.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ You have access to following tools and capabilities:
5959
- Arguments: none
6060
- Name: "clear_memories"
6161
- Arguments: none
62+
- CLIPBOARD_TOOLS:
63+
- Name: "read_clipboard"
64+
- Arguments: none
6265
6366
Before taking any action, follow these steps:
6467
@@ -101,7 +104,7 @@ export const API_CONFIG = {
101104
MAX_INTENT_TOKENS: 20,
102105
}
103106

104-
export const MEMORY_TOOLS: Anthropic.Beta.BetaToolUnion[] = [
107+
export const MEMORY_TOOLS: Anthropic.Beta.BetaTool[] = [
105108
{
106109
name: "add_memory",
107110
description: "Add a new memory to the system",
@@ -136,3 +139,12 @@ export const MEMORY_TOOLS: Anthropic.Beta.BetaToolUnion[] = [
136139
},
137140
},
138141
]
142+
export const CLIPBOARD_TOOLS: Anthropic.Beta.BetaTool[] = [
143+
{
144+
type: "custom",
145+
name: "read_clipboard",
146+
description: "Read content from system clipboard",
147+
input_schema: {type: "object", properties: {}, required: []}
148+
149+
}
150+
]

src/modules/clipboard/clipboard.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import {log} from "../../config/logging.ts"
2+
3+
export class ClipboardManager {
4+
private async readLinux(): Promise<string> {
5+
try {
6+
// Try xclip first
7+
try {
8+
const command = new Deno.Command("xclip", {
9+
args: ["-o", "-selection", "clipboard"],
10+
})
11+
const output = await command.output()
12+
return new TextDecoder().decode(output.stdout).trim()
13+
} catch {
14+
// Fallback to xsel
15+
const command = new Deno.Command("xsel", {
16+
args: ["--clipboard", "--output"],
17+
})
18+
const output = await command.output()
19+
return new TextDecoder().decode(output.stdout).trim()
20+
}
21+
} catch (error) {
22+
throw new Error("Clipboard access requires xclip or xsel to be installed")
23+
}
24+
}
25+
26+
private async readMac(): Promise<string> {
27+
const command = new Deno.Command("pbpaste")
28+
const output = await command.output()
29+
return new TextDecoder().decode(output.stdout).trim()
30+
}
31+
32+
private async readWindows(): Promise<string> {
33+
const command = new Deno.Command("powershell.exe", {
34+
args: ["-Command", "Get-Clipboard"],
35+
})
36+
const output = await command.output()
37+
return new TextDecoder().decode(output.stdout).trim()
38+
}
39+
40+
async readClipboard(): Promise<string> {
41+
try {
42+
const os = Deno.build.os
43+
const isWsl = Deno.env.get("WSL_DISTRO_NAME") !== undefined
44+
45+
if (isWsl || os === "windows") {
46+
return await this.readWindows()
47+
} else if (os === "darwin") {
48+
return await this.readMac()
49+
} else if (os === "linux") {
50+
return await this.readLinux()
51+
} else {
52+
throw new Error(`Unsupported platform: ${os}`)
53+
}
54+
} catch (error) {
55+
log.error(`Error reading clipboard: ${error}`)
56+
throw new Error(`Failed to read clipboard: ${error}`)
57+
}
58+
}
59+
}
Lines changed: 34 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
import { BaseSession } from "../../utils/session.ts";
2-
import { COMBINED_SYSTEM_PROMPT, API_CONFIG, MEMORY_TOOLS } from "../../config/constants.ts";
3-
import { log } from "../../config/logging.ts";
4-
import { ToolHandler } from "../../utils/tool_handler.ts";
1+
import {BaseSession} from "../../utils/session.ts"
2+
import {COMBINED_SYSTEM_PROMPT, API_CONFIG, MEMORY_TOOLS, CLIPBOARD_TOOLS} from "../../config/constants.ts"
3+
import {log} from "../../config/logging.ts"
4+
import {ToolHandler} from "../../utils/tool_handler.ts"
5+
6+
57

68
export class HybridSession extends BaseSession {
7-
private toolHandler: ToolHandler;
9+
private toolHandler: ToolHandler
810

911
constructor(sessionId: string, noAgi = false) {
10-
super(sessionId);
11-
this.toolHandler = new ToolHandler(noAgi);
12+
super(sessionId)
13+
this.toolHandler = new ToolHandler(noAgi)
1214
}
1315

1416
async process(prompt: string): Promise<void> {
@@ -19,67 +21,68 @@ export class HybridSession extends BaseSession {
1921
isWsl: Deno.env.get("WSL_DISTRO_NAME") !== undefined,
2022
shell: Deno.env.get("SHELL") || "Unknown",
2123
cwd: Deno.cwd(),
22-
};
24+
}
2325

2426
const message = {
2527
role: "user",
26-
content: [{ type: "text", text: prompt }],
27-
};
28-
this.messages = [message];
28+
content: [{type: "text", text: prompt}],
29+
}
30+
this.messages = [message]
2931

30-
log.info(`User input: ${JSON.stringify(message)}`);
32+
log.info(`User input: ${JSON.stringify(message)}`)
3133

3234
while (true) {
3335
const response = await this.client.beta.messages.create({
3436
model: API_CONFIG.MODEL,
3537
max_tokens: API_CONFIG.MAX_TOKENS,
3638
messages: this.messages,
3739
tools: [
38-
{ type: "bash_20241022", name: "bash" },
39-
{ type: "text_editor_20241022", name: "str_replace_editor" },
40+
{type: "bash_20241022", name: "bash"},
41+
{type: "text_editor_20241022", name: "str_replace_editor"},
4042
...MEMORY_TOOLS,
43+
...CLIPBOARD_TOOLS,
4144
],
4245
system: `${COMBINED_SYSTEM_PROMPT}\nSystem Context: ${JSON.stringify(systemInfo)}`,
4346
betas: ["computer-use-2024-10-22"],
44-
});
47+
})
4548

46-
const inputTokens = response.usage?.input_tokens ?? 0;
47-
const outputTokens = response.usage?.output_tokens ?? 0;
48-
this.logger.updateTokenUsage(inputTokens, outputTokens);
49+
const inputTokens = response.usage?.input_tokens ?? 0
50+
const outputTokens = response.usage?.output_tokens ?? 0
51+
this.logger.updateTokenUsage(inputTokens, outputTokens)
4952

5053
const responseContent = response.content.map((block) =>
51-
block.type === "text" ? { type: "text", text: block.text } : block
52-
);
54+
block.type === "text" ? {type: "text", text: block.text} : block
55+
)
5356

54-
this.messages.push({ role: "assistant", content: responseContent });
57+
this.messages.push({role: "assistant", content: responseContent})
5558

5659
if (response.stop_reason !== "tool_use") {
57-
console.log(response.content.find((block) => block.type === "text")?.text ?? "");
58-
break;
60+
console.log(response.content.find((block) => block.type === "text")?.text ?? "")
61+
break
5962
}
6063

61-
const toolResults = await this.toolHandler.processToolCalls(response.content);
64+
const toolResults = await this.toolHandler.processToolCalls(response.content)
6265

6366
if (toolResults?.length) {
6467
this.messages.push({
6568
role: "user",
6669
content: [toolResults[0].output],
67-
});
70+
})
6871

6972
if (toolResults[0].output.is_error) {
70-
log.error(`Error: ${toolResults[0].output.content[0].text}`);
71-
break;
73+
log.error(`Error: ${toolResults[0].output.content[0].text}`)
74+
break
7275
}
7376
}
7477
}
7578

76-
this.logger.logTotalCost();
79+
this.logger.logTotalCost()
7780
} catch (error) {
78-
log.error(`Error in hybrid process: ${error instanceof Error ? error.message : String(error)}`);
81+
log.error(`Error in hybrid process: ${error instanceof Error ? error.message : String(error)}`)
7982
if (error instanceof Error && error.stack) {
80-
log.error(error.stack);
83+
log.error(error.stack)
8184
}
82-
throw error;
85+
throw error
8386
}
8487
}
8588
}

src/utils/tool_handler.ts

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,37 @@
1-
import { BashHandlers } from "../modules/bash/handlers.ts";
2-
import { EditorHandlers } from "../modules/editor/handlers.ts";
3-
import { MemoryManager } from "../modules/memory/memory_manager.ts";
4-
import { ToolResult } from "../types/interfaces.ts";
5-
import { log } from "../config/logging.ts";
1+
import {BashHandlers} from "../modules/bash/handlers.ts"
2+
import {EditorHandlers} from "../modules/editor/handlers.ts"
3+
import {MemoryManager} from "../modules/memory/memory_manager.ts"
4+
import {ToolResult} from "../types/interfaces.ts"
5+
import {log} from "../config/logging.ts"
6+
import {ClipboardManager} from "../modules/clipboard/clipboard.ts"
67

78
export class ToolHandler {
8-
private bashHandlers: BashHandlers;
9-
private editorHandlers: EditorHandlers;
10-
private memoryManager: MemoryManager;
9+
private bashHandlers: BashHandlers
10+
private editorHandlers: EditorHandlers
11+
private memoryManager: MemoryManager
12+
private clipboardManager: ClipboardManager
1113

1214
constructor(noAgi = false) {
13-
this.bashHandlers = new BashHandlers(noAgi);
14-
this.editorHandlers = new EditorHandlers();
15-
this.memoryManager = new MemoryManager();
15+
this.bashHandlers = new BashHandlers(noAgi)
16+
this.editorHandlers = new EditorHandlers()
17+
this.memoryManager = new MemoryManager()
18+
this.clipboardManager = new ClipboardManager()
1619
}
1720

1821
async processToolCalls(content: any[]): Promise<ToolResult[]> {
19-
const results: ToolResult[] = [];
22+
const results: ToolResult[] = []
2023

2124
for (const toolCall of content) {
2225
if (toolCall.type === "tool_use") {
23-
let result;
24-
let isError = false;
26+
let result
27+
let isError = false
2528

2629
try {
27-
result = await this.handleTool(toolCall);
28-
isError = "error" in result;
30+
result = await this.handleTool(toolCall)
31+
isError = typeof result === 'object' && result !== null && 'error' in result
2932
} catch (error) {
30-
result = { error: String(error) };
31-
isError = true;
33+
result = {error: String(error)}
34+
isError = true
3235
}
3336

3437
results.push({
@@ -42,28 +45,30 @@ export class ToolHandler {
4245
tool_use_id: toolCall.id,
4346
is_error: isError,
4447
},
45-
});
48+
})
4649
}
4750
}
4851

49-
return results;
52+
return results
5053
}
5154

5255
private async handleTool(toolCall: any) {
5356
switch (toolCall.name) {
5457
case "bash":
55-
return await this.bashHandlers.handleBashCommand(toolCall.input);
58+
return await this.bashHandlers.handleBashCommand(toolCall.input)
5659
case "str_replace_editor":
57-
return await this.editorHandlers.handleTextEditorTool(toolCall.input);
60+
return await this.editorHandlers.handleTextEditorTool(toolCall.input)
5861
case "add_memory":
59-
return await this.memoryManager.addMemory(toolCall.input.content);
62+
return await this.memoryManager.addMemory(toolCall.input.content)
6063
case "get_memories":
61-
return await this.memoryManager.getMemories();
64+
return await this.memoryManager.getMemories()
6265
case "clear_memories":
63-
await this.memoryManager.clearMemories();
64-
return { message: "Memories cleared successfully" };
66+
await this.memoryManager.clearMemories()
67+
return {message: "Memories cleared successfully"}
68+
case "read_clipboard":
69+
return await this.clipboardManager.readClipboard()
6570
default:
66-
throw new Error(`Unknown tool: ${toolCall.name}`);
71+
throw new Error(`Unknown tool: ${toolCall.name}`)
6772
}
6873
}
6974
}

0 commit comments

Comments
 (0)