@mikode13/harness 5.3.0 → 5.5.0
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/README.md +36 -3
- package/dist/agent/domain/agent.d.ts +15 -3
- package/dist/agent/domain/agent.js.map +1 -1
- package/dist/agent/domain/question.d.ts +35 -0
- package/dist/agent/domain/question.js +35 -0
- package/dist/agent/domain/question.js.map +1 -0
- package/dist/engines/domain/model/askUser.d.ts +14 -0
- package/dist/engines/domain/model/askUser.js +107 -0
- package/dist/engines/domain/model/askUser.js.map +1 -0
- package/dist/engines/domain/model/llmAgent.d.ts +5 -0
- package/dist/engines/domain/model/llmAgent.js +55 -7
- package/dist/engines/domain/model/llmAgent.js.map +1 -1
- package/dist/engines/domain/model/stepContext.d.ts +1 -1
- package/dist/engines/domain/model/stepContext.js +8 -1
- package/dist/engines/domain/model/stepContext.js.map +1 -1
- package/dist/factory/infrastructure/agentLLMFactory.d.ts +17 -2
- package/dist/factory/infrastructure/agentLLMFactory.js +57 -27
- package/dist/factory/infrastructure/agentLLMFactory.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js.map +1 -1
- package/dist/llm/domain/llm.d.ts +2 -2
- package/dist/llm/domain/llm.js.map +1 -1
- package/dist/llm/domain/message.d.ts +17 -1
- package/dist/llm/domain/message.js +14 -1
- package/dist/llm/domain/message.js.map +1 -1
- package/dist/llm/infrastructure/claudeLLMClient.d.ts +5 -2
- package/dist/llm/infrastructure/claudeLLMClient.js +61 -5
- package/dist/llm/infrastructure/claudeLLMClient.js.map +1 -1
- package/dist/llm/infrastructure/openAILLMClient.d.ts +5 -2
- package/dist/llm/infrastructure/openAILLMClient.js +25 -2
- package/dist/llm/infrastructure/openAILLMClient.js.map +1 -1
- package/dist/retry/domain/model/retryingAgent.js +11 -2
- package/dist/retry/domain/model/retryingAgent.js.map +1 -1
- package/dist/tools/infrastructure/workspaceTools.d.ts +1 -0
- package/dist/tools/infrastructure/workspaceTools.js +32 -10
- package/dist/tools/infrastructure/workspaceTools.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ILogger } from '#src/shared/domain/logger';
|
|
2
2
|
import type { LLMClient, LLMResponse } from '../domain/llm.ts';
|
|
3
|
-
import type
|
|
3
|
+
import { type Message } from '../domain/message.ts';
|
|
4
4
|
import { type NativeTool, type ToolDefinition } from '../domain/tool.ts';
|
|
5
5
|
export declare const openAIModels: readonly ["gpt-6-astra", "gpt-5.6-sol", "gpt-5.6-luna", "gpt-5.6-terra"];
|
|
6
6
|
export type OpenAIModel = (typeof openAIModels)[number];
|
|
@@ -15,12 +15,15 @@ export declare class OpenAILLMClient implements LLMClient {
|
|
|
15
15
|
private readonly client;
|
|
16
16
|
private readonly model;
|
|
17
17
|
private readonly reasoningEffort;
|
|
18
|
+
private readonly compactAt;
|
|
18
19
|
readonly systemPrompt: string;
|
|
19
20
|
readonly providerDataSource = "openai";
|
|
20
21
|
private readonly logger;
|
|
21
|
-
constructor({ model, reasoningEffort, systemPrompt, logger, }: {
|
|
22
|
+
constructor({ model, reasoningEffort, compactAt, systemPrompt, logger, }: {
|
|
22
23
|
model: string;
|
|
23
24
|
reasoningEffort?: string;
|
|
25
|
+
/** The input tokens at which OpenAI compacts the context on its side. Without it, never. */
|
|
26
|
+
compactAt?: number | undefined;
|
|
24
27
|
systemPrompt: string;
|
|
25
28
|
logger: ILogger;
|
|
26
29
|
});
|
|
@@ -3,6 +3,7 @@ import { InvalidAgentConfigError, RecoverableError, UnrecoverableError, withSpen
|
|
|
3
3
|
import { classifyHostFailure, classifyLocalFailure, classifyProviderFailure, describeFailure, treatErrors, } from '#src/shared/domain/providerFailure';
|
|
4
4
|
import { isOneOf } from '#src/shared/domain/isOneOf';
|
|
5
5
|
import { MaxContextError } from "../domain/errors.js";
|
|
6
|
+
import { sentContext } from "../domain/message.js";
|
|
6
7
|
import { nativeOf } from "../domain/tool.js";
|
|
7
8
|
// The SDK types `model` as a plain string, so this list is maintained by hand.
|
|
8
9
|
export const openAIModels = [
|
|
@@ -88,6 +89,9 @@ function toOpenAIItems(part, role, applyPatch) {
|
|
|
88
89
|
case 'providerData':
|
|
89
90
|
// Another provider's item would be rejected; losing it only costs that provider's reasoning.
|
|
90
91
|
return part.source === openAISource ? [part.data] : [];
|
|
92
|
+
case 'compaction':
|
|
93
|
+
// Another provider's summary means nothing here; this client sends the turns it replaced.
|
|
94
|
+
return part.source === openAISource ? [part.data] : [];
|
|
91
95
|
case 'reasoning':
|
|
92
96
|
return [];
|
|
93
97
|
}
|
|
@@ -150,6 +154,15 @@ function describeItem(item, logger, applyPatch) {
|
|
|
150
154
|
}
|
|
151
155
|
return parts;
|
|
152
156
|
}
|
|
157
|
+
case 'compaction': {
|
|
158
|
+
// Encrypted: OpenAI's summary of the context before it, which it alone reads back.
|
|
159
|
+
const compaction = {
|
|
160
|
+
type: 'compaction',
|
|
161
|
+
id: item.id,
|
|
162
|
+
encrypted_content: item.encrypted_content,
|
|
163
|
+
};
|
|
164
|
+
return [{ type: 'compaction', source: openAISource, data: compaction }];
|
|
165
|
+
}
|
|
153
166
|
case 'function_call':
|
|
154
167
|
return [
|
|
155
168
|
{
|
|
@@ -255,18 +268,22 @@ export class OpenAILLMClient {
|
|
|
255
268
|
client;
|
|
256
269
|
model;
|
|
257
270
|
reasoningEffort;
|
|
271
|
+
compactAt;
|
|
258
272
|
systemPrompt;
|
|
259
273
|
providerDataSource = openAISource;
|
|
260
274
|
logger;
|
|
261
275
|
// `model` and `reasoningEffort` are untyped on purpose: this adapter is the one that knows
|
|
262
276
|
// what OpenAI supports.
|
|
263
|
-
constructor({ model, reasoningEffort = 'high', systemPrompt, logger, }) {
|
|
277
|
+
constructor({ model, reasoningEffort = 'high', compactAt, systemPrompt, logger, }) {
|
|
264
278
|
if (!isOneOf(openAIModels, model)) {
|
|
265
279
|
throw new InvalidAgentConfigError(`"${model}" is not an OpenAI model; expected one of: ${openAIModels.join(', ')}`);
|
|
266
280
|
}
|
|
267
281
|
if (!isOneOf(openAIReasoningEfforts, reasoningEffort)) {
|
|
268
282
|
throw new InvalidAgentConfigError(`"${reasoningEffort}" is not an OpenAI reasoning effort; expected one of: ${openAIReasoningEfforts.join(', ')}`);
|
|
269
283
|
}
|
|
284
|
+
if (compactAt !== undefined && !(Number.isInteger(compactAt) && compactAt > 0)) {
|
|
285
|
+
throw new InvalidAgentConfigError(`compactAt must be a positive whole number of tokens, not ${String(compactAt)}`);
|
|
286
|
+
}
|
|
270
287
|
try {
|
|
271
288
|
// The SDK's default retries stay on: they back off and honour `Retry-After`, which
|
|
272
289
|
// `RetryingAgent` does not. See "The provider SDKs keep their transport retries" in decisions.md.
|
|
@@ -280,6 +297,7 @@ export class OpenAILLMClient {
|
|
|
280
297
|
}
|
|
281
298
|
this.model = model;
|
|
282
299
|
this.reasoningEffort = reasoningEffort;
|
|
300
|
+
this.compactAt = compactAt;
|
|
283
301
|
this.systemPrompt = systemPrompt;
|
|
284
302
|
this.logger = logger;
|
|
285
303
|
}
|
|
@@ -293,9 +311,14 @@ export class OpenAILLMClient {
|
|
|
293
311
|
response = await this.client.responses.create({
|
|
294
312
|
model: this.model,
|
|
295
313
|
instructions: this.systemPrompt,
|
|
296
|
-
|
|
314
|
+
// The latest compaction carries everything before it, which OpenAI would only ignore.
|
|
315
|
+
input: sentContext(context, openAISource).flatMap(message => toOpenAIInput(message, applyPatch)),
|
|
297
316
|
// Left out when empty, so a request without tools stays as it always was.
|
|
298
317
|
...(tools.length > 0 && { tools: tools.map(toOpenAITool) }),
|
|
318
|
+
// OpenAI compacts inside the call that passes it, and answers with the compaction first.
|
|
319
|
+
...(this.compactAt !== undefined && {
|
|
320
|
+
context_management: [{ type: 'compaction', compact_threshold: this.compactAt }],
|
|
321
|
+
}),
|
|
299
322
|
store: false,
|
|
300
323
|
reasoning: { effort: this.reasoningEffort, summary: 'concise' },
|
|
301
324
|
// With `store: false` this is the only way to send the reasoning back.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openAILLMClient.js","sourceRoot":"","sources":["../../../src/llm/infrastructure/openAILLMClient.ts"],"names":[],"mappings":"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","sourcesContent":["import OpenAI, { APIError } from 'openai';\nimport type { ReasoningEffort } from 'openai/resources/shared';\nimport type {\n\tApplyPatchTool,\n\tFunctionTool,\n\tResponse,\n\tResponseApplyPatchToolCall,\n\tResponseInputItem,\n\tResponseOutputItem,\n\tResponseReasoningItem,\n\tResponseUsage,\n} from 'openai/resources/responses/responses';\nimport {\n\tInvalidAgentConfigError,\n\tRecoverableError,\n\tUnrecoverableError,\n\twithSpentTokens,\n} from '#src/shared/domain/errors';\nimport {\n\tclassifyHostFailure,\n\tclassifyLocalFailure,\n\tclassifyProviderFailure,\n\tdescribeFailure,\n\ttreatErrors,\n} from '#src/shared/domain/providerFailure';\nimport { isOneOf } from '#src/shared/domain/isOneOf';\nimport type { ILogger } from '#src/shared/domain/logger';\nimport type { Tokens } from '#src/shared/domain/tokens';\nimport { MaxContextError } from '../domain/errors.ts';\nimport type { LLMClient, LLMResponse, StopReason } from '../domain/llm.ts';\nimport type { Message, MessagePart } from '../domain/message.ts';\nimport { type NativeTool, nativeOf, type ToolDefinition } from '../domain/tool.ts';\n\n// The SDK types `model` as a plain string, so this list is maintained by hand.\nexport const openAIModels = [\n\t'gpt-6-astra',\n\t'gpt-5.6-sol',\n\t'gpt-5.6-luna',\n\t'gpt-5.6-terra',\n] as const;\nexport type OpenAIModel = (typeof openAIModels)[number];\n\n// The efforts both the Responses API and Codex's model catalog list for the models above.\nexport const openAIReasoningEfforts = [\n\t'low',\n\t'medium',\n\t'high',\n\t'xhigh',\n\t'max',\n] as const satisfies readonly NonNullable<ReasoningEffort>[];\nexport type OpenAIReasoningEffort = (typeof openAIReasoningEfforts)[number];\n\n// The request itself is wrong or not allowed, so sending it again cannot succeed.\nconst unrecoverableStatuses = [400, 401, 403, 404, 422];\n\n// Out of credit or over a spend limit. These arrive as a 429 like a rate limit, but the account\n// stays blocked however often the request is sent.\nconst exhaustedQuotaCodes = [\n\t'insufficient_quota',\n\t'credit_balance_exhausted',\n\t'project_spend_limit_exceeded',\n];\n\n// The codes OpenAI gives a failed response that a new attempt can succeed past.\nconst transientResponseErrorCodes = ['server_error', 'rate_limit_exceeded'];\n\n// This client's label on the reasoning items it keeps for replay; see `ProviderDataPart`.\nconst openAISource = 'openai';\n\n// Marks a failed call's output, which would otherwise read the same as a successful one.\nconst failedToolPrefix = 'The tool call failed: ';\n\n/**\n * OpenAI takes a flat list of items, so every part becomes its own, in order. Reasoning crosses\n * back only as the encrypted item this client kept, which OpenAI requires beside the tool calls\n * that followed it; a reasoning part is its summary, kept for narration. A call to the tool\n * offered as `apply_patch`, named `applyPatch` here, and its result go back as that tool's own\n * items.\n */\nfunction toOpenAIItems(\n\tpart: MessagePart,\n\trole: 'user' | 'assistant',\n\tapplyPatch: string | undefined,\n): ResponseInputItem[] {\n\tswitch (part.type) {\n\t\tcase 'text':\n\t\t\treturn part.text ? [{ role, content: part.text }] : [];\n\t\tcase 'toolCall':\n\t\t\tif (part.name === applyPatch) {\n\t\t\t\treturn [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: 'apply_patch_call',\n\t\t\t\t\t\tcall_id: part.id,\n\t\t\t\t\t\t// Kept as OpenAI sent it: the conversation holds the operation unchanged.\n\t\t\t\t\t\toperation: part.input as ResponseApplyPatchToolCall['operation'],\n\t\t\t\t\t\tstatus: 'completed',\n\t\t\t\t\t},\n\t\t\t\t];\n\t\t\t}\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'function_call',\n\t\t\t\t\tcall_id: part.id,\n\t\t\t\t\tname: part.name,\n\t\t\t\t\t// A string is arguments that did not parse, sent back as the model wrote them.\n\t\t\t\t\targuments: typeof part.input === 'string' ? part.input : JSON.stringify(part.input),\n\t\t\t\t},\n\t\t\t];\n\t\tcase 'toolResult':\n\t\t\tif (part.name === applyPatch) {\n\t\t\t\treturn [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: 'apply_patch_call_output',\n\t\t\t\t\t\tcall_id: part.callId,\n\t\t\t\t\t\tstatus: part.isError ? 'failed' : 'completed',\n\t\t\t\t\t\toutput: part.output,\n\t\t\t\t\t},\n\t\t\t\t];\n\t\t\t}\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'function_call_output',\n\t\t\t\t\tcall_id: part.callId,\n\t\t\t\t\t// OpenAI has no error flag, so the text says it; the conversation keeps the output as it was.\n\t\t\t\t\toutput: part.isError ? `${failedToolPrefix}${part.output}` : part.output,\n\t\t\t\t},\n\t\t\t];\n\t\tcase 'providerData':\n\t\t\t// Another provider's item would be rejected; losing it only costs that provider's reasoning.\n\t\t\treturn part.source === openAISource ? [part.data as ResponseReasoningItem] : [];\n\t\tcase 'reasoning':\n\t\t\treturn [];\n\t}\n}\n\nfunction toOpenAIInput(message: Message, applyPatch: string | undefined): ResponseInputItem[] {\n\t// A tool message holds only results, which carry no role.\n\tconst role = message.role === 'assistant' ? 'assistant' : 'user';\n\treturn message.content.flatMap(part => toOpenAIItems(part, role, applyPatch));\n}\n\n/**\n * Strict: the API constrains the model to the schema, so its arguments always parse and match.\n * A schema outside what strict mode supports fails the request with a 400. The native\n * `apply_patch` has OpenAI's own schema.\n */\nfunction toOpenAITool(definition: ToolDefinition): FunctionTool | ApplyPatchTool {\n\tconst native = nativeOf(definition);\n\tif (native === 'applyPatch') return { type: 'apply_patch' };\n\tif (native !== undefined) {\n\t\tthrow new InvalidAgentConfigError(`OpenAI has no native tool \"${native}\"`);\n\t}\n\tconst { name, description, inputSchema } = definition;\n\treturn { type: 'function', name, description, parameters: inputSchema, strict: true };\n}\n\n/** The name the tool offered as `apply_patch` has in the conversation, if one is offered. */\nfunction applyPatchName(tools: ToolDefinition[]): string | undefined {\n\treturn tools.find(tool => nativeOf(tool) === 'applyPatch')?.name;\n}\n\n/**\n * Strict mode makes arguments that do not parse rare: a response cut short mid-call ends as\n * `truncated` before any tool runs. Should one arrive, the tool gets the raw string and its\n * rejection lets the model correct itself, instead of the run failing.\n */\nfunction parseArguments(serialized: string): unknown {\n\ttry {\n\t\treturn JSON.parse(serialized);\n\t} catch {\n\t\treturn serialized;\n\t}\n}\n\nfunction describeItem(\n\titem: ResponseOutputItem,\n\tlogger: ILogger,\n\tapplyPatch: string | undefined,\n): MessagePart[] {\n\tswitch (item.type) {\n\t\tcase 'message':\n\t\t\t// A refusal is not an answer: it only sets the stop reason.\n\t\t\treturn item.content.flatMap(content =>\n\t\t\t\tcontent.type === 'output_text' ? [{ type: 'text' as const, text: content.text }] : [],\n\t\t\t);\n\t\tcase 'reasoning': {\n\t\t\t// The summary is the readable part; the encrypted item is what OpenAI reads back.\n\t\t\tconst text = item.summary.map(summary => summary.text).join('\\n');\n\t\t\tconst parts: MessagePart[] = text ? [{ type: 'reasoning', text }] : [];\n\t\t\t// Without its encrypted content the item cannot be replayed: OpenAI stores nothing.\n\t\t\tif (item.encrypted_content) {\n\t\t\t\tconst encrypted: ResponseReasoningItem = {\n\t\t\t\t\ttype: 'reasoning',\n\t\t\t\t\tid: item.id,\n\t\t\t\t\tsummary: item.summary,\n\t\t\t\t\tencrypted_content: item.encrypted_content,\n\t\t\t\t};\n\t\t\t\tparts.push({ type: 'providerData', source: openAISource, data: encrypted });\n\t\t\t}\n\t\t\treturn parts;\n\t\t}\n\t\tcase 'function_call':\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'toolCall',\n\t\t\t\t\tid: item.call_id,\n\t\t\t\t\tname: item.name,\n\t\t\t\t\tinput: parseArguments(item.arguments),\n\t\t\t\t},\n\t\t\t];\n\t\tcase 'apply_patch_call':\n\t\t\t// Only when `apply_patch` was offered; otherwise it is unexpected, as below.\n\t\t\tif (applyPatch !== undefined) {\n\t\t\t\treturn [{ type: 'toolCall', id: item.call_id, name: applyPatch, input: item.operation }];\n\t\t\t}\n\t\t\treturn unmapped(item, logger);\n\t\tdefault:\n\t\t\treturn unmapped(item, logger);\n\t}\n}\n\n/** An item this client does not map, which it reports and leaves out. */\nfunction unmapped(item: ResponseOutputItem, logger: ILogger): MessagePart[] {\n\t// No other built-in tool is offered, so any other item is output this client does not expect.\n\ttreatErrors(\n\t\t() => {\n\t\t\tlogger.warn(item, 'OpenAI returned an output item the client does not map');\n\t\t},\n\t\tclassifyHostFailure,\n\t\t'OpenAI client logger failed while mapping a response',\n\t);\n\treturn [];\n}\n\nfunction toStopReason(response: Response): StopReason {\n\tif (response.status === 'incomplete') {\n\t\treturn response.incomplete_details?.reason === 'content_filter' ? 'refused' : 'truncated';\n\t}\n\tif (response.status !== 'completed') {\n\t\tconst cause =\n\t\t\tresponse.error?.message ??\n\t\t\t`The response ended with status \"${response.status ?? 'unknown'}\".`;\n\t\t// The same failure is retried when it arrives as an SDK error, so it must be here too.\n\t\tif (response.error && transientResponseErrorCodes.includes(response.error.code)) {\n\t\t\tthrow new RecoverableError('The OpenAI response failed', { cause });\n\t\t}\n\t\tthrow new UnrecoverableError('The OpenAI response did not complete', { cause });\n\t}\n\n\tconst refused = response.output.some(\n\t\titem => item.type === 'message' && item.content.some(content => content.type === 'refusal'),\n\t);\n\treturn refused ? 'refused' : 'completed';\n}\n\n/** OpenAI counts both cache reads and cache writes inside `input_tokens`. */\nfunction toTokens(usage: ResponseUsage | undefined, logger: ILogger): Tokens | undefined {\n\tif (!usage) {\n\t\t// Zeros would report a call that may have been billed as free.\n\t\ttreatErrors(\n\t\t\t() => {\n\t\t\t\tlogger.warn('OpenAI returned a response without usage; it cannot be accounted for');\n\t\t\t},\n\t\t\tclassifyHostFailure,\n\t\t\t'OpenAI client logger failed while reading usage',\n\t\t);\n\t\treturn undefined;\n\t}\n\n\tconst { cached_tokens, cache_write_tokens } = usage.input_tokens_details;\n\treturn {\n\t\tinputTokens: usage.input_tokens - cached_tokens - cache_write_tokens,\n\t\treadCacheTokens: cached_tokens,\n\t\twrittenCacheTokens: cache_write_tokens,\n\t\toutputTokens: usage.output_tokens,\n\t};\n}\n\nfunction toLLMResponse(\n\tresponse: Response,\n\tlogger: ILogger,\n\tapplyPatch: string | undefined,\n): LLMResponse {\n\tlet usage: Tokens | undefined;\n\n\ttry {\n\t\t// Inside the try: it warns when the usage is missing, and that warning can throw too.\n\t\tusage = toTokens(response.usage, logger);\n\t\treturn {\n\t\t\tmessage: {\n\t\t\t\trole: 'assistant',\n\t\t\t\tcontent: response.output.flatMap(item => describeItem(item, logger, applyPatch)),\n\t\t\t},\n\t\t\tusage,\n\t\t\tstopReason: toStopReason(response),\n\t\t};\n\t} catch (error) {\n\t\t// The call was billed whether the response failed or a logger did while mapping it, and\n\t\t// an answer without usage leaves the run's count unknown.\n\t\tthrow withSpentTokens(error, usage, !response.usage);\n\t}\n}\n\nfunction classifyOpenAIFailure(error: unknown): Error {\n\tif (error instanceof APIError) {\n\t\tif (error.code === 'context_length_exceeded') {\n\t\t\treturn new MaxContextError('The context no longer fits the OpenAI model', {\n\t\t\t\tcause: describeFailure(error),\n\t\t\t});\n\t\t}\n\t\tif (\n\t\t\texhaustedQuotaCodes.some(code => code === error.code) ||\n\t\t\tunrecoverableStatuses.some(status => status === error.status)\n\t\t) {\n\t\t\treturn new UnrecoverableError('OpenAI rejected the request', {\n\t\t\t\tcause: describeFailure(error),\n\t\t\t});\n\t\t}\n\t}\n\n\treturn classifyProviderFailure(error, 'The OpenAI request failed');\n}\n\n/**\n * A stateless `LLMClient` on the OpenAI Responses API. With `store: false` OpenAI keeps\n * nothing between calls, so every call carries the whole context: the conversation is\n * MiKode's, not the provider's.\n */\nexport class OpenAILLMClient implements LLMClient {\n\tprivate readonly client: OpenAI;\n\tprivate readonly model: OpenAIModel;\n\tprivate readonly reasoningEffort: OpenAIReasoningEffort;\n\treadonly systemPrompt: string;\n\treadonly providerDataSource = openAISource;\n\tprivate readonly logger: ILogger;\n\n\t// `model` and `reasoningEffort` are untyped on purpose: this adapter is the one that knows\n\t// what OpenAI supports.\n\tconstructor({\n\t\tmodel,\n\t\treasoningEffort = 'high',\n\t\tsystemPrompt,\n\t\tlogger,\n\t}: {\n\t\tmodel: string;\n\t\treasoningEffort?: string;\n\t\tsystemPrompt: string;\n\t\tlogger: ILogger;\n\t}) {\n\t\tif (!isOneOf(openAIModels, model)) {\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t`\"${model}\" is not an OpenAI model; expected one of: ${openAIModels.join(', ')}`,\n\t\t\t);\n\t\t}\n\t\tif (!isOneOf(openAIReasoningEfforts, reasoningEffort)) {\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t`\"${reasoningEffort}\" is not an OpenAI reasoning effort; expected one of: ${openAIReasoningEfforts.join(', ')}`,\n\t\t\t);\n\t\t}\n\n\t\ttry {\n\t\t\t// The SDK's default retries stay on: they back off and honour `Retry-After`, which\n\t\t\t// `RetryingAgent` does not. See \"The provider SDKs keep their transport retries\" in decisions.md.\n\t\t\tthis.client = new OpenAI();\n\t\t} catch (error) {\n\t\t\t// A missing API key, for example: building the client again cannot fix it.\n\t\t\tthrow new UnrecoverableError('The OpenAI client could not be set up', {\n\t\t\t\tcause: describeFailure(error),\n\t\t\t});\n\t\t}\n\n\t\tthis.model = model;\n\t\tthis.reasoningEffort = reasoningEffort;\n\t\tthis.systemPrompt = systemPrompt;\n\t\tthis.logger = logger;\n\t}\n\n\tsupportsNative(tool: NativeTool): boolean {\n\t\treturn tool === 'applyPatch';\n\t}\n\n\tasync send(\n\t\t{ context, tools }: { context: Message[]; tools: ToolDefinition[] },\n\t\tsignal: AbortSignal,\n\t): Promise<LLMResponse> {\n\t\tlet response: Response;\n\t\tconst applyPatch = applyPatchName(tools);\n\n\t\ttry {\n\t\t\tresponse = await this.client.responses.create(\n\t\t\t\t{\n\t\t\t\t\tmodel: this.model,\n\t\t\t\t\tinstructions: this.systemPrompt,\n\t\t\t\t\tinput: context.flatMap(message => toOpenAIInput(message, applyPatch)),\n\t\t\t\t\t// Left out when empty, so a request without tools stays as it always was.\n\t\t\t\t\t...(tools.length > 0 && { tools: tools.map(toOpenAITool) }),\n\t\t\t\t\tstore: false,\n\t\t\t\t\treasoning: { effort: this.reasoningEffort, summary: 'concise' },\n\t\t\t\t\t// With `store: false` this is the only way to send the reasoning back.\n\t\t\t\t\tinclude: ['reasoning.encrypted_content'],\n\t\t\t\t},\n\t\t\t\t{ signal },\n\t\t\t);\n\t\t} catch (error) {\n\t\t\t// The SDK's abort error is named 'Error', so it would pass for a provider failure.\n\t\t\tsignal.throwIfAborted();\n\t\t\tthrow classifyOpenAIFailure(error);\n\t\t}\n\n\t\treturn treatErrors(\n\t\t\t() => toLLMResponse(response, this.logger, applyPatch),\n\t\t\tclassifyLocalFailure,\n\t\t\t'OpenAI response mapping failed',\n\t\t);\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"openAILLMClient.js","sourceRoot":"","sources":["../../../src/llm/infrastructure/openAILLMClient.ts"],"names":[],"mappings":"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","sourcesContent":["import OpenAI, { APIError } from 'openai';\nimport type { ReasoningEffort } from 'openai/resources/shared';\nimport type {\n\tApplyPatchTool,\n\tFunctionTool,\n\tResponse,\n\tResponseApplyPatchToolCall,\n\tResponseCompactionItemParam,\n\tResponseInputItem,\n\tResponseOutputItem,\n\tResponseReasoningItem,\n\tResponseUsage,\n} from 'openai/resources/responses/responses';\nimport {\n\tInvalidAgentConfigError,\n\tRecoverableError,\n\tUnrecoverableError,\n\twithSpentTokens,\n} from '#src/shared/domain/errors';\nimport {\n\tclassifyHostFailure,\n\tclassifyLocalFailure,\n\tclassifyProviderFailure,\n\tdescribeFailure,\n\ttreatErrors,\n} from '#src/shared/domain/providerFailure';\nimport { isOneOf } from '#src/shared/domain/isOneOf';\nimport type { ILogger } from '#src/shared/domain/logger';\nimport type { Tokens } from '#src/shared/domain/tokens';\nimport { MaxContextError } from '../domain/errors.ts';\nimport type { LLMClient, LLMResponse, StopReason } from '../domain/llm.ts';\nimport { type Message, type MessagePart, sentContext } from '../domain/message.ts';\nimport { type NativeTool, nativeOf, type ToolDefinition } from '../domain/tool.ts';\n\n// The SDK types `model` as a plain string, so this list is maintained by hand.\nexport const openAIModels = [\n\t'gpt-6-astra',\n\t'gpt-5.6-sol',\n\t'gpt-5.6-luna',\n\t'gpt-5.6-terra',\n] as const;\nexport type OpenAIModel = (typeof openAIModels)[number];\n\n// The efforts both the Responses API and Codex's model catalog list for the models above.\nexport const openAIReasoningEfforts = [\n\t'low',\n\t'medium',\n\t'high',\n\t'xhigh',\n\t'max',\n] as const satisfies readonly NonNullable<ReasoningEffort>[];\nexport type OpenAIReasoningEffort = (typeof openAIReasoningEfforts)[number];\n\n// The request itself is wrong or not allowed, so sending it again cannot succeed.\nconst unrecoverableStatuses = [400, 401, 403, 404, 422];\n\n// Out of credit or over a spend limit. These arrive as a 429 like a rate limit, but the account\n// stays blocked however often the request is sent.\nconst exhaustedQuotaCodes = [\n\t'insufficient_quota',\n\t'credit_balance_exhausted',\n\t'project_spend_limit_exceeded',\n];\n\n// The codes OpenAI gives a failed response that a new attempt can succeed past.\nconst transientResponseErrorCodes = ['server_error', 'rate_limit_exceeded'];\n\n// This client's label on the reasoning items it keeps for replay; see `ProviderDataPart`.\nconst openAISource = 'openai';\n\n// Marks a failed call's output, which would otherwise read the same as a successful one.\nconst failedToolPrefix = 'The tool call failed: ';\n\n/**\n * OpenAI takes a flat list of items, so every part becomes its own, in order. Reasoning crosses\n * back only as the encrypted item this client kept, which OpenAI requires beside the tool calls\n * that followed it; a reasoning part is its summary, kept for narration. A call to the tool\n * offered as `apply_patch`, named `applyPatch` here, and its result go back as that tool's own\n * items.\n */\nfunction toOpenAIItems(\n\tpart: MessagePart,\n\trole: 'user' | 'assistant',\n\tapplyPatch: string | undefined,\n): ResponseInputItem[] {\n\tswitch (part.type) {\n\t\tcase 'text':\n\t\t\treturn part.text ? [{ role, content: part.text }] : [];\n\t\tcase 'toolCall':\n\t\t\tif (part.name === applyPatch) {\n\t\t\t\treturn [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: 'apply_patch_call',\n\t\t\t\t\t\tcall_id: part.id,\n\t\t\t\t\t\t// Kept as OpenAI sent it: the conversation holds the operation unchanged.\n\t\t\t\t\t\toperation: part.input as ResponseApplyPatchToolCall['operation'],\n\t\t\t\t\t\tstatus: 'completed',\n\t\t\t\t\t},\n\t\t\t\t];\n\t\t\t}\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'function_call',\n\t\t\t\t\tcall_id: part.id,\n\t\t\t\t\tname: part.name,\n\t\t\t\t\t// A string is arguments that did not parse, sent back as the model wrote them.\n\t\t\t\t\targuments: typeof part.input === 'string' ? part.input : JSON.stringify(part.input),\n\t\t\t\t},\n\t\t\t];\n\t\tcase 'toolResult':\n\t\t\tif (part.name === applyPatch) {\n\t\t\t\treturn [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: 'apply_patch_call_output',\n\t\t\t\t\t\tcall_id: part.callId,\n\t\t\t\t\t\tstatus: part.isError ? 'failed' : 'completed',\n\t\t\t\t\t\toutput: part.output,\n\t\t\t\t\t},\n\t\t\t\t];\n\t\t\t}\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'function_call_output',\n\t\t\t\t\tcall_id: part.callId,\n\t\t\t\t\t// OpenAI has no error flag, so the text says it; the conversation keeps the output as it was.\n\t\t\t\t\toutput: part.isError ? `${failedToolPrefix}${part.output}` : part.output,\n\t\t\t\t},\n\t\t\t];\n\t\tcase 'providerData':\n\t\t\t// Another provider's item would be rejected; losing it only costs that provider's reasoning.\n\t\t\treturn part.source === openAISource ? [part.data as ResponseReasoningItem] : [];\n\t\tcase 'compaction':\n\t\t\t// Another provider's summary means nothing here; this client sends the turns it replaced.\n\t\t\treturn part.source === openAISource ? [part.data as ResponseCompactionItemParam] : [];\n\t\tcase 'reasoning':\n\t\t\treturn [];\n\t}\n}\n\nfunction toOpenAIInput(message: Message, applyPatch: string | undefined): ResponseInputItem[] {\n\t// A tool message holds only results, which carry no role.\n\tconst role = message.role === 'assistant' ? 'assistant' : 'user';\n\treturn message.content.flatMap(part => toOpenAIItems(part, role, applyPatch));\n}\n\n/**\n * Strict: the API constrains the model to the schema, so its arguments always parse and match.\n * A schema outside what strict mode supports fails the request with a 400. The native\n * `apply_patch` has OpenAI's own schema.\n */\nfunction toOpenAITool(definition: ToolDefinition): FunctionTool | ApplyPatchTool {\n\tconst native = nativeOf(definition);\n\tif (native === 'applyPatch') return { type: 'apply_patch' };\n\tif (native !== undefined) {\n\t\tthrow new InvalidAgentConfigError(`OpenAI has no native tool \"${native}\"`);\n\t}\n\tconst { name, description, inputSchema } = definition;\n\treturn { type: 'function', name, description, parameters: inputSchema, strict: true };\n}\n\n/** The name the tool offered as `apply_patch` has in the conversation, if one is offered. */\nfunction applyPatchName(tools: ToolDefinition[]): string | undefined {\n\treturn tools.find(tool => nativeOf(tool) === 'applyPatch')?.name;\n}\n\n/**\n * Strict mode makes arguments that do not parse rare: a response cut short mid-call ends as\n * `truncated` before any tool runs. Should one arrive, the tool gets the raw string and its\n * rejection lets the model correct itself, instead of the run failing.\n */\nfunction parseArguments(serialized: string): unknown {\n\ttry {\n\t\treturn JSON.parse(serialized);\n\t} catch {\n\t\treturn serialized;\n\t}\n}\n\nfunction describeItem(\n\titem: ResponseOutputItem,\n\tlogger: ILogger,\n\tapplyPatch: string | undefined,\n): MessagePart[] {\n\tswitch (item.type) {\n\t\tcase 'message':\n\t\t\t// A refusal is not an answer: it only sets the stop reason.\n\t\t\treturn item.content.flatMap(content =>\n\t\t\t\tcontent.type === 'output_text' ? [{ type: 'text' as const, text: content.text }] : [],\n\t\t\t);\n\t\tcase 'reasoning': {\n\t\t\t// The summary is the readable part; the encrypted item is what OpenAI reads back.\n\t\t\tconst text = item.summary.map(summary => summary.text).join('\\n');\n\t\t\tconst parts: MessagePart[] = text ? [{ type: 'reasoning', text }] : [];\n\t\t\t// Without its encrypted content the item cannot be replayed: OpenAI stores nothing.\n\t\t\tif (item.encrypted_content) {\n\t\t\t\tconst encrypted: ResponseReasoningItem = {\n\t\t\t\t\ttype: 'reasoning',\n\t\t\t\t\tid: item.id,\n\t\t\t\t\tsummary: item.summary,\n\t\t\t\t\tencrypted_content: item.encrypted_content,\n\t\t\t\t};\n\t\t\t\tparts.push({ type: 'providerData', source: openAISource, data: encrypted });\n\t\t\t}\n\t\t\treturn parts;\n\t\t}\n\t\tcase 'compaction': {\n\t\t\t// Encrypted: OpenAI's summary of the context before it, which it alone reads back.\n\t\t\tconst compaction: ResponseCompactionItemParam = {\n\t\t\t\ttype: 'compaction',\n\t\t\t\tid: item.id,\n\t\t\t\tencrypted_content: item.encrypted_content,\n\t\t\t};\n\t\t\treturn [{ type: 'compaction', source: openAISource, data: compaction }];\n\t\t}\n\t\tcase 'function_call':\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'toolCall',\n\t\t\t\t\tid: item.call_id,\n\t\t\t\t\tname: item.name,\n\t\t\t\t\tinput: parseArguments(item.arguments),\n\t\t\t\t},\n\t\t\t];\n\t\tcase 'apply_patch_call':\n\t\t\t// Only when `apply_patch` was offered; otherwise it is unexpected, as below.\n\t\t\tif (applyPatch !== undefined) {\n\t\t\t\treturn [{ type: 'toolCall', id: item.call_id, name: applyPatch, input: item.operation }];\n\t\t\t}\n\t\t\treturn unmapped(item, logger);\n\t\tdefault:\n\t\t\treturn unmapped(item, logger);\n\t}\n}\n\n/** An item this client does not map, which it reports and leaves out. */\nfunction unmapped(item: ResponseOutputItem, logger: ILogger): MessagePart[] {\n\t// No other built-in tool is offered, so any other item is output this client does not expect.\n\ttreatErrors(\n\t\t() => {\n\t\t\tlogger.warn(item, 'OpenAI returned an output item the client does not map');\n\t\t},\n\t\tclassifyHostFailure,\n\t\t'OpenAI client logger failed while mapping a response',\n\t);\n\treturn [];\n}\n\nfunction toStopReason(response: Response): StopReason {\n\tif (response.status === 'incomplete') {\n\t\treturn response.incomplete_details?.reason === 'content_filter' ? 'refused' : 'truncated';\n\t}\n\tif (response.status !== 'completed') {\n\t\tconst cause =\n\t\t\tresponse.error?.message ??\n\t\t\t`The response ended with status \"${response.status ?? 'unknown'}\".`;\n\t\t// The same failure is retried when it arrives as an SDK error, so it must be here too.\n\t\tif (response.error && transientResponseErrorCodes.includes(response.error.code)) {\n\t\t\tthrow new RecoverableError('The OpenAI response failed', { cause });\n\t\t}\n\t\tthrow new UnrecoverableError('The OpenAI response did not complete', { cause });\n\t}\n\n\tconst refused = response.output.some(\n\t\titem => item.type === 'message' && item.content.some(content => content.type === 'refusal'),\n\t);\n\treturn refused ? 'refused' : 'completed';\n}\n\n/** OpenAI counts both cache reads and cache writes inside `input_tokens`. */\nfunction toTokens(usage: ResponseUsage | undefined, logger: ILogger): Tokens | undefined {\n\tif (!usage) {\n\t\t// Zeros would report a call that may have been billed as free.\n\t\ttreatErrors(\n\t\t\t() => {\n\t\t\t\tlogger.warn('OpenAI returned a response without usage; it cannot be accounted for');\n\t\t\t},\n\t\t\tclassifyHostFailure,\n\t\t\t'OpenAI client logger failed while reading usage',\n\t\t);\n\t\treturn undefined;\n\t}\n\n\tconst { cached_tokens, cache_write_tokens } = usage.input_tokens_details;\n\treturn {\n\t\tinputTokens: usage.input_tokens - cached_tokens - cache_write_tokens,\n\t\treadCacheTokens: cached_tokens,\n\t\twrittenCacheTokens: cache_write_tokens,\n\t\toutputTokens: usage.output_tokens,\n\t};\n}\n\nfunction toLLMResponse(\n\tresponse: Response,\n\tlogger: ILogger,\n\tapplyPatch: string | undefined,\n): LLMResponse {\n\tlet usage: Tokens | undefined;\n\n\ttry {\n\t\t// Inside the try: it warns when the usage is missing, and that warning can throw too.\n\t\tusage = toTokens(response.usage, logger);\n\t\treturn {\n\t\t\tmessage: {\n\t\t\t\trole: 'assistant',\n\t\t\t\tcontent: response.output.flatMap(item => describeItem(item, logger, applyPatch)),\n\t\t\t},\n\t\t\tusage,\n\t\t\tstopReason: toStopReason(response),\n\t\t};\n\t} catch (error) {\n\t\t// The call was billed whether the response failed or a logger did while mapping it, and\n\t\t// an answer without usage leaves the run's count unknown.\n\t\tthrow withSpentTokens(error, usage, !response.usage);\n\t}\n}\n\nfunction classifyOpenAIFailure(error: unknown): Error {\n\tif (error instanceof APIError) {\n\t\tif (error.code === 'context_length_exceeded') {\n\t\t\treturn new MaxContextError('The context no longer fits the OpenAI model', {\n\t\t\t\tcause: describeFailure(error),\n\t\t\t});\n\t\t}\n\t\tif (\n\t\t\texhaustedQuotaCodes.some(code => code === error.code) ||\n\t\t\tunrecoverableStatuses.some(status => status === error.status)\n\t\t) {\n\t\t\treturn new UnrecoverableError('OpenAI rejected the request', {\n\t\t\t\tcause: describeFailure(error),\n\t\t\t});\n\t\t}\n\t}\n\n\treturn classifyProviderFailure(error, 'The OpenAI request failed');\n}\n\n/**\n * A stateless `LLMClient` on the OpenAI Responses API. With `store: false` OpenAI keeps\n * nothing between calls, so every call carries the whole context: the conversation is\n * MiKode's, not the provider's.\n */\nexport class OpenAILLMClient implements LLMClient {\n\tprivate readonly client: OpenAI;\n\tprivate readonly model: OpenAIModel;\n\tprivate readonly reasoningEffort: OpenAIReasoningEffort;\n\tprivate readonly compactAt: number | undefined;\n\treadonly systemPrompt: string;\n\treadonly providerDataSource = openAISource;\n\tprivate readonly logger: ILogger;\n\n\t// `model` and `reasoningEffort` are untyped on purpose: this adapter is the one that knows\n\t// what OpenAI supports.\n\tconstructor({\n\t\tmodel,\n\t\treasoningEffort = 'high',\n\t\tcompactAt,\n\t\tsystemPrompt,\n\t\tlogger,\n\t}: {\n\t\tmodel: string;\n\t\treasoningEffort?: string;\n\t\t/** The input tokens at which OpenAI compacts the context on its side. Without it, never. */\n\t\tcompactAt?: number | undefined;\n\t\tsystemPrompt: string;\n\t\tlogger: ILogger;\n\t}) {\n\t\tif (!isOneOf(openAIModels, model)) {\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t`\"${model}\" is not an OpenAI model; expected one of: ${openAIModels.join(', ')}`,\n\t\t\t);\n\t\t}\n\t\tif (!isOneOf(openAIReasoningEfforts, reasoningEffort)) {\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t`\"${reasoningEffort}\" is not an OpenAI reasoning effort; expected one of: ${openAIReasoningEfforts.join(', ')}`,\n\t\t\t);\n\t\t}\n\t\tif (compactAt !== undefined && !(Number.isInteger(compactAt) && compactAt > 0)) {\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t`compactAt must be a positive whole number of tokens, not ${String(compactAt)}`,\n\t\t\t);\n\t\t}\n\n\t\ttry {\n\t\t\t// The SDK's default retries stay on: they back off and honour `Retry-After`, which\n\t\t\t// `RetryingAgent` does not. See \"The provider SDKs keep their transport retries\" in decisions.md.\n\t\t\tthis.client = new OpenAI();\n\t\t} catch (error) {\n\t\t\t// A missing API key, for example: building the client again cannot fix it.\n\t\t\tthrow new UnrecoverableError('The OpenAI client could not be set up', {\n\t\t\t\tcause: describeFailure(error),\n\t\t\t});\n\t\t}\n\n\t\tthis.model = model;\n\t\tthis.reasoningEffort = reasoningEffort;\n\t\tthis.compactAt = compactAt;\n\t\tthis.systemPrompt = systemPrompt;\n\t\tthis.logger = logger;\n\t}\n\n\tsupportsNative(tool: NativeTool): boolean {\n\t\treturn tool === 'applyPatch';\n\t}\n\n\tasync send(\n\t\t{ context, tools }: { context: Message[]; tools: ToolDefinition[] },\n\t\tsignal: AbortSignal,\n\t): Promise<LLMResponse> {\n\t\tlet response: Response;\n\t\tconst applyPatch = applyPatchName(tools);\n\n\t\ttry {\n\t\t\tresponse = await this.client.responses.create(\n\t\t\t\t{\n\t\t\t\t\tmodel: this.model,\n\t\t\t\t\tinstructions: this.systemPrompt,\n\t\t\t\t\t// The latest compaction carries everything before it, which OpenAI would only ignore.\n\t\t\t\t\tinput: sentContext(context, openAISource).flatMap(message =>\n\t\t\t\t\t\ttoOpenAIInput(message, applyPatch),\n\t\t\t\t\t),\n\t\t\t\t\t// Left out when empty, so a request without tools stays as it always was.\n\t\t\t\t\t...(tools.length > 0 && { tools: tools.map(toOpenAITool) }),\n\t\t\t\t\t// OpenAI compacts inside the call that passes it, and answers with the compaction first.\n\t\t\t\t\t...(this.compactAt !== undefined && {\n\t\t\t\t\t\tcontext_management: [{ type: 'compaction', compact_threshold: this.compactAt }],\n\t\t\t\t\t}),\n\t\t\t\t\tstore: false,\n\t\t\t\t\treasoning: { effort: this.reasoningEffort, summary: 'concise' },\n\t\t\t\t\t// With `store: false` this is the only way to send the reasoning back.\n\t\t\t\t\tinclude: ['reasoning.encrypted_content'],\n\t\t\t\t},\n\t\t\t\t{ signal },\n\t\t\t);\n\t\t} catch (error) {\n\t\t\t// The SDK's abort error is named 'Error', so it would pass for a provider failure.\n\t\t\tsignal.throwIfAborted();\n\t\t\tthrow classifyOpenAIFailure(error);\n\t\t}\n\n\t\treturn treatErrors(\n\t\t\t() => toLLMResponse(response, this.logger, applyPatch),\n\t\t\tclassifyLocalFailure,\n\t\t\t'OpenAI response mapping failed',\n\t\t);\n\t}\n}\n"]}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { describeAnswered, rememberAnswers } from '#src/agent/domain/question';
|
|
1
2
|
import { RecoverableError, UnrecoverableError, withSpentTokens } from '#src/shared/domain/errors';
|
|
2
3
|
import { classifyHostFailure, describeFailure, treatErrors, } from '#src/shared/domain/providerFailure';
|
|
3
4
|
import { isAbortError } from '#src/shared/domain/isAbortError';
|
|
@@ -23,10 +24,18 @@ export class RetryingAgent {
|
|
|
23
24
|
let spent;
|
|
24
25
|
// An attempt was answered without usage, so `spent` is only a subtotal.
|
|
25
26
|
let unreported = false;
|
|
27
|
+
// What the user answered stays answered: a retry neither asks it again nor forgets it.
|
|
28
|
+
const remembered = options.ask ? rememberAnswers(options.ask) : undefined;
|
|
29
|
+
const attemptOptions = remembered ? { ...options, ask: remembered.ask } : options;
|
|
26
30
|
for (let attempt = 1;; attempt++) {
|
|
27
31
|
try {
|
|
28
|
-
const
|
|
29
|
-
|
|
32
|
+
const base = lastPrompt ?? prompt;
|
|
33
|
+
// After an attempt the user answered, the model is told so, or it would ask again
|
|
34
|
+
// in its own words, which the memory does not recognise.
|
|
35
|
+
const promptToSend = attempt > 1 && remembered && remembered.answered.length > 0
|
|
36
|
+
? `${base}\n\n${describeAnswered(remembered.answered)}`
|
|
37
|
+
: base;
|
|
38
|
+
const response = await this.inner.run(promptToSend, attemptOptions);
|
|
30
39
|
return {
|
|
31
40
|
...response,
|
|
32
41
|
// Any call answered without usage makes the whole count unknown, not partial.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"retryingAgent.js","sourceRoot":"","sources":["../../../../src/retry/domain/model/retryingAgent.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAClG,OAAO,EACN,mBAAmB,EACnB,eAAe,EACf,WAAW,GACX,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAE/D,OAAO,EAAE,SAAS,EAAe,MAAM,2BAA2B,CAAC;AAEnE,MAAM,OAAO,aAAa;IACjB,KAAK,CAAQ;IACb,WAAW,CAAS;IACpB,MAAM,CAAU;IAChB,YAAY,CAAU;IAE9B,YAAY,EACX,KAAK,EACL,WAAW,GAAG,CAAC,EACf,MAAM,EACN,YAAY,GAAG,IAAI,GAYnB;QACA,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,IAAI,WAAW,GAAG,CAAC,EAAE,CAAC;YACvD,MAAM,IAAI,UAAU,CAAC,wCAAwC,CAAC,CAAC;QAChE,CAAC;QAED,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,MAAc,EAAE,OAAmB;QAC5C,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACzB,IAAI,UAAU,GAAkB,IAAI,CAAC;QACrC,mFAAmF;QACnF,IAAI,KAAyB,CAAC;QAC9B,wEAAwE;QACxE,IAAI,UAAU,GAAG,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"retryingAgent.js","sourceRoot":"","sources":["../../../../src/retry/domain/model/retryingAgent.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Agent, AgentResponse, RunOptions } from '#src/agent/domain/agent';\nimport { describeAnswered, rememberAnswers } from '#src/agent/domain/question';\nimport { RecoverableError, UnrecoverableError, withSpentTokens } from '#src/shared/domain/errors';\nimport {\n\tclassifyHostFailure,\n\tdescribeFailure,\n\ttreatErrors,\n} from '#src/shared/domain/providerFailure';\nimport { isAbortError } from '#src/shared/domain/isAbortError';\nimport type { ILogger } from '#src/shared/domain/logger';\nimport { addTokens, type Tokens } from '#src/shared/domain/tokens';\n\nexport class RetryingAgent implements Agent {\n\tprivate inner: Agent;\n\tprivate maxAttempts: number;\n\tprivate logger: ILogger;\n\tprivate noteFailures: boolean;\n\n\tconstructor({\n\t\tinner,\n\t\tmaxAttempts = 3,\n\t\tlogger,\n\t\tnoteFailures = true,\n\t}: {\n\t\tinner: Agent;\n\t\tmaxAttempts?: number;\n\t\tlogger: ILogger;\n\t\t/**\n\t\t * Appends the previous failure to the retried prompt, so a provider session that may\n\t\t * remember the failed turn is told what happened. Off for an agent that records nothing\n\t\t * from a failed call, such as `LLMAgent`: there the note would become a permanent user\n\t\t * message resent on every later turn.\n\t\t */\n\t\tnoteFailures?: boolean;\n\t}) {\n\t\tif (!Number.isInteger(maxAttempts) || maxAttempts < 1) {\n\t\t\tthrow new RangeError('maxAttempts must be a positive integer');\n\t\t}\n\n\t\tthis.inner = inner;\n\t\tthis.maxAttempts = maxAttempts;\n\t\tthis.logger = logger;\n\t\tthis.noteFailures = noteFailures;\n\t}\n\n\tasync run(prompt: string, options: RunOptions): Promise<AgentResponse> {\n\t\tconst start = Date.now();\n\t\tlet lastPrompt: string | null = null;\n\t\t// Failed attempts were billed too, so they travel with whichever way the run ends.\n\t\tlet spent: Tokens | undefined;\n\t\t// An attempt was answered without usage, so `spent` is only a subtotal.\n\t\tlet unreported = false;\n\t\t// What the user answered stays answered: a retry neither asks it again nor forgets it.\n\t\tconst remembered = options.ask ? rememberAnswers(options.ask) : undefined;\n\t\tconst attemptOptions = remembered ? { ...options, ask: remembered.ask } : options;\n\n\t\tfor (let attempt = 1; ; attempt++) {\n\t\t\ttry {\n\t\t\t\tconst base = lastPrompt ?? prompt;\n\t\t\t\t// After an attempt the user answered, the model is told so, or it would ask again\n\t\t\t\t// in its own words, which the memory does not recognise.\n\t\t\t\tconst promptToSend =\n\t\t\t\t\tattempt > 1 && remembered && remembered.answered.length > 0\n\t\t\t\t\t\t? `${base}\\n\\n${describeAnswered(remembered.answered)}`\n\t\t\t\t\t\t: base;\n\t\t\t\tconst response = await this.inner.run(promptToSend, attemptOptions);\n\t\t\t\treturn {\n\t\t\t\t\t...response,\n\t\t\t\t\t// Any call answered without usage makes the whole count unknown, not partial.\n\t\t\t\t\ttokens: unreported ? undefined : response.tokens && addTokens(spent, response.tokens),\n\t\t\t\t\t// Every attempt was waited for, not only the last one.\n\t\t\t\t\tduration: (Date.now() - start) / 1000,\n\t\t\t\t};\n\t\t\t} catch (e) {\n\t\t\t\tif (isAbortError(e)) throw e;\n\t\t\t\tif (e instanceof UnrecoverableError) throw withSpentTokens(e, spent, unreported);\n\n\t\t\t\t// Only a failure the agent classified as recoverable earns another call. An\n\t\t\t\t// unclassified one breaks the `Agent` contract, so nothing here knows whether\n\t\t\t\t// replaying it is safe — it may already have written files or run commands.\n\t\t\t\tif (!(e instanceof RecoverableError))\n\t\t\t\t\tthrow new UnrecoverableError('The agent failed without classifying the failure', {\n\t\t\t\t\t\tcause: describeFailure(e),\n\t\t\t\t\t\ttokens: spent,\n\t\t\t\t\t\tusageUnreported: unreported,\n\t\t\t\t\t});\n\n\t\t\t\tspent = addTokens(spent, e.tokens);\n\t\t\t\tunreported ||= e.usageUnreported;\n\n\t\t\t\tif (attempt === this.maxAttempts)\n\t\t\t\t\tthrow new UnrecoverableError('Max attempts exhausted', {\n\t\t\t\t\t\tcause: `Gave up after ${String(this.maxAttempts)} attempts. Last failure: ${e.cause}`,\n\t\t\t\t\t\ttokens: spent,\n\t\t\t\t\t\tusageUnreported: unreported,\n\t\t\t\t\t});\n\n\t\t\t\t// Keeps the original request: an attempt that failed before the provider registered\n\t\t\t\t// the turn left no session that remembers it.\n\t\t\t\tif (this.noteFailures) {\n\t\t\t\t\tlastPrompt = `${prompt}\\n\\nThe previous attempt failed for the following reason: ${e.cause}`;\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\ttreatErrors(\n\t\t\t\t\t\t() => {\n\t\t\t\t\t\t\tthis.logger.warn(\n\t\t\t\t\t\t\t\t`Attempt ${String(attempt)}/${String(this.maxAttempts)} failed; retrying`,\n\t\t\t\t\t\t\t\te,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t},\n\t\t\t\t\t\tclassifyHostFailure,\n\t\t\t\t\t\t'RetryingAgent logger failed while reporting a retry',\n\t\t\t\t\t);\n\t\t\t\t} catch (logFailure) {\n\t\t\t\t\t// The attempts already spent their tokens, whatever the logger did.\n\t\t\t\t\tthrow withSpentTokens(logFailure, spent, unreported);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { Tool } from '../domain/tool.ts';
|
|
3
3
|
import type { Workspace } from '../domain/workspace.ts';
|
|
4
|
+
export declare const maxResultBytes: number;
|
|
4
5
|
/**
|
|
5
6
|
* Lines of a file as the model reads them: each numbered, a line too long cut, and a notice for
|
|
6
7
|
* what was cut or lies beyond.
|
|
@@ -5,7 +5,9 @@ import { cutLine, maxLineLength } from "../domain/lineLength.js";
|
|
|
5
5
|
const maxFiles = 200;
|
|
6
6
|
const maxMatches = 100;
|
|
7
7
|
const maxLines = 200;
|
|
8
|
-
//
|
|
8
|
+
// What one read or one search may bring into the context, whatever its lines: 200 lines of
|
|
9
|
+
// prose can be 50 KB, and the context carries them on every step after.
|
|
10
|
+
export const maxResultBytes = 16 * 1024;
|
|
9
11
|
// Strict mode has no optional fields: an argument the model may leave out is nullable.
|
|
10
12
|
const scope = {
|
|
11
13
|
path: z.string().nullable().describe('A folder or file to stay inside, relative to the root.'),
|
|
@@ -23,21 +25,39 @@ export function renderLines({ path, lines, from, totalLines, truncated, }) {
|
|
|
23
25
|
if (lines.length === 0) {
|
|
24
26
|
return `No lines from ${String(from)}: the file has ${String(totalLines)} lines.`;
|
|
25
27
|
}
|
|
26
|
-
const numbered = lines.map((text, index) => `${String(from + index)}: ${cutLine(path, text)}`);
|
|
28
|
+
const numbered = withinBytes(lines.map((text, index) => `${String(from + index)}: ${cutLine(path, text)}`));
|
|
29
|
+
const shown = lines.slice(0, numbered.length);
|
|
27
30
|
const max = maxLineLength(path);
|
|
28
|
-
const cut =
|
|
31
|
+
const cut = shown.filter(text => text.length > max).length;
|
|
32
|
+
const next = from + shown.length;
|
|
29
33
|
const notices = [
|
|
30
34
|
...(cut > 0
|
|
31
35
|
? [`[${String(cut)} lines were longer than ${String(max)} characters and were cut.]`]
|
|
32
36
|
: []),
|
|
33
|
-
...(
|
|
37
|
+
...(shown.length < lines.length
|
|
34
38
|
? [
|
|
35
|
-
`[The file has ${String(totalLines)} lines. Read from line ${String(
|
|
39
|
+
`[Lines ${String(from)} to ${String(next - 1)} fill the ${String(maxResultBytes / 1024)} KB one read may return. The file has ${String(totalLines)} lines. Read from line ${String(next)} to continue.]`,
|
|
36
40
|
]
|
|
37
|
-
:
|
|
41
|
+
: truncated
|
|
42
|
+
? [
|
|
43
|
+
`[The file has ${String(totalLines)} lines. Read from line ${String(next)} to continue.]`,
|
|
44
|
+
]
|
|
45
|
+
: []),
|
|
38
46
|
];
|
|
39
47
|
return [...numbered, ...notices].join('\n');
|
|
40
48
|
}
|
|
49
|
+
/** The first of `lines` that fit in `maxResultBytes` together, and always the first one. */
|
|
50
|
+
function withinBytes(lines) {
|
|
51
|
+
let bytes = 0;
|
|
52
|
+
const kept = [];
|
|
53
|
+
for (const line of lines) {
|
|
54
|
+
bytes += Buffer.byteLength(line, 'utf8') + 1;
|
|
55
|
+
if (bytes > maxResultBytes && kept.length > 0)
|
|
56
|
+
break;
|
|
57
|
+
kept.push(line);
|
|
58
|
+
}
|
|
59
|
+
return kept;
|
|
60
|
+
}
|
|
41
61
|
/** What `readFile` takes, shared with the harness's own version of it. */
|
|
42
62
|
export const readFileDescription = 'Reads lines of a file, each prefixed with its number.';
|
|
43
63
|
export const readFileInput = z.object({
|
|
@@ -117,10 +137,12 @@ export function createWorkspaceTools(workspace) {
|
|
|
117
137
|
if (matches.length === 0) {
|
|
118
138
|
return 'No matches.';
|
|
119
139
|
}
|
|
120
|
-
const results = matches.map(match => `${match.path}:${String(match.line)}: ${cutLine(match.path, match.text)}`);
|
|
121
|
-
const notice =
|
|
122
|
-
? `\n[Showing ${String(results.length)} of ${String(total)} matches. Narrow the path or glob to see the rest.]`
|
|
123
|
-
:
|
|
140
|
+
const results = withinBytes(matches.map(match => `${match.path}:${String(match.line)}: ${cutLine(match.path, match.text)}`));
|
|
141
|
+
const notice = results.length < matches.length
|
|
142
|
+
? `\n[Showing ${String(results.length)} of ${String(total)} matches, the ${String(maxResultBytes / 1024)} KB one search may return. Narrow the pattern, the path or the glob to see the rest.]`
|
|
143
|
+
: truncated
|
|
144
|
+
? `\n[Showing ${String(results.length)} of ${String(total)} matches. Narrow the path or glob to see the rest.]`
|
|
145
|
+
: '';
|
|
124
146
|
return results.join('\n') + notice;
|
|
125
147
|
},
|
|
126
148
|
}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspaceTools.js","sourceRoot":"","sources":["../../../src/tools/infrastructure/workspaceTools.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod';\nimport type { Tool } from '../domain/tool.ts';\nimport type { Workspace } from '../domain/workspace.ts';\nimport { defineTool } from './defineTool.ts';\nimport { cutLine, maxLineLength } from '../domain/lineLength.ts';\n\n// What one call may return. The model can ask for fewer lines, never for more.\nconst maxFiles = 200;\nconst maxMatches = 100;\nconst maxLines = 200;\n// One minified line can be larger than everything else the model has read.\n\n// Strict mode has no optional fields: an argument the model may leave out is nullable.\nconst scope = {\n\tpath: z.string().nullable().describe('A folder or file to stay inside, relative to the root.'),\n\tglob: z\n\t\t.string()\n\t\t.nullable()\n\t\t.describe(\n\t\t\t'Only paths matching this glob, relative to the root. \"*\" stays inside one folder and \"**\" crosses folders: \"**/*.ts\" is every TypeScript file, \"*.ts\" only those at the top.',\n\t\t),\n};\n\n/**\n * Lines of a file as the model reads them: each numbered, a line too long cut, and a notice for\n * what was cut or lies beyond.\n */\nexport function renderLines({\n\tpath,\n\tlines,\n\tfrom,\n\ttotalLines,\n\ttruncated,\n}: {\n\t/** The file, which decides how long a line may be. */\n\tpath: string;\n\tlines: string[];\n\tfrom: number;\n\ttotalLines: number;\n\ttruncated: boolean;\n}): string {\n\t// The file's length tells the model where its range went wrong.\n\tif (lines.length === 0) {\n\t\treturn `No lines from ${String(from)}: the file has ${String(totalLines)} lines.`;\n\t}\n\n\tconst numbered = lines.map((text, index) => `${String(from + index)}: ${cutLine(path, text)}`);\n\tconst max = maxLineLength(path);\n\tconst cut = lines.filter(text => text.length > max).length;\n\tconst notices = [\n\t\t...(cut > 0\n\t\t\t? [`[${String(cut)} lines were longer than ${String(max)} characters and were cut.]`]\n\t\t\t: []),\n\t\t...(truncated\n\t\t\t? [\n\t\t\t\t\t`[The file has ${String(totalLines)} lines. Read from line ${String(from + lines.length)} to continue.]`,\n\t\t\t\t]\n\t\t\t: []),\n\t];\n\treturn [...numbered, ...notices].join('\\n');\n}\n\n/** What `readFile` takes, shared with the harness's own version of it. */\nexport const readFileDescription = 'Reads lines of a file, each prefixed with its number.';\nexport const readFileInput = z.object({\n\tpath: z.string().describe('The file, relative to the root.'),\n\tfromLine: z.number().nullable().describe('The first line to read, from 1. Defaults to 1.'),\n\tlineCount: z\n\t\t.number()\n\t\t.nullable()\n\t\t.describe(\n\t\t\t`How many lines to read, at most ${String(maxLines)}. Defaults to ${String(maxLines)}.`,\n\t\t),\n});\n\n/** The range a `readFile` call asks for, checked: strict mode allows the schema no minimum. */\nexport function readRange({\n\tfromLine,\n\tlineCount,\n}: {\n\tfromLine: number | null;\n\tlineCount: number | null;\n}): { from: number; count: number } {\n\tconst from = fromLine ?? 1;\n\tconst requested = lineCount ?? maxLines;\n\tassertPositiveInteger('fromLine', from);\n\tassertPositiveInteger('lineCount', requested);\n\treturn { from, count: Math.min(requested, maxLines) };\n}\n\n/** A stable description of a read request, using only the range the model supplied. */\nexport function readFileSummary({\n\tpath,\n\tfromLine,\n\tlineCount,\n}: {\n\tpath: string;\n\tfromLine: number | null;\n\tlineCount: number | null;\n}): string {\n\tconst suppliedFrom = fromLine ?? 1;\n\tconst from = Number.isInteger(suppliedFrom) && suppliedFrom > 0 ? suppliedFrom : 1;\n\tconst suppliedCount = lineCount ?? maxLines;\n\tconst requested = Number.isInteger(suppliedCount) && suppliedCount > 0 ? suppliedCount : maxLines;\n\tconst count = Math.min(requested, maxLines);\n\treturn `${path}:${String(from)}-${String(from + count - 1)}`;\n}\n\nfunction scopeSummary(path: string | null, glob: string | null): string {\n\tconst scopePath = path ?? '.';\n\treturn glob ? `${scopePath === '.' ? '' : `${scopePath}/`}${glob}` : scopePath;\n}\n\nfunction assertPositiveInteger(name: string, value: number): void {\n\tif (!Number.isInteger(value) || value < 1) {\n\t\tthrow new Error(`${name} must be a whole number of at least 1; got ${String(value)}`);\n\t}\n}\n\n/** The three read-only tools an agent explores a repository with. */\nexport function createWorkspaceTools(workspace: Workspace): Tool[] {\n\treturn [\n\t\tdefineTool({\n\t\t\tname: 'listFiles',\n\t\t\tdescription:\n\t\t\t\t'Lists the files of the repository, sorted. Ignored files such as dependencies are left out.',\n\t\t\trisk: 'safe',\n\t\t\tinput: z.object(scope),\n\t\t\tsummarize: ({ path, glob }) => scopeSummary(path, glob),\n\t\t\texecute: async ({ path, glob }, signal) => {\n\t\t\t\t// The port takes \"no scope\" as a missing field.\n\t\t\t\tconst { files, total, truncated } = await workspace.listFiles(\n\t\t\t\t\t{ path: path ?? undefined, glob: glob ?? undefined, limit: maxFiles },\n\t\t\t\t\tsignal,\n\t\t\t\t);\n\n\t\t\t\t// Nothing found is an answer too, not an empty string.\n\t\t\t\tif (files.length === 0) {\n\t\t\t\t\treturn 'No files.';\n\t\t\t\t}\n\n\t\t\t\tconst notice = truncated\n\t\t\t\t\t? `\\n[Showing ${String(files.length)} of ${String(total)} files. Narrow the path or glob to see the rest.]`\n\t\t\t\t\t: '';\n\t\t\t\treturn files.join('\\n') + notice;\n\t\t\t},\n\t\t}),\n\n\t\tdefineTool({\n\t\t\tname: 'searchText',\n\t\t\tdescription:\n\t\t\t\t'Searches the repository for a regular expression and returns each matching line as path:line: text.',\n\t\t\trisk: 'safe',\n\t\t\tinput: z.object({\n\t\t\t\tpattern: z.string().describe('A regular expression.'),\n\t\t\t\tignoreCase: z.boolean().nullable().describe('Match regardless of case. Defaults to false.'),\n\t\t\t\t...scope,\n\t\t\t}),\n\t\t\tsummarize: ({ path, glob, pattern }) => `\"${pattern}\" in ${scopeSummary(path, glob)}`,\n\t\t\texecute: async ({ path, glob, ignoreCase, pattern }, signal) => {\n\t\t\t\tconst { matches, total, truncated } = await workspace.searchText(\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: path ?? undefined,\n\t\t\t\t\t\tglob: glob ?? undefined,\n\t\t\t\t\t\tignoreCase: ignoreCase ?? false,\n\t\t\t\t\t\tpattern,\n\t\t\t\t\t\tlimit: maxMatches,\n\t\t\t\t\t},\n\t\t\t\t\tsignal,\n\t\t\t\t);\n\n\t\t\t\tif (matches.length === 0) {\n\t\t\t\t\treturn 'No matches.';\n\t\t\t\t}\n\n\t\t\t\tconst results = matches.map(\n\t\t\t\t\tmatch => `${match.path}:${String(match.line)}: ${cutLine(match.path, match.text)}`,\n\t\t\t\t);\n\n\t\t\t\tconst notice = truncated\n\t\t\t\t\t? `\\n[Showing ${String(results.length)} of ${String(total)} matches. Narrow the path or glob to see the rest.]`\n\t\t\t\t\t: '';\n\t\t\t\treturn results.join('\\n') + notice;\n\t\t\t},\n\t\t}),\n\n\t\tdefineTool({\n\t\t\tname: 'readFile',\n\t\t\tdescription: readFileDescription,\n\t\t\trisk: 'safe',\n\t\t\tinput: readFileInput,\n\t\t\tsummarize: readFileSummary,\n\t\t\texecute: async ({ fromLine, lineCount, path }, signal) => {\n\t\t\t\t// Checked before reading.\n\t\t\t\tconst { from, count } = readRange({ fromLine, lineCount });\n\t\t\t\tconst { lines, totalLines, truncated } = await workspace.readFile(\n\t\t\t\t\t{ path, fromLine: from, lineCount: count },\n\t\t\t\t\tsignal,\n\t\t\t\t);\n\t\t\t\treturn renderLines({ path, lines, from, totalLines, truncated });\n\t\t\t},\n\t\t}),\n\t];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"workspaceTools.js","sourceRoot":"","sources":["../../../src/tools/infrastructure/workspaceTools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAEjE,+EAA+E;AAC/E,MAAM,QAAQ,GAAG,GAAG,CAAC;AACrB,MAAM,UAAU,GAAG,GAAG,CAAC;AACvB,MAAM,QAAQ,GAAG,GAAG,CAAC;AACrB,2FAA2F;AAC3F,wEAAwE;AACxE,MAAM,CAAC,MAAM,cAAc,GAAG,EAAE,GAAG,IAAI,CAAC;AAExC,uFAAuF;AACvF,MAAM,KAAK,GAAG;IACb,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,wDAAwD,CAAC;IAC9F,IAAI,EAAE,CAAC;SACL,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CACR,8KAA8K,CAC9K;CACF,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,EAC3B,IAAI,EACJ,KAAK,EACL,IAAI,EACJ,UAAU,EACV,SAAS,GAQT;IACA,gEAAgE;IAChE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,iBAAiB,MAAM,CAAC,IAAI,CAAC,kBAAkB,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC;IACnF,CAAC;IAED,MAAM,QAAQ,GAAG,WAAW,CAC3B,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,MAAM,CAAC,IAAI,GAAG,KAAK,CAAC,KAAK,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC,CAC7E,CAAC;IACF,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC9C,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;IAChC,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3D,MAAM,IAAI,GAAG,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC;IACjC,MAAM,OAAO,GAAG;QACf,GAAG,CAAC,GAAG,GAAG,CAAC;YACV,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,2BAA2B,MAAM,CAAC,GAAG,CAAC,4BAA4B,CAAC;YACrF,CAAC,CAAC,EAAE,CAAC;QACN,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM;YAC9B,CAAC,CAAC;gBACA,UAAU,MAAM,CAAC,IAAI,CAAC,OAAO,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC,aAAa,MAAM,CAAC,cAAc,GAAG,IAAI,CAAC,yCAAyC,MAAM,CAAC,UAAU,CAAC,0BAA0B,MAAM,CAAC,IAAI,CAAC,gBAAgB;aACxM;YACF,CAAC,CAAC,SAAS;gBACV,CAAC,CAAC;oBACA,iBAAiB,MAAM,CAAC,UAAU,CAAC,0BAA0B,MAAM,CAAC,IAAI,CAAC,gBAAgB;iBACzF;gBACF,CAAC,CAAC,EAAE,CAAC;KACP,CAAC;IACF,OAAO,CAAC,GAAG,QAAQ,EAAE,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC7C,CAAC;AAED,4FAA4F;AAC5F,SAAS,WAAW,CAAC,KAAe;IACnC,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QAC1B,KAAK,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7C,IAAI,KAAK,GAAG,cAAc,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;YAAE,MAAM;QACrD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjB,CAAC;IACD,OAAO,IAAI,CAAC;AACb,CAAC;AAED,0EAA0E;AAC1E,MAAM,CAAC,MAAM,mBAAmB,GAAG,uDAAuD,CAAC;AAC3F,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC;IACrC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,iCAAiC,CAAC;IAC5D,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,gDAAgD,CAAC;IAC1F,SAAS,EAAE,CAAC;SACV,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CACR,mCAAmC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,MAAM,CAAC,QAAQ,CAAC,GAAG,CACvF;CACF,CAAC,CAAC;AAEH,+FAA+F;AAC/F,MAAM,UAAU,SAAS,CAAC,EACzB,QAAQ,EACR,SAAS,GAIT;IACA,MAAM,IAAI,GAAG,QAAQ,IAAI,CAAC,CAAC;IAC3B,MAAM,SAAS,GAAG,SAAS,IAAI,QAAQ,CAAC;IACxC,qBAAqB,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;IACxC,qBAAqB,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;IAC9C,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAC,EAAE,CAAC;AACvD,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,eAAe,CAAC,EAC/B,IAAI,EACJ,QAAQ,EACR,SAAS,GAKT;IACA,MAAM,YAAY,GAAG,QAAQ,IAAI,CAAC,CAAC;IACnC,MAAM,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,IAAI,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;IACnF,MAAM,aAAa,GAAG,SAAS,IAAI,QAAQ,CAAC;IAC5C,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC,aAAa,CAAC,IAAI,aAAa,GAAG,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC;IAClG,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IAC5C,OAAO,GAAG,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,GAAG,KAAK,GAAG,CAAC,CAAC,EAAE,CAAC;AAC9D,CAAC;AAED,SAAS,YAAY,CAAC,IAAmB,EAAE,IAAmB;IAC7D,MAAM,SAAS,GAAG,IAAI,IAAI,GAAG,CAAC;IAC9B,OAAO,IAAI,CAAC,CAAC,CAAC,GAAG,SAAS,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,SAAS,GAAG,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AAChF,CAAC;AAED,SAAS,qBAAqB,CAAC,IAAY,EAAE,KAAa;IACzD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QAC3C,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,8CAA8C,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACvF,CAAC;AACF,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,oBAAoB,CAAC,SAAoB;IACxD,OAAO;QACN,UAAU,CAAC;YACV,IAAI,EAAE,WAAW;YACjB,WAAW,EACV,6FAA6F;YAC9F,IAAI,EAAE,MAAM;YACZ,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;YACtB,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC;YACvD,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,EAAE;gBACzC,gDAAgD;gBAChD,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,MAAM,SAAS,CAAC,SAAS,CAC5D,EAAE,IAAI,EAAE,IAAI,IAAI,SAAS,EAAE,IAAI,EAAE,IAAI,IAAI,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,EACrE,MAAM,CACN,CAAC;gBAEF,uDAAuD;gBACvD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBACxB,OAAO,WAAW,CAAC;gBACpB,CAAC;gBAED,MAAM,MAAM,GAAG,SAAS;oBACvB,CAAC,CAAC,cAAc,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,MAAM,CAAC,KAAK,CAAC,mDAAmD;oBAC3G,CAAC,CAAC,EAAE,CAAC;gBACN,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC;YAClC,CAAC;SACD,CAAC;QAEF,UAAU,CAAC;YACV,IAAI,EAAE,YAAY;YAClB,WAAW,EACV,qGAAqG;YACtG,IAAI,EAAE,MAAM;YACZ,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC;gBACf,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,uBAAuB,CAAC;gBACrD,UAAU,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,8CAA8C,CAAC;gBAC3F,GAAG,KAAK;aACR,CAAC;YACF,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,IAAI,OAAO,QAAQ,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE;YACrF,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,EAAE;gBAC9D,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,MAAM,SAAS,CAAC,UAAU,CAC/D;oBACC,IAAI,EAAE,IAAI,IAAI,SAAS;oBACvB,IAAI,EAAE,IAAI,IAAI,SAAS;oBACvB,UAAU,EAAE,UAAU,IAAI,KAAK;oBAC/B,OAAO;oBACP,KAAK,EAAE,UAAU;iBACjB,EACD,MAAM,CACN,CAAC;gBAEF,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBAC1B,OAAO,aAAa,CAAC;gBACtB,CAAC;gBAED,MAAM,OAAO,GAAG,WAAW,CAC1B,OAAO,CAAC,GAAG,CACV,KAAK,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,IAAI,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,CAClF,CACD,CAAC;gBAEF,MAAM,MAAM,GACX,OAAO,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM;oBAC9B,CAAC,CAAC,cAAc,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,MAAM,CAAC,KAAK,CAAC,iBAAiB,MAAM,CAAC,cAAc,GAAG,IAAI,CAAC,uFAAuF;oBAC/L,CAAC,CAAC,SAAS;wBACV,CAAC,CAAC,cAAc,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,MAAM,CAAC,KAAK,CAAC,qDAAqD;wBAC/G,CAAC,CAAC,EAAE,CAAC;gBACR,OAAO,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC;YACpC,CAAC;SACD,CAAC;QAEF,UAAU,CAAC;YACV,IAAI,EAAE,UAAU;YAChB,WAAW,EAAE,mBAAmB;YAChC,IAAI,EAAE,MAAM;YACZ,KAAK,EAAE,aAAa;YACpB,SAAS,EAAE,eAAe;YAC1B,OAAO,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,EAAE;gBACxD,0BAA0B;gBAC1B,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,SAAS,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC;gBAC3D,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,MAAM,SAAS,CAAC,QAAQ,CAChE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,EAC1C,MAAM,CACN,CAAC;gBACF,OAAO,WAAW,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC,CAAC;YAClE,CAAC;SACD,CAAC;KACF,CAAC;AACH,CAAC","sourcesContent":["import { z } from 'zod';\nimport type { Tool } from '../domain/tool.ts';\nimport type { Workspace } from '../domain/workspace.ts';\nimport { defineTool } from './defineTool.ts';\nimport { cutLine, maxLineLength } from '../domain/lineLength.ts';\n\n// What one call may return. The model can ask for fewer lines, never for more.\nconst maxFiles = 200;\nconst maxMatches = 100;\nconst maxLines = 200;\n// What one read or one search may bring into the context, whatever its lines: 200 lines of\n// prose can be 50 KB, and the context carries them on every step after.\nexport const maxResultBytes = 16 * 1024;\n\n// Strict mode has no optional fields: an argument the model may leave out is nullable.\nconst scope = {\n\tpath: z.string().nullable().describe('A folder or file to stay inside, relative to the root.'),\n\tglob: z\n\t\t.string()\n\t\t.nullable()\n\t\t.describe(\n\t\t\t'Only paths matching this glob, relative to the root. \"*\" stays inside one folder and \"**\" crosses folders: \"**/*.ts\" is every TypeScript file, \"*.ts\" only those at the top.',\n\t\t),\n};\n\n/**\n * Lines of a file as the model reads them: each numbered, a line too long cut, and a notice for\n * what was cut or lies beyond.\n */\nexport function renderLines({\n\tpath,\n\tlines,\n\tfrom,\n\ttotalLines,\n\ttruncated,\n}: {\n\t/** The file, which decides how long a line may be. */\n\tpath: string;\n\tlines: string[];\n\tfrom: number;\n\ttotalLines: number;\n\ttruncated: boolean;\n}): string {\n\t// The file's length tells the model where its range went wrong.\n\tif (lines.length === 0) {\n\t\treturn `No lines from ${String(from)}: the file has ${String(totalLines)} lines.`;\n\t}\n\n\tconst numbered = withinBytes(\n\t\tlines.map((text, index) => `${String(from + index)}: ${cutLine(path, text)}`),\n\t);\n\tconst shown = lines.slice(0, numbered.length);\n\tconst max = maxLineLength(path);\n\tconst cut = shown.filter(text => text.length > max).length;\n\tconst next = from + shown.length;\n\tconst notices = [\n\t\t...(cut > 0\n\t\t\t? [`[${String(cut)} lines were longer than ${String(max)} characters and were cut.]`]\n\t\t\t: []),\n\t\t...(shown.length < lines.length\n\t\t\t? [\n\t\t\t\t\t`[Lines ${String(from)} to ${String(next - 1)} fill the ${String(maxResultBytes / 1024)} KB one read may return. The file has ${String(totalLines)} lines. Read from line ${String(next)} to continue.]`,\n\t\t\t\t]\n\t\t\t: truncated\n\t\t\t\t? [\n\t\t\t\t\t\t`[The file has ${String(totalLines)} lines. Read from line ${String(next)} to continue.]`,\n\t\t\t\t\t]\n\t\t\t\t: []),\n\t];\n\treturn [...numbered, ...notices].join('\\n');\n}\n\n/** The first of `lines` that fit in `maxResultBytes` together, and always the first one. */\nfunction withinBytes(lines: string[]): string[] {\n\tlet bytes = 0;\n\tconst kept: string[] = [];\n\tfor (const line of lines) {\n\t\tbytes += Buffer.byteLength(line, 'utf8') + 1;\n\t\tif (bytes > maxResultBytes && kept.length > 0) break;\n\t\tkept.push(line);\n\t}\n\treturn kept;\n}\n\n/** What `readFile` takes, shared with the harness's own version of it. */\nexport const readFileDescription = 'Reads lines of a file, each prefixed with its number.';\nexport const readFileInput = z.object({\n\tpath: z.string().describe('The file, relative to the root.'),\n\tfromLine: z.number().nullable().describe('The first line to read, from 1. Defaults to 1.'),\n\tlineCount: z\n\t\t.number()\n\t\t.nullable()\n\t\t.describe(\n\t\t\t`How many lines to read, at most ${String(maxLines)}. Defaults to ${String(maxLines)}.`,\n\t\t),\n});\n\n/** The range a `readFile` call asks for, checked: strict mode allows the schema no minimum. */\nexport function readRange({\n\tfromLine,\n\tlineCount,\n}: {\n\tfromLine: number | null;\n\tlineCount: number | null;\n}): { from: number; count: number } {\n\tconst from = fromLine ?? 1;\n\tconst requested = lineCount ?? maxLines;\n\tassertPositiveInteger('fromLine', from);\n\tassertPositiveInteger('lineCount', requested);\n\treturn { from, count: Math.min(requested, maxLines) };\n}\n\n/** A stable description of a read request, using only the range the model supplied. */\nexport function readFileSummary({\n\tpath,\n\tfromLine,\n\tlineCount,\n}: {\n\tpath: string;\n\tfromLine: number | null;\n\tlineCount: number | null;\n}): string {\n\tconst suppliedFrom = fromLine ?? 1;\n\tconst from = Number.isInteger(suppliedFrom) && suppliedFrom > 0 ? suppliedFrom : 1;\n\tconst suppliedCount = lineCount ?? maxLines;\n\tconst requested = Number.isInteger(suppliedCount) && suppliedCount > 0 ? suppliedCount : maxLines;\n\tconst count = Math.min(requested, maxLines);\n\treturn `${path}:${String(from)}-${String(from + count - 1)}`;\n}\n\nfunction scopeSummary(path: string | null, glob: string | null): string {\n\tconst scopePath = path ?? '.';\n\treturn glob ? `${scopePath === '.' ? '' : `${scopePath}/`}${glob}` : scopePath;\n}\n\nfunction assertPositiveInteger(name: string, value: number): void {\n\tif (!Number.isInteger(value) || value < 1) {\n\t\tthrow new Error(`${name} must be a whole number of at least 1; got ${String(value)}`);\n\t}\n}\n\n/** The three read-only tools an agent explores a repository with. */\nexport function createWorkspaceTools(workspace: Workspace): Tool[] {\n\treturn [\n\t\tdefineTool({\n\t\t\tname: 'listFiles',\n\t\t\tdescription:\n\t\t\t\t'Lists the files of the repository, sorted. Ignored files such as dependencies are left out.',\n\t\t\trisk: 'safe',\n\t\t\tinput: z.object(scope),\n\t\t\tsummarize: ({ path, glob }) => scopeSummary(path, glob),\n\t\t\texecute: async ({ path, glob }, signal) => {\n\t\t\t\t// The port takes \"no scope\" as a missing field.\n\t\t\t\tconst { files, total, truncated } = await workspace.listFiles(\n\t\t\t\t\t{ path: path ?? undefined, glob: glob ?? undefined, limit: maxFiles },\n\t\t\t\t\tsignal,\n\t\t\t\t);\n\n\t\t\t\t// Nothing found is an answer too, not an empty string.\n\t\t\t\tif (files.length === 0) {\n\t\t\t\t\treturn 'No files.';\n\t\t\t\t}\n\n\t\t\t\tconst notice = truncated\n\t\t\t\t\t? `\\n[Showing ${String(files.length)} of ${String(total)} files. Narrow the path or glob to see the rest.]`\n\t\t\t\t\t: '';\n\t\t\t\treturn files.join('\\n') + notice;\n\t\t\t},\n\t\t}),\n\n\t\tdefineTool({\n\t\t\tname: 'searchText',\n\t\t\tdescription:\n\t\t\t\t'Searches the repository for a regular expression and returns each matching line as path:line: text.',\n\t\t\trisk: 'safe',\n\t\t\tinput: z.object({\n\t\t\t\tpattern: z.string().describe('A regular expression.'),\n\t\t\t\tignoreCase: z.boolean().nullable().describe('Match regardless of case. Defaults to false.'),\n\t\t\t\t...scope,\n\t\t\t}),\n\t\t\tsummarize: ({ path, glob, pattern }) => `\"${pattern}\" in ${scopeSummary(path, glob)}`,\n\t\t\texecute: async ({ path, glob, ignoreCase, pattern }, signal) => {\n\t\t\t\tconst { matches, total, truncated } = await workspace.searchText(\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: path ?? undefined,\n\t\t\t\t\t\tglob: glob ?? undefined,\n\t\t\t\t\t\tignoreCase: ignoreCase ?? false,\n\t\t\t\t\t\tpattern,\n\t\t\t\t\t\tlimit: maxMatches,\n\t\t\t\t\t},\n\t\t\t\t\tsignal,\n\t\t\t\t);\n\n\t\t\t\tif (matches.length === 0) {\n\t\t\t\t\treturn 'No matches.';\n\t\t\t\t}\n\n\t\t\t\tconst results = withinBytes(\n\t\t\t\t\tmatches.map(\n\t\t\t\t\t\tmatch => `${match.path}:${String(match.line)}: ${cutLine(match.path, match.text)}`,\n\t\t\t\t\t),\n\t\t\t\t);\n\n\t\t\t\tconst notice =\n\t\t\t\t\tresults.length < matches.length\n\t\t\t\t\t\t? `\\n[Showing ${String(results.length)} of ${String(total)} matches, the ${String(maxResultBytes / 1024)} KB one search may return. Narrow the pattern, the path or the glob to see the rest.]`\n\t\t\t\t\t\t: truncated\n\t\t\t\t\t\t\t? `\\n[Showing ${String(results.length)} of ${String(total)} matches. Narrow the path or glob to see the rest.]`\n\t\t\t\t\t\t\t: '';\n\t\t\t\treturn results.join('\\n') + notice;\n\t\t\t},\n\t\t}),\n\n\t\tdefineTool({\n\t\t\tname: 'readFile',\n\t\t\tdescription: readFileDescription,\n\t\t\trisk: 'safe',\n\t\t\tinput: readFileInput,\n\t\t\tsummarize: readFileSummary,\n\t\t\texecute: async ({ fromLine, lineCount, path }, signal) => {\n\t\t\t\t// Checked before reading.\n\t\t\t\tconst { from, count } = readRange({ fromLine, lineCount });\n\t\t\t\tconst { lines, totalLines, truncated } = await workspace.readFile(\n\t\t\t\t\t{ path, fromLine: from, lineCount: count },\n\t\t\t\t\tsignal,\n\t\t\t\t);\n\t\t\t\treturn renderLines({ path, lines, from, totalLines, truncated });\n\t\t\t},\n\t\t}),\n\t];\n}\n"]}
|