@clinebot/core 0.0.7 → 0.0.10
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/auth/cline.d.ts +2 -0
- package/dist/auth/codex.d.ts +5 -1
- package/dist/auth/oca.d.ts +7 -1
- package/dist/auth/types.d.ts +2 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.node.d.ts +1 -0
- package/dist/index.node.js +164 -162
- package/dist/input/mention-enricher.d.ts +1 -0
- package/dist/providers/local-provider-service.d.ts +1 -1
- package/dist/runtime/session-runtime.d.ts +1 -1
- package/dist/session/default-session-manager.d.ts +13 -17
- package/dist/session/runtime-oauth-token-manager.d.ts +4 -2
- package/dist/session/session-agent-events.d.ts +15 -0
- package/dist/session/session-config-builder.d.ts +13 -0
- package/dist/session/session-manager.d.ts +2 -2
- package/dist/session/session-team-coordination.d.ts +12 -0
- package/dist/session/session-telemetry.d.ts +9 -0
- package/dist/session/unified-session-persistence-service.d.ts +12 -16
- package/dist/session/utils/helpers.d.ts +1 -1
- package/dist/session/utils/types.d.ts +1 -1
- package/dist/telemetry/core-events.d.ts +122 -0
- package/dist/tools/definitions.d.ts +1 -1
- package/dist/tools/executors/file-read.d.ts +1 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/presets.d.ts +1 -1
- package/dist/tools/schemas.d.ts +46 -3
- package/dist/tools/types.d.ts +3 -3
- package/dist/types/config.d.ts +1 -1
- package/dist/types/provider-settings.d.ts +4 -4
- package/dist/types.d.ts +1 -1
- package/package.json +4 -3
- package/src/auth/cline.ts +35 -1
- package/src/auth/codex.ts +27 -2
- package/src/auth/oca.ts +31 -4
- package/src/auth/types.ts +3 -0
- package/src/index.ts +27 -0
- package/src/input/mention-enricher.test.ts +3 -0
- package/src/input/mention-enricher.ts +3 -0
- package/src/providers/local-provider-service.ts +6 -7
- package/src/runtime/hook-file-hooks.ts +11 -10
- package/src/runtime/session-runtime.ts +1 -1
- package/src/session/default-session-manager.e2e.test.ts +2 -1
- package/src/session/default-session-manager.ts +367 -601
- package/src/session/runtime-oauth-token-manager.ts +21 -14
- package/src/session/session-agent-events.ts +159 -0
- package/src/session/session-config-builder.ts +111 -0
- package/src/session/session-host.ts +13 -0
- package/src/session/session-manager.ts +2 -2
- package/src/session/session-team-coordination.ts +198 -0
- package/src/session/session-telemetry.ts +95 -0
- package/src/session/unified-session-persistence-service.test.ts +81 -0
- package/src/session/unified-session-persistence-service.ts +470 -469
- package/src/session/utils/helpers.ts +1 -1
- package/src/session/utils/types.ts +1 -1
- package/src/storage/provider-settings-legacy-migration.ts +3 -3
- package/src/telemetry/core-events.ts +344 -0
- package/src/tools/definitions.test.ts +121 -7
- package/src/tools/definitions.ts +60 -24
- package/src/tools/executors/file-read.test.ts +29 -5
- package/src/tools/executors/file-read.ts +17 -6
- package/src/tools/index.ts +2 -0
- package/src/tools/presets.ts +1 -1
- package/src/tools/schemas.ts +65 -5
- package/src/tools/types.ts +7 -3
- package/src/types/config.ts +1 -1
- package/src/types/provider-settings.ts +6 -6
- package/src/types.ts +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AgentConfig, AgentEvent, AgentResult } from "@clinebot/agents";
|
|
2
|
-
import type {
|
|
2
|
+
import type { LlmsProviders } from "@clinebot/llms";
|
|
3
3
|
import type { SessionSource } from "../../types/common";
|
|
4
4
|
import type { SessionRecord } from "../../types/sessions";
|
|
5
5
|
import { nowIso } from "../session-artifacts";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Agent } from "@clinebot/agents";
|
|
2
|
-
import type {
|
|
2
|
+
import type { LlmsProviders } from "@clinebot/llms";
|
|
3
3
|
import type { BuiltRuntime } from "../../runtime/session-runtime";
|
|
4
4
|
import type { SessionSource } from "../../types/common";
|
|
5
5
|
import type { CoreSessionConfig } from "../../types/config";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { LlmsModels, LlmsProviders } from "@clinebot/llms";
|
|
4
4
|
import { resolveClineDataDir } from "@clinebot/shared/storage";
|
|
5
5
|
import type { ProviderSettings } from "../types/provider-settings";
|
|
6
6
|
import { emptyStoredProviderSettings } from "../types/provider-settings";
|
|
@@ -373,7 +373,7 @@ function resolveLegacyCodexAuth(
|
|
|
373
373
|
}
|
|
374
374
|
|
|
375
375
|
function getDefaultModelForProvider(providerId: string): string | undefined {
|
|
376
|
-
const builtInModels =
|
|
376
|
+
const builtInModels = LlmsModels.getGeneratedModelsForProvider(providerId);
|
|
377
377
|
const firstModelId = Object.keys(builtInModels)[0];
|
|
378
378
|
return firstModelId ?? undefined;
|
|
379
379
|
}
|
|
@@ -568,7 +568,7 @@ function buildLegacyProviderSettings(
|
|
|
568
568
|
...(timeout ? { timeout } : {}),
|
|
569
569
|
...providerSpecific,
|
|
570
570
|
};
|
|
571
|
-
const parsed =
|
|
571
|
+
const parsed = LlmsProviders.ProviderSettingsSchema.safeParse(settings);
|
|
572
572
|
if (!parsed.success) {
|
|
573
573
|
return undefined;
|
|
574
574
|
}
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import type { ITelemetryService, TelemetryProperties } from "@clinebot/shared";
|
|
2
|
+
|
|
3
|
+
const MAX_ERROR_MESSAGE_LENGTH = 500;
|
|
4
|
+
|
|
5
|
+
export const LegacyTelemetryEvents = {
|
|
6
|
+
USER: {
|
|
7
|
+
AUTH_STARTED: "user.auth_started",
|
|
8
|
+
AUTH_SUCCEEDED: "user.auth_succeeded",
|
|
9
|
+
AUTH_FAILED: "user.auth_failed",
|
|
10
|
+
AUTH_LOGGED_OUT: "user.auth_logged_out",
|
|
11
|
+
},
|
|
12
|
+
TASK: {
|
|
13
|
+
CREATED: "task.created",
|
|
14
|
+
RESTARTED: "task.restarted",
|
|
15
|
+
COMPLETED: "task.completed",
|
|
16
|
+
CONVERSATION_TURN: "task.conversation_turn",
|
|
17
|
+
TOKEN_USAGE: "task.tokens",
|
|
18
|
+
MODE_SWITCH: "task.mode",
|
|
19
|
+
TOOL_USED: "task.tool_used",
|
|
20
|
+
SKILL_USED: "task.skill_used",
|
|
21
|
+
DIFF_EDIT_FAILED: "task.diff_edit_failed",
|
|
22
|
+
PROVIDER_API_ERROR: "task.provider_api_error",
|
|
23
|
+
MENTION_USED: "task.mention_used",
|
|
24
|
+
MENTION_FAILED: "task.mention_failed",
|
|
25
|
+
MENTION_SEARCH_RESULTS: "task.mention_search_results",
|
|
26
|
+
SUBAGENT_STARTED: "task.subagent_started",
|
|
27
|
+
SUBAGENT_COMPLETED: "task.subagent_completed",
|
|
28
|
+
},
|
|
29
|
+
HOOKS: {
|
|
30
|
+
DISCOVERY_COMPLETED: "hooks.discovery_completed",
|
|
31
|
+
},
|
|
32
|
+
} as const;
|
|
33
|
+
|
|
34
|
+
function emit(
|
|
35
|
+
telemetry: ITelemetryService | undefined,
|
|
36
|
+
event: string,
|
|
37
|
+
properties?: TelemetryProperties,
|
|
38
|
+
): void {
|
|
39
|
+
telemetry?.capture({ event, properties });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function truncateErrorMessage(errorMessage?: string): string | undefined {
|
|
43
|
+
if (!errorMessage) {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
return errorMessage.substring(0, MAX_ERROR_MESSAGE_LENGTH);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function captureAuthStarted(
|
|
50
|
+
telemetry: ITelemetryService | undefined,
|
|
51
|
+
provider?: string,
|
|
52
|
+
): void {
|
|
53
|
+
emit(telemetry, LegacyTelemetryEvents.USER.AUTH_STARTED, { provider });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function captureAuthSucceeded(
|
|
57
|
+
telemetry: ITelemetryService | undefined,
|
|
58
|
+
provider?: string,
|
|
59
|
+
): void {
|
|
60
|
+
emit(telemetry, LegacyTelemetryEvents.USER.AUTH_SUCCEEDED, { provider });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function captureAuthFailed(
|
|
64
|
+
telemetry: ITelemetryService | undefined,
|
|
65
|
+
provider?: string,
|
|
66
|
+
errorMessage?: string,
|
|
67
|
+
): void {
|
|
68
|
+
emit(telemetry, LegacyTelemetryEvents.USER.AUTH_FAILED, {
|
|
69
|
+
provider,
|
|
70
|
+
errorMessage: truncateErrorMessage(errorMessage),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function captureAuthLoggedOut(
|
|
75
|
+
telemetry: ITelemetryService | undefined,
|
|
76
|
+
provider?: string,
|
|
77
|
+
reason?: string,
|
|
78
|
+
): void {
|
|
79
|
+
emit(telemetry, LegacyTelemetryEvents.USER.AUTH_LOGGED_OUT, {
|
|
80
|
+
provider,
|
|
81
|
+
reason,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function identifyAccount(
|
|
86
|
+
telemetry: ITelemetryService | undefined,
|
|
87
|
+
account: {
|
|
88
|
+
id?: string;
|
|
89
|
+
email?: string;
|
|
90
|
+
provider?: string;
|
|
91
|
+
organizationId?: string;
|
|
92
|
+
organizationName?: string;
|
|
93
|
+
memberId?: string;
|
|
94
|
+
},
|
|
95
|
+
): void {
|
|
96
|
+
const distinctId = account.id?.trim();
|
|
97
|
+
if (distinctId) {
|
|
98
|
+
telemetry?.setDistinctId(distinctId);
|
|
99
|
+
}
|
|
100
|
+
telemetry?.updateCommonProperties({
|
|
101
|
+
account_id: account.id,
|
|
102
|
+
account_email: account.email,
|
|
103
|
+
provider: account.provider,
|
|
104
|
+
organization_id: account.organizationId,
|
|
105
|
+
organization_name: account.organizationName,
|
|
106
|
+
member_id: account.memberId,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function captureTaskCreated(
|
|
111
|
+
telemetry: ITelemetryService | undefined,
|
|
112
|
+
properties: {
|
|
113
|
+
ulid: string;
|
|
114
|
+
apiProvider?: string;
|
|
115
|
+
openAiCompatibleDomain?: string;
|
|
116
|
+
},
|
|
117
|
+
): void {
|
|
118
|
+
emit(telemetry, LegacyTelemetryEvents.TASK.CREATED, properties);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function captureTaskRestarted(
|
|
122
|
+
telemetry: ITelemetryService | undefined,
|
|
123
|
+
properties: {
|
|
124
|
+
ulid: string;
|
|
125
|
+
apiProvider?: string;
|
|
126
|
+
openAiCompatibleDomain?: string;
|
|
127
|
+
},
|
|
128
|
+
): void {
|
|
129
|
+
emit(telemetry, LegacyTelemetryEvents.TASK.RESTARTED, properties);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function captureTaskCompleted(
|
|
133
|
+
telemetry: ITelemetryService | undefined,
|
|
134
|
+
properties: {
|
|
135
|
+
ulid: string;
|
|
136
|
+
provider?: string;
|
|
137
|
+
modelId?: string;
|
|
138
|
+
mode?: string;
|
|
139
|
+
durationMs?: number;
|
|
140
|
+
},
|
|
141
|
+
): void {
|
|
142
|
+
emit(telemetry, LegacyTelemetryEvents.TASK.COMPLETED, properties);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function captureConversationTurnEvent(
|
|
146
|
+
telemetry: ITelemetryService | undefined,
|
|
147
|
+
properties: {
|
|
148
|
+
ulid: string;
|
|
149
|
+
provider?: string;
|
|
150
|
+
model?: string;
|
|
151
|
+
source: "user" | "assistant";
|
|
152
|
+
mode?: string;
|
|
153
|
+
tokensIn?: number;
|
|
154
|
+
tokensOut?: number;
|
|
155
|
+
cacheWriteTokens?: number;
|
|
156
|
+
cacheReadTokens?: number;
|
|
157
|
+
totalCost?: number;
|
|
158
|
+
isNativeToolCall?: boolean;
|
|
159
|
+
},
|
|
160
|
+
): void {
|
|
161
|
+
emit(telemetry, LegacyTelemetryEvents.TASK.CONVERSATION_TURN, {
|
|
162
|
+
...properties,
|
|
163
|
+
timestamp: new Date().toISOString(),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function captureTokenUsage(
|
|
168
|
+
telemetry: ITelemetryService | undefined,
|
|
169
|
+
properties: {
|
|
170
|
+
ulid: string;
|
|
171
|
+
tokensIn: number;
|
|
172
|
+
tokensOut: number;
|
|
173
|
+
model: string;
|
|
174
|
+
},
|
|
175
|
+
): void {
|
|
176
|
+
emit(telemetry, LegacyTelemetryEvents.TASK.TOKEN_USAGE, properties);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function captureModeSwitch(
|
|
180
|
+
telemetry: ITelemetryService | undefined,
|
|
181
|
+
ulid: string,
|
|
182
|
+
mode?: string,
|
|
183
|
+
): void {
|
|
184
|
+
emit(telemetry, LegacyTelemetryEvents.TASK.MODE_SWITCH, { ulid, mode });
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function captureToolUsage(
|
|
188
|
+
telemetry: ITelemetryService | undefined,
|
|
189
|
+
properties: {
|
|
190
|
+
ulid: string;
|
|
191
|
+
tool: string;
|
|
192
|
+
modelId?: string;
|
|
193
|
+
provider?: string;
|
|
194
|
+
autoApproved?: boolean;
|
|
195
|
+
success: boolean;
|
|
196
|
+
isNativeToolCall?: boolean;
|
|
197
|
+
},
|
|
198
|
+
): void {
|
|
199
|
+
emit(telemetry, LegacyTelemetryEvents.TASK.TOOL_USED, properties);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function captureSkillUsed(
|
|
203
|
+
telemetry: ITelemetryService | undefined,
|
|
204
|
+
properties: {
|
|
205
|
+
ulid: string;
|
|
206
|
+
skillName: string;
|
|
207
|
+
skillSource: "global" | "project";
|
|
208
|
+
skillsAvailableGlobal: number;
|
|
209
|
+
skillsAvailableProject: number;
|
|
210
|
+
provider?: string;
|
|
211
|
+
modelId?: string;
|
|
212
|
+
},
|
|
213
|
+
): void {
|
|
214
|
+
emit(telemetry, LegacyTelemetryEvents.TASK.SKILL_USED, properties);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function captureDiffEditFailure(
|
|
218
|
+
telemetry: ITelemetryService | undefined,
|
|
219
|
+
properties: {
|
|
220
|
+
ulid: string;
|
|
221
|
+
modelId?: string;
|
|
222
|
+
provider?: string;
|
|
223
|
+
errorType?: string;
|
|
224
|
+
isNativeToolCall?: boolean;
|
|
225
|
+
},
|
|
226
|
+
): void {
|
|
227
|
+
emit(telemetry, LegacyTelemetryEvents.TASK.DIFF_EDIT_FAILED, properties);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function captureProviderApiError(
|
|
231
|
+
telemetry: ITelemetryService | undefined,
|
|
232
|
+
properties: {
|
|
233
|
+
ulid: string;
|
|
234
|
+
model: string;
|
|
235
|
+
errorMessage: string;
|
|
236
|
+
provider?: string;
|
|
237
|
+
errorStatus?: number;
|
|
238
|
+
requestId?: string;
|
|
239
|
+
isNativeToolCall?: boolean;
|
|
240
|
+
},
|
|
241
|
+
): void {
|
|
242
|
+
emit(telemetry, LegacyTelemetryEvents.TASK.PROVIDER_API_ERROR, {
|
|
243
|
+
...properties,
|
|
244
|
+
errorMessage: truncateErrorMessage(properties.errorMessage) ?? "unknown",
|
|
245
|
+
timestamp: new Date().toISOString(),
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function captureMentionUsed(
|
|
250
|
+
telemetry: ITelemetryService | undefined,
|
|
251
|
+
mentionType:
|
|
252
|
+
| "file"
|
|
253
|
+
| "folder"
|
|
254
|
+
| "url"
|
|
255
|
+
| "problems"
|
|
256
|
+
| "terminal"
|
|
257
|
+
| "git-changes"
|
|
258
|
+
| "commit",
|
|
259
|
+
contentLength?: number,
|
|
260
|
+
): void {
|
|
261
|
+
emit(telemetry, LegacyTelemetryEvents.TASK.MENTION_USED, {
|
|
262
|
+
mentionType,
|
|
263
|
+
contentLength,
|
|
264
|
+
timestamp: new Date().toISOString(),
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export function captureMentionFailed(
|
|
269
|
+
telemetry: ITelemetryService | undefined,
|
|
270
|
+
mentionType:
|
|
271
|
+
| "file"
|
|
272
|
+
| "folder"
|
|
273
|
+
| "url"
|
|
274
|
+
| "problems"
|
|
275
|
+
| "terminal"
|
|
276
|
+
| "git-changes"
|
|
277
|
+
| "commit",
|
|
278
|
+
errorType:
|
|
279
|
+
| "not_found"
|
|
280
|
+
| "permission_denied"
|
|
281
|
+
| "network_error"
|
|
282
|
+
| "parse_error"
|
|
283
|
+
| "unknown",
|
|
284
|
+
errorMessage?: string,
|
|
285
|
+
): void {
|
|
286
|
+
emit(telemetry, LegacyTelemetryEvents.TASK.MENTION_FAILED, {
|
|
287
|
+
mentionType,
|
|
288
|
+
errorType,
|
|
289
|
+
errorMessage: truncateErrorMessage(errorMessage),
|
|
290
|
+
timestamp: new Date().toISOString(),
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export function captureMentionSearchResults(
|
|
295
|
+
telemetry: ITelemetryService | undefined,
|
|
296
|
+
query: string,
|
|
297
|
+
resultCount: number,
|
|
298
|
+
searchType: "file" | "folder" | "all",
|
|
299
|
+
isEmpty: boolean,
|
|
300
|
+
): void {
|
|
301
|
+
emit(telemetry, LegacyTelemetryEvents.TASK.MENTION_SEARCH_RESULTS, {
|
|
302
|
+
queryLength: query.length,
|
|
303
|
+
resultCount,
|
|
304
|
+
searchType,
|
|
305
|
+
isEmpty,
|
|
306
|
+
timestamp: new Date().toISOString(),
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export function captureSubagentExecution(
|
|
311
|
+
telemetry: ITelemetryService | undefined,
|
|
312
|
+
properties: {
|
|
313
|
+
ulid: string;
|
|
314
|
+
durationMs: number;
|
|
315
|
+
outputLines: number;
|
|
316
|
+
success: boolean;
|
|
317
|
+
},
|
|
318
|
+
): void {
|
|
319
|
+
emit(
|
|
320
|
+
telemetry,
|
|
321
|
+
properties.success
|
|
322
|
+
? LegacyTelemetryEvents.TASK.SUBAGENT_COMPLETED
|
|
323
|
+
: LegacyTelemetryEvents.TASK.SUBAGENT_STARTED,
|
|
324
|
+
{
|
|
325
|
+
...properties,
|
|
326
|
+
timestamp: new Date().toISOString(),
|
|
327
|
+
},
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export function captureHookDiscovery(
|
|
332
|
+
telemetry: ITelemetryService | undefined,
|
|
333
|
+
hookName: string,
|
|
334
|
+
globalCount: number,
|
|
335
|
+
workspaceCount: number,
|
|
336
|
+
): void {
|
|
337
|
+
emit(telemetry, LegacyTelemetryEvents.HOOKS.DISCOVERY_COMPLETED, {
|
|
338
|
+
hookName,
|
|
339
|
+
globalCount,
|
|
340
|
+
workspaceCount,
|
|
341
|
+
totalCount: globalCount + workspaceCount,
|
|
342
|
+
timestamp: new Date().toISOString(),
|
|
343
|
+
});
|
|
344
|
+
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from "vitest";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
createBashTool,
|
|
4
|
+
createDefaultTools,
|
|
5
|
+
createReadFilesTool,
|
|
6
|
+
} from "./definitions.js";
|
|
3
7
|
|
|
4
8
|
describe("default skills tool", () => {
|
|
5
9
|
it("is included only when enabled with a skills executor", () => {
|
|
@@ -213,23 +217,133 @@ describe("default apply_patch tool", () => {
|
|
|
213
217
|
});
|
|
214
218
|
});
|
|
215
219
|
|
|
220
|
+
describe("default run_commands tool", () => {
|
|
221
|
+
it("accepts object input with commands as a single string", async () => {
|
|
222
|
+
const execute = vi.fn(async (command: string) => `ran:${command}`);
|
|
223
|
+
const tool = createBashTool(execute);
|
|
224
|
+
|
|
225
|
+
const result = await tool.execute({ commands: "ls" } as never, {
|
|
226
|
+
agentId: "agent-1",
|
|
227
|
+
conversationId: "conv-1",
|
|
228
|
+
iteration: 1,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
expect(result).toEqual([
|
|
232
|
+
{
|
|
233
|
+
query: "ls",
|
|
234
|
+
result: "ran:ls",
|
|
235
|
+
success: true,
|
|
236
|
+
},
|
|
237
|
+
]);
|
|
238
|
+
expect(execute).toHaveBeenCalledTimes(1);
|
|
239
|
+
expect(execute).toHaveBeenCalledWith(
|
|
240
|
+
"ls",
|
|
241
|
+
process.cwd(),
|
|
242
|
+
expect.objectContaining({
|
|
243
|
+
agentId: "agent-1",
|
|
244
|
+
conversationId: "conv-1",
|
|
245
|
+
iteration: 1,
|
|
246
|
+
}),
|
|
247
|
+
);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
describe("default read_files tool", () => {
|
|
252
|
+
it("normalizes ranged file requests and passes them to the executor", async () => {
|
|
253
|
+
const execute = vi.fn(async () => "selected lines");
|
|
254
|
+
const tool = createReadFilesTool(execute);
|
|
255
|
+
|
|
256
|
+
const result = await tool.execute(
|
|
257
|
+
{
|
|
258
|
+
files: [
|
|
259
|
+
{
|
|
260
|
+
path: "/tmp/example.ts",
|
|
261
|
+
start_line: 3,
|
|
262
|
+
end_line: 5,
|
|
263
|
+
},
|
|
264
|
+
],
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
agentId: "agent-1",
|
|
268
|
+
conversationId: "conv-1",
|
|
269
|
+
iteration: 1,
|
|
270
|
+
},
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
expect(result).toEqual([
|
|
274
|
+
{
|
|
275
|
+
query: "/tmp/example.ts:3-5",
|
|
276
|
+
result: "selected lines",
|
|
277
|
+
success: true,
|
|
278
|
+
},
|
|
279
|
+
]);
|
|
280
|
+
expect(execute).toHaveBeenCalledWith(
|
|
281
|
+
{
|
|
282
|
+
path: "/tmp/example.ts",
|
|
283
|
+
start_line: 3,
|
|
284
|
+
end_line: 5,
|
|
285
|
+
},
|
|
286
|
+
expect.objectContaining({
|
|
287
|
+
agentId: "agent-1",
|
|
288
|
+
conversationId: "conv-1",
|
|
289
|
+
iteration: 1,
|
|
290
|
+
}),
|
|
291
|
+
);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it("keeps legacy string inputs reading full file content", async () => {
|
|
295
|
+
const execute = vi.fn(async () => "full file");
|
|
296
|
+
const tool = createReadFilesTool(execute);
|
|
297
|
+
|
|
298
|
+
await tool.execute("/tmp/example.ts" as never, {
|
|
299
|
+
agentId: "agent-1",
|
|
300
|
+
conversationId: "conv-1",
|
|
301
|
+
iteration: 1,
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
expect(execute).toHaveBeenCalledWith(
|
|
305
|
+
{ path: "/tmp/example.ts" },
|
|
306
|
+
expect.objectContaining({
|
|
307
|
+
agentId: "agent-1",
|
|
308
|
+
conversationId: "conv-1",
|
|
309
|
+
iteration: 1,
|
|
310
|
+
}),
|
|
311
|
+
);
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
216
315
|
describe("zod schema conversion", () => {
|
|
217
316
|
it("preserves read_files required properties in generated JSON schema", () => {
|
|
218
317
|
const tool = createReadFilesTool(async () => "ok");
|
|
219
318
|
const inputSchema = tool.inputSchema as Record<string, unknown>;
|
|
220
319
|
const properties = inputSchema.properties as Record<string, unknown>;
|
|
221
320
|
expect(inputSchema.type).toBe("object");
|
|
222
|
-
expect(properties.
|
|
321
|
+
expect(properties.files).toMatchObject({
|
|
223
322
|
type: "array",
|
|
224
323
|
items: {
|
|
225
|
-
type: "
|
|
226
|
-
|
|
227
|
-
|
|
324
|
+
type: "object",
|
|
325
|
+
properties: {
|
|
326
|
+
path: {
|
|
327
|
+
type: "string",
|
|
328
|
+
description:
|
|
329
|
+
"The absolute file path of a text file to read content from",
|
|
330
|
+
},
|
|
331
|
+
start_line: {
|
|
332
|
+
type: "integer",
|
|
333
|
+
description: "Optional one-based starting line number to read from",
|
|
334
|
+
},
|
|
335
|
+
end_line: {
|
|
336
|
+
type: "integer",
|
|
337
|
+
description:
|
|
338
|
+
"Optional one-based ending line number to read through",
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
required: ["path"],
|
|
228
342
|
},
|
|
229
343
|
description:
|
|
230
|
-
"Array of
|
|
344
|
+
"Array of file read requests. Omit start_line and end_line to return the full file content; provide them to return only that inclusive one-based line range. Prefer this tool over running terminal command to get file content for better performance and reliability.",
|
|
231
345
|
});
|
|
232
|
-
expect(inputSchema.required).toEqual(["
|
|
346
|
+
expect(inputSchema.required).toEqual(["files"]);
|
|
233
347
|
});
|
|
234
348
|
|
|
235
349
|
it("exposes skills args as optional nullable in tool schemas", () => {
|
package/src/tools/definitions.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
EditFileInputSchema,
|
|
17
17
|
type FetchWebContentInput,
|
|
18
18
|
FetchWebContentInputSchema,
|
|
19
|
+
type ReadFileRequest,
|
|
19
20
|
type ReadFilesInput,
|
|
20
21
|
ReadFilesInputSchema,
|
|
21
22
|
ReadFilesInputUnionSchema,
|
|
@@ -72,6 +73,46 @@ function withTimeout<T>(
|
|
|
72
73
|
]);
|
|
73
74
|
}
|
|
74
75
|
|
|
76
|
+
function normalizeReadFileRequests(input: unknown): ReadFileRequest[] {
|
|
77
|
+
const validate = validateWithZod(ReadFilesInputUnionSchema, input);
|
|
78
|
+
|
|
79
|
+
if (typeof validate === "string") {
|
|
80
|
+
return [{ path: validate }];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (Array.isArray(validate)) {
|
|
84
|
+
return validate.map((value) =>
|
|
85
|
+
typeof value === "string" ? { path: value } : value,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if ("files" in validate) {
|
|
90
|
+
const files = Array.isArray(validate.files)
|
|
91
|
+
? validate.files
|
|
92
|
+
: [validate.files];
|
|
93
|
+
return files;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if ("file_paths" in validate) {
|
|
97
|
+
const filePaths = Array.isArray(validate.file_paths)
|
|
98
|
+
? validate.file_paths
|
|
99
|
+
: [validate.file_paths];
|
|
100
|
+
return filePaths.map((filePath) => ({ path: filePath }));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return [validate];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function formatReadFileQuery(request: ReadFileRequest): string {
|
|
107
|
+
const { path, start_line, end_line } = request;
|
|
108
|
+
if (start_line === undefined && end_line === undefined) {
|
|
109
|
+
return path;
|
|
110
|
+
}
|
|
111
|
+
const start = start_line ?? 1;
|
|
112
|
+
const end = end_line ?? "EOF";
|
|
113
|
+
return `${path}:${start}-${end}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
75
116
|
const APPLY_PATCH_TOOL_DESC = `This is a custom utility that makes it more convenient to add, remove, move, or edit code in a single file. \`apply_patch\` effectively allows you to execute a diff/patch against a file, but the format of the diff specification is unique to this task, so pay careful attention to these instructions. To use the \`apply_patch\` command, you should pass a message of the following structure as "input":
|
|
76
117
|
|
|
77
118
|
%%bash
|
|
@@ -147,38 +188,32 @@ export function createReadFilesTool(
|
|
|
147
188
|
return createTool<ReadFilesInput, ToolOperationResult[]>({
|
|
148
189
|
name: "read_files",
|
|
149
190
|
description:
|
|
150
|
-
"Read the
|
|
151
|
-
"Returns file contents or error messages for each path.
|
|
191
|
+
"Read the full content of text files at the provided absolute paths, or return only an inclusive one-based line range when start_line/end_line are provided. " +
|
|
192
|
+
"Returns file contents or error messages for each path.",
|
|
152
193
|
inputSchema: zodToJsonSchema(ReadFilesInputSchema),
|
|
153
194
|
timeoutMs: timeoutMs * 2, // Account for multiple files
|
|
154
195
|
retryable: true,
|
|
155
196
|
maxRetries: 1,
|
|
156
197
|
execute: async (input, context) => {
|
|
157
|
-
|
|
158
|
-
const validate = validateWithZod(ReadFilesInputUnionSchema, input);
|
|
159
|
-
const filePaths = Array.isArray(validate)
|
|
160
|
-
? validate
|
|
161
|
-
: typeof validate === "object"
|
|
162
|
-
? validate.file_paths
|
|
163
|
-
: [validate];
|
|
198
|
+
const requests = normalizeReadFileRequests(input);
|
|
164
199
|
|
|
165
200
|
return Promise.all(
|
|
166
|
-
|
|
201
|
+
requests.map(async (request): Promise<ToolOperationResult> => {
|
|
167
202
|
try {
|
|
168
203
|
const content = await withTimeout(
|
|
169
|
-
executor(
|
|
204
|
+
executor(request, context),
|
|
170
205
|
timeoutMs,
|
|
171
206
|
`File read timed out after ${timeoutMs}ms`,
|
|
172
207
|
);
|
|
173
208
|
return {
|
|
174
|
-
query:
|
|
209
|
+
query: formatReadFileQuery(request),
|
|
175
210
|
result: content,
|
|
176
211
|
success: true,
|
|
177
212
|
};
|
|
178
213
|
} catch (error) {
|
|
179
214
|
const msg = formatError(error);
|
|
180
215
|
return {
|
|
181
|
-
query:
|
|
216
|
+
query: formatReadFileQuery(request),
|
|
182
217
|
result: "",
|
|
183
218
|
error: `Error reading file: ${msg}`,
|
|
184
219
|
success: false,
|
|
@@ -214,15 +249,14 @@ export function createSearchTool(
|
|
|
214
249
|
maxRetries: 1,
|
|
215
250
|
execute: async (input, context) => {
|
|
216
251
|
// Validate input with Zod schema
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
: [validatedInput];
|
|
252
|
+
const validate = validateWithZod(SearchCodebaseUnionInputSchema, input);
|
|
253
|
+
const queries = Array.isArray(validate)
|
|
254
|
+
? validate
|
|
255
|
+
: typeof validate === "object"
|
|
256
|
+
? Array.isArray(validate.queries)
|
|
257
|
+
? validate.queries
|
|
258
|
+
: [validate.queries]
|
|
259
|
+
: [validate];
|
|
226
260
|
|
|
227
261
|
return Promise.all(
|
|
228
262
|
queries.map(async (query): Promise<ToolOperationResult> => {
|
|
@@ -282,7 +316,9 @@ export function createBashTool(
|
|
|
282
316
|
const commands = Array.isArray(validate)
|
|
283
317
|
? validate
|
|
284
318
|
: typeof validate === "object"
|
|
285
|
-
? validate.commands
|
|
319
|
+
? Array.isArray(validate.commands)
|
|
320
|
+
? validate.commands
|
|
321
|
+
: [validate.commands]
|
|
286
322
|
: [validate];
|
|
287
323
|
|
|
288
324
|
return Promise.all(
|
|
@@ -562,7 +598,7 @@ export function createAskQuestionTool(
|
|
|
562
598
|
*
|
|
563
599
|
* const tools = createDefaultTools({
|
|
564
600
|
* executors: {
|
|
565
|
-
* readFile: async (path) => fs.readFile(path, "utf-8"),
|
|
601
|
+
* readFile: async ({ path }) => fs.readFile(path, "utf-8"),
|
|
566
602
|
* bash: async (cmd, cwd) => {
|
|
567
603
|
* return new Promise((resolve, reject) => {
|
|
568
604
|
* exec(cmd, { cwd }, (err, stdout, stderr) => {
|