@ai-sdk/harness-cline 0.0.0 → 1.0.1
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/CHANGELOG.md +23 -0
- package/LICENSE +13 -0
- package/README.md +56 -3
- package/dist/index.d.ts +127 -0
- package/dist/index.js +1655 -0
- package/dist/index.js.map +1 -0
- package/package.json +63 -11
- package/src/cline-auth.ts +40 -0
- package/src/cline-harness.ts +197 -0
- package/src/cline-mcp.ts +153 -0
- package/src/cline-remote-ops.ts +243 -0
- package/src/cline-resume-state.ts +139 -0
- package/src/cline-session.ts +915 -0
- package/src/cline-skills.ts +59 -0
- package/src/cline-tools.ts +381 -0
- package/src/cline-translate.ts +261 -0
- package/src/cline-utils.ts +151 -0
- package/src/index.ts +13 -0
- package/src/version.ts +6 -0
- package/index.js +0 -1
package/dist/index.js
ADDED
|
@@ -0,0 +1,1655 @@
|
|
|
1
|
+
// src/cline-harness.ts
|
|
2
|
+
import {
|
|
3
|
+
commonTool
|
|
4
|
+
} from "@ai-sdk/harness";
|
|
5
|
+
import { tool } from "@ai-sdk/provider-utils";
|
|
6
|
+
import { z as z2 } from "zod/v4";
|
|
7
|
+
|
|
8
|
+
// src/cline-auth.ts
|
|
9
|
+
import { getAiGatewayAuthFromEnv } from "@ai-sdk/harness/utils";
|
|
10
|
+
function resolveClineEnv({
|
|
11
|
+
auth = "auto",
|
|
12
|
+
env = process.env
|
|
13
|
+
}) {
|
|
14
|
+
if (auth === "direct") {
|
|
15
|
+
return pickDirectClineEnv({ env });
|
|
16
|
+
}
|
|
17
|
+
const gatewayAuth = getAiGatewayAuthFromEnv({ env });
|
|
18
|
+
if (auth === "ai-gateway" || gatewayAuth.apiKey) {
|
|
19
|
+
return {
|
|
20
|
+
...gatewayAuth.apiKey ? { AI_GATEWAY_API_KEY: gatewayAuth.apiKey } : {},
|
|
21
|
+
AI_GATEWAY_BASE_URL: gatewayAuth.baseUrl
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
return pickDirectClineEnv({ env });
|
|
25
|
+
}
|
|
26
|
+
function pickDirectClineEnv({
|
|
27
|
+
env
|
|
28
|
+
}) {
|
|
29
|
+
return {
|
|
30
|
+
...env.CLINE_API_KEY ? { CLINE_API_KEY: env.CLINE_API_KEY } : {},
|
|
31
|
+
...env.CLINE_API_BASE_URL ? { CLINE_API_BASE_URL: env.CLINE_API_BASE_URL } : {}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/cline-resume-state.ts
|
|
36
|
+
import { createHash } from "crypto";
|
|
37
|
+
import path from "path";
|
|
38
|
+
import {
|
|
39
|
+
safeParseJSON
|
|
40
|
+
} from "@ai-sdk/provider-utils";
|
|
41
|
+
import { z } from "zod/v4";
|
|
42
|
+
var CLINE_HISTORY_FILE_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*\.json$/;
|
|
43
|
+
function safeClineHistoryFileName(historyFileName) {
|
|
44
|
+
if (!CLINE_HISTORY_FILE_NAME_PATTERN.test(historyFileName)) {
|
|
45
|
+
throw new Error(`Invalid Cline history file name: ${historyFileName}`);
|
|
46
|
+
}
|
|
47
|
+
return historyFileName;
|
|
48
|
+
}
|
|
49
|
+
var clineHistoryFileNameSchema = z.string().refine(
|
|
50
|
+
(historyFileName) => CLINE_HISTORY_FILE_NAME_PATTERN.test(historyFileName),
|
|
51
|
+
"Cline historyFileName must be a safe .json basename."
|
|
52
|
+
);
|
|
53
|
+
var clineResumeStateSchema = z.looseObject({
|
|
54
|
+
historyFileName: clineHistoryFileNameSchema.optional()
|
|
55
|
+
});
|
|
56
|
+
var CLINE_DEFAULT_HISTORY_FILE_NAME = "history.json";
|
|
57
|
+
function resolveClinePrivateSessionDirectory(input) {
|
|
58
|
+
const sessionKey = createHash("sha256").update(input.sessionId).digest("hex");
|
|
59
|
+
const privateSessionDir = path.posix.join(
|
|
60
|
+
input.sandboxHomeDir,
|
|
61
|
+
".ai-sdk",
|
|
62
|
+
"harness-cline",
|
|
63
|
+
sessionKey
|
|
64
|
+
);
|
|
65
|
+
const relativePath = path.posix.relative(
|
|
66
|
+
input.sessionWorkDir,
|
|
67
|
+
privateSessionDir
|
|
68
|
+
);
|
|
69
|
+
if (relativePath === "" || !relativePath.startsWith("../") && !path.posix.isAbsolute(relativePath)) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`Cline private session directory ${JSON.stringify(privateSessionDir)} must be outside sessionWorkDir ${JSON.stringify(input.sessionWorkDir)}.`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
return privateSessionDir;
|
|
75
|
+
}
|
|
76
|
+
function resolveContainedSandboxPath(input) {
|
|
77
|
+
const historyDir = path.posix.resolve(input.privateSessionDir);
|
|
78
|
+
const filePath = path.posix.resolve(
|
|
79
|
+
historyDir,
|
|
80
|
+
safeClineHistoryFileName(input.historyFileName)
|
|
81
|
+
);
|
|
82
|
+
const relativePath = path.posix.relative(historyDir, filePath);
|
|
83
|
+
if (relativePath === "" || relativePath.startsWith("..") || path.posix.isAbsolute(relativePath)) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`Invalid Cline history file name: ${input.historyFileName}`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
return filePath;
|
|
89
|
+
}
|
|
90
|
+
async function persistHistoryToSandbox(args) {
|
|
91
|
+
const remotePath = resolveContainedSandboxPath({
|
|
92
|
+
privateSessionDir: args.privateSessionDir,
|
|
93
|
+
historyFileName: args.historyFileName
|
|
94
|
+
});
|
|
95
|
+
await args.sandbox.writeTextFile({
|
|
96
|
+
path: remotePath,
|
|
97
|
+
content: JSON.stringify(args.messages),
|
|
98
|
+
...args.abortSignal ? { abortSignal: args.abortSignal } : {}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
async function pullHistoryFromSandbox(args) {
|
|
102
|
+
const remotePath = resolveContainedSandboxPath({
|
|
103
|
+
privateSessionDir: args.privateSessionDir,
|
|
104
|
+
historyFileName: args.historyFileName
|
|
105
|
+
});
|
|
106
|
+
const content = await args.sandbox.readTextFile({
|
|
107
|
+
path: remotePath,
|
|
108
|
+
...args.abortSignal ? { abortSignal: args.abortSignal } : {}
|
|
109
|
+
});
|
|
110
|
+
if (content == null) return void 0;
|
|
111
|
+
const parsed = await safeParseJSON({ text: content });
|
|
112
|
+
return parsed.success && Array.isArray(parsed.value) ? parsed.value : void 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// src/cline-session.ts
|
|
116
|
+
import {
|
|
117
|
+
HarnessCapabilityUnsupportedError as HarnessCapabilityUnsupportedError2
|
|
118
|
+
} from "@ai-sdk/harness";
|
|
119
|
+
import { resolveSandboxHomeDir } from "@ai-sdk/harness/utils";
|
|
120
|
+
import {
|
|
121
|
+
Agent,
|
|
122
|
+
createTool as createTool2
|
|
123
|
+
} from "@cline/agents";
|
|
124
|
+
import { Llms } from "@cline/core";
|
|
125
|
+
|
|
126
|
+
// src/cline-mcp.ts
|
|
127
|
+
var CLINE_CORE_PACKAGE = "@cline/core";
|
|
128
|
+
function isRecord(value) {
|
|
129
|
+
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
130
|
+
}
|
|
131
|
+
function optionalStringArray(value) {
|
|
132
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : void 0;
|
|
133
|
+
}
|
|
134
|
+
function optionalStringRecord(value) {
|
|
135
|
+
return isRecord(value) && Object.values(value).every((item) => typeof item === "string") ? value : void 0;
|
|
136
|
+
}
|
|
137
|
+
function resolveTransport(input) {
|
|
138
|
+
if (isRecord(input.config.transport)) {
|
|
139
|
+
return input.config.transport;
|
|
140
|
+
}
|
|
141
|
+
if (typeof input.config.command === "string") {
|
|
142
|
+
const args = optionalStringArray(input.config.args);
|
|
143
|
+
const env = optionalStringRecord(input.config.env);
|
|
144
|
+
return {
|
|
145
|
+
type: "stdio",
|
|
146
|
+
command: input.config.command,
|
|
147
|
+
...args ? { args } : {},
|
|
148
|
+
...typeof input.config.cwd === "string" ? { cwd: input.config.cwd } : {},
|
|
149
|
+
...env ? { env } : {}
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
if (typeof input.config.url === "string") {
|
|
153
|
+
const configuredType = input.config.type ?? input.config.transportType;
|
|
154
|
+
const headers = optionalStringRecord(input.config.headers);
|
|
155
|
+
return {
|
|
156
|
+
type: configuredType === "http" || configuredType === "streamableHttp" ? "streamableHttp" : "sse",
|
|
157
|
+
url: input.config.url,
|
|
158
|
+
...headers ? { headers } : {}
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
throw new Error(
|
|
162
|
+
`Cline MCP server ${JSON.stringify(input.serverName)} must define a transport, command, or URL.`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
function resolveRegistration(input) {
|
|
166
|
+
if (!isRecord(input.config)) {
|
|
167
|
+
throw new Error(
|
|
168
|
+
`Cline MCP server ${JSON.stringify(input.serverName)} must be configured with an object value.`
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
name: input.serverName,
|
|
173
|
+
transport: resolveTransport({
|
|
174
|
+
serverName: input.serverName,
|
|
175
|
+
config: input.config
|
|
176
|
+
}),
|
|
177
|
+
...typeof input.config.disabled === "boolean" ? { disabled: input.config.disabled } : {},
|
|
178
|
+
...isRecord(input.config.metadata) ? { metadata: input.config.metadata } : {},
|
|
179
|
+
...isRecord(input.config.oauth) ? { oauth: input.config.oauth } : {}
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
async function createClineMcpRuntime(input) {
|
|
183
|
+
if (input.mcpServers == null || Object.keys(input.mcpServers).length === 0) {
|
|
184
|
+
return {
|
|
185
|
+
tools: [],
|
|
186
|
+
toolNames: /* @__PURE__ */ new Set(),
|
|
187
|
+
dispose: async () => {
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
const {
|
|
192
|
+
createDefaultMcpServerClientFactory,
|
|
193
|
+
createMcpTools,
|
|
194
|
+
InMemoryMcpManager
|
|
195
|
+
} = await import(CLINE_CORE_PACKAGE);
|
|
196
|
+
const manager = new InMemoryMcpManager({
|
|
197
|
+
clientFactory: createDefaultMcpServerClientFactory()
|
|
198
|
+
});
|
|
199
|
+
const tools = [];
|
|
200
|
+
try {
|
|
201
|
+
for (const [serverName, config] of Object.entries(input.mcpServers)) {
|
|
202
|
+
const registration = resolveRegistration({ serverName, config });
|
|
203
|
+
await manager.registerServer(registration);
|
|
204
|
+
if (registration.disabled) continue;
|
|
205
|
+
tools.push(
|
|
206
|
+
...await createMcpTools({
|
|
207
|
+
serverName,
|
|
208
|
+
provider: manager
|
|
209
|
+
})
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
} catch (error) {
|
|
213
|
+
await manager.dispose().catch(() => {
|
|
214
|
+
});
|
|
215
|
+
throw error;
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
tools,
|
|
219
|
+
toolNames: new Set(tools.map((tool2) => tool2.name)),
|
|
220
|
+
dispose: async () => manager.dispose()
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// src/cline-remote-ops.ts
|
|
225
|
+
import path2 from "path";
|
|
226
|
+
import { shellQuote } from "@ai-sdk/harness/utils";
|
|
227
|
+
function createClineRemoteOps(options) {
|
|
228
|
+
const { sandbox, workDir } = options;
|
|
229
|
+
const resolvePath = (inputPath) => {
|
|
230
|
+
const normalized = path2.posix.isAbsolute(inputPath) ? path2.posix.normalize(inputPath) : path2.posix.normalize(path2.posix.join(workDir, inputPath));
|
|
231
|
+
return normalized;
|
|
232
|
+
};
|
|
233
|
+
const runShell = async (command, input) => {
|
|
234
|
+
const result = await sandbox.run({
|
|
235
|
+
command,
|
|
236
|
+
workingDirectory: workDir,
|
|
237
|
+
...input?.signal ? { abortSignal: input.signal } : {}
|
|
238
|
+
});
|
|
239
|
+
return {
|
|
240
|
+
exitCode: result.exitCode,
|
|
241
|
+
output: `${result.stdout}${result.stderr}`
|
|
242
|
+
};
|
|
243
|
+
};
|
|
244
|
+
const readFile = async (inputPath) => {
|
|
245
|
+
const resolved = resolvePath(inputPath);
|
|
246
|
+
const content = await sandbox.readTextFile({ path: resolved });
|
|
247
|
+
if (content == null) {
|
|
248
|
+
throw new Error(`File not found: ${inputPath}`);
|
|
249
|
+
}
|
|
250
|
+
return content;
|
|
251
|
+
};
|
|
252
|
+
const writeFile = async (inputPath, content) => {
|
|
253
|
+
const resolved = resolvePath(inputPath);
|
|
254
|
+
await sandbox.writeTextFile({ path: resolved, content });
|
|
255
|
+
};
|
|
256
|
+
const editFile = async (inputPath, oldText, newText) => {
|
|
257
|
+
const current = await readFile(inputPath);
|
|
258
|
+
const index = current.indexOf(oldText);
|
|
259
|
+
if (index === -1) {
|
|
260
|
+
throw new Error(`Text to replace was not found in ${inputPath}`);
|
|
261
|
+
}
|
|
262
|
+
const updated = `${current.slice(0, index)}${newText}${current.slice(
|
|
263
|
+
index + oldText.length
|
|
264
|
+
)}`;
|
|
265
|
+
await writeFile(inputPath, updated);
|
|
266
|
+
};
|
|
267
|
+
return {
|
|
268
|
+
resolvePath,
|
|
269
|
+
readFile,
|
|
270
|
+
writeFile,
|
|
271
|
+
editFile,
|
|
272
|
+
async bash(command, input) {
|
|
273
|
+
const controller = new AbortController();
|
|
274
|
+
const timeoutId = typeof input?.timeout === "number" && input.timeout > 0 ? setTimeout(() => controller.abort(), input.timeout * 1e3) : void 0;
|
|
275
|
+
const forwardedSignal = input?.signal;
|
|
276
|
+
const onAbort = () => controller.abort();
|
|
277
|
+
forwardedSignal?.addEventListener("abort", onAbort, { once: true });
|
|
278
|
+
try {
|
|
279
|
+
const result = await runShell(command, { signal: controller.signal });
|
|
280
|
+
return { output: result.output, exitCode: result.exitCode };
|
|
281
|
+
} finally {
|
|
282
|
+
forwardedSignal?.removeEventListener("abort", onAbort);
|
|
283
|
+
if (timeoutId) {
|
|
284
|
+
clearTimeout(timeoutId);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
async grep(pattern, input = {}) {
|
|
289
|
+
const target = resolvePath(input.path ?? ".");
|
|
290
|
+
const flags = [
|
|
291
|
+
"-R",
|
|
292
|
+
"-n",
|
|
293
|
+
"--binary-files=without-match",
|
|
294
|
+
...input.ignoreCase ? ["-i"] : [],
|
|
295
|
+
...input.literal ? ["-F"] : [],
|
|
296
|
+
...typeof input.context === "number" && input.context > 0 ? ["-C", String(input.context)] : [],
|
|
297
|
+
...input.glob ? ["--include", input.glob] : []
|
|
298
|
+
];
|
|
299
|
+
const limit = Math.max(1, input.limit ?? 100);
|
|
300
|
+
const result = await runShell(
|
|
301
|
+
[
|
|
302
|
+
`if [ ! -e ${shellQuote(
|
|
303
|
+
target
|
|
304
|
+
)} ]; then echo "__CLINE_GREP_NOT_FOUND__"; exit 2; fi`,
|
|
305
|
+
`grep ${flags.map(shellQuote).join(" ")} -- ${shellQuote(
|
|
306
|
+
pattern
|
|
307
|
+
)} ${shellQuote(target)} 2>/dev/null | head -n ${limit}`
|
|
308
|
+
].join("; ")
|
|
309
|
+
);
|
|
310
|
+
const output = result.output.trim();
|
|
311
|
+
if (output.includes("__CLINE_GREP_NOT_FOUND__")) {
|
|
312
|
+
throw new Error(`Path not found: ${input.path ?? "."}`);
|
|
313
|
+
}
|
|
314
|
+
return output || "No matches found";
|
|
315
|
+
},
|
|
316
|
+
async glob(pattern, inputPath = ".", limit = 1e3) {
|
|
317
|
+
const target = resolvePath(inputPath);
|
|
318
|
+
const result = await runShell(
|
|
319
|
+
[
|
|
320
|
+
`if [ ! -e ${shellQuote(
|
|
321
|
+
target
|
|
322
|
+
)} ]; then echo "__CLINE_FIND_NOT_FOUND__"; exit 2; fi`,
|
|
323
|
+
`if [ -d ${shellQuote(target)} ]; then find ${shellQuote(
|
|
324
|
+
target
|
|
325
|
+
)} -type f -print; else printf '%s\\n' ${shellQuote(target)}; fi`
|
|
326
|
+
].join("; ")
|
|
327
|
+
);
|
|
328
|
+
const output = result.output.trim();
|
|
329
|
+
if (output.includes("__CLINE_FIND_NOT_FOUND__")) {
|
|
330
|
+
throw new Error(`Path not found: ${inputPath}`);
|
|
331
|
+
}
|
|
332
|
+
return output.split("\n").filter(Boolean).map((absolutePath) => {
|
|
333
|
+
if (absolutePath === target) {
|
|
334
|
+
return path2.posix.basename(absolutePath);
|
|
335
|
+
}
|
|
336
|
+
return path2.posix.relative(target, absolutePath);
|
|
337
|
+
}).filter(
|
|
338
|
+
(candidate) => candidate.length > 0 && path2.matchesGlob(candidate, pattern)
|
|
339
|
+
).sort(
|
|
340
|
+
(left, right) => left.toLowerCase().localeCompare(right.toLowerCase())
|
|
341
|
+
).slice(0, limit);
|
|
342
|
+
},
|
|
343
|
+
async ls(inputPath = ".", limit = 500) {
|
|
344
|
+
const target = resolvePath(inputPath);
|
|
345
|
+
const result = await runShell(
|
|
346
|
+
[
|
|
347
|
+
`if [ ! -e ${shellQuote(
|
|
348
|
+
target
|
|
349
|
+
)} ]; then echo "__CLINE_LS_NOT_FOUND__"; exit 2; fi`,
|
|
350
|
+
`if [ ! -d ${shellQuote(
|
|
351
|
+
target
|
|
352
|
+
)} ]; then echo "__CLINE_LS_NOT_DIR__"; exit 3; fi`,
|
|
353
|
+
`cd ${shellQuote(target)}`,
|
|
354
|
+
"ls -1Ap"
|
|
355
|
+
].join("; ")
|
|
356
|
+
);
|
|
357
|
+
const output = result.output.trim();
|
|
358
|
+
if (output.includes("__CLINE_LS_NOT_FOUND__")) {
|
|
359
|
+
throw new Error(`Path not found: ${inputPath}`);
|
|
360
|
+
}
|
|
361
|
+
if (output.includes("__CLINE_LS_NOT_DIR__")) {
|
|
362
|
+
throw new Error(`Not a directory: ${inputPath}`);
|
|
363
|
+
}
|
|
364
|
+
return output.split("\n").filter(Boolean).map((line) => line.replace(/[*=@|]$/, "")).sort(
|
|
365
|
+
(left, right) => left.toLowerCase().localeCompare(right.toLowerCase())
|
|
366
|
+
).slice(0, limit);
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// src/cline-skills.ts
|
|
372
|
+
import path3 from "path";
|
|
373
|
+
import { writeSkills } from "@ai-sdk/harness/utils";
|
|
374
|
+
async function writeClineSkills({
|
|
375
|
+
sandbox,
|
|
376
|
+
sandboxHomeDir,
|
|
377
|
+
skills,
|
|
378
|
+
abortSignal
|
|
379
|
+
}) {
|
|
380
|
+
if (skills.length === 0) return void 0;
|
|
381
|
+
const skillRootDir = path3.posix.join(sandboxHomeDir, ".agents", "skills");
|
|
382
|
+
await writeSkills({
|
|
383
|
+
sandbox,
|
|
384
|
+
rootDir: skillRootDir,
|
|
385
|
+
skills,
|
|
386
|
+
...abortSignal ? { abortSignal } : {}
|
|
387
|
+
});
|
|
388
|
+
return skillRootDir;
|
|
389
|
+
}
|
|
390
|
+
function renderSkillsPromptSection(skills, skillRootDir) {
|
|
391
|
+
const entries = skills.map(
|
|
392
|
+
(skill) => `- ${skill.name}: ${skill.description}
|
|
393
|
+
SKILL.md: ${path3.posix.join(
|
|
394
|
+
skillRootDir,
|
|
395
|
+
skill.name,
|
|
396
|
+
"SKILL.md"
|
|
397
|
+
)}`
|
|
398
|
+
).join("\n");
|
|
399
|
+
return "## Skills\n\nThe following skills are available. When a task matches a skill, read its SKILL.md with the `read` tool and follow its instructions.\n\n" + entries;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// src/cline-tools.ts
|
|
403
|
+
import {
|
|
404
|
+
createTool
|
|
405
|
+
} from "@cline/agents";
|
|
406
|
+
|
|
407
|
+
// src/cline-utils.ts
|
|
408
|
+
import {
|
|
409
|
+
HarnessCapabilityUnsupportedError
|
|
410
|
+
} from "@ai-sdk/harness";
|
|
411
|
+
import { secureJsonParse } from "@ai-sdk/provider-utils";
|
|
412
|
+
var HARNESS_ID = "cline";
|
|
413
|
+
function extractUserText(prompt) {
|
|
414
|
+
if (typeof prompt === "string") {
|
|
415
|
+
return prompt;
|
|
416
|
+
}
|
|
417
|
+
const { content } = prompt;
|
|
418
|
+
if (typeof content === "string") {
|
|
419
|
+
return content;
|
|
420
|
+
}
|
|
421
|
+
const parts = [];
|
|
422
|
+
for (const part of content) {
|
|
423
|
+
if (part.type !== "text") {
|
|
424
|
+
throw new HarnessCapabilityUnsupportedError({
|
|
425
|
+
message: `cline: only text user-message parts are supported; got '${part.type}'.`,
|
|
426
|
+
harnessId: HARNESS_ID
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
parts.push(part.text);
|
|
430
|
+
}
|
|
431
|
+
return parts.join("\n\n");
|
|
432
|
+
}
|
|
433
|
+
function getErrorText(error) {
|
|
434
|
+
return error instanceof Error ? error.message : String(error);
|
|
435
|
+
}
|
|
436
|
+
function toToolResultValue(output) {
|
|
437
|
+
if (output === void 0 || output === null) {
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
if (typeof output === "string") {
|
|
441
|
+
return output;
|
|
442
|
+
}
|
|
443
|
+
try {
|
|
444
|
+
const serialized = JSON.stringify(output);
|
|
445
|
+
return serialized === void 0 ? String(output) : secureJsonParse(serialized);
|
|
446
|
+
} catch {
|
|
447
|
+
return String(output);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
function mapModelFinishReason(reason) {
|
|
451
|
+
switch (reason) {
|
|
452
|
+
case "stop":
|
|
453
|
+
return { unified: "stop", raw: reason };
|
|
454
|
+
case "tool-calls":
|
|
455
|
+
return { unified: "tool-calls", raw: reason };
|
|
456
|
+
case "max-tokens":
|
|
457
|
+
return { unified: "length", raw: reason };
|
|
458
|
+
case "error":
|
|
459
|
+
return { unified: "error", raw: reason };
|
|
460
|
+
default:
|
|
461
|
+
return { unified: "other", raw: reason };
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
function mapRunFinishReason(status) {
|
|
465
|
+
switch (status) {
|
|
466
|
+
case "completed":
|
|
467
|
+
return { unified: "stop", raw: status };
|
|
468
|
+
case "aborted":
|
|
469
|
+
return { unified: "other", raw: status };
|
|
470
|
+
case "failed":
|
|
471
|
+
return { unified: "error", raw: status };
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
function usageFromAgentUsage(usage) {
|
|
475
|
+
return {
|
|
476
|
+
inputTokens: {
|
|
477
|
+
total: usage.inputTokens,
|
|
478
|
+
noCache: void 0,
|
|
479
|
+
cacheRead: usage.cacheReadTokens,
|
|
480
|
+
cacheWrite: usage.cacheWriteTokens
|
|
481
|
+
},
|
|
482
|
+
outputTokens: {
|
|
483
|
+
total: usage.outputTokens,
|
|
484
|
+
text: void 0,
|
|
485
|
+
reasoning: usage.reasoningTokenCount
|
|
486
|
+
},
|
|
487
|
+
...usage.totalCost !== void 0 ? { raw: { totalCost: usage.totalCost } } : {}
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
function usageFromMessageMetrics(metrics) {
|
|
491
|
+
return {
|
|
492
|
+
inputTokens: {
|
|
493
|
+
total: metrics?.inputTokens,
|
|
494
|
+
noCache: void 0,
|
|
495
|
+
cacheRead: metrics?.cacheReadTokens,
|
|
496
|
+
cacheWrite: metrics?.cacheWriteTokens
|
|
497
|
+
},
|
|
498
|
+
outputTokens: {
|
|
499
|
+
total: metrics?.outputTokens,
|
|
500
|
+
text: void 0,
|
|
501
|
+
reasoning: metrics?.reasoningTokenCount
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// src/cline-tools.ts
|
|
507
|
+
var CLINE_NATIVE_BUILTIN_NAMES = [
|
|
508
|
+
"read",
|
|
509
|
+
"write",
|
|
510
|
+
"edit",
|
|
511
|
+
"bash",
|
|
512
|
+
"grep",
|
|
513
|
+
"glob",
|
|
514
|
+
"ls"
|
|
515
|
+
];
|
|
516
|
+
var CLINE_NATIVE_TOOL_KINDS = {
|
|
517
|
+
read: "readonly",
|
|
518
|
+
write: "edit",
|
|
519
|
+
edit: "edit",
|
|
520
|
+
bash: "bash",
|
|
521
|
+
grep: "readonly",
|
|
522
|
+
glob: "readonly",
|
|
523
|
+
ls: "readonly"
|
|
524
|
+
};
|
|
525
|
+
function isClineBuiltinToolName(name) {
|
|
526
|
+
return CLINE_NATIVE_BUILTIN_NAMES.includes(name);
|
|
527
|
+
}
|
|
528
|
+
function resolveActiveClineBuiltinNames(toolFiltering) {
|
|
529
|
+
if (toolFiltering == null) return CLINE_NATIVE_BUILTIN_NAMES;
|
|
530
|
+
if (toolFiltering.mode === "allow") {
|
|
531
|
+
return CLINE_NATIVE_BUILTIN_NAMES.filter(
|
|
532
|
+
(name) => toolFiltering.toolNames.includes(name)
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
return CLINE_NATIVE_BUILTIN_NAMES.filter(
|
|
536
|
+
(name) => !toolFiltering.toolNames.includes(name)
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
var clineToolResultMarker = /* @__PURE__ */ Symbol("cline-tool-result");
|
|
540
|
+
function createClineToolResult({
|
|
541
|
+
output,
|
|
542
|
+
isError
|
|
543
|
+
}) {
|
|
544
|
+
return {
|
|
545
|
+
[clineToolResultMarker]: true,
|
|
546
|
+
output,
|
|
547
|
+
...isError ? { isError: true } : {}
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
function unwrapClineToolResult(value) {
|
|
551
|
+
if (value == null || typeof value !== "object" || value[clineToolResultMarker] !== true) {
|
|
552
|
+
return void 0;
|
|
553
|
+
}
|
|
554
|
+
const result = value;
|
|
555
|
+
return {
|
|
556
|
+
output: result.output,
|
|
557
|
+
...result.isError ? { isError: true } : {}
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
async function guarded(run) {
|
|
561
|
+
try {
|
|
562
|
+
return createClineToolResult({ output: await run() });
|
|
563
|
+
} catch (error) {
|
|
564
|
+
return createClineToolResult({
|
|
565
|
+
output: { error: getErrorText(error) },
|
|
566
|
+
isError: true
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
function buildBuiltinAgentTools({
|
|
571
|
+
ops,
|
|
572
|
+
activeNames
|
|
573
|
+
}) {
|
|
574
|
+
const tools = {
|
|
575
|
+
read: createTool({
|
|
576
|
+
name: "read",
|
|
577
|
+
description: "Read file contents.",
|
|
578
|
+
inputSchema: {
|
|
579
|
+
type: "object",
|
|
580
|
+
properties: {
|
|
581
|
+
file_path: {
|
|
582
|
+
type: "string",
|
|
583
|
+
description: "Path of the file to read."
|
|
584
|
+
}
|
|
585
|
+
},
|
|
586
|
+
required: ["file_path"]
|
|
587
|
+
},
|
|
588
|
+
execute: async (input) => guarded(() => ops.readFile(input.file_path))
|
|
589
|
+
}),
|
|
590
|
+
write: createTool({
|
|
591
|
+
name: "write",
|
|
592
|
+
description: "Overwrite or create a file.",
|
|
593
|
+
inputSchema: {
|
|
594
|
+
type: "object",
|
|
595
|
+
properties: {
|
|
596
|
+
file_path: {
|
|
597
|
+
type: "string",
|
|
598
|
+
description: "Path of the file to write."
|
|
599
|
+
},
|
|
600
|
+
content: {
|
|
601
|
+
type: "string",
|
|
602
|
+
description: "Full file content to write."
|
|
603
|
+
}
|
|
604
|
+
},
|
|
605
|
+
required: ["file_path", "content"]
|
|
606
|
+
},
|
|
607
|
+
execute: async (input) => guarded(async () => {
|
|
608
|
+
await ops.writeFile(input.file_path, input.content);
|
|
609
|
+
return `Wrote ${input.file_path}`;
|
|
610
|
+
})
|
|
611
|
+
}),
|
|
612
|
+
edit: createTool({
|
|
613
|
+
name: "edit",
|
|
614
|
+
description: "Edit a file by exact string replacement.",
|
|
615
|
+
inputSchema: {
|
|
616
|
+
type: "object",
|
|
617
|
+
properties: {
|
|
618
|
+
file_path: {
|
|
619
|
+
type: "string",
|
|
620
|
+
description: "Path of the file to edit."
|
|
621
|
+
},
|
|
622
|
+
old_string: { type: "string", description: "Exact text to replace." },
|
|
623
|
+
new_string: { type: "string", description: "Replacement text." }
|
|
624
|
+
},
|
|
625
|
+
required: ["file_path", "old_string", "new_string"]
|
|
626
|
+
},
|
|
627
|
+
execute: async (input) => guarded(async () => {
|
|
628
|
+
await ops.editFile(
|
|
629
|
+
input.file_path,
|
|
630
|
+
input.old_string,
|
|
631
|
+
input.new_string
|
|
632
|
+
);
|
|
633
|
+
return `Edited ${input.file_path}`;
|
|
634
|
+
})
|
|
635
|
+
}),
|
|
636
|
+
bash: createTool({
|
|
637
|
+
name: "bash",
|
|
638
|
+
description: "Execute a shell command in the sandbox workspace. Returns combined stdout/stderr and the exit code.",
|
|
639
|
+
inputSchema: {
|
|
640
|
+
type: "object",
|
|
641
|
+
properties: {
|
|
642
|
+
command: { type: "string", description: "Shell command to execute." },
|
|
643
|
+
timeout: {
|
|
644
|
+
type: "number",
|
|
645
|
+
description: "Optional timeout in seconds."
|
|
646
|
+
}
|
|
647
|
+
},
|
|
648
|
+
required: ["command"]
|
|
649
|
+
},
|
|
650
|
+
execute: async (input, context) => guarded(async () => {
|
|
651
|
+
const result = await ops.bash(input.command, {
|
|
652
|
+
...typeof input.timeout === "number" ? { timeout: input.timeout } : {},
|
|
653
|
+
...context.signal ? { signal: context.signal } : {}
|
|
654
|
+
});
|
|
655
|
+
return `${result.output}${result.exitCode != null ? `
|
|
656
|
+
|
|
657
|
+
(exit ${result.exitCode})` : ""}`.trim();
|
|
658
|
+
})
|
|
659
|
+
}),
|
|
660
|
+
grep: createTool({
|
|
661
|
+
name: "grep",
|
|
662
|
+
description: "Search file contents with regex.",
|
|
663
|
+
inputSchema: {
|
|
664
|
+
type: "object",
|
|
665
|
+
properties: {
|
|
666
|
+
pattern: {
|
|
667
|
+
type: "string",
|
|
668
|
+
description: "Regex pattern to search for."
|
|
669
|
+
},
|
|
670
|
+
path: { type: "string", description: "File or directory to search." },
|
|
671
|
+
glob: { type: "string", description: "Glob filter for file names." },
|
|
672
|
+
ignoreCase: { type: "boolean" },
|
|
673
|
+
literal: {
|
|
674
|
+
type: "boolean",
|
|
675
|
+
description: "Treat pattern as a literal string."
|
|
676
|
+
},
|
|
677
|
+
context: {
|
|
678
|
+
type: "number",
|
|
679
|
+
description: "Lines of context around matches."
|
|
680
|
+
},
|
|
681
|
+
limit: {
|
|
682
|
+
type: "number",
|
|
683
|
+
description: "Maximum matching lines to return."
|
|
684
|
+
}
|
|
685
|
+
},
|
|
686
|
+
required: ["pattern"]
|
|
687
|
+
},
|
|
688
|
+
execute: async (input) => guarded(() => ops.grep(input.pattern, input))
|
|
689
|
+
}),
|
|
690
|
+
glob: createTool({
|
|
691
|
+
name: "glob",
|
|
692
|
+
description: "Find files matching a glob pattern.",
|
|
693
|
+
inputSchema: {
|
|
694
|
+
type: "object",
|
|
695
|
+
properties: {
|
|
696
|
+
pattern: { type: "string", description: "Glob pattern to match." },
|
|
697
|
+
path: { type: "string", description: "Directory to search from." },
|
|
698
|
+
limit: { type: "number", description: "Maximum paths to return." }
|
|
699
|
+
},
|
|
700
|
+
required: ["pattern"]
|
|
701
|
+
},
|
|
702
|
+
execute: async (input) => guarded(async () => {
|
|
703
|
+
const matches = await ops.glob(
|
|
704
|
+
input.pattern,
|
|
705
|
+
input.path ?? ".",
|
|
706
|
+
input.limit ?? 1e3
|
|
707
|
+
);
|
|
708
|
+
return matches.length > 0 ? matches.join("\n") : "No files found";
|
|
709
|
+
})
|
|
710
|
+
}),
|
|
711
|
+
ls: createTool({
|
|
712
|
+
name: "ls",
|
|
713
|
+
description: "List directory entries.",
|
|
714
|
+
inputSchema: {
|
|
715
|
+
type: "object",
|
|
716
|
+
properties: {
|
|
717
|
+
path: { type: "string", description: "Directory to list." },
|
|
718
|
+
limit: { type: "number", description: "Maximum entries to return." }
|
|
719
|
+
}
|
|
720
|
+
},
|
|
721
|
+
execute: async (input) => guarded(async () => {
|
|
722
|
+
const entries = await ops.ls(input.path ?? ".", input.limit ?? 500);
|
|
723
|
+
return entries.join("\n");
|
|
724
|
+
})
|
|
725
|
+
})
|
|
726
|
+
};
|
|
727
|
+
return activeNames.map((name) => tools[name]).filter((tool2) => tool2 != null);
|
|
728
|
+
}
|
|
729
|
+
function buildUserAgentTools({
|
|
730
|
+
specs,
|
|
731
|
+
pendingToolResults
|
|
732
|
+
}) {
|
|
733
|
+
return specs.map(
|
|
734
|
+
(spec) => createTool({
|
|
735
|
+
name: spec.name,
|
|
736
|
+
description: spec.description ?? `User-registered tool ${spec.name}`,
|
|
737
|
+
inputSchema: spec.inputSchema ?? {
|
|
738
|
+
type: "object",
|
|
739
|
+
properties: {},
|
|
740
|
+
additionalProperties: true
|
|
741
|
+
},
|
|
742
|
+
execute: async (_input, context) => {
|
|
743
|
+
const toolCallId = context.toolCallId;
|
|
744
|
+
if (!toolCallId) {
|
|
745
|
+
return createClineToolResult({
|
|
746
|
+
output: { error: "Tool call is missing a toolCallId." },
|
|
747
|
+
isError: true
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
return new Promise((resolve) => {
|
|
751
|
+
pendingToolResults.set(toolCallId, { resolve });
|
|
752
|
+
context.signal?.addEventListener(
|
|
753
|
+
"abort",
|
|
754
|
+
() => {
|
|
755
|
+
if (pendingToolResults.delete(toolCallId)) {
|
|
756
|
+
resolve(
|
|
757
|
+
createClineToolResult({
|
|
758
|
+
output: {
|
|
759
|
+
error: "Turn was aborted before a tool result was submitted."
|
|
760
|
+
},
|
|
761
|
+
isError: true
|
|
762
|
+
})
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
},
|
|
766
|
+
{ once: true }
|
|
767
|
+
);
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
})
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// src/cline-translate.ts
|
|
775
|
+
function createClineTranslatorState({
|
|
776
|
+
builtinToolNames,
|
|
777
|
+
hostToolNames = [],
|
|
778
|
+
ignoredToolNames = [],
|
|
779
|
+
mcpToolNames = []
|
|
780
|
+
}) {
|
|
781
|
+
return {
|
|
782
|
+
builtinToolNames: new Set(builtinToolNames),
|
|
783
|
+
dynamicToolCallIds: /* @__PURE__ */ new Set(),
|
|
784
|
+
hostToolNames: new Set(hostToolNames),
|
|
785
|
+
ignoredToolNames: new Set(ignoredToolNames),
|
|
786
|
+
mcpToolNames: new Set(mcpToolNames),
|
|
787
|
+
emittedToolCalls: /* @__PURE__ */ new Set(),
|
|
788
|
+
blockCounter: 0
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
function closeOpenBlocks(state) {
|
|
792
|
+
const parts = [];
|
|
793
|
+
if (state.openReasoningBlockId) {
|
|
794
|
+
parts.push({ type: "reasoning-end", id: state.openReasoningBlockId });
|
|
795
|
+
state.openReasoningBlockId = void 0;
|
|
796
|
+
}
|
|
797
|
+
if (state.openTextBlockId) {
|
|
798
|
+
parts.push({ type: "text-end", id: state.openTextBlockId });
|
|
799
|
+
state.openTextBlockId = void 0;
|
|
800
|
+
}
|
|
801
|
+
return parts;
|
|
802
|
+
}
|
|
803
|
+
function toolCallPart(state, toolCall) {
|
|
804
|
+
const isMcpTool = state.mcpToolNames.has(toolCall.toolName) && !state.hostToolNames.has(toolCall.toolName);
|
|
805
|
+
if (isMcpTool) {
|
|
806
|
+
state.dynamicToolCallIds.add(toolCall.toolCallId);
|
|
807
|
+
}
|
|
808
|
+
return {
|
|
809
|
+
type: "tool-call",
|
|
810
|
+
toolCallId: toolCall.toolCallId,
|
|
811
|
+
toolName: toolCall.toolName,
|
|
812
|
+
input: JSON.stringify(toolCall.input ?? {}),
|
|
813
|
+
/*
|
|
814
|
+
* Built-ins and external MCP tools are executed by the Cline runtime.
|
|
815
|
+
* Typed host tools wait for dispatch through `submitToolResult`, including
|
|
816
|
+
* when a host tool has the same name as a configured MCP tool.
|
|
817
|
+
*/
|
|
818
|
+
...state.builtinToolNames.has(toolCall.toolName) || isMcpTool ? { providerExecuted: true } : {},
|
|
819
|
+
...isMcpTool ? { dynamic: true } : {}
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
function toolApprovalParts(state, toolCall) {
|
|
823
|
+
const parts = [];
|
|
824
|
+
if (!state.emittedToolCalls.has(toolCall.toolCallId)) {
|
|
825
|
+
state.emittedToolCalls.add(toolCall.toolCallId);
|
|
826
|
+
parts.push(toolCallPart(state, toolCall));
|
|
827
|
+
}
|
|
828
|
+
parts.push({
|
|
829
|
+
type: "tool-approval-request",
|
|
830
|
+
approvalId: toolCall.toolCallId,
|
|
831
|
+
toolCallId: toolCall.toolCallId
|
|
832
|
+
});
|
|
833
|
+
return parts;
|
|
834
|
+
}
|
|
835
|
+
function translateClineEvent(event, state) {
|
|
836
|
+
switch (event.type) {
|
|
837
|
+
case "assistant-reasoning-delta": {
|
|
838
|
+
const parts = [];
|
|
839
|
+
if (state.openTextBlockId) {
|
|
840
|
+
parts.push({ type: "text-end", id: state.openTextBlockId });
|
|
841
|
+
state.openTextBlockId = void 0;
|
|
842
|
+
}
|
|
843
|
+
if (!state.openReasoningBlockId) {
|
|
844
|
+
state.openReasoningBlockId = `reasoning-${++state.blockCounter}`;
|
|
845
|
+
parts.push({ type: "reasoning-start", id: state.openReasoningBlockId });
|
|
846
|
+
}
|
|
847
|
+
parts.push({
|
|
848
|
+
type: "reasoning-delta",
|
|
849
|
+
id: state.openReasoningBlockId,
|
|
850
|
+
delta: event.text
|
|
851
|
+
});
|
|
852
|
+
return parts;
|
|
853
|
+
}
|
|
854
|
+
case "assistant-text-delta": {
|
|
855
|
+
const parts = [];
|
|
856
|
+
if (state.openReasoningBlockId) {
|
|
857
|
+
parts.push({ type: "reasoning-end", id: state.openReasoningBlockId });
|
|
858
|
+
state.openReasoningBlockId = void 0;
|
|
859
|
+
}
|
|
860
|
+
if (!state.openTextBlockId) {
|
|
861
|
+
state.openTextBlockId = `text-${++state.blockCounter}`;
|
|
862
|
+
parts.push({ type: "text-start", id: state.openTextBlockId });
|
|
863
|
+
}
|
|
864
|
+
parts.push({
|
|
865
|
+
type: "text-delta",
|
|
866
|
+
id: state.openTextBlockId,
|
|
867
|
+
delta: event.text
|
|
868
|
+
});
|
|
869
|
+
return parts;
|
|
870
|
+
}
|
|
871
|
+
case "assistant-message": {
|
|
872
|
+
const parts = closeOpenBlocks(state);
|
|
873
|
+
state.pendingFinishStep = {
|
|
874
|
+
type: "finish-step",
|
|
875
|
+
finishReason: mapModelFinishReason(event.finishReason),
|
|
876
|
+
usage: usageFromMessageMetrics(event.message.metrics)
|
|
877
|
+
};
|
|
878
|
+
return parts;
|
|
879
|
+
}
|
|
880
|
+
case "turn-finished": {
|
|
881
|
+
const pendingFinishStep = state.pendingFinishStep;
|
|
882
|
+
if (!pendingFinishStep) {
|
|
883
|
+
return [];
|
|
884
|
+
}
|
|
885
|
+
state.pendingFinishStep = void 0;
|
|
886
|
+
return [pendingFinishStep];
|
|
887
|
+
}
|
|
888
|
+
case "tool-started": {
|
|
889
|
+
if (state.ignoredToolNames.has(event.toolCall.toolName)) return [];
|
|
890
|
+
if (state.emittedToolCalls.has(event.toolCall.toolCallId)) {
|
|
891
|
+
return [];
|
|
892
|
+
}
|
|
893
|
+
state.emittedToolCalls.add(event.toolCall.toolCallId);
|
|
894
|
+
return [toolCallPart(state, event.toolCall)];
|
|
895
|
+
}
|
|
896
|
+
case "tool-finished": {
|
|
897
|
+
if (state.ignoredToolNames.has(event.toolCall.toolName)) return [];
|
|
898
|
+
const resultPart = event.message.content.find(
|
|
899
|
+
(part) => part.type === "tool-result" && part.toolCallId === event.toolCall.toolCallId
|
|
900
|
+
);
|
|
901
|
+
const output = resultPart?.output;
|
|
902
|
+
const dynamic = state.dynamicToolCallIds.delete(
|
|
903
|
+
event.toolCall.toolCallId
|
|
904
|
+
);
|
|
905
|
+
return [
|
|
906
|
+
{
|
|
907
|
+
type: "tool-result",
|
|
908
|
+
toolCallId: event.toolCall.toolCallId,
|
|
909
|
+
toolName: event.toolCall.toolName,
|
|
910
|
+
result: toToolResultValue(output),
|
|
911
|
+
...resultPart?.isError ? { isError: true } : {},
|
|
912
|
+
...dynamic ? { dynamic: true } : {}
|
|
913
|
+
}
|
|
914
|
+
];
|
|
915
|
+
}
|
|
916
|
+
case "tool-updated":
|
|
917
|
+
return [
|
|
918
|
+
{
|
|
919
|
+
type: "raw",
|
|
920
|
+
rawValue: {
|
|
921
|
+
type: "tool-updated",
|
|
922
|
+
toolCallId: event.toolCall.toolCallId,
|
|
923
|
+
toolName: event.toolCall.toolName,
|
|
924
|
+
update: event.update
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
];
|
|
928
|
+
case "status-notice":
|
|
929
|
+
return [
|
|
930
|
+
{
|
|
931
|
+
type: "raw",
|
|
932
|
+
rawValue: {
|
|
933
|
+
type: "status-notice",
|
|
934
|
+
message: event.message,
|
|
935
|
+
...event.metadata ? { metadata: event.metadata } : {}
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
];
|
|
939
|
+
default:
|
|
940
|
+
return [];
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
function finishClineTranslation(state) {
|
|
944
|
+
const parts = closeOpenBlocks(state);
|
|
945
|
+
state.pendingFinishStep = void 0;
|
|
946
|
+
return parts;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// src/cline-session.ts
|
|
950
|
+
var HARNESS_ID2 = "cline";
|
|
951
|
+
var parkedClineSessions = /* @__PURE__ */ new Map();
|
|
952
|
+
function isAbortError(value) {
|
|
953
|
+
if (value == null) return false;
|
|
954
|
+
const name = value.name;
|
|
955
|
+
if (name === "AbortError" || name === "AgentRuntimeAbortError") {
|
|
956
|
+
return true;
|
|
957
|
+
}
|
|
958
|
+
const text = typeof value === "string" ? value : value instanceof Error ? value.message : String(value);
|
|
959
|
+
return /\baborted\b|AbortError/i.test(text);
|
|
960
|
+
}
|
|
961
|
+
function clineBuiltinToolRequiresApproval(input) {
|
|
962
|
+
if (input.permissionMode === "allow-all") return false;
|
|
963
|
+
if (input.permissionMode === "allow-edits") return input.kind === "bash";
|
|
964
|
+
return input.kind === "edit" || input.kind === "bash";
|
|
965
|
+
}
|
|
966
|
+
function buildSystemPrompt(input) {
|
|
967
|
+
const sections = [
|
|
968
|
+
"You are Cline, an autonomous coding agent operating inside a sandboxed workspace.",
|
|
969
|
+
`## Workspace
|
|
970
|
+
|
|
971
|
+
The workspace root is \`${input.sessionWorkDir}\`. All relative file paths and shell commands resolve against it. Use the provided tools (read, write, edit, bash, grep, glob, ls) to inspect and modify the workspace.`,
|
|
972
|
+
`## Sandbox
|
|
973
|
+
|
|
974
|
+
${input.sandboxDescription}`
|
|
975
|
+
];
|
|
976
|
+
if (input.skillsSection) {
|
|
977
|
+
sections.push(input.skillsSection);
|
|
978
|
+
}
|
|
979
|
+
return sections.join("\n\n");
|
|
980
|
+
}
|
|
981
|
+
function toClineBackendProviderBaseUrl(baseUrl) {
|
|
982
|
+
return `${baseUrl.replace(/\/+$/, "")}/api/v1`;
|
|
983
|
+
}
|
|
984
|
+
function toAiGatewayProviderBaseUrl(baseUrl) {
|
|
985
|
+
const trimmed = baseUrl.replace(/\/+$/, "");
|
|
986
|
+
return trimmed.endsWith("/v1") ? trimmed : `${trimmed}/v1`;
|
|
987
|
+
}
|
|
988
|
+
function createClineAgentModel({
|
|
989
|
+
settings,
|
|
990
|
+
clientApp
|
|
991
|
+
}) {
|
|
992
|
+
const gatewayBaseUrl = settings.authEnv.AI_GATEWAY_BASE_URL;
|
|
993
|
+
const isAiGateway = gatewayBaseUrl != null;
|
|
994
|
+
const providerId = isAiGateway ? "cline" : settings.providerId ?? "cline";
|
|
995
|
+
const apiKey = isAiGateway ? settings.authEnv.AI_GATEWAY_API_KEY : settings.apiKey ?? (providerId === "cline" ? settings.authEnv.CLINE_API_KEY : void 0);
|
|
996
|
+
const baseUrl = isAiGateway ? toAiGatewayProviderBaseUrl(gatewayBaseUrl) : settings.baseUrl ?? (providerId === "cline" && settings.authEnv.CLINE_API_BASE_URL ? toClineBackendProviderBaseUrl(settings.authEnv.CLINE_API_BASE_URL) : void 0);
|
|
997
|
+
const headers = isAiGateway ? {
|
|
998
|
+
...settings.headers,
|
|
999
|
+
"User-Agent": clientApp,
|
|
1000
|
+
"x-client-app": clientApp
|
|
1001
|
+
} : settings.headers;
|
|
1002
|
+
const gateway = Llms.createGateway({
|
|
1003
|
+
providerConfigs: [
|
|
1004
|
+
{
|
|
1005
|
+
providerId,
|
|
1006
|
+
...apiKey ? { apiKey } : {},
|
|
1007
|
+
...baseUrl ? { baseUrl } : {},
|
|
1008
|
+
...headers ? { headers } : {},
|
|
1009
|
+
...isAiGateway ? { apiKeyEnv: [] } : {}
|
|
1010
|
+
}
|
|
1011
|
+
]
|
|
1012
|
+
});
|
|
1013
|
+
const modelSelection = {
|
|
1014
|
+
providerId,
|
|
1015
|
+
...settings.modelId ? { modelId: settings.modelId } : {}
|
|
1016
|
+
};
|
|
1017
|
+
const modelOptions = settings.reasoningEffort === "none" ? { reasoning: { enabled: false } } : {
|
|
1018
|
+
reasoning: {
|
|
1019
|
+
enabled: true,
|
|
1020
|
+
effort: settings.reasoningEffort
|
|
1021
|
+
}
|
|
1022
|
+
};
|
|
1023
|
+
const model = settings.reasoningEffort === void 0 ? gateway.createAgentModel(modelSelection) : gateway.createAgentModel(modelSelection, modelOptions);
|
|
1024
|
+
return {
|
|
1025
|
+
model,
|
|
1026
|
+
providerId
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
async function createClineSession(input) {
|
|
1030
|
+
if (input.isResume) {
|
|
1031
|
+
const parkedSession = parkedClineSessions.get(input.sessionId);
|
|
1032
|
+
if (parkedSession) {
|
|
1033
|
+
parkedClineSessions.delete(input.sessionId);
|
|
1034
|
+
return {
|
|
1035
|
+
...parkedSession,
|
|
1036
|
+
isResume: true
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
const sandbox = input.sandboxSession.restricted();
|
|
1041
|
+
const sandboxHomeDir = await resolveSandboxHomeDir({
|
|
1042
|
+
sandbox,
|
|
1043
|
+
...input.abortSignal ? { abortSignal: input.abortSignal } : {}
|
|
1044
|
+
});
|
|
1045
|
+
const privateSessionDir = resolveClinePrivateSessionDirectory({
|
|
1046
|
+
sandboxHomeDir,
|
|
1047
|
+
sessionWorkDir: input.sessionWorkDir,
|
|
1048
|
+
sessionId: input.sessionId
|
|
1049
|
+
});
|
|
1050
|
+
const permissionMode = input.permissionMode ?? "allow-all";
|
|
1051
|
+
const activeBuiltinNames = resolveActiveClineBuiltinNames(
|
|
1052
|
+
input.builtinToolFiltering
|
|
1053
|
+
);
|
|
1054
|
+
const ops = createClineRemoteOps({
|
|
1055
|
+
sandbox,
|
|
1056
|
+
workDir: input.sessionWorkDir
|
|
1057
|
+
});
|
|
1058
|
+
let skillsSection;
|
|
1059
|
+
if (input.skills.length > 0) {
|
|
1060
|
+
const skillRootDir = await writeClineSkills({
|
|
1061
|
+
sandbox,
|
|
1062
|
+
sandboxHomeDir,
|
|
1063
|
+
skills: input.skills,
|
|
1064
|
+
...input.abortSignal ? { abortSignal: input.abortSignal } : {}
|
|
1065
|
+
});
|
|
1066
|
+
if (skillRootDir) {
|
|
1067
|
+
skillsSection = renderSkillsPromptSection(input.skills, skillRootDir);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
const baseSystemPrompt = buildSystemPrompt({
|
|
1071
|
+
sessionWorkDir: input.sessionWorkDir,
|
|
1072
|
+
sandboxDescription: sandbox.description,
|
|
1073
|
+
...skillsSection ? { skillsSection } : {}
|
|
1074
|
+
});
|
|
1075
|
+
let currentMessages = [];
|
|
1076
|
+
if (input.isResume && input.resumeHistoryFileName) {
|
|
1077
|
+
const restored = await pullHistoryFromSandbox({
|
|
1078
|
+
sandbox,
|
|
1079
|
+
privateSessionDir,
|
|
1080
|
+
historyFileName: safeClineHistoryFileName(input.resumeHistoryFileName),
|
|
1081
|
+
...input.abortSignal ? { abortSignal: input.abortSignal } : {}
|
|
1082
|
+
});
|
|
1083
|
+
if (restored) {
|
|
1084
|
+
currentMessages = restored;
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
const mcpRuntime = await createClineMcpRuntime({
|
|
1088
|
+
mcpServers: input.settings.mcpServers
|
|
1089
|
+
});
|
|
1090
|
+
const { model, providerId } = createClineAgentModel({
|
|
1091
|
+
settings: input.settings,
|
|
1092
|
+
clientApp: input.clientApp
|
|
1093
|
+
});
|
|
1094
|
+
let agent;
|
|
1095
|
+
let unsubscribe;
|
|
1096
|
+
let lastToolsSignature;
|
|
1097
|
+
let agentHasRun = false;
|
|
1098
|
+
let stopped = false;
|
|
1099
|
+
let suspending = false;
|
|
1100
|
+
const pendingToolResults = /* @__PURE__ */ new Map();
|
|
1101
|
+
const pendingToolApprovals = /* @__PURE__ */ new Map();
|
|
1102
|
+
const pendingUserMessages = [];
|
|
1103
|
+
let currentEmit;
|
|
1104
|
+
let translatorState;
|
|
1105
|
+
let activeTurn;
|
|
1106
|
+
function settlePendingToolResults(reason) {
|
|
1107
|
+
for (const pending of pendingToolResults.values()) {
|
|
1108
|
+
pending.resolve(
|
|
1109
|
+
createClineToolResult({
|
|
1110
|
+
output: { error: reason },
|
|
1111
|
+
isError: true
|
|
1112
|
+
})
|
|
1113
|
+
);
|
|
1114
|
+
}
|
|
1115
|
+
pendingToolResults.clear();
|
|
1116
|
+
}
|
|
1117
|
+
function settlePendingToolApprovals(reason) {
|
|
1118
|
+
for (const pending of pendingToolApprovals.values()) {
|
|
1119
|
+
pending.resolve({ approved: false, reason });
|
|
1120
|
+
}
|
|
1121
|
+
pendingToolApprovals.clear();
|
|
1122
|
+
}
|
|
1123
|
+
async function persistHistory() {
|
|
1124
|
+
const messages = agent?.snapshot().messages ?? currentMessages;
|
|
1125
|
+
await persistHistoryToSandbox({
|
|
1126
|
+
sandbox,
|
|
1127
|
+
privateSessionDir,
|
|
1128
|
+
historyFileName: CLINE_DEFAULT_HISTORY_FILE_NAME,
|
|
1129
|
+
messages
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
function requestBuiltinToolApproval(toolCall) {
|
|
1133
|
+
if (translatorState && currentEmit) {
|
|
1134
|
+
for (const part of toolApprovalParts(translatorState, toolCall)) {
|
|
1135
|
+
currentEmit(part);
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
return new Promise((resolve) => {
|
|
1139
|
+
pendingToolApprovals.set(toolCall.toolCallId, { resolve });
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
function rebuildAgent(rebuildInput) {
|
|
1143
|
+
unsubscribe?.();
|
|
1144
|
+
unsubscribe = void 0;
|
|
1145
|
+
agent = new Agent({
|
|
1146
|
+
model,
|
|
1147
|
+
...input.settings.modelId ? {
|
|
1148
|
+
messageModelInfo: {
|
|
1149
|
+
id: input.settings.modelId,
|
|
1150
|
+
provider: providerId
|
|
1151
|
+
}
|
|
1152
|
+
} : {},
|
|
1153
|
+
sessionId: input.sessionId,
|
|
1154
|
+
systemPrompt: rebuildInput.instructions ? `${baseSystemPrompt}
|
|
1155
|
+
|
|
1156
|
+
${rebuildInput.instructions}` : baseSystemPrompt,
|
|
1157
|
+
initialMessages: currentMessages,
|
|
1158
|
+
toolExecution: "parallel",
|
|
1159
|
+
...input.settings.maxIterations !== void 0 ? { maxIterations: input.settings.maxIterations } : {},
|
|
1160
|
+
tools: [
|
|
1161
|
+
...buildBuiltinAgentTools({ ops, activeNames: activeBuiltinNames }),
|
|
1162
|
+
...mcpRuntime.tools,
|
|
1163
|
+
...buildUserAgentTools({
|
|
1164
|
+
specs: rebuildInput.userTools,
|
|
1165
|
+
pendingToolResults
|
|
1166
|
+
}),
|
|
1167
|
+
...rebuildInput.responseFormat?.type === "json" && rebuildInput.responseFormat.schema != null ? [
|
|
1168
|
+
createTool2({
|
|
1169
|
+
name: "structured_output",
|
|
1170
|
+
description: rebuildInput.responseFormat.description ?? "Return the final structured output.",
|
|
1171
|
+
inputSchema: {
|
|
1172
|
+
type: "object",
|
|
1173
|
+
properties: {
|
|
1174
|
+
output: rebuildInput.responseFormat.schema
|
|
1175
|
+
},
|
|
1176
|
+
required: ["output"],
|
|
1177
|
+
additionalProperties: false
|
|
1178
|
+
},
|
|
1179
|
+
lifecycle: { completesRun: true },
|
|
1180
|
+
execute: async ({ output }) => JSON.stringify(output)
|
|
1181
|
+
})
|
|
1182
|
+
] : []
|
|
1183
|
+
],
|
|
1184
|
+
...rebuildInput.responseFormat?.type === "json" ? { completionPolicy: { requireCompletionTool: true } } : {},
|
|
1185
|
+
hooks: {
|
|
1186
|
+
afterTool: ({ result }) => {
|
|
1187
|
+
const toolResult = unwrapClineToolResult(result.output);
|
|
1188
|
+
return toolResult == null ? void 0 : { result: toolResult };
|
|
1189
|
+
}
|
|
1190
|
+
},
|
|
1191
|
+
// Built-in tools whose kind requires approval under the session's
|
|
1192
|
+
// permission mode are marked `autoApprove: false`; the runtime then
|
|
1193
|
+
// routes them through `requestToolApproval` below. User tools are
|
|
1194
|
+
// approved by the harness framework, never by the adapter.
|
|
1195
|
+
toolPolicies: Object.fromEntries(
|
|
1196
|
+
activeBuiltinNames.filter(
|
|
1197
|
+
(name) => clineBuiltinToolRequiresApproval({
|
|
1198
|
+
permissionMode,
|
|
1199
|
+
kind: CLINE_NATIVE_TOOL_KINDS[name]
|
|
1200
|
+
})
|
|
1201
|
+
).map((name) => [name, { autoApprove: false }])
|
|
1202
|
+
),
|
|
1203
|
+
requestToolApproval: (request) => {
|
|
1204
|
+
if (!isClineBuiltinToolName(request.toolName)) {
|
|
1205
|
+
return { approved: true };
|
|
1206
|
+
}
|
|
1207
|
+
return requestBuiltinToolApproval({
|
|
1208
|
+
toolCallId: request.toolCallId,
|
|
1209
|
+
toolName: request.toolName,
|
|
1210
|
+
input: request.input
|
|
1211
|
+
});
|
|
1212
|
+
},
|
|
1213
|
+
// Mid-turn user messages injected via `submitUserMessage` are consumed
|
|
1214
|
+
// by the runtime between loop iterations, before the next model call.
|
|
1215
|
+
consumePendingUserMessage: () => pendingUserMessages.shift()
|
|
1216
|
+
});
|
|
1217
|
+
agentHasRun = false;
|
|
1218
|
+
unsubscribe = agent.subscribe((event) => {
|
|
1219
|
+
if (!translatorState || !currentEmit) return;
|
|
1220
|
+
for (const part of translateClineEvent(event, translatorState)) {
|
|
1221
|
+
currentEmit(part);
|
|
1222
|
+
}
|
|
1223
|
+
});
|
|
1224
|
+
}
|
|
1225
|
+
function createPromptControl(controlInput) {
|
|
1226
|
+
const abortHandler = () => {
|
|
1227
|
+
settlePendingToolResults("Turn aborted");
|
|
1228
|
+
settlePendingToolApprovals("Turn aborted");
|
|
1229
|
+
agent?.abort("Turn aborted by caller");
|
|
1230
|
+
};
|
|
1231
|
+
if (controlInput.abortSignal) {
|
|
1232
|
+
controlInput.abortSignal.addEventListener("abort", abortHandler, {
|
|
1233
|
+
once: true
|
|
1234
|
+
});
|
|
1235
|
+
void controlInput.done.then(
|
|
1236
|
+
() => {
|
|
1237
|
+
controlInput.abortSignal?.removeEventListener("abort", abortHandler);
|
|
1238
|
+
},
|
|
1239
|
+
() => {
|
|
1240
|
+
controlInput.abortSignal?.removeEventListener("abort", abortHandler);
|
|
1241
|
+
}
|
|
1242
|
+
);
|
|
1243
|
+
}
|
|
1244
|
+
return {
|
|
1245
|
+
async submitToolResult(args) {
|
|
1246
|
+
const pending = pendingToolResults.get(args.toolCallId);
|
|
1247
|
+
if (!pending) return;
|
|
1248
|
+
pendingToolResults.delete(args.toolCallId);
|
|
1249
|
+
pending.resolve(
|
|
1250
|
+
createClineToolResult({
|
|
1251
|
+
output: args.output,
|
|
1252
|
+
isError: args.isError
|
|
1253
|
+
})
|
|
1254
|
+
);
|
|
1255
|
+
},
|
|
1256
|
+
async submitToolApproval(args) {
|
|
1257
|
+
const pending = pendingToolApprovals.get(args.approvalId);
|
|
1258
|
+
if (!pending) return;
|
|
1259
|
+
pendingToolApprovals.delete(args.approvalId);
|
|
1260
|
+
pending.resolve({
|
|
1261
|
+
approved: args.approved,
|
|
1262
|
+
...args.reason !== void 0 ? { reason: args.reason } : {}
|
|
1263
|
+
});
|
|
1264
|
+
},
|
|
1265
|
+
async submitUserMessage(text) {
|
|
1266
|
+
pendingUserMessages.push(text);
|
|
1267
|
+
},
|
|
1268
|
+
done: controlInput.done
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
function runTurn(turnOpts) {
|
|
1272
|
+
if (stopped) {
|
|
1273
|
+
throw new Error("Cline session has been stopped.");
|
|
1274
|
+
}
|
|
1275
|
+
const userTools = turnOpts.tools;
|
|
1276
|
+
if (turnOpts.responseFormat?.type === "json" && turnOpts.responseFormat.schema == null) {
|
|
1277
|
+
throw new HarnessCapabilityUnsupportedError2({
|
|
1278
|
+
message: "Harness 'cline' requires a JSON schema for structured output.",
|
|
1279
|
+
harnessId: HARNESS_ID2
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
if (turnOpts.responseFormat?.type === "json" && providerId === "openai-codex-cli") {
|
|
1283
|
+
throw new HarnessCapabilityUnsupportedError2({
|
|
1284
|
+
message: "Harness 'cline' cannot require structured output with the openai-codex-cli provider because that provider does not expose external tools.",
|
|
1285
|
+
harnessId: HARNESS_ID2
|
|
1286
|
+
});
|
|
1287
|
+
}
|
|
1288
|
+
const signature = JSON.stringify({
|
|
1289
|
+
tools: userTools.map((t) => t.name).sort(),
|
|
1290
|
+
responseFormat: turnOpts.responseFormat
|
|
1291
|
+
});
|
|
1292
|
+
if (agent == null || signature !== lastToolsSignature) {
|
|
1293
|
+
currentMessages = agent?.snapshot().messages ?? currentMessages;
|
|
1294
|
+
rebuildAgent({
|
|
1295
|
+
userTools,
|
|
1296
|
+
responseFormat: turnOpts.responseFormat,
|
|
1297
|
+
...turnOpts.instructions ? { instructions: turnOpts.instructions } : {}
|
|
1298
|
+
});
|
|
1299
|
+
lastToolsSignature = signature;
|
|
1300
|
+
}
|
|
1301
|
+
currentEmit = turnOpts.emit;
|
|
1302
|
+
translatorState = createClineTranslatorState({
|
|
1303
|
+
builtinToolNames: activeBuiltinNames,
|
|
1304
|
+
hostToolNames: userTools.map((tool2) => tool2.name),
|
|
1305
|
+
ignoredToolNames: turnOpts.responseFormat?.type === "json" ? ["structured_output"] : [],
|
|
1306
|
+
mcpToolNames: mcpRuntime.toolNames
|
|
1307
|
+
});
|
|
1308
|
+
turnOpts.emit({
|
|
1309
|
+
type: "stream-start",
|
|
1310
|
+
...input.settings.modelId ? { modelId: input.settings.modelId } : {}
|
|
1311
|
+
});
|
|
1312
|
+
const turnPromise = (async () => {
|
|
1313
|
+
const runtime = agent;
|
|
1314
|
+
let result;
|
|
1315
|
+
try {
|
|
1316
|
+
result = turnOpts.text === void 0 ? await runtime.continue() : agentHasRun ? await runtime.continue(turnOpts.text) : await runtime.run(turnOpts.text);
|
|
1317
|
+
agentHasRun = true;
|
|
1318
|
+
} catch (error) {
|
|
1319
|
+
if (suspending && isAbortError(error)) return;
|
|
1320
|
+
currentEmit?.({ type: "error", error });
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1323
|
+
currentMessages = result.messages;
|
|
1324
|
+
if (translatorState) {
|
|
1325
|
+
for (const part of finishClineTranslation(translatorState)) {
|
|
1326
|
+
currentEmit?.(part);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
if (turnOpts.responseFormat?.type === "json" && result.status === "completed") {
|
|
1330
|
+
const id = `structured-output-${input.sessionId}`;
|
|
1331
|
+
currentEmit?.({ type: "text-start", id });
|
|
1332
|
+
currentEmit?.({
|
|
1333
|
+
type: "text-delta",
|
|
1334
|
+
id,
|
|
1335
|
+
delta: result.outputText
|
|
1336
|
+
});
|
|
1337
|
+
currentEmit?.({ type: "text-end", id });
|
|
1338
|
+
currentEmit?.({
|
|
1339
|
+
type: "finish-step",
|
|
1340
|
+
finishReason: { unified: "stop", raw: "structured-output" },
|
|
1341
|
+
usage: {
|
|
1342
|
+
inputTokens: {
|
|
1343
|
+
total: 0,
|
|
1344
|
+
noCache: void 0,
|
|
1345
|
+
cacheRead: void 0,
|
|
1346
|
+
cacheWrite: void 0
|
|
1347
|
+
},
|
|
1348
|
+
outputTokens: {
|
|
1349
|
+
total: 0,
|
|
1350
|
+
text: void 0,
|
|
1351
|
+
reasoning: void 0
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
if (result.status === "aborted") {
|
|
1357
|
+
if (suspending) return;
|
|
1358
|
+
currentEmit?.({
|
|
1359
|
+
type: "finish",
|
|
1360
|
+
finishReason: mapRunFinishReason(result.status),
|
|
1361
|
+
totalUsage: usageFromAgentUsage(result.usage)
|
|
1362
|
+
});
|
|
1363
|
+
return;
|
|
1364
|
+
}
|
|
1365
|
+
if (result.status === "failed") {
|
|
1366
|
+
currentEmit?.({
|
|
1367
|
+
type: "error",
|
|
1368
|
+
error: result.error ?? new Error("Cline agent run failed")
|
|
1369
|
+
});
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
currentEmit?.({
|
|
1373
|
+
type: "finish",
|
|
1374
|
+
finishReason: mapRunFinishReason(result.status),
|
|
1375
|
+
totalUsage: usageFromAgentUsage(result.usage)
|
|
1376
|
+
});
|
|
1377
|
+
})();
|
|
1378
|
+
const activeTurnToken = {};
|
|
1379
|
+
const done = turnPromise.finally(() => {
|
|
1380
|
+
if (activeTurn?.token === activeTurnToken) {
|
|
1381
|
+
activeTurn = void 0;
|
|
1382
|
+
}
|
|
1383
|
+
currentEmit = void 0;
|
|
1384
|
+
});
|
|
1385
|
+
activeTurn = {
|
|
1386
|
+
token: activeTurnToken,
|
|
1387
|
+
done
|
|
1388
|
+
};
|
|
1389
|
+
return createPromptControl({
|
|
1390
|
+
done,
|
|
1391
|
+
...turnOpts.abortSignal ? { abortSignal: turnOpts.abortSignal } : {}
|
|
1392
|
+
});
|
|
1393
|
+
}
|
|
1394
|
+
async function teardown() {
|
|
1395
|
+
unsubscribe?.();
|
|
1396
|
+
unsubscribe = void 0;
|
|
1397
|
+
agent = void 0;
|
|
1398
|
+
await mcpRuntime.dispose();
|
|
1399
|
+
}
|
|
1400
|
+
const doStop = async () => {
|
|
1401
|
+
if (stopped) {
|
|
1402
|
+
throw new Error("Cline session has been stopped.");
|
|
1403
|
+
}
|
|
1404
|
+
stopped = true;
|
|
1405
|
+
parkedClineSessions.delete(input.sessionId);
|
|
1406
|
+
settlePendingToolResults("Cline session stopped");
|
|
1407
|
+
settlePendingToolApprovals("Cline session stopped");
|
|
1408
|
+
try {
|
|
1409
|
+
await persistHistory();
|
|
1410
|
+
} catch {
|
|
1411
|
+
}
|
|
1412
|
+
agent?.abort("Cline session stopped");
|
|
1413
|
+
await teardown();
|
|
1414
|
+
return {
|
|
1415
|
+
type: "resume-session",
|
|
1416
|
+
harnessId: HARNESS_ID2,
|
|
1417
|
+
specificationVersion: "harness-v1",
|
|
1418
|
+
data: { historyFileName: CLINE_DEFAULT_HISTORY_FILE_NAME }
|
|
1419
|
+
};
|
|
1420
|
+
};
|
|
1421
|
+
const sessionImpl = {
|
|
1422
|
+
sessionId: input.sessionId,
|
|
1423
|
+
isResume: input.isResume,
|
|
1424
|
+
...input.settings.modelId ? { modelId: input.settings.modelId } : {},
|
|
1425
|
+
// The Cline runtime has no bridge to attach to and no in-sandbox event
|
|
1426
|
+
// log to replay; its only cross-process resume path is restoring the
|
|
1427
|
+
// persisted history on a fresh runtime, i.e. `rerun`.
|
|
1428
|
+
doPromptTurn: async (promptOpts) => {
|
|
1429
|
+
return runTurn({
|
|
1430
|
+
text: extractUserText(promptOpts.prompt),
|
|
1431
|
+
tools: promptOpts.tools ?? [],
|
|
1432
|
+
...promptOpts.instructions ? { instructions: promptOpts.instructions } : {},
|
|
1433
|
+
emit: promptOpts.emit,
|
|
1434
|
+
...promptOpts.abortSignal ? { abortSignal: promptOpts.abortSignal } : {},
|
|
1435
|
+
responseFormat: promptOpts.responseFormat
|
|
1436
|
+
});
|
|
1437
|
+
},
|
|
1438
|
+
doContinueTurn: async (continueOpts) => {
|
|
1439
|
+
if (activeTurn != null) {
|
|
1440
|
+
currentEmit = continueOpts.emit;
|
|
1441
|
+
return createPromptControl({
|
|
1442
|
+
done: activeTurn.done,
|
|
1443
|
+
...continueOpts.abortSignal ? { abortSignal: continueOpts.abortSignal } : {}
|
|
1444
|
+
});
|
|
1445
|
+
}
|
|
1446
|
+
return runTurn({
|
|
1447
|
+
text: void 0,
|
|
1448
|
+
tools: continueOpts.tools ?? [],
|
|
1449
|
+
...continueOpts.instructions ? { instructions: continueOpts.instructions } : {},
|
|
1450
|
+
emit: continueOpts.emit,
|
|
1451
|
+
...continueOpts.abortSignal ? { abortSignal: continueOpts.abortSignal } : {},
|
|
1452
|
+
responseFormat: continueOpts.responseFormat
|
|
1453
|
+
});
|
|
1454
|
+
},
|
|
1455
|
+
doCompact: async () => {
|
|
1456
|
+
throw new HarnessCapabilityUnsupportedError2({
|
|
1457
|
+
message: "cline: manual compaction is not supported by the standalone Cline runtime.",
|
|
1458
|
+
harnessId: HARNESS_ID2
|
|
1459
|
+
});
|
|
1460
|
+
},
|
|
1461
|
+
doDestroy: async () => {
|
|
1462
|
+
if (stopped) return;
|
|
1463
|
+
stopped = true;
|
|
1464
|
+
parkedClineSessions.delete(input.sessionId);
|
|
1465
|
+
settlePendingToolResults("Cline session stopped");
|
|
1466
|
+
settlePendingToolApprovals("Cline session stopped");
|
|
1467
|
+
agent?.abort("Cline session destroyed");
|
|
1468
|
+
await teardown();
|
|
1469
|
+
},
|
|
1470
|
+
doStop,
|
|
1471
|
+
doDetach: async () => {
|
|
1472
|
+
if (activeTurn != null || pendingToolResults.size > 0) {
|
|
1473
|
+
parkedClineSessions.set(input.sessionId, sessionImpl);
|
|
1474
|
+
try {
|
|
1475
|
+
await persistHistory();
|
|
1476
|
+
} catch {
|
|
1477
|
+
}
|
|
1478
|
+
return {
|
|
1479
|
+
type: "resume-session",
|
|
1480
|
+
harnessId: HARNESS_ID2,
|
|
1481
|
+
specificationVersion: "harness-v1",
|
|
1482
|
+
data: { historyFileName: CLINE_DEFAULT_HISTORY_FILE_NAME }
|
|
1483
|
+
};
|
|
1484
|
+
}
|
|
1485
|
+
return doStop();
|
|
1486
|
+
},
|
|
1487
|
+
doSuspendTurn: async () => {
|
|
1488
|
+
if (stopped) {
|
|
1489
|
+
throw new Error("Cline session has been stopped.");
|
|
1490
|
+
}
|
|
1491
|
+
if (activeTurn != null && (pendingToolResults.size > 0 || pendingToolApprovals.size > 0)) {
|
|
1492
|
+
parkedClineSessions.set(input.sessionId, sessionImpl);
|
|
1493
|
+
try {
|
|
1494
|
+
await persistHistory();
|
|
1495
|
+
} catch {
|
|
1496
|
+
}
|
|
1497
|
+
return {
|
|
1498
|
+
type: "continue-turn",
|
|
1499
|
+
harnessId: HARNESS_ID2,
|
|
1500
|
+
specificationVersion: "harness-v1",
|
|
1501
|
+
data: { historyFileName: CLINE_DEFAULT_HISTORY_FILE_NAME }
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1504
|
+
suspending = true;
|
|
1505
|
+
agent?.abort("Cline session suspended");
|
|
1506
|
+
if (activeTurn) {
|
|
1507
|
+
await activeTurn.done.catch(() => {
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
try {
|
|
1511
|
+
await persistHistory();
|
|
1512
|
+
} catch {
|
|
1513
|
+
}
|
|
1514
|
+
stopped = true;
|
|
1515
|
+
parkedClineSessions.delete(input.sessionId);
|
|
1516
|
+
settlePendingToolResults("Cline session suspended");
|
|
1517
|
+
settlePendingToolApprovals("Cline session suspended");
|
|
1518
|
+
await teardown();
|
|
1519
|
+
return {
|
|
1520
|
+
type: "continue-turn",
|
|
1521
|
+
harnessId: HARNESS_ID2,
|
|
1522
|
+
specificationVersion: "harness-v1",
|
|
1523
|
+
data: { historyFileName: CLINE_DEFAULT_HISTORY_FILE_NAME }
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
};
|
|
1527
|
+
return sessionImpl;
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
// src/version.ts
|
|
1531
|
+
var VERSION = true ? "1.0.1" : "0.0.0-test";
|
|
1532
|
+
|
|
1533
|
+
// src/cline-harness.ts
|
|
1534
|
+
var CLINE_CLIENT_APP = `ai-sdk/harness-cline/${VERSION}`;
|
|
1535
|
+
var CLINE_BUILTIN_TOOLS = {
|
|
1536
|
+
read: commonTool("read", {
|
|
1537
|
+
nativeName: "read",
|
|
1538
|
+
toolUseKind: "readonly",
|
|
1539
|
+
description: "Read file contents.",
|
|
1540
|
+
inputSchema: z2.object({
|
|
1541
|
+
file_path: z2.string()
|
|
1542
|
+
})
|
|
1543
|
+
}),
|
|
1544
|
+
write: commonTool("write", {
|
|
1545
|
+
nativeName: "write",
|
|
1546
|
+
toolUseKind: "edit",
|
|
1547
|
+
description: "Overwrite or create a file.",
|
|
1548
|
+
inputSchema: z2.object({
|
|
1549
|
+
file_path: z2.string(),
|
|
1550
|
+
content: z2.string()
|
|
1551
|
+
})
|
|
1552
|
+
}),
|
|
1553
|
+
edit: commonTool("edit", {
|
|
1554
|
+
nativeName: "edit",
|
|
1555
|
+
toolUseKind: "edit",
|
|
1556
|
+
description: "Edit a file by exact string replacement.",
|
|
1557
|
+
inputSchema: z2.object({
|
|
1558
|
+
file_path: z2.string(),
|
|
1559
|
+
old_string: z2.string(),
|
|
1560
|
+
new_string: z2.string()
|
|
1561
|
+
})
|
|
1562
|
+
}),
|
|
1563
|
+
bash: commonTool("bash", {
|
|
1564
|
+
nativeName: "bash",
|
|
1565
|
+
toolUseKind: "bash",
|
|
1566
|
+
description: "Execute a shell command in the sandbox.",
|
|
1567
|
+
inputSchema: z2.object({
|
|
1568
|
+
command: z2.string(),
|
|
1569
|
+
timeout: z2.number().optional()
|
|
1570
|
+
})
|
|
1571
|
+
}),
|
|
1572
|
+
grep: commonTool("grep", {
|
|
1573
|
+
nativeName: "grep",
|
|
1574
|
+
toolUseKind: "readonly",
|
|
1575
|
+
description: "Search file contents with regex.",
|
|
1576
|
+
inputSchema: z2.object({
|
|
1577
|
+
pattern: z2.string(),
|
|
1578
|
+
path: z2.string().optional(),
|
|
1579
|
+
glob: z2.string().optional(),
|
|
1580
|
+
ignoreCase: z2.boolean().optional(),
|
|
1581
|
+
literal: z2.boolean().optional(),
|
|
1582
|
+
context: z2.number().optional(),
|
|
1583
|
+
limit: z2.number().optional()
|
|
1584
|
+
})
|
|
1585
|
+
}),
|
|
1586
|
+
glob: commonTool("glob", {
|
|
1587
|
+
nativeName: "glob",
|
|
1588
|
+
toolUseKind: "readonly",
|
|
1589
|
+
description: "Find files matching a glob pattern.",
|
|
1590
|
+
inputSchema: z2.object({
|
|
1591
|
+
pattern: z2.string(),
|
|
1592
|
+
path: z2.string().optional(),
|
|
1593
|
+
limit: z2.number().optional()
|
|
1594
|
+
})
|
|
1595
|
+
}),
|
|
1596
|
+
ls: {
|
|
1597
|
+
...tool({
|
|
1598
|
+
description: "List directory entries.",
|
|
1599
|
+
inputSchema: z2.object({
|
|
1600
|
+
path: z2.string().optional(),
|
|
1601
|
+
limit: z2.number().optional()
|
|
1602
|
+
}),
|
|
1603
|
+
outputSchema: z2.unknown()
|
|
1604
|
+
}),
|
|
1605
|
+
nativeName: "ls",
|
|
1606
|
+
toolUseKind: "readonly"
|
|
1607
|
+
}
|
|
1608
|
+
};
|
|
1609
|
+
function createCline(settings = {}) {
|
|
1610
|
+
return {
|
|
1611
|
+
specificationVersion: "harness-v1",
|
|
1612
|
+
harnessId: "cline",
|
|
1613
|
+
builtinTools: CLINE_BUILTIN_TOOLS,
|
|
1614
|
+
supportsBuiltinToolApprovals: true,
|
|
1615
|
+
supportsBuiltinToolFiltering: true,
|
|
1616
|
+
lifecycleStateSchema: clineResumeStateSchema,
|
|
1617
|
+
doStart: async (startOpts) => {
|
|
1618
|
+
const authEnv = resolveClineEnv({ auth: settings.auth });
|
|
1619
|
+
const lifecycleState = startOpts.continueFrom ?? startOpts.resumeFrom;
|
|
1620
|
+
const resumeData = lifecycleState?.data;
|
|
1621
|
+
return createClineSession({
|
|
1622
|
+
sessionId: startOpts.sessionId,
|
|
1623
|
+
sandboxSession: startOpts.sandboxSession,
|
|
1624
|
+
sessionWorkDir: startOpts.sessionWorkDir,
|
|
1625
|
+
skills: startOpts.skills ?? [],
|
|
1626
|
+
settings: {
|
|
1627
|
+
authEnv,
|
|
1628
|
+
...settings.mcpServers ? { mcpServers: settings.mcpServers } : {},
|
|
1629
|
+
...settings.providerId ? { providerId: settings.providerId } : {},
|
|
1630
|
+
...settings.modelId ? { modelId: settings.modelId } : {},
|
|
1631
|
+
...settings.apiKey ? { apiKey: settings.apiKey } : {},
|
|
1632
|
+
...settings.baseUrl ? { baseUrl: settings.baseUrl } : {},
|
|
1633
|
+
...settings.headers ? { headers: settings.headers } : {},
|
|
1634
|
+
...settings.reasoningEffort !== void 0 ? { reasoningEffort: settings.reasoningEffort } : {},
|
|
1635
|
+
...settings.maxIterations !== void 0 ? { maxIterations: settings.maxIterations } : {}
|
|
1636
|
+
},
|
|
1637
|
+
clientApp: CLINE_CLIENT_APP,
|
|
1638
|
+
isResume: lifecycleState != null,
|
|
1639
|
+
...startOpts.permissionMode ? { permissionMode: startOpts.permissionMode } : {},
|
|
1640
|
+
...startOpts.builtinToolFiltering ? { builtinToolFiltering: startOpts.builtinToolFiltering } : {},
|
|
1641
|
+
...resumeData?.historyFileName ? { resumeHistoryFileName: resumeData.historyFileName } : {},
|
|
1642
|
+
...startOpts.abortSignal ? { abortSignal: startOpts.abortSignal } : {}
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
// src/index.ts
|
|
1649
|
+
var cline = createCline();
|
|
1650
|
+
export {
|
|
1651
|
+
VERSION,
|
|
1652
|
+
cline,
|
|
1653
|
+
createCline
|
|
1654
|
+
};
|
|
1655
|
+
//# sourceMappingURL=index.js.map
|