@tontoko/fast-playwright-mcp 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +1047 -0
- package/cli.js +18 -0
- package/config.d.ts +124 -0
- package/index.d.ts +25 -0
- package/index.js +18 -0
- package/lib/actions.d.js +0 -0
- package/lib/batch/batch-executor.js +137 -0
- package/lib/browser-context-factory.js +252 -0
- package/lib/browser-server-backend.js +139 -0
- package/lib/config/constants.js +80 -0
- package/lib/config.js +405 -0
- package/lib/context.js +274 -0
- package/lib/diagnostics/common/diagnostic-base.js +63 -0
- package/lib/diagnostics/common/error-enrichment-utils.js +212 -0
- package/lib/diagnostics/common/index.js +56 -0
- package/lib/diagnostics/common/initialization-manager.js +210 -0
- package/lib/diagnostics/common/performance-tracker.js +132 -0
- package/lib/diagnostics/diagnostic-error.js +140 -0
- package/lib/diagnostics/diagnostic-level.js +123 -0
- package/lib/diagnostics/diagnostic-thresholds.js +347 -0
- package/lib/diagnostics/element-discovery.js +441 -0
- package/lib/diagnostics/enhanced-error-handler.js +376 -0
- package/lib/diagnostics/error-enrichment.js +157 -0
- package/lib/diagnostics/frame-reference-manager.js +179 -0
- package/lib/diagnostics/page-analyzer.js +639 -0
- package/lib/diagnostics/parallel-page-analyzer.js +129 -0
- package/lib/diagnostics/resource-manager.js +134 -0
- package/lib/diagnostics/smart-config.js +482 -0
- package/lib/diagnostics/smart-handle.js +118 -0
- package/lib/diagnostics/unified-system.js +717 -0
- package/lib/extension/cdp-relay.js +486 -0
- package/lib/extension/extension-context-factory.js +74 -0
- package/lib/extension/main.js +41 -0
- package/lib/file-utils.js +42 -0
- package/lib/generate-keys.js +75 -0
- package/lib/http-server.js +50 -0
- package/lib/in-process-client.js +64 -0
- package/lib/index.js +48 -0
- package/lib/javascript.js +90 -0
- package/lib/log.js +33 -0
- package/lib/loop/loop-claude.js +247 -0
- package/lib/loop/loop-open-ai.js +222 -0
- package/lib/loop/loop.js +174 -0
- package/lib/loop/main.js +46 -0
- package/lib/loopTools/context.js +76 -0
- package/lib/loopTools/main.js +65 -0
- package/lib/loopTools/perform.js +40 -0
- package/lib/loopTools/snapshot.js +37 -0
- package/lib/loopTools/tool.js +26 -0
- package/lib/manual-promise.js +125 -0
- package/lib/mcp/in-process-transport.js +91 -0
- package/lib/mcp/proxy-backend.js +127 -0
- package/lib/mcp/server.js +123 -0
- package/lib/mcp/transport.js +159 -0
- package/lib/package.js +28 -0
- package/lib/program.js +82 -0
- package/lib/response.js +493 -0
- package/lib/schemas/expectation.js +152 -0
- package/lib/session-log.js +210 -0
- package/lib/tab.js +417 -0
- package/lib/tools/base-tool-handler.js +141 -0
- package/lib/tools/batch-execute.js +150 -0
- package/lib/tools/common.js +65 -0
- package/lib/tools/console.js +60 -0
- package/lib/tools/diagnose/diagnose-analysis-runner.js +101 -0
- package/lib/tools/diagnose/diagnose-config-handler.js +130 -0
- package/lib/tools/diagnose/diagnose-report-builder.js +394 -0
- package/lib/tools/diagnose.js +147 -0
- package/lib/tools/dialogs.js +57 -0
- package/lib/tools/evaluate.js +67 -0
- package/lib/tools/files.js +53 -0
- package/lib/tools/find-elements.js +307 -0
- package/lib/tools/install.js +60 -0
- package/lib/tools/keyboard.js +93 -0
- package/lib/tools/mouse.js +110 -0
- package/lib/tools/navigate.js +82 -0
- package/lib/tools/network.js +50 -0
- package/lib/tools/pdf.js +46 -0
- package/lib/tools/screenshot.js +113 -0
- package/lib/tools/snapshot.js +158 -0
- package/lib/tools/tabs.js +97 -0
- package/lib/tools/tool.js +47 -0
- package/lib/tools/utils.js +131 -0
- package/lib/tools/wait.js +64 -0
- package/lib/tools.js +65 -0
- package/lib/types/batch.js +47 -0
- package/lib/types/diff.js +0 -0
- package/lib/types/performance.js +0 -0
- package/lib/types/threshold-base.js +0 -0
- package/lib/utils/array-utils.js +44 -0
- package/lib/utils/code-deduplication-utils.js +141 -0
- package/lib/utils/common-formatters.js +252 -0
- package/lib/utils/console-filter.js +64 -0
- package/lib/utils/diagnostic-report-utils.js +178 -0
- package/lib/utils/diff-formatter.js +126 -0
- package/lib/utils/disposable-manager.js +135 -0
- package/lib/utils/error-handler-middleware.js +77 -0
- package/lib/utils/image-processor.js +137 -0
- package/lib/utils/index.js +92 -0
- package/lib/utils/report-builder.js +189 -0
- package/lib/utils/request-logger.js +82 -0
- package/lib/utils/response-diff-detector.js +150 -0
- package/lib/utils/section-builder.js +62 -0
- package/lib/utils/tool-patterns.js +153 -0
- package/lib/utils.js +46 -0
- package/package.json +77 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
+
for (let key of __getOwnPropNames(mod))
|
|
11
|
+
if (!__hasOwnProp.call(to, key))
|
|
12
|
+
__defProp(to, key, {
|
|
13
|
+
get: () => mod[key],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
|
+
|
|
20
|
+
// src/loop/loop-open-ai.ts
|
|
21
|
+
var model = "gpt-4.1";
|
|
22
|
+
|
|
23
|
+
class OpenAIDelegate {
|
|
24
|
+
_openai;
|
|
25
|
+
async openai() {
|
|
26
|
+
if (!this._openai) {
|
|
27
|
+
const oai = await import("openai");
|
|
28
|
+
this._openai = new oai.OpenAI;
|
|
29
|
+
}
|
|
30
|
+
return this._openai;
|
|
31
|
+
}
|
|
32
|
+
createConversation(task, tools, oneShot) {
|
|
33
|
+
const genericTools = tools.map((tool) => ({
|
|
34
|
+
name: tool.name,
|
|
35
|
+
description: tool.description ?? "",
|
|
36
|
+
inputSchema: tool.inputSchema
|
|
37
|
+
}));
|
|
38
|
+
if (!oneShot) {
|
|
39
|
+
genericTools.push({
|
|
40
|
+
name: "done",
|
|
41
|
+
description: "Call this tool when the task is complete.",
|
|
42
|
+
inputSchema: {
|
|
43
|
+
type: "object",
|
|
44
|
+
properties: {}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
messages: [
|
|
50
|
+
{
|
|
51
|
+
role: "user",
|
|
52
|
+
content: task
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
tools: genericTools
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
async makeApiCall(conversation) {
|
|
59
|
+
const formattedData = this.formatConversationForOpenAI(conversation);
|
|
60
|
+
const response = await this.executeOpenAIRequest(formattedData.messages, formattedData.tools);
|
|
61
|
+
return this.processApiResponse(conversation, response);
|
|
62
|
+
}
|
|
63
|
+
processApiResponse(conversation, response) {
|
|
64
|
+
return this.handleResponseProcessing(conversation, response);
|
|
65
|
+
}
|
|
66
|
+
handleResponseProcessing(conversation, response) {
|
|
67
|
+
const message = this.extractMessageFromResponse(response);
|
|
68
|
+
const toolCalls = this.extractToolCallsFromResponse(message);
|
|
69
|
+
this.updateConversationWithResponse(conversation, message, toolCalls);
|
|
70
|
+
return toolCalls;
|
|
71
|
+
}
|
|
72
|
+
extractMessageFromResponse(response) {
|
|
73
|
+
return response.choices[0].message;
|
|
74
|
+
}
|
|
75
|
+
updateConversationWithResponse(conversation, message, toolCalls) {
|
|
76
|
+
this.addAssistantMessageToConversation(conversation, message, toolCalls);
|
|
77
|
+
}
|
|
78
|
+
formatConversationForOpenAI(conversation) {
|
|
79
|
+
return {
|
|
80
|
+
messages: this.convertMessagesToOpenAIFormat(conversation.messages),
|
|
81
|
+
tools: this.convertToolsToOpenAIFormat(conversation.tools)
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
async executeOpenAIRequest(messages, tools) {
|
|
85
|
+
const openai = await this.openai();
|
|
86
|
+
return await openai.chat.completions.create({
|
|
87
|
+
model,
|
|
88
|
+
messages,
|
|
89
|
+
tools,
|
|
90
|
+
tool_choice: "auto"
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
convertMessagesToOpenAIFormat(messages) {
|
|
94
|
+
return this.transformMessagesToOpenAIFormat(messages);
|
|
95
|
+
}
|
|
96
|
+
transformMessagesToOpenAIFormat(messages) {
|
|
97
|
+
const convertedMessages = this.mapMessagesToOpenAIFormat(messages);
|
|
98
|
+
return this.filterValidMessages(convertedMessages);
|
|
99
|
+
}
|
|
100
|
+
mapMessagesToOpenAIFormat(messages) {
|
|
101
|
+
return messages.map((message) => this.convertSingleMessageToOpenAI(message));
|
|
102
|
+
}
|
|
103
|
+
filterValidMessages(messages) {
|
|
104
|
+
return messages.filter((message) => message !== null);
|
|
105
|
+
}
|
|
106
|
+
convertSingleMessageToOpenAI(message) {
|
|
107
|
+
return this.handleMessageConversion(message);
|
|
108
|
+
}
|
|
109
|
+
handleMessageConversion(message) {
|
|
110
|
+
switch (message.role) {
|
|
111
|
+
case "user":
|
|
112
|
+
return this.createUserMessage(message);
|
|
113
|
+
case "assistant":
|
|
114
|
+
return this.convertAssistantMessage(message);
|
|
115
|
+
case "tool":
|
|
116
|
+
return this.createToolMessage(message);
|
|
117
|
+
default:
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
createUserMessage(message) {
|
|
122
|
+
return {
|
|
123
|
+
role: "user",
|
|
124
|
+
content: message.content
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
createToolMessage(message) {
|
|
128
|
+
return {
|
|
129
|
+
role: "tool",
|
|
130
|
+
tool_call_id: message.toolCallId,
|
|
131
|
+
content: message.content
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
convertAssistantMessage(message) {
|
|
135
|
+
if (message.role !== "assistant") {
|
|
136
|
+
throw new Error("Expected assistant message");
|
|
137
|
+
}
|
|
138
|
+
const assistantMessage = { role: "assistant" };
|
|
139
|
+
if (message.content) {
|
|
140
|
+
assistantMessage.content = message.content;
|
|
141
|
+
}
|
|
142
|
+
if (this.hasValidToolCalls(message) && message.toolCalls) {
|
|
143
|
+
assistantMessage.tool_calls = this.convertToolCallsToOpenAI(message.toolCalls);
|
|
144
|
+
}
|
|
145
|
+
return assistantMessage;
|
|
146
|
+
}
|
|
147
|
+
hasValidToolCalls(message) {
|
|
148
|
+
return message.role === "assistant" && !!message.toolCalls && message.toolCalls.length > 0;
|
|
149
|
+
}
|
|
150
|
+
convertToolCallsToOpenAI(toolCalls) {
|
|
151
|
+
return toolCalls.map((toolCall) => ({
|
|
152
|
+
id: toolCall.id,
|
|
153
|
+
type: "function",
|
|
154
|
+
function: {
|
|
155
|
+
name: toolCall.name,
|
|
156
|
+
arguments: JSON.stringify(toolCall.arguments)
|
|
157
|
+
}
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
convertToolsToOpenAIFormat(tools) {
|
|
161
|
+
return tools.map((tool) => ({
|
|
162
|
+
type: "function",
|
|
163
|
+
function: {
|
|
164
|
+
name: tool.name,
|
|
165
|
+
description: tool.description,
|
|
166
|
+
parameters: tool.inputSchema
|
|
167
|
+
}
|
|
168
|
+
}));
|
|
169
|
+
}
|
|
170
|
+
extractToolCallsFromResponse(message) {
|
|
171
|
+
const toolCalls = this.getToolCallsFromMessage(message);
|
|
172
|
+
return toolCalls.map((toolCall) => this.convertSingleToolCall(toolCall));
|
|
173
|
+
}
|
|
174
|
+
getToolCallsFromMessage(message) {
|
|
175
|
+
return message.tool_calls ?? [];
|
|
176
|
+
}
|
|
177
|
+
convertSingleToolCall(toolCall) {
|
|
178
|
+
if (toolCall.type === "function") {
|
|
179
|
+
return {
|
|
180
|
+
name: toolCall.function.name,
|
|
181
|
+
arguments: JSON.parse(toolCall.function.arguments),
|
|
182
|
+
id: toolCall.id
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
throw new Error(`Unsupported tool call type: ${toolCall.type}`);
|
|
186
|
+
}
|
|
187
|
+
addAssistantMessageToConversation(conversation, message, genericToolCalls) {
|
|
188
|
+
conversation.messages.push({
|
|
189
|
+
role: "assistant",
|
|
190
|
+
content: message.content ?? "",
|
|
191
|
+
toolCalls: genericToolCalls.length > 0 ? genericToolCalls : undefined
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
addToolResults(conversation, results) {
|
|
195
|
+
for (const result of results) {
|
|
196
|
+
this.addSingleToolResult(conversation, result);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
addSingleToolResult(conversation, result) {
|
|
200
|
+
conversation.messages.push({
|
|
201
|
+
role: "tool",
|
|
202
|
+
toolCallId: result.toolCallId,
|
|
203
|
+
content: result.content,
|
|
204
|
+
isError: result.isError
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
checkDoneToolCall(toolCall) {
|
|
208
|
+
if (!this.isDoneToolCall(toolCall)) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
return this.extractDoneToolResult(toolCall);
|
|
212
|
+
}
|
|
213
|
+
isDoneToolCall(toolCall) {
|
|
214
|
+
return toolCall.name === "done";
|
|
215
|
+
}
|
|
216
|
+
extractDoneToolResult(toolCall) {
|
|
217
|
+
return toolCall.arguments.result ?? "";
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
export {
|
|
221
|
+
OpenAIDelegate
|
|
222
|
+
};
|
package/lib/loop/loop.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
+
for (let key of __getOwnPropNames(mod))
|
|
11
|
+
if (!__hasOwnProp.call(to, key))
|
|
12
|
+
__defProp(to, key, {
|
|
13
|
+
get: () => mod[key],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
|
+
|
|
20
|
+
// src/loop/loop.ts
|
|
21
|
+
import debug from "debug";
|
|
22
|
+
import { getErrorMessage } from "../utils/common-formatters.js";
|
|
23
|
+
async function runTask(delegate, client, task, oneShot = false) {
|
|
24
|
+
const { tools } = await client.listTools();
|
|
25
|
+
const taskContent = createTaskContent(task, oneShot);
|
|
26
|
+
const conversation = delegate.createConversation(taskContent, tools, oneShot);
|
|
27
|
+
return await runConversationLoop(delegate, client, conversation, oneShot);
|
|
28
|
+
}
|
|
29
|
+
async function runConversationLoop(delegate, client, conversation, oneShot) {
|
|
30
|
+
const MAX_ITERATIONS = 5;
|
|
31
|
+
return await runConversationLoopRecursive(delegate, client, conversation, oneShot, 0, MAX_ITERATIONS);
|
|
32
|
+
}
|
|
33
|
+
async function runConversationLoopRecursive(delegate, client, conversation, oneShot, iteration, maxIterations) {
|
|
34
|
+
if (iteration >= maxIterations) {
|
|
35
|
+
throw new Error("Failed to perform step, max attempts reached");
|
|
36
|
+
}
|
|
37
|
+
const result = await executeIteration(delegate, client, conversation, iteration);
|
|
38
|
+
if (shouldTerminateLoop(result, oneShot)) {
|
|
39
|
+
return conversation.messages;
|
|
40
|
+
}
|
|
41
|
+
return await runConversationLoopRecursive(delegate, client, conversation, oneShot, iteration + 1, maxIterations);
|
|
42
|
+
}
|
|
43
|
+
function shouldTerminateLoop(result, oneShot) {
|
|
44
|
+
return result.isDone || oneShot;
|
|
45
|
+
}
|
|
46
|
+
async function executeIteration(delegate, client, conversation, iteration) {
|
|
47
|
+
debug("history")("Making API call for iteration", iteration);
|
|
48
|
+
const toolCalls = await delegate.makeApiCall(conversation);
|
|
49
|
+
validateToolCallsPresent(toolCalls);
|
|
50
|
+
const { toolResults, isDone } = await processToolCalls(delegate, client, toolCalls);
|
|
51
|
+
return handleIterationResult(delegate, conversation, toolResults, isDone);
|
|
52
|
+
}
|
|
53
|
+
function validateToolCallsPresent(toolCalls) {
|
|
54
|
+
if (toolCalls.length === 0) {
|
|
55
|
+
throw new Error('Call the "done" tool when the task is complete.');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function handleIterationResult(delegate, conversation, toolResults, isDone) {
|
|
59
|
+
if (isDone) {
|
|
60
|
+
return { isDone: true };
|
|
61
|
+
}
|
|
62
|
+
delegate.addToolResults(conversation, toolResults);
|
|
63
|
+
return { isDone: false };
|
|
64
|
+
}
|
|
65
|
+
function createTaskContent(task, oneShot) {
|
|
66
|
+
if (oneShot) {
|
|
67
|
+
return `Perform following task: ${task}.`;
|
|
68
|
+
}
|
|
69
|
+
return `Perform following task: ${task}. Once the task is complete, call the "done" tool.`;
|
|
70
|
+
}
|
|
71
|
+
async function processToolCalls(delegate, client, toolCalls) {
|
|
72
|
+
const toolResults = createEmptyToolResults();
|
|
73
|
+
const processResult = await processAllToolCallsSequentially(delegate, client, toolCalls, toolResults);
|
|
74
|
+
return processResult || { toolResults, isDone: false };
|
|
75
|
+
}
|
|
76
|
+
async function processAllToolCallsSequentially(delegate, client, toolCalls, toolResults) {
|
|
77
|
+
return await processToolCallsRecursive(delegate, client, toolCalls, toolResults, 0);
|
|
78
|
+
}
|
|
79
|
+
async function processToolCallsRecursive(delegate, client, toolCalls, toolResults, index) {
|
|
80
|
+
if (index >= toolCalls.length) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
const toolCall = toolCalls[index];
|
|
84
|
+
const processingResult = await processSingleToolCall(delegate, client, toolCall, toolCalls, toolResults);
|
|
85
|
+
if (shouldReturnEarly(processingResult)) {
|
|
86
|
+
return createProcessResult(processingResult, toolResults);
|
|
87
|
+
}
|
|
88
|
+
return await processToolCallsRecursive(delegate, client, toolCalls, toolResults, index + 1);
|
|
89
|
+
}
|
|
90
|
+
function shouldReturnEarly(processingResult) {
|
|
91
|
+
return processingResult.isDone || processingResult.shouldBreak;
|
|
92
|
+
}
|
|
93
|
+
function createProcessResult(processingResult, toolResults) {
|
|
94
|
+
return {
|
|
95
|
+
toolResults,
|
|
96
|
+
isDone: processingResult.isDone
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function createEmptyToolResults() {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
async function processSingleToolCall(delegate, client, toolCall, allToolCalls, toolResults) {
|
|
103
|
+
if (isToolCallDone(delegate, toolCall)) {
|
|
104
|
+
return createDoneResult();
|
|
105
|
+
}
|
|
106
|
+
const executionResult = await processIndividualToolCall(client, toolCall, allToolCalls, toolResults);
|
|
107
|
+
return createContinueResult(executionResult.shouldBreak);
|
|
108
|
+
}
|
|
109
|
+
function isToolCallDone(delegate, toolCall) {
|
|
110
|
+
const doneCheck = checkForDoneToolCall(delegate, toolCall);
|
|
111
|
+
return doneCheck.isDone;
|
|
112
|
+
}
|
|
113
|
+
function createDoneResult() {
|
|
114
|
+
return { isDone: true, shouldBreak: false };
|
|
115
|
+
}
|
|
116
|
+
function createContinueResult(shouldBreak) {
|
|
117
|
+
return { isDone: false, shouldBreak };
|
|
118
|
+
}
|
|
119
|
+
async function processIndividualToolCall(client, toolCall, allToolCalls, toolResults) {
|
|
120
|
+
const result = await executeToolCall(client, toolCall);
|
|
121
|
+
toolResults.push(result);
|
|
122
|
+
if (shouldBreakOnError(result, allToolCalls, toolCall, toolResults)) {
|
|
123
|
+
return { shouldBreak: true };
|
|
124
|
+
}
|
|
125
|
+
return { shouldBreak: false };
|
|
126
|
+
}
|
|
127
|
+
function checkForDoneToolCall(delegate, toolCall) {
|
|
128
|
+
const doneResult = delegate.checkDoneToolCall(toolCall);
|
|
129
|
+
return { isDone: doneResult !== null };
|
|
130
|
+
}
|
|
131
|
+
function shouldBreakOnError(result, toolCalls, currentToolCall, toolResults) {
|
|
132
|
+
if (result.isError) {
|
|
133
|
+
addSkippedToolResults(toolCalls, currentToolCall, toolResults);
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
async function executeToolCall(client, toolCall) {
|
|
139
|
+
const { name, arguments: args, id } = toolCall;
|
|
140
|
+
try {
|
|
141
|
+
debug("tool")(name, args);
|
|
142
|
+
const response = await client.callTool({ name, arguments: args });
|
|
143
|
+
const responseContent = response.content ?? [];
|
|
144
|
+
debug("tool")(responseContent);
|
|
145
|
+
const text = extractTextFromResponse(responseContent);
|
|
146
|
+
return { toolCallId: id, content: text };
|
|
147
|
+
} catch (error) {
|
|
148
|
+
debug("tool")(error);
|
|
149
|
+
return {
|
|
150
|
+
toolCallId: id,
|
|
151
|
+
content: `Error while executing tool "${name}": ${getErrorMessage(error)}
|
|
152
|
+
|
|
153
|
+
Please try to recover and complete the task.`,
|
|
154
|
+
isError: true
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function extractTextFromResponse(responseContent) {
|
|
159
|
+
return responseContent.filter((part) => part.type === "text").map((part) => part.text).join(`
|
|
160
|
+
`);
|
|
161
|
+
}
|
|
162
|
+
function addSkippedToolResults(toolCalls, currentToolCall, toolResults) {
|
|
163
|
+
const remainingToolCalls = toolCalls.slice(toolCalls.indexOf(currentToolCall) + 1);
|
|
164
|
+
for (const remainingToolCall of remainingToolCalls) {
|
|
165
|
+
toolResults.push({
|
|
166
|
+
toolCallId: remainingToolCall.id,
|
|
167
|
+
content: "This tool call is skipped due to previous error.",
|
|
168
|
+
isError: true
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
export {
|
|
173
|
+
runTask
|
|
174
|
+
};
|
package/lib/loop/main.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// src/loop/main.ts
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import url from "node:url";
|
|
4
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
5
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
6
|
+
import { program } from "commander";
|
|
7
|
+
import dotenv from "dotenv";
|
|
8
|
+
import { runTask } from "./loop.js";
|
|
9
|
+
import { ClaudeDelegate } from "./loop-claude.js";
|
|
10
|
+
import { OpenAIDelegate } from "./loop-open-ai.js";
|
|
11
|
+
dotenv.config();
|
|
12
|
+
var __filename2 = url.fileURLToPath(import.meta.url);
|
|
13
|
+
async function run(delegate) {
|
|
14
|
+
const transport = new StdioClientTransport({
|
|
15
|
+
command: "node",
|
|
16
|
+
args: [
|
|
17
|
+
path.resolve(__filename2, "../../../cli.js"),
|
|
18
|
+
"--save-session",
|
|
19
|
+
"--output-dir",
|
|
20
|
+
path.resolve(__filename2, "../../../sessions")
|
|
21
|
+
],
|
|
22
|
+
stderr: "inherit",
|
|
23
|
+
env: process.env
|
|
24
|
+
});
|
|
25
|
+
const client = new Client({ name: "test", version: "1.0.0" });
|
|
26
|
+
await client.connect(transport);
|
|
27
|
+
await client.ping();
|
|
28
|
+
await Promise.all(tasks.map((task) => runTask(delegate, client, task)));
|
|
29
|
+
await client.close();
|
|
30
|
+
}
|
|
31
|
+
var tasks = ["Open https://playwright.dev/"];
|
|
32
|
+
program.option("--model <model>", "model to use").action(async (options) => {
|
|
33
|
+
if (options.model === "claude") {
|
|
34
|
+
await run(new ClaudeDelegate);
|
|
35
|
+
} else {
|
|
36
|
+
await run(new OpenAIDelegate);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
async function startCLI() {
|
|
40
|
+
try {
|
|
41
|
+
await program.parseAsync(process.argv);
|
|
42
|
+
} catch {
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
startCLI();
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
+
for (let key of __getOwnPropNames(mod))
|
|
11
|
+
if (!__hasOwnProp.call(to, key))
|
|
12
|
+
__defProp(to, key, {
|
|
13
|
+
get: () => mod[key],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
|
+
|
|
20
|
+
// src/loopTools/context.ts
|
|
21
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
22
|
+
import { contextFactory } from "../browser-context-factory.js";
|
|
23
|
+
import { BrowserServerBackend } from "../browser-server-backend.js";
|
|
24
|
+
import { Context as BrowserContext } from "../context.js";
|
|
25
|
+
import { runTask } from "../loop/loop.js";
|
|
26
|
+
import { ClaudeDelegate } from "../loop/loop-claude.js";
|
|
27
|
+
import { OpenAIDelegate } from "../loop/loop-open-ai.js";
|
|
28
|
+
import { InProcessTransport } from "../mcp/in-process-transport.js";
|
|
29
|
+
import { createServer } from "../mcp/server.js";
|
|
30
|
+
|
|
31
|
+
class Context {
|
|
32
|
+
config;
|
|
33
|
+
_client;
|
|
34
|
+
_delegate;
|
|
35
|
+
constructor(config, client) {
|
|
36
|
+
this.config = config;
|
|
37
|
+
this._client = client;
|
|
38
|
+
if (process.env.OPENAI_API_KEY) {
|
|
39
|
+
this._delegate = new OpenAIDelegate;
|
|
40
|
+
} else if (process.env.ANTHROPIC_API_KEY) {
|
|
41
|
+
this._delegate = new ClaudeDelegate;
|
|
42
|
+
} else {
|
|
43
|
+
throw new Error("No LLM API key found. Please set OPENAI_API_KEY or ANTHROPIC_API_KEY environment variable.");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
static async create(config) {
|
|
47
|
+
const client = new Client({ name: "Playwright Proxy", version: "1.0.0" });
|
|
48
|
+
const browserContextFactory = contextFactory(config);
|
|
49
|
+
const server = createServer(new BrowserServerBackend(config, [browserContextFactory]), false);
|
|
50
|
+
await client.connect(new InProcessTransport(server));
|
|
51
|
+
await client.ping();
|
|
52
|
+
return new Context(config, client);
|
|
53
|
+
}
|
|
54
|
+
async runTask(task, oneShot = false) {
|
|
55
|
+
const messages = await runTask(this._delegate, this._client, task, oneShot);
|
|
56
|
+
const lines = [];
|
|
57
|
+
for (const message of messages.slice(1)) {
|
|
58
|
+
if (!message.content.trim()) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const index = oneShot ? -1 : message.content.indexOf("### Page state");
|
|
62
|
+
const trimmedContent = index === -1 ? message.content : message.content.substring(0, index);
|
|
63
|
+
lines.push(`[${message.role}]:`, trimmedContent);
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
content: [{ type: "text", text: lines.join(`
|
|
67
|
+
`) }]
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
async close() {
|
|
71
|
+
await BrowserContext.disposeAll();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export {
|
|
75
|
+
Context
|
|
76
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
+
for (let key of __getOwnPropNames(mod))
|
|
11
|
+
if (!__hasOwnProp.call(to, key))
|
|
12
|
+
__defProp(to, key, {
|
|
13
|
+
get: () => mod[key],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
|
+
|
|
20
|
+
// src/loopTools/main.ts
|
|
21
|
+
import dotenv from "dotenv";
|
|
22
|
+
import { start } from "../mcp/transport.js";
|
|
23
|
+
import { packageJSON } from "../package.js";
|
|
24
|
+
import { Context } from "./context.js";
|
|
25
|
+
import { perform } from "./perform.js";
|
|
26
|
+
import { snapshot } from "./snapshot.js";
|
|
27
|
+
async function runLoopTools(config) {
|
|
28
|
+
dotenv.config();
|
|
29
|
+
const serverBackendFactory = () => new LoopToolsServerBackend(config);
|
|
30
|
+
await start(serverBackendFactory, config.server);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
class LoopToolsServerBackend {
|
|
34
|
+
name = "Playwright";
|
|
35
|
+
version = packageJSON.version;
|
|
36
|
+
_config;
|
|
37
|
+
_context;
|
|
38
|
+
_tools = [perform, snapshot];
|
|
39
|
+
constructor(config) {
|
|
40
|
+
this._config = config;
|
|
41
|
+
}
|
|
42
|
+
async initialize() {
|
|
43
|
+
this._context = await Context.create(this._config);
|
|
44
|
+
}
|
|
45
|
+
tools() {
|
|
46
|
+
return this._tools.map((tool) => tool.schema);
|
|
47
|
+
}
|
|
48
|
+
async callTool(schema, rawArguments) {
|
|
49
|
+
const tool = this._tools.find((t) => t.schema.name === schema.name);
|
|
50
|
+
if (!tool) {
|
|
51
|
+
throw new Error(`Tool not found: ${schema.name}`);
|
|
52
|
+
}
|
|
53
|
+
if (!this._context) {
|
|
54
|
+
throw new Error("Context not initialized");
|
|
55
|
+
}
|
|
56
|
+
const parsedArguments = schema.inputSchema.parse(rawArguments || {});
|
|
57
|
+
return await tool.handle(this._context, parsedArguments);
|
|
58
|
+
}
|
|
59
|
+
serverClosed() {
|
|
60
|
+
this._context?.close().catch(() => {});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export {
|
|
64
|
+
runLoopTools
|
|
65
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
+
for (let key of __getOwnPropNames(mod))
|
|
11
|
+
if (!__hasOwnProp.call(to, key))
|
|
12
|
+
__defProp(to, key, {
|
|
13
|
+
get: () => mod[key],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
|
+
|
|
20
|
+
// src/loopTools/perform.ts
|
|
21
|
+
import { z } from "zod";
|
|
22
|
+
import { defineTool } from "./tool.js";
|
|
23
|
+
var performSchema = z.object({
|
|
24
|
+
task: z.string().describe("The task to perform with the browser")
|
|
25
|
+
});
|
|
26
|
+
var perform = defineTool({
|
|
27
|
+
schema: {
|
|
28
|
+
name: "browser_perform",
|
|
29
|
+
title: "Perform a task with the browser",
|
|
30
|
+
description: "Perform a task with the browser. It can click, type, export, capture screenshot, drag, hover, select options, etc.",
|
|
31
|
+
inputSchema: performSchema,
|
|
32
|
+
type: "destructive"
|
|
33
|
+
},
|
|
34
|
+
handle: async (context, params) => {
|
|
35
|
+
return await context.runTask(params.task);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
export {
|
|
39
|
+
perform
|
|
40
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
+
for (let key of __getOwnPropNames(mod))
|
|
11
|
+
if (!__hasOwnProp.call(to, key))
|
|
12
|
+
__defProp(to, key, {
|
|
13
|
+
get: () => mod[key],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
|
+
|
|
20
|
+
// src/loopTools/snapshot.ts
|
|
21
|
+
import { z } from "zod";
|
|
22
|
+
import { defineTool } from "./tool.js";
|
|
23
|
+
var snapshot = defineTool({
|
|
24
|
+
schema: {
|
|
25
|
+
name: "browser_snapshot",
|
|
26
|
+
title: "Take a snapshot of the browser",
|
|
27
|
+
description: "Take a snapshot of the browser to read what is on the page.",
|
|
28
|
+
inputSchema: z.object({}),
|
|
29
|
+
type: "readOnly"
|
|
30
|
+
},
|
|
31
|
+
handle: async (context, _params) => {
|
|
32
|
+
return await context.runTask("Capture browser snapshot", true);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
export {
|
|
36
|
+
snapshot
|
|
37
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
+
for (let key of __getOwnPropNames(mod))
|
|
11
|
+
if (!__hasOwnProp.call(to, key))
|
|
12
|
+
__defProp(to, key, {
|
|
13
|
+
get: () => mod[key],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
|
+
|
|
20
|
+
// src/loopTools/tool.ts
|
|
21
|
+
function defineTool(tool) {
|
|
22
|
+
return tool;
|
|
23
|
+
}
|
|
24
|
+
export {
|
|
25
|
+
defineTool
|
|
26
|
+
};
|