@clinebot/core 0.0.6 → 0.0.7
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/dist/agents/hooks-config-loader.d.ts +1 -0
- package/dist/index.node.d.ts +1 -0
- package/dist/index.node.js +81 -81
- package/dist/session/rpc-spawn-lease.d.ts +7 -0
- package/dist/storage/provider-settings-legacy-migration.d.ts +25 -0
- package/dist/tools/schemas.d.ts +2 -8
- package/dist/types/events.d.ts +1 -1
- package/package.json +3 -3
- package/src/agents/hooks-config-loader.ts +2 -0
- package/src/index.node.ts +4 -0
- package/src/input/file-indexer.test.ts +40 -0
- package/src/input/file-indexer.ts +21 -0
- package/src/runtime/hook-file-hooks.test.ts +51 -1
- package/src/runtime/hook-file-hooks.ts +90 -11
- package/src/session/rpc-spawn-lease.test.ts +49 -0
- package/src/session/rpc-spawn-lease.ts +122 -0
- package/src/session/session-graph.ts +2 -0
- package/src/session/session-host.ts +8 -0
- package/src/storage/provider-settings-legacy-migration.test.ts +133 -1
- package/src/storage/provider-settings-legacy-migration.ts +60 -8
- package/src/tools/definitions.test.ts +82 -29
- package/src/tools/definitions.ts +6 -4
- package/src/tools/executors/editor.test.ts +35 -0
- package/src/tools/executors/editor.ts +33 -46
- package/src/tools/schemas.ts +25 -35
- package/src/types/events.ts +6 -1
|
@@ -2,7 +2,11 @@ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
type LegacyClineUserInfo,
|
|
7
|
+
migrateLegacyProviderSettings,
|
|
8
|
+
resolveLegacyClineAuth,
|
|
9
|
+
} from "./provider-settings-legacy-migration";
|
|
6
10
|
import { ProviderSettingsManager } from "./provider-settings-manager";
|
|
7
11
|
|
|
8
12
|
describe("migrateLegacyProviderSettings", () => {
|
|
@@ -173,3 +177,131 @@ describe("migrateLegacyProviderSettings", () => {
|
|
|
173
177
|
);
|
|
174
178
|
});
|
|
175
179
|
});
|
|
180
|
+
|
|
181
|
+
// =============================================================================
|
|
182
|
+
// resolveLegacyClineAuth – pure in-memory tests
|
|
183
|
+
// =============================================================================
|
|
184
|
+
|
|
185
|
+
/** Builds a realistic LegacyClineUserInfo JSON string. */
|
|
186
|
+
function makeClineAccountJson(
|
|
187
|
+
overrides: Partial<LegacyClineUserInfo> & { userId?: string } = {},
|
|
188
|
+
): string {
|
|
189
|
+
return JSON.stringify({
|
|
190
|
+
idToken: overrides.idToken ?? "id-token-abc",
|
|
191
|
+
expiresAt: overrides.expiresAt ?? 1750000000000,
|
|
192
|
+
refreshToken: overrides.refreshToken ?? "refresh-token-xyz",
|
|
193
|
+
userInfo: overrides.userInfo ?? {
|
|
194
|
+
id: overrides.userId ?? "user-42",
|
|
195
|
+
email: "test@example.com",
|
|
196
|
+
displayName: "Test User",
|
|
197
|
+
termsAcceptedAt: "2025-01-01T00:00:00Z",
|
|
198
|
+
clineBenchConsent: false,
|
|
199
|
+
createdAt: "2025-01-01T00:00:00Z",
|
|
200
|
+
updatedAt: "2025-01-01T00:00:00Z",
|
|
201
|
+
},
|
|
202
|
+
provider: overrides.provider ?? "google",
|
|
203
|
+
startedAt: overrides.startedAt ?? Date.now(),
|
|
204
|
+
} satisfies LegacyClineUserInfo);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
describe("resolveLegacyClineAuth", () => {
|
|
208
|
+
it("extracts all auth fields from a complete legacy account JSON", () => {
|
|
209
|
+
const result = resolveLegacyClineAuth(
|
|
210
|
+
makeClineAccountJson({
|
|
211
|
+
idToken: "my-id-token",
|
|
212
|
+
expiresAt: 1750000000000,
|
|
213
|
+
refreshToken: "my-refresh",
|
|
214
|
+
userId: "user-123",
|
|
215
|
+
}),
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
expect(result).toEqual({
|
|
219
|
+
accessToken: "my-id-token",
|
|
220
|
+
refreshToken: "my-refresh",
|
|
221
|
+
expiresAt: 1750000000000,
|
|
222
|
+
accountId: "user-123",
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("maps idToken to accessToken", () => {
|
|
227
|
+
const result = resolveLegacyClineAuth(
|
|
228
|
+
makeClineAccountJson({ idToken: "tok-abc" }),
|
|
229
|
+
);
|
|
230
|
+
expect(result?.accessToken).toBe("tok-abc");
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("preserves expiresAt as a number", () => {
|
|
234
|
+
const result = resolveLegacyClineAuth(
|
|
235
|
+
makeClineAccountJson({ expiresAt: 9999999999999 }),
|
|
236
|
+
);
|
|
237
|
+
expect(result?.expiresAt).toBe(9999999999999);
|
|
238
|
+
expect(typeof result?.expiresAt).toBe("number");
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it("maps userInfo.id to accountId", () => {
|
|
242
|
+
const result = resolveLegacyClineAuth(
|
|
243
|
+
makeClineAccountJson({ userId: "uid-xyz" }),
|
|
244
|
+
);
|
|
245
|
+
expect(result?.accountId).toBe("uid-xyz");
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it("returns undefined accountId when userInfo is missing entirely", () => {
|
|
249
|
+
const raw = JSON.stringify({
|
|
250
|
+
idToken: "tok",
|
|
251
|
+
expiresAt: 1000,
|
|
252
|
+
refreshToken: "ref",
|
|
253
|
+
provider: "google",
|
|
254
|
+
startedAt: 1,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
const result = resolveLegacyClineAuth(raw);
|
|
258
|
+
expect(result).toBeDefined();
|
|
259
|
+
expect(result?.accessToken).toBe("tok");
|
|
260
|
+
expect(result?.accountId).toBeUndefined();
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("returns undefined accountId when userInfo.id is missing", () => {
|
|
264
|
+
const raw = JSON.stringify({
|
|
265
|
+
idToken: "tok",
|
|
266
|
+
expiresAt: 1000,
|
|
267
|
+
refreshToken: "ref",
|
|
268
|
+
userInfo: {
|
|
269
|
+
email: "x@y.com",
|
|
270
|
+
displayName: "X",
|
|
271
|
+
termsAcceptedAt: "2025-01-01T00:00:00Z",
|
|
272
|
+
clineBenchConsent: false,
|
|
273
|
+
createdAt: "2025-01-01T00:00:00Z",
|
|
274
|
+
updatedAt: "2025-01-01T00:00:00Z",
|
|
275
|
+
},
|
|
276
|
+
provider: "google",
|
|
277
|
+
startedAt: 1,
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
const result = resolveLegacyClineAuth(raw);
|
|
281
|
+
expect(result).toBeDefined();
|
|
282
|
+
expect(result?.accountId).toBeUndefined();
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("returns undefined for invalid json", () => {
|
|
286
|
+
expect(resolveLegacyClineAuth(undefined)).toBeUndefined();
|
|
287
|
+
expect(resolveLegacyClineAuth("")).toBeUndefined();
|
|
288
|
+
expect(resolveLegacyClineAuth(" \n\t ")).toBeUndefined();
|
|
289
|
+
expect(resolveLegacyClineAuth("not-json{{{")).toBeUndefined();
|
|
290
|
+
expect(resolveLegacyClineAuth("null")).toBeUndefined();
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("returns undefined fields when idToken/refreshToken are missing from JSON", () => {
|
|
294
|
+
const raw = JSON.stringify({
|
|
295
|
+
userInfo: { id: "uid" },
|
|
296
|
+
provider: "google",
|
|
297
|
+
startedAt: 1,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
const result = resolveLegacyClineAuth(raw);
|
|
301
|
+
expect(result).toBeDefined();
|
|
302
|
+
expect(result?.accessToken).toBeUndefined();
|
|
303
|
+
expect(result?.refreshToken).toBeUndefined();
|
|
304
|
+
expect(result?.expiresAt).toBeUndefined();
|
|
305
|
+
expect(result?.accountId).toBe("uid");
|
|
306
|
+
});
|
|
307
|
+
});
|
|
@@ -162,6 +162,53 @@ export interface MigrateLegacyProviderSettingsResult {
|
|
|
162
162
|
lastUsedProvider?: string;
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
export type LegacyClineUserInfo = {
|
|
166
|
+
idToken: string;
|
|
167
|
+
expiresAt: number;
|
|
168
|
+
refreshToken: string;
|
|
169
|
+
userInfo: {
|
|
170
|
+
id: string;
|
|
171
|
+
email: string;
|
|
172
|
+
displayName: string;
|
|
173
|
+
termsAcceptedAt: string;
|
|
174
|
+
clineBenchConsent: boolean;
|
|
175
|
+
createdAt: string;
|
|
176
|
+
updatedAt: string;
|
|
177
|
+
};
|
|
178
|
+
provider: string;
|
|
179
|
+
startedAt: number;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Resolves legacy Cline account auth data from the raw `cline:clineAccountId`
|
|
184
|
+
* secret string into the auth fields used by `ProviderSettings`.
|
|
185
|
+
*
|
|
186
|
+
* Returns `undefined` when the input is missing, empty, whitespace-only, or
|
|
187
|
+
* unparseable JSON.
|
|
188
|
+
*/
|
|
189
|
+
export function resolveLegacyClineAuth(
|
|
190
|
+
rawAccountData: string | undefined,
|
|
191
|
+
): ProviderSettings["auth"] | undefined {
|
|
192
|
+
const trimmed = rawAccountData?.trim();
|
|
193
|
+
if (!trimmed) {
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
const data = JSON.parse(trimmed) as LegacyClineUserInfo;
|
|
198
|
+
if (!data) {
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
accessToken: data.idToken,
|
|
203
|
+
refreshToken: data.refreshToken,
|
|
204
|
+
expiresAt: data.expiresAt,
|
|
205
|
+
accountId: data.userInfo?.id,
|
|
206
|
+
};
|
|
207
|
+
} catch {
|
|
208
|
+
return undefined;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
165
212
|
function trimNonEmpty(value: string | undefined): string | undefined {
|
|
166
213
|
const trimmed = value?.trim();
|
|
167
214
|
return trimmed ? trimmed : undefined;
|
|
@@ -400,14 +447,19 @@ function buildLegacyProviderSettings(
|
|
|
400
447
|
Object.assign(providerSpecific, resolveLegacyCodexAuth(legacySecrets));
|
|
401
448
|
}
|
|
402
449
|
if (providerId === "cline") {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
450
|
+
try {
|
|
451
|
+
const legacyAuthString = trimNonEmpty(
|
|
452
|
+
legacySecrets["cline:clineAccountId"],
|
|
453
|
+
);
|
|
454
|
+
|
|
455
|
+
if (legacyAuthString) {
|
|
456
|
+
providerSpecific.auth = {
|
|
457
|
+
...(providerSpecific.auth ?? {}),
|
|
458
|
+
...resolveLegacyClineAuth(legacyAuthString),
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
} catch {
|
|
462
|
+
// Failed to parse stored cline auth data
|
|
411
463
|
}
|
|
412
464
|
}
|
|
413
465
|
if (providerId === "openai" && legacyGlobalState.openAiHeaders) {
|
|
@@ -261,7 +261,7 @@ describe("zod schema conversion", () => {
|
|
|
261
261
|
});
|
|
262
262
|
|
|
263
263
|
describe("default editor tool", () => {
|
|
264
|
-
it("accepts
|
|
264
|
+
it("accepts replacement edits without insert fields", async () => {
|
|
265
265
|
const execute = vi.fn(async () => "patched");
|
|
266
266
|
const tools = createDefaultTools({
|
|
267
267
|
executors: {
|
|
@@ -284,12 +284,9 @@ describe("default editor tool", () => {
|
|
|
284
284
|
|
|
285
285
|
const result = await editorTool.execute(
|
|
286
286
|
{
|
|
287
|
-
command: "str_replace",
|
|
288
287
|
path: "/tmp/example.ts",
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
file_text: null,
|
|
292
|
-
insert_line: null,
|
|
288
|
+
old_text: "before",
|
|
289
|
+
new_text: "after",
|
|
293
290
|
},
|
|
294
291
|
{
|
|
295
292
|
agentId: "agent-1",
|
|
@@ -299,18 +296,15 @@ describe("default editor tool", () => {
|
|
|
299
296
|
);
|
|
300
297
|
|
|
301
298
|
expect(result).toEqual({
|
|
302
|
-
query: "
|
|
299
|
+
query: "edit:/tmp/example.ts",
|
|
303
300
|
result: "patched",
|
|
304
301
|
success: true,
|
|
305
302
|
});
|
|
306
303
|
expect(execute).toHaveBeenCalledWith(
|
|
307
304
|
expect.objectContaining({
|
|
308
|
-
command: "str_replace",
|
|
309
305
|
path: "/tmp/example.ts",
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
file_text: null,
|
|
313
|
-
insert_line: null,
|
|
306
|
+
old_text: "before",
|
|
307
|
+
new_text: "after",
|
|
314
308
|
}),
|
|
315
309
|
process.cwd(),
|
|
316
310
|
expect.objectContaining({
|
|
@@ -321,7 +315,7 @@ describe("default editor tool", () => {
|
|
|
321
315
|
);
|
|
322
316
|
});
|
|
323
317
|
|
|
324
|
-
it("
|
|
318
|
+
it("allows edit without old_text so missing files can be created", async () => {
|
|
325
319
|
const execute = vi.fn(async () => "patched");
|
|
326
320
|
const tools = createDefaultTools({
|
|
327
321
|
executors: {
|
|
@@ -342,21 +336,80 @@ describe("default editor tool", () => {
|
|
|
342
336
|
throw new Error("Expected editor tool to be defined.");
|
|
343
337
|
}
|
|
344
338
|
|
|
345
|
-
await
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
339
|
+
const result = await editorTool.execute(
|
|
340
|
+
{
|
|
341
|
+
path: "/tmp/example.ts",
|
|
342
|
+
new_text: "created",
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
agentId: "agent-1",
|
|
346
|
+
conversationId: "conv-1",
|
|
347
|
+
iteration: 1,
|
|
348
|
+
},
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
expect(result).toEqual({
|
|
352
|
+
query: "edit:/tmp/example.ts",
|
|
353
|
+
result: "patched",
|
|
354
|
+
success: true,
|
|
355
|
+
});
|
|
356
|
+
expect(execute).toHaveBeenCalledWith(
|
|
357
|
+
expect.objectContaining({
|
|
358
|
+
path: "/tmp/example.ts",
|
|
359
|
+
new_text: "created",
|
|
360
|
+
}),
|
|
361
|
+
process.cwd(),
|
|
362
|
+
expect.anything(),
|
|
363
|
+
);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it("treats insert_line as an insert operation", async () => {
|
|
367
|
+
const execute = vi.fn(async () => "patched");
|
|
368
|
+
const tools = createDefaultTools({
|
|
369
|
+
executors: {
|
|
370
|
+
editor: execute,
|
|
371
|
+
},
|
|
372
|
+
enableReadFiles: false,
|
|
373
|
+
enableSearch: false,
|
|
374
|
+
enableBash: false,
|
|
375
|
+
enableWebFetch: false,
|
|
376
|
+
enableSkills: false,
|
|
377
|
+
enableAskQuestion: false,
|
|
378
|
+
enableApplyPatch: false,
|
|
379
|
+
enableEditor: true,
|
|
380
|
+
});
|
|
381
|
+
const editorTool = tools.find((tool) => tool.name === "editor");
|
|
382
|
+
expect(editorTool).toBeDefined();
|
|
383
|
+
if (!editorTool) {
|
|
384
|
+
throw new Error("Expected editor tool to be defined.");
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const result = await editorTool.execute(
|
|
388
|
+
{
|
|
389
|
+
path: "/tmp/example.ts",
|
|
390
|
+
new_text: "after",
|
|
391
|
+
insert_line: 3,
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
agentId: "agent-1",
|
|
395
|
+
conversationId: "conv-1",
|
|
396
|
+
iteration: 1,
|
|
397
|
+
},
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
expect(result).toEqual({
|
|
401
|
+
query: "insert:/tmp/example.ts",
|
|
402
|
+
result: "patched",
|
|
403
|
+
success: true,
|
|
404
|
+
});
|
|
405
|
+
expect(execute).toHaveBeenCalledWith(
|
|
406
|
+
expect.objectContaining({
|
|
407
|
+
path: "/tmp/example.ts",
|
|
408
|
+
new_text: "after",
|
|
409
|
+
insert_line: 3,
|
|
410
|
+
}),
|
|
411
|
+
process.cwd(),
|
|
412
|
+
expect.anything(),
|
|
413
|
+
);
|
|
361
414
|
});
|
|
362
415
|
});
|
package/src/tools/definitions.ts
CHANGED
|
@@ -432,14 +432,16 @@ export function createEditorTool(
|
|
|
432
432
|
return createTool<EditFileInput, ToolOperationResult>({
|
|
433
433
|
name: "editor",
|
|
434
434
|
description:
|
|
435
|
-
"
|
|
436
|
-
"
|
|
435
|
+
"An editor for controlled filesystem edits on the text file at the provided path. " +
|
|
436
|
+
"Provide `insert_line` to insert `new_text` at a specific line number. " +
|
|
437
|
+
"Otherwise, the tool replaces `old_text` with `new_text`, or creates the file with `new_text` if it does not exist.",
|
|
437
438
|
inputSchema: zodToJsonSchema(EditFileInputSchema),
|
|
438
439
|
timeoutMs,
|
|
439
440
|
retryable: false, // Editing operations are stateful and should not auto-retry
|
|
440
441
|
maxRetries: 0,
|
|
441
442
|
execute: async (input, context) => {
|
|
442
443
|
const validatedInput = validateWithZod(EditFileInputSchema, input);
|
|
444
|
+
const operation = validatedInput.insert_line == null ? "edit" : "insert";
|
|
443
445
|
|
|
444
446
|
try {
|
|
445
447
|
const result = await withTimeout(
|
|
@@ -449,14 +451,14 @@ export function createEditorTool(
|
|
|
449
451
|
);
|
|
450
452
|
|
|
451
453
|
return {
|
|
452
|
-
query: `${
|
|
454
|
+
query: `${operation}:${validatedInput.path}`,
|
|
453
455
|
result,
|
|
454
456
|
success: true,
|
|
455
457
|
};
|
|
456
458
|
} catch (error) {
|
|
457
459
|
const msg = formatError(error);
|
|
458
460
|
return {
|
|
459
|
-
query: `${
|
|
461
|
+
query: `${operation}:${validatedInput.path}`,
|
|
460
462
|
result: "",
|
|
461
463
|
error: `Editor operation failed: ${msg}`,
|
|
462
464
|
success: false,
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
import { createEditorExecutor } from "./editor.js";
|
|
6
|
+
|
|
7
|
+
describe("createEditorExecutor", () => {
|
|
8
|
+
it("creates a missing file when edit is used", async () => {
|
|
9
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "agents-editor-"));
|
|
10
|
+
const filePath = path.join(dir, "example.txt");
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const editor = createEditorExecutor();
|
|
14
|
+
const result = await editor(
|
|
15
|
+
{
|
|
16
|
+
path: filePath,
|
|
17
|
+
new_text: "created with edit",
|
|
18
|
+
},
|
|
19
|
+
dir,
|
|
20
|
+
{
|
|
21
|
+
agentId: "agent-1",
|
|
22
|
+
conversationId: "conv-1",
|
|
23
|
+
iteration: 1,
|
|
24
|
+
},
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
expect(result).toBe(`File created successfully at: ${filePath}`);
|
|
28
|
+
await expect(fs.readFile(filePath, "utf-8")).resolves.toBe(
|
|
29
|
+
"created with edit",
|
|
30
|
+
);
|
|
31
|
+
} finally {
|
|
32
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -113,6 +113,15 @@ async function createFile(
|
|
|
113
113
|
return `File created successfully at: ${filePath}`;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
async function fileExists(filePath: string): Promise<boolean> {
|
|
117
|
+
try {
|
|
118
|
+
await fs.access(filePath);
|
|
119
|
+
return true;
|
|
120
|
+
} catch {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
116
125
|
async function replaceInFile(
|
|
117
126
|
filePath: string,
|
|
118
127
|
oldStr: string,
|
|
@@ -181,52 +190,30 @@ export function createEditorExecutor(
|
|
|
181
190
|
): Promise<string> => {
|
|
182
191
|
const filePath = resolveFilePath(cwd, input.path, restrictToCwd);
|
|
183
192
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
filePath,
|
|
201
|
-
input.old_str,
|
|
202
|
-
input.new_str,
|
|
203
|
-
encoding,
|
|
204
|
-
maxDiffLines,
|
|
205
|
-
);
|
|
206
|
-
|
|
207
|
-
case "insert":
|
|
208
|
-
if (input.insert_line == null) {
|
|
209
|
-
throw new Error(
|
|
210
|
-
"Parameter `insert_line` is required for insert command.",
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
|
-
if (input.new_str == null) {
|
|
214
|
-
throw new Error(
|
|
215
|
-
"Parameter `new_str` is required for insert command.",
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
return insertInFile(
|
|
219
|
-
filePath,
|
|
220
|
-
input.insert_line, // One-based index
|
|
221
|
-
input.new_str,
|
|
222
|
-
encoding,
|
|
223
|
-
);
|
|
224
|
-
|
|
225
|
-
default:
|
|
226
|
-
throw new Error(
|
|
227
|
-
`Unrecognized command ${(input as { command: string }).command}. ` +
|
|
228
|
-
"Allowed commands are: create, str_replace, insert",
|
|
229
|
-
);
|
|
193
|
+
if (input.insert_line != null) {
|
|
194
|
+
return insertInFile(
|
|
195
|
+
filePath,
|
|
196
|
+
input.insert_line, // One-based index
|
|
197
|
+
input.new_text,
|
|
198
|
+
encoding,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (!(await fileExists(filePath))) {
|
|
203
|
+
return createFile(filePath, input.new_text, encoding);
|
|
204
|
+
}
|
|
205
|
+
if (input.old_text == null) {
|
|
206
|
+
throw new Error(
|
|
207
|
+
"Parameter `old_text` is required when editing an existing file without `insert_line`",
|
|
208
|
+
);
|
|
230
209
|
}
|
|
210
|
+
|
|
211
|
+
return replaceInFile(
|
|
212
|
+
filePath,
|
|
213
|
+
input.old_text,
|
|
214
|
+
input.new_text,
|
|
215
|
+
encoding,
|
|
216
|
+
maxDiffLines,
|
|
217
|
+
);
|
|
231
218
|
};
|
|
232
219
|
}
|
package/src/tools/schemas.ts
CHANGED
|
@@ -52,14 +52,16 @@ export const SearchCodebaseUnionInputSchema = z.union([
|
|
|
52
52
|
z.string(),
|
|
53
53
|
]);
|
|
54
54
|
|
|
55
|
-
const CommandInputSchema = z
|
|
55
|
+
const CommandInputSchema = z
|
|
56
|
+
.string()
|
|
57
|
+
.describe("The non-interactive shell command to execute");
|
|
56
58
|
/**
|
|
57
59
|
* Schema for run_commands tool input
|
|
58
60
|
*/
|
|
59
61
|
export const RunCommandsInputSchema = z.object({
|
|
60
62
|
commands: z
|
|
61
63
|
.array(CommandInputSchema)
|
|
62
|
-
.describe("Array of shell commands to execute
|
|
64
|
+
.describe("Array of shell commands to execute"),
|
|
63
65
|
});
|
|
64
66
|
|
|
65
67
|
/**
|
|
@@ -67,8 +69,8 @@ export const RunCommandsInputSchema = z.object({
|
|
|
67
69
|
*/
|
|
68
70
|
export const RunCommandsInputUnionSchema = z.union([
|
|
69
71
|
RunCommandsInputSchema,
|
|
70
|
-
z.array(
|
|
71
|
-
|
|
72
|
+
z.array(z.string()),
|
|
73
|
+
z.string(),
|
|
72
74
|
]);
|
|
73
75
|
|
|
74
76
|
/**
|
|
@@ -93,46 +95,34 @@ export const FetchWebContentInputSchema = z.object({
|
|
|
93
95
|
*/
|
|
94
96
|
export const EditFileInputSchema = z
|
|
95
97
|
.object({
|
|
96
|
-
|
|
97
|
-
.enum(["create", "str_replace", "insert"])
|
|
98
|
-
.describe("Editor command to execute: create, str_replace, insert"),
|
|
99
|
-
path: z.string().min(1).describe("Absolute file path"),
|
|
100
|
-
file_text: z
|
|
98
|
+
path: z
|
|
101
99
|
.string()
|
|
102
|
-
.
|
|
103
|
-
.describe("
|
|
104
|
-
|
|
100
|
+
.min(1)
|
|
101
|
+
.describe("The absolute file path for the action to be performed on"),
|
|
102
|
+
old_text: z
|
|
105
103
|
.string()
|
|
106
|
-
.
|
|
104
|
+
.nullable()
|
|
105
|
+
.optional()
|
|
107
106
|
.describe(
|
|
108
|
-
"Exact text to replace (must match exactly once)
|
|
107
|
+
"Exact text to replace (must match exactly once). Omit this when creating a missing file or inserting via insert_line.",
|
|
109
108
|
),
|
|
110
|
-
|
|
109
|
+
new_text: z
|
|
111
110
|
.string()
|
|
112
|
-
.
|
|
113
|
-
|
|
111
|
+
.describe(
|
|
112
|
+
"The new content to write when creating a missing file, the replacement text for edits, or the inserted text when insert_line is provided",
|
|
113
|
+
),
|
|
114
114
|
insert_line: z
|
|
115
115
|
.number()
|
|
116
116
|
.int()
|
|
117
|
-
.
|
|
118
|
-
.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
message: "file_text is required for command=create",
|
|
123
|
-
})
|
|
124
|
-
.refine((v) => v.command !== "str_replace" || v.old_str != null, {
|
|
125
|
-
path: ["old_str"],
|
|
126
|
-
message: "old_str is required for command=str_replace",
|
|
127
|
-
})
|
|
128
|
-
.refine((v) => v.command !== "insert" || v.insert_line != null, {
|
|
129
|
-
path: ["insert_line"],
|
|
130
|
-
message: "insert_line is required for command=insert",
|
|
117
|
+
.nullable()
|
|
118
|
+
.optional()
|
|
119
|
+
.describe(
|
|
120
|
+
"Optional one-based line index. When provided, the tool inserts new_text at that line instead of performing a replacement edit.",
|
|
121
|
+
),
|
|
131
122
|
})
|
|
132
|
-
.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
});
|
|
123
|
+
.describe(
|
|
124
|
+
"Edit a text file by replacing old_text with new_text, create the file with new_text if it does not exist, or insert new_text at insert_line when insert_line is provided. IMPORTANT: large edits can time out, so use small chunks and multiple calls when possible.",
|
|
125
|
+
);
|
|
136
126
|
|
|
137
127
|
/**
|
|
138
128
|
* Schema for apply_patch tool input
|
package/src/types/events.ts
CHANGED
|
@@ -13,7 +13,12 @@ export interface SessionEndedEvent {
|
|
|
13
13
|
|
|
14
14
|
export interface SessionToolEvent {
|
|
15
15
|
sessionId: string;
|
|
16
|
-
hookEventName:
|
|
16
|
+
hookEventName:
|
|
17
|
+
| "tool_call"
|
|
18
|
+
| "tool_result"
|
|
19
|
+
| "agent_end"
|
|
20
|
+
| "agent_error"
|
|
21
|
+
| "session_shutdown";
|
|
17
22
|
agentId?: string;
|
|
18
23
|
conversationId?: string;
|
|
19
24
|
parentAgentId?: string;
|