@mikode13/harness 6.0.1 → 6.2.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 +31 -4
- package/dist/agent/domain/agent.d.ts +14 -0
- package/dist/agent/domain/agent.js.map +1 -1
- package/dist/engines/domain/model/llmAgent.js +31 -3
- package/dist/engines/domain/model/llmAgent.js.map +1 -1
- package/dist/factory/infrastructure/agentLLMFactory.d.ts +21 -6
- package/dist/factory/infrastructure/agentLLMFactory.js +54 -18
- package/dist/factory/infrastructure/agentLLMFactory.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/tools/domain/commands.d.ts +27 -0
- package/dist/tools/domain/commands.js +43 -15
- package/dist/tools/domain/commands.js.map +1 -1
- package/dist/tools/domain/readRegistry.d.ts +17 -2
- package/dist/tools/domain/readRegistry.js.map +1 -1
- package/dist/tools/infrastructure/commandTool.d.ts +6 -1
- package/dist/tools/infrastructure/commandTool.js +11 -5
- package/dist/tools/infrastructure/commandTool.js.map +1 -1
- package/dist/tools/infrastructure/exploreTool.d.ts +10 -0
- package/dist/tools/infrastructure/exploreTool.js +88 -0
- package/dist/tools/infrastructure/exploreTool.js.map +1 -0
- package/dist/tools/infrastructure/fileFormatter.d.ts +39 -0
- package/dist/tools/infrastructure/fileFormatter.js +114 -0
- package/dist/tools/infrastructure/fileFormatter.js.map +1 -0
- package/dist/tools/infrastructure/fileTools.d.ts +10 -2
- package/dist/tools/infrastructure/fileTools.js +14 -2
- package/dist/tools/infrastructure/fileTools.js.map +1 -1
- package/dist/tools/infrastructure/trackedEdits.d.ts +4 -4
- package/dist/tools/infrastructure/trackedEdits.js +32 -9
- package/dist/tools/infrastructure/trackedEdits.js.map +1 -1
- package/dist/tools/infrastructure/workspaceWrites.d.ts +5 -1
- package/dist/tools/infrastructure/workspaceWrites.js +4 -1
- package/dist/tools/infrastructure/workspaceWrites.js.map +1 -1
- package/package.json +1 -1
|
@@ -8,8 +8,9 @@ import { RetryingAgent } from '#src/retry/domain/model/retryingAgent';
|
|
|
8
8
|
import { InvalidAgentConfigError } from '#src/shared/domain/errors';
|
|
9
9
|
import { isOneOf } from '#src/shared/domain/isOneOf';
|
|
10
10
|
import { Logger } from '#src/shared/infrastructure/logger';
|
|
11
|
-
import { commandsFor, validateCommands, } from '#src/tools/domain/commands';
|
|
11
|
+
import { commandsFor, validateCommands, validateFormatter, } from '#src/tools/domain/commands';
|
|
12
12
|
import { commandToolFor, fileToolsFor, openStore, openWorkspace, stateDirectoryOf, } from '#src/tools/infrastructure/fileTools';
|
|
13
|
+
import { createExploreTool } from '#src/tools/infrastructure/exploreTool';
|
|
13
14
|
import { createShowChangesTool } from '#src/tools/infrastructure/showChangesTool';
|
|
14
15
|
import { createTrackedReadFile } from '#src/tools/infrastructure/trackedReadFile';
|
|
15
16
|
import { createWorkspaceTools } from '#src/tools/infrastructure/workspaceTools';
|
|
@@ -229,17 +230,26 @@ export function createLLMAgent(provider, { model, reasoningEffort, systemPrompt,
|
|
|
229
230
|
* in the history too. It is trusted execution, not a sandbox: a command can reach whatever
|
|
230
231
|
* this user can. Not supported on Windows yet.
|
|
231
232
|
*
|
|
233
|
+
* With `formatter`, every file an edit creates or changes is formatted right after, its path
|
|
234
|
+
* the formatter's last argument, and the edit returns the file as the formatter left it. Like a
|
|
235
|
+
* command it runs without a shell, in a small environment, and what it changes in that file is
|
|
236
|
+
* in the history. When it fails, the file stays as the edit wrote it, and the model is told
|
|
237
|
+
* why. Not supported on Windows yet.
|
|
238
|
+
*
|
|
232
239
|
* @throws {InvalidAgentConfigError} for an unknown provider, a workspace with no root, or an
|
|
233
|
-
* invalid command, including one whose `cwd` is not in a `write` root, or any on
|
|
240
|
+
* invalid command or formatter, including one whose `cwd` is not in a `write` root, or any on
|
|
241
|
+
* Windows.
|
|
234
242
|
* @throws {UnrecoverableError} when no program can read the first root.
|
|
235
243
|
*/
|
|
236
|
-
export async function createFileTools(provider, workspace, { logger = new Logger(), commands } = {}) {
|
|
244
|
+
export async function createFileTools(provider, workspace, { logger = new Logger(), commands, formatter, } = {}) {
|
|
237
245
|
// Reachable from untyped input.
|
|
238
246
|
if (!isAgentProvider(provider))
|
|
239
247
|
throw unknownProvider(provider);
|
|
240
248
|
if (commands !== undefined)
|
|
241
249
|
validateCommands(commands);
|
|
242
|
-
|
|
250
|
+
if (formatter !== undefined)
|
|
251
|
+
validateFormatter(formatter);
|
|
252
|
+
const opened = await openWorkspace(workspace, logger, formatter ? { formatter } : {});
|
|
243
253
|
return (await executorTools(provider, opened, commands));
|
|
244
254
|
}
|
|
245
255
|
/** The executor's file tools, and `runCommand` when there are commands. */
|
|
@@ -250,17 +260,21 @@ async function executorTools(provider, workspace, commands) {
|
|
|
250
260
|
return tools;
|
|
251
261
|
return [...tools, await commandToolFor(workspace, commands)];
|
|
252
262
|
}
|
|
263
|
+
/** What an explorer finds and reads with: no edits, no changes, no commands. */
|
|
264
|
+
function explorerTools({ read, policy }) {
|
|
265
|
+
const finding = createWorkspaceTools(read).filter(tool => tool.name !== 'readFile');
|
|
266
|
+
return [...finding, createTrackedReadFile(policy)];
|
|
267
|
+
}
|
|
253
268
|
/**
|
|
254
|
-
* What the planner and the reviewer read with: no edits, the current task's changes,
|
|
255
|
-
* commands given to their role.
|
|
269
|
+
* What the planner and the reviewer read with: no edits, the current task's changes, `explore`
|
|
270
|
+
* with `explorer`, and the commands given to their role.
|
|
256
271
|
*/
|
|
257
|
-
async function readingTools(workspace, role, commands) {
|
|
258
|
-
const {
|
|
259
|
-
const finding = createWorkspaceTools(read).filter(tool => tool.name !== 'readFile');
|
|
272
|
+
async function readingTools(workspace, role, commands, explorer) {
|
|
273
|
+
const { policy, store } = workspace;
|
|
260
274
|
const tools = [
|
|
261
|
-
...
|
|
262
|
-
createTrackedReadFile(policy),
|
|
275
|
+
...explorerTools(workspace),
|
|
263
276
|
createShowChangesTool({ store, policy }),
|
|
277
|
+
createExploreTool(explorer),
|
|
264
278
|
];
|
|
265
279
|
const own = commands && commandsFor(commands, role);
|
|
266
280
|
if (!own || Object.keys(own).length === 0)
|
|
@@ -268,21 +282,32 @@ async function readingTools(workspace, role, commands) {
|
|
|
268
282
|
return [...tools, await commandToolFor(workspace, own)];
|
|
269
283
|
}
|
|
270
284
|
const batching = 'When you know you need several files, or several searches, ask for all of them in one answer rather than one at a time: each answer is a step, and steps are limited.';
|
|
285
|
+
const exploring = 'To learn an area of the repository, send explorers: ask `explore` the independent questions you have in one call, and read yourself only what you must see exactly.';
|
|
286
|
+
// Each provider's cheaper model, which only finds and reads. Haiku, which names Claude Haiku 4.5,
|
|
287
|
+
// takes no reasoning effort.
|
|
288
|
+
const explorerRoles = {
|
|
289
|
+
anthropic: { model: 'haiku' },
|
|
290
|
+
openai: { model: 'gpt-5.6-luna', reasoningEffort: 'medium' },
|
|
291
|
+
};
|
|
292
|
+
const explorerSteps = 30;
|
|
293
|
+
const explorerInstructions = `You explore a repository for another agent, which is planning or reviewing a change and asked you one question. Find what it asks with listFiles, searchText and readFile, reading only what the question needs. Answer with what you found: the files and line numbers, what that code does, and short excerpts where the exact text matters. Say what you looked for and could not find. Do not propose changes or a plan, and do not copy whole files. ${batching}`;
|
|
271
294
|
// How each role finds its way, sent after the workspace's roots, so it stays short. Only the
|
|
272
295
|
// planner learns the repository; the executor and the reviewer work from its plan, so a plan
|
|
273
296
|
// that leaves them guessing is the planner's to fix, not theirs to make up for.
|
|
274
297
|
const repositoryGuidance = {
|
|
275
|
-
planner: `Start with AGENTS.md, which says where the architecture and the decisions are, and read only the parts your task needs. Name in the plan every file to change and what to change in it, so the executor needs nothing else. ${batching}`,
|
|
298
|
+
planner: `Start with AGENTS.md, which says where the architecture and the decisions are, and read only the parts your task needs. Name in the plan every file to change and what to change in it, so the executor needs nothing else. ${exploring} ${batching}`,
|
|
276
299
|
executor: `Work from the plan: read the files it names, and only what you need to change them. Do not study the repository's architecture or decisions; the plan already applied them. ${batching}`,
|
|
277
|
-
reviewer: `Work from the plan and the changes: read what they touch, and only what you need to judge them. ${batching}`,
|
|
300
|
+
reviewer: `Work from the plan and the changes: read what they touch, and only what you need to judge them. ${exploring} ${batching}`,
|
|
278
301
|
};
|
|
279
302
|
/**
|
|
280
303
|
* Builds the planner → executor → reviewer workflow with every role on the model APIs, each
|
|
281
304
|
* holding its role's instructions, or `systemPrompts`, as its system prompt, followed by the
|
|
282
305
|
* workspace's roots.
|
|
283
306
|
*
|
|
284
|
-
* - The planner and the reviewer read: `listFiles`, `searchText`, `readFile`,
|
|
285
|
-
*
|
|
307
|
+
* - The planner and the reviewer read: `listFiles`, `searchText`, `readFile`, `showChanges`,
|
|
308
|
+
* the current task's changes as far as each may read them, and `explore`, which sends up to 4
|
|
309
|
+
* read-only explorers at once on the role's provider's cheaper model, each a new agent that
|
|
310
|
+
* starts from its question.
|
|
286
311
|
* - The executor gets its provider's file tools (see `createFileTools`), and changes files only
|
|
287
312
|
* in a `write` root; `autoApprove` reaches only it.
|
|
288
313
|
* - Each role has its own step limit: `maxSteps`, 80 for the executor and 65 for the others by
|
|
@@ -301,7 +326,7 @@ const repositoryGuidance = {
|
|
|
301
326
|
* @throws {UnrecoverableError} when a role cannot be set up, for example because
|
|
302
327
|
* `OPENAI_API_KEY` is missing, or when no program can read the repository.
|
|
303
328
|
*/
|
|
304
|
-
export async function createLLMOrchestrator({ provider, autoApprove, systemPrompts = {}, workspace = { roots: [{ path: process.cwd(), access: 'read' }] }, maxSteps, compactAt, commands, summarizeUndone = true, logger = new Logger(), } = {}) {
|
|
329
|
+
export async function createLLMOrchestrator({ provider, autoApprove, systemPrompts = {}, workspace = { roots: [{ path: process.cwd(), access: 'read' }] }, maxSteps, compactAt, commands, formatter, summarizeUndone = true, logger = new Logger(), } = {}) {
|
|
305
330
|
const roles = orchestratorRolesFor(provider);
|
|
306
331
|
const steps = perRole('maxSteps', maxSteps, defaultRoleSteps, {
|
|
307
332
|
unit: 'steps',
|
|
@@ -311,7 +336,9 @@ export async function createLLMOrchestrator({ provider, autoApprove, systemPromp
|
|
|
311
336
|
unit: 'tokens',
|
|
312
337
|
example: '{ "executor": 200000 }',
|
|
313
338
|
});
|
|
314
|
-
|
|
339
|
+
if (formatter !== undefined)
|
|
340
|
+
validateFormatter(formatter);
|
|
341
|
+
const opened = await openWorkspace(workspace, logger, formatter ? { formatter } : {});
|
|
315
342
|
const guidance = workspaceGuidance(workspace.roots);
|
|
316
343
|
const summarizer = summarizeUndone ? summarizerFor(roles.planner.provider, logger) : undefined;
|
|
317
344
|
const shared = { history: opened.store, ...(summarizer ? { summarizer } : {}), logger };
|
|
@@ -323,11 +350,20 @@ export async function createLLMOrchestrator({ provider, autoApprove, systemPromp
|
|
|
323
350
|
// Every command, checked once, before any role takes its share.
|
|
324
351
|
if (commands !== undefined)
|
|
325
352
|
validateCommands(commands);
|
|
353
|
+
// A new explorer for each question, on the role's provider: it starts from the question alone.
|
|
354
|
+
const explorerToolset = explorerTools(opened);
|
|
355
|
+
const explorer = (provider) => () => buildLLMAgent(provider, {
|
|
356
|
+
...explorerRoles[provider],
|
|
357
|
+
systemPrompt: withGuidance(explorerInstructions, guidance),
|
|
358
|
+
tools: explorerToolset,
|
|
359
|
+
maxSteps: explorerSteps,
|
|
360
|
+
logger,
|
|
361
|
+
});
|
|
326
362
|
const reader = async ({ provider, model, reasoningEffort }, role, systemPrompt) => buildLLMAgent(provider, {
|
|
327
363
|
model,
|
|
328
364
|
reasoningEffort,
|
|
329
365
|
systemPrompt,
|
|
330
|
-
tools: await readingTools(opened, role, commands),
|
|
366
|
+
tools: await readingTools(opened, role, commands, explorer(provider)),
|
|
331
367
|
maxSteps: steps[role],
|
|
332
368
|
compactAt: compaction[role],
|
|
333
369
|
...shared,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agentLLMFactory.js","sourceRoot":"","sources":["../../../src/factory/infrastructure/agentLLMFactory.ts"],"names":[],"mappings":"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","sourcesContent":["import { resolve } from 'node:path';\nimport type { Agent } from '#src/agent/domain/agent';\nimport type { HistoryReader } from '#src/engines/domain/model/historyFollower';\nimport { LLMAgent } from '#src/engines/domain/model/llmAgent';\nimport type { LLMClient } from '#src/llm/domain/llm';\nimport {\n\tClaudeLLMClient,\n\tclaudeLLMModels,\n\tclaudeLLMReasoningEfforts,\n} from '#src/llm/infrastructure/claudeLLMClient';\nimport {\n\tOpenAILLMClient,\n\topenAIModels,\n\topenAIReasoningEfforts,\n} from '#src/llm/infrastructure/openAILLMClient';\nimport {\n\texecutorInstructions,\n\texecutorInstructionsWithoutCommands,\n\tOrchestratorAgent,\n\tplannerInstructions,\n\treviewerInstructions,\n\treviewerInstructionsWithoutCommands,\n} from '#src/orchestration/domain/model/orchestratorAgent';\nimport { ReviewerDecisionValidator } from '#src/orchestration/infrastructure/model/reviewerDecisionValidator';\nimport type { RecoveryStore } from '#src/recovery/domain/recoveryStore';\nimport { RetryingAgent } from '#src/retry/domain/model/retryingAgent';\nimport { InvalidAgentConfigError } from '#src/shared/domain/errors';\nimport { isOneOf } from '#src/shared/domain/isOneOf';\nimport type { ILogger } from '#src/shared/domain/logger';\nimport { Logger } from '#src/shared/infrastructure/logger';\nimport type { WorkspaceRoot } from '#src/tools/domain/accessPolicy';\nimport {\n\ttype CommandRole,\n\ttype Commands,\n\tcommandsFor,\n\tvalidateCommands,\n} from '#src/tools/domain/commands';\nimport type { AgentTool } from '#src/tools/domain/preparedCall';\nimport type { Tool } from '#src/tools/domain/tool';\nimport {\n\tcommandToolFor,\n\ttype FileTool,\n\tfileToolsFor,\n\ttype OpenedWorkspace,\n\topenStore,\n\topenWorkspace,\n\tstateDirectoryOf,\n\ttype WorkspaceOptions,\n} from '#src/tools/infrastructure/fileTools';\nimport { createShowChangesTool } from '#src/tools/infrastructure/showChangesTool';\nimport { createTrackedReadFile } from '#src/tools/infrastructure/trackedReadFile';\nimport { createWorkspaceTools } from '#src/tools/infrastructure/workspaceTools';\nimport { isAgentProvider, orchestratorRolesFor, type Role } from './agentFactory.ts';\nimport {\n\tagentProviders,\n\ttype AgentModel,\n\ttype AgentProvider,\n\ttype CreateOrchestratorOptions,\n\ttype ReasoningEffort,\n} from './types.ts';\n\n/** The models each provider's model API accepts. */\nexport const llmModels = {\n\tanthropic: claudeLLMModels,\n\topenai: openAIModels,\n} as const satisfies Readonly<Record<AgentProvider, readonly AgentModel[]>>;\n\n/** The reasoning efforts each provider's model API accepts. */\nexport const llmReasoningEfforts = {\n\tanthropic: claudeLLMReasoningEfforts,\n\topenai: openAIReasoningEfforts,\n} as const satisfies Readonly<Record<AgentProvider, readonly ReasoningEffort[]>>;\n\n/** Narrows a model to one accepted by the selected provider's model API. */\nexport function isLLMModelOf<Provider extends AgentProvider>(\n\tprovider: Provider,\n\tvalue: string,\n): value is (typeof llmModels)[Provider][number] {\n\treturn isOneOf(llmModels[provider], value);\n}\n\n/** Narrows a reasoning effort to one accepted by the selected provider's model API. */\nexport function isLLMReasoningEffortOf<Provider extends AgentProvider>(\n\tprovider: Provider,\n\tvalue: string,\n): value is (typeof llmReasoningEfforts)[Provider][number] {\n\treturn isOneOf(llmReasoningEfforts[provider], value);\n}\n\nexport interface CreateLLMAgentOptions {\n\t/**\n\t * Defaults to `'sonnet'` on Anthropic and `'gpt-5.6-luna'` on OpenAI. The provider's client\n\t * rejects a model it does not support.\n\t */\n\tmodel?: AgentModel;\n\t/** Defaults to `'high'`. The provider's client rejects an effort it does not support. */\n\treasoningEffort?: ReasoningEffort;\n\t/** Sent as the model's instructions on every call. */\n\tsystemPrompt: string;\n\t/** The tools the model may call: the consumer's own, and those of `createFileTools`. Defaults to none. */\n\ttools?: (Tool | FileTool)[];\n\t/**\n\t * The folders the agent works on, the same as given to `createFileTools`. With it, the agent\n\t * follows the workspace's history: after the user undoes runs, its next run starts from the\n\t * conversation as it was, and is told what was undone. The system prompt is followed by the\n\t * roots and how to name a file in them.\n\t */\n\tworkspace?: WorkspaceOptions;\n\t/**\n\t * With `workspace`, a summary of what was undone, from the provider's cheap model, in place\n\t * of the undone prompts. That is Claude Haiku without thinking, or `gpt-5.6-luna` at high\n\t * effort, and the call is billed with the run's tokens. Defaults to `true`.\n\t */\n\tsummarizeUndone?: boolean;\n\t/**\n\t * How many calls to the model one run may make before it fails. Defaults to 65, with no upper\n\t * bound. A run that asks for exactly the same tool calls 5 times in a row fails before that.\n\t */\n\tmaxSteps?: number;\n\t/**\n\t * The input tokens at which the provider compacts the conversation on its side: it replaces\n\t * every turn before that call with its own summary, which only its model reads back. The\n\t * conversation keeps every original turn. Without it, the conversation is never compacted.\n\t * At least 50,000 on Anthropic, where Claude Haiku cannot compact.\n\t */\n\tcompactAt?: number;\n\t/**\n\t * Runs every tool call without asking, `destructive` ones included, for CI where no one can\n\t * answer. Without it, a `destructive` call goes to the run's `approve`, or is denied.\n\t */\n\tautoApprove?: boolean;\n\t/** Defaults to warnings on stderr. */\n\tlogger?: ILogger;\n}\n\n/** A limit per role of `createLLMOrchestrator`; a role left out keeps its default. */\nexport type RoleSteps = Partial<Record<OrchestratorRole, number>>;\n\n/** A number of tokens per role of `createLLMOrchestrator`; a role left out keeps its default. */\nexport type RoleTokens = Partial<Record<OrchestratorRole, number>>;\n\n// The same three roles a command can be given to.\ntype OrchestratorRole = CommandRole;\n\n// The executor reads, edits and checks, so it needs more calls than the roles that only read.\nconst defaultRoleSteps: Record<OrchestratorRole, number> = {\n\tplanner: 65,\n\texecutor: 80,\n\treviewer: 65,\n};\n\n// No role compacts unless asked to.\nconst defaultRoleCompaction: Record<OrchestratorRole, number | undefined> = {\n\tplanner: undefined,\n\texecutor: undefined,\n\treviewer: undefined,\n};\n\n/**\n * Each role's value of the option `name`, from one number for all of them or a number per role.\n * Each number is checked by whatever takes it.\n *\n * @throws {InvalidAgentConfigError} for a value that is neither, and for a role that does not\n * exist, which is a typo that would otherwise leave that role on its default without a word.\n */\nfunction perRole<T extends number | undefined>(\n\tname: string,\n\tvalue: unknown,\n\tdefaults: Record<OrchestratorRole, T>,\n\t{ unit, example }: { unit: string; example: string },\n): Record<OrchestratorRole, T | number> {\n\tif (value === undefined) return defaults;\n\tif (typeof value === 'number') return { planner: value, executor: value, reviewer: value };\n\t// Untyped input, such as the CLI's configuration file, can hold `null` or a list here.\n\tif (typeof value !== 'object' || value === null || Array.isArray(value)) {\n\t\tthrow new InvalidAgentConfigError(\n\t\t\t`${name} must be a number or an object of ${unit} per role, such as ${example}`,\n\t\t);\n\t}\n\tconst unknown = Object.keys(value).filter(role => !(role in defaults));\n\tif (unknown.length > 0) {\n\t\tthrow new InvalidAgentConfigError(\n\t\t\t`${name} names roles that do not exist: ${unknown.join(', ')}; expected planner, executor or reviewer`,\n\t\t);\n\t}\n\t// A role given as `undefined`, which untyped input can do, keeps its default.\n\tconst given = Object.entries(value as Record<string, unknown>).filter(\n\t\t([, each]) => each !== undefined,\n\t);\n\treturn { ...defaults, ...Object.fromEntries(given) };\n}\n\nexport interface CreateLLMOrchestratorOptions extends CreateOrchestratorOptions {\n\t/**\n\t * The folders the agents work on. The executor changes files only in a `write` root, every\n\t * change recorded in the workspace's history. Defaults to the current working directory,\n\t * read only.\n\t */\n\tworkspace?: WorkspaceOptions;\n\t/**\n\t * How many calls to the model one run of each role may make: one number for every role, or a\n\t * number per role. Defaults to 80 for the executor and 65 for the planner and the reviewer,\n\t * with no upper bound. A run that asks for exactly the same tool calls 5 times in a row fails\n\t * before its limit.\n\t */\n\tmaxSteps?: number | RoleSteps;\n\t/**\n\t * The input tokens at which each role's provider compacts its conversation, as\n\t * `CreateLLMAgentOptions.compactAt`: one number for every role, or a number per role. Without\n\t * it, no role compacts.\n\t */\n\tcompactAt?: number | RoleTokens;\n\t/**\n\t * Commands the roles may run, such as the project's tests, through `runCommand`. Each goes to\n\t * the roles its `roles` names, the executor by default. See `createFileTools`. Each needs a\n\t * `write` root to run in.\n\t */\n\tcommands?: Commands;\n\t/**\n\t * A summary of what the user undid, from the planner's provider's cheap model, for every\n\t * role. See `CreateLLMAgentOptions.summarizeUndone`. Defaults to `true`.\n\t */\n\tsummarizeUndone?: boolean;\n}\n\nconst defaultLLMAgentModels = {\n\tanthropic: 'sonnet',\n\topenai: 'gpt-5.6-luna',\n} as const satisfies Record<AgentProvider, AgentModel>;\n\nfunction unknownProvider(provider: never): InvalidAgentConfigError {\n\treturn new InvalidAgentConfigError(\n\t\t`\"${String(provider)}\" is not an agent provider; expected one of: ${agentProviders.join(', ')}`,\n\t);\n}\n\nfunction llmClientFor(\n\tprovider: AgentProvider,\n\t{\n\t\tmodel,\n\t\treasoningEffort,\n\t\tcompactAt,\n\t\tsystemPrompt,\n\t\tlogger,\n\t}: {\n\t\tmodel?: AgentModel;\n\t\treasoningEffort?: ReasoningEffort;\n\t\tcompactAt?: number | undefined;\n\t\tsystemPrompt: string;\n\t\tlogger: ILogger;\n\t},\n): LLMClient {\n\tswitch (provider) {\n\t\tcase 'anthropic':\n\t\t\treturn new ClaudeLLMClient({\n\t\t\t\tmodel: model ?? defaultLLMAgentModels.anthropic,\n\t\t\t\treasoningEffort,\n\t\t\t\tcompactAt,\n\t\t\t\tsystemPrompt,\n\t\t\t\tlogger,\n\t\t\t});\n\t\tcase 'openai':\n\t\t\treturn new OpenAILLMClient({\n\t\t\t\tmodel: model ?? defaultLLMAgentModels.openai,\n\t\t\t\treasoningEffort,\n\t\t\t\tcompactAt,\n\t\t\t\tsystemPrompt,\n\t\t\t\tlogger,\n\t\t\t});\n\t\tdefault:\n\t\t\t// Reachable from untyped input.\n\t\t\tthrow unknownProvider(provider);\n\t}\n}\n\n/** The provider's cheap model, for the summary of what was undone. */\nfunction summarizerFor(provider: AgentProvider, logger: ILogger): LLMClient {\n\treturn provider === 'anthropic'\n\t\t? new ClaudeLLMClient({ model: 'haiku', systemPrompt: '', logger })\n\t\t: new OpenAILLMClient({\n\t\t\t\tmodel: 'gpt-5.6-luna',\n\t\t\t\treasoningEffort: 'high',\n\t\t\t\tsystemPrompt: '',\n\t\t\t\tlogger,\n\t\t\t});\n}\n\n/**\n * The workspace's history, opened on first use, so a factory that builds an agent stays\n * synchronous. Every agent and every file tool of one workspace reads the same files on disk.\n */\nfunction historyOf(workspace: WorkspaceOptions): HistoryReader {\n\t// Checked now: an agent with no root to follow is a setup error, not a first-run one.\n\tif (workspace.roots.length === 0) {\n\t\tthrow new InvalidAgentConfigError('A workspace needs at least one root');\n\t}\n\tlet store: Promise<RecoveryStore> | undefined;\n\tconst open = () => {\n\t\tstore ??= stateDirectoryOf(workspace)\n\t\t\t.then(directory => openStore(workspace, directory))\n\t\t\t.catch((error: unknown) => {\n\t\t\t\t// Not kept: the next run tries again, as one whose disk was full for a moment.\n\t\t\t\tstore = undefined;\n\t\t\t\tthrow error;\n\t\t\t});\n\t\treturn store;\n\t};\n\treturn {\n\t\tlistRuns: async () => (await open()).listRuns(),\n\t\tlistRevisions: async () => (await open()).listRevisions(),\n\t\treadRun: async runId => (await open()).readRun(runId),\n\t};\n}\n\n/**\n * What the model is told about the workspace after its instructions: the roots, which it may\n * change, and how to name a file. A host path in an error sends the model looking for it, so\n * the real root is given here once.\n */\nfunction workspaceGuidance(roots: WorkspaceRoot[]): string {\n\tconst [first, ...others] = roots.map(root => ({ ...root, path: resolve(root.path) }));\n\tif (!first) return '';\n\tconst described = [first, ...others].map(\n\t\t({ path, access }) => `- ${path}${access === 'write' ? '' : ' (read only)'}`,\n\t);\n\treturn [\n\t\t'The workspace:',\n\t\t...described,\n\t\t`Name a file by its path relative to ${first.path}, such as src/index.ts, and a file in another folder of the workspace by its full path. Read a file before you change it.`,\n\t].join('\\n');\n}\n\nfunction withGuidance(systemPrompt: string, guidance: string): string {\n\treturn guidance === '' ? systemPrompt : `${systemPrompt}\\n\\n${guidance}`;\n}\n\n/** An `LLMAgent` on `provider`, wrapped in the harness's retry policy. */\nfunction buildLLMAgent(\n\tprovider: AgentProvider,\n\t{\n\t\tmodel,\n\t\treasoningEffort,\n\t\tsystemPrompt,\n\t\ttools,\n\t\thistory,\n\t\tsummarizer,\n\t\tmaxSteps,\n\t\tcompactAt,\n\t\tautoApprove,\n\t\tlogger,\n\t}: {\n\t\tmodel?: AgentModel;\n\t\treasoningEffort?: ReasoningEffort;\n\t\tsystemPrompt: string;\n\t\ttools: AgentTool[];\n\t\thistory?: HistoryReader;\n\t\tsummarizer?: LLMClient;\n\t\tmaxSteps?: number;\n\t\tcompactAt?: number | undefined;\n\t\tautoApprove?: boolean;\n\t\tlogger: ILogger;\n\t},\n): Agent {\n\tconst llmClient = llmClientFor(provider, {\n\t\tmodel,\n\t\treasoningEffort,\n\t\tcompactAt,\n\t\tsystemPrompt,\n\t\tlogger,\n\t});\n\t// LLMAgent records nothing from a failed call, so the original prompt is the whole story.\n\treturn new RetryingAgent({\n\t\tinner: new LLMAgent({\n\t\t\tllmClient,\n\t\t\ttools,\n\t\t\tautoApprove,\n\t\t\tlogger,\n\t\t\t...(history ? { history } : {}),\n\t\t\t...(summarizer ? { summarizer } : {}),\n\t\t\t...(maxSteps === undefined ? {} : { maxSteps }),\n\t\t}),\n\t\tlogger,\n\t\tnoteFailures: false,\n\t});\n}\n\n/**\n * Builds an agent whose conversation MiKode owns, on a provider's model API, already wrapped in\n * the harness's retry policy. It needs the provider's API key and bills per token, where\n * `createAgent` uses the Agent SDK's login. Each tool judges the risk of each call; a\n * `destructive` one runs only when the run's `approve` allows it, or with `autoApprove`.\n *\n * @throws {InvalidAgentConfigError} for an unknown provider, a model or reasoning effort its\n * client does not support, or two tools that share a name.\n * @throws {UnrecoverableError} when the client cannot be set up, for example because\n * `OPENAI_API_KEY` is missing. The Anthropic SDK resolves credentials lazily, so a missing\n * `ANTHROPIC_API_KEY` fails the first run instead, with an `UnrecoverableError`.\n */\nexport function createLLMAgent(\n\tprovider: AgentProvider,\n\t{\n\t\tmodel,\n\t\treasoningEffort,\n\t\tsystemPrompt,\n\t\ttools = [],\n\t\tworkspace,\n\t\tsummarizeUndone = true,\n\t\tmaxSteps,\n\t\tcompactAt,\n\t\tautoApprove,\n\t\tlogger = new Logger(),\n\t}: CreateLLMAgentOptions,\n): Agent {\n\treturn buildLLMAgent(provider, {\n\t\tmodel,\n\t\treasoningEffort,\n\t\tsystemPrompt: workspace\n\t\t\t? withGuidance(systemPrompt, workspaceGuidance(workspace.roots))\n\t\t\t: systemPrompt,\n\t\t// A file tool is a harness tool under its public name.\n\t\ttools: tools as AgentTool[],\n\t\t...(workspace ? { history: historyOf(workspace) } : {}),\n\t\t...(workspace && summarizeUndone ? { summarizer: summarizerFor(provider, logger) } : {}),\n\t\tmaxSteps,\n\t\tcompactAt,\n\t\tautoApprove,\n\t\tlogger,\n\t});\n}\n\n/**\n * The file tools for an agent from `createLLMAgent` on `provider`, over `workspace`, in the\n * provider's own format:\n *\n * - OpenAI: `listFiles`, `searchText`, `readFile` and its native `apply_patch`;\n * - Anthropic: `listFiles`, `searchText`, its native text editor, whose `view` reads, and\n * `delete_file`.\n *\n * Every one goes through the workspace's access policy: a path outside the roots, a secret,\n * a file `.gitignore` excludes or the harness protects is refused. A change is made only in a\n * `write` root, only from a version the agent read, and is recorded in the workspace's history,\n * so it can be undone. Without a `write` root the tools only read. Give the agent the same\n * `workspace`, so it follows what the history undoes.\n *\n * With `commands`, they include `runCommand`, which runs one of them by name, with only the\n * parameters it declares and never through a shell. Each run asks the approver, showing the\n * program exactly as it will run, and gets a small environment without the provider keys. The\n * files of the `write` roots are captured before it and compared after, so what it changed is\n * in the history too. It is trusted execution, not a sandbox: a command can reach whatever\n * this user can. Not supported on Windows yet.\n *\n * @throws {InvalidAgentConfigError} for an unknown provider, a workspace with no root, or an\n * invalid command, including one whose `cwd` is not in a `write` root, or any on Windows.\n * @throws {UnrecoverableError} when no program can read the first root.\n */\nexport async function createFileTools(\n\tprovider: AgentProvider,\n\tworkspace: WorkspaceOptions,\n\t{ logger = new Logger(), commands }: { logger?: ILogger; commands?: Commands } = {},\n): Promise<FileTool[]> {\n\t// Reachable from untyped input.\n\tif (!isAgentProvider(provider)) throw unknownProvider(provider);\n\tif (commands !== undefined) validateCommands(commands);\n\tconst opened = await openWorkspace(workspace, logger);\n\treturn (await executorTools(provider, opened, commands)) as unknown as FileTool[];\n}\n\n/** The executor's file tools, and `runCommand` when there are commands. */\nasync function executorTools(\n\tprovider: AgentProvider,\n\tworkspace: OpenedWorkspace,\n\tcommands: Commands | undefined,\n): Promise<AgentTool[]> {\n\tconst tools = fileToolsFor(provider, workspace);\n\t// Every command may belong to other roles, leaving none for this one.\n\tif (commands === undefined || Object.keys(commands).length === 0) return tools;\n\treturn [...tools, await commandToolFor(workspace, commands)];\n}\n\n/**\n * What the planner and the reviewer read with: no edits, the current task's changes, and the\n * commands given to their role.\n */\nasync function readingTools(\n\tworkspace: OpenedWorkspace,\n\trole: CommandRole,\n\tcommands: Commands | undefined,\n): Promise<AgentTool[]> {\n\tconst { read, policy, store } = workspace;\n\tconst finding = createWorkspaceTools(read).filter(tool => tool.name !== 'readFile');\n\tconst tools = [\n\t\t...finding,\n\t\tcreateTrackedReadFile(policy),\n\t\tcreateShowChangesTool({ store, policy }),\n\t];\n\tconst own = commands && commandsFor(commands, role);\n\tif (!own || Object.keys(own).length === 0) return tools;\n\treturn [...tools, await commandToolFor(workspace, own)];\n}\n\nconst batching =\n\t'When you know you need several files, or several searches, ask for all of them in one answer rather than one at a time: each answer is a step, and steps are limited.';\n\n// How each role finds its way, sent after the workspace's roots, so it stays short. Only the\n// planner learns the repository; the executor and the reviewer work from its plan, so a plan\n// that leaves them guessing is the planner's to fix, not theirs to make up for.\nconst repositoryGuidance: Record<CommandRole, string> = {\n\tplanner: `Start with AGENTS.md, which says where the architecture and the decisions are, and read only the parts your task needs. Name in the plan every file to change and what to change in it, so the executor needs nothing else. ${batching}`,\n\texecutor: `Work from the plan: read the files it names, and only what you need to change them. Do not study the repository's architecture or decisions; the plan already applied them. ${batching}`,\n\treviewer: `Work from the plan and the changes: read what they touch, and only what you need to judge them. ${batching}`,\n};\n\n/**\n * Builds the planner → executor → reviewer workflow with every role on the model APIs, each\n * holding its role's instructions, or `systemPrompts`, as its system prompt, followed by the\n * workspace's roots.\n *\n * - The planner and the reviewer read: `listFiles`, `searchText`, `readFile`, and\n * `showChanges`, the current task's changes as far as each may read them.\n * - The executor gets its provider's file tools (see `createFileTools`), and changes files only\n * in a `write` root; `autoApprove` reaches only it.\n * - Each role has its own step limit: `maxSteps`, 80 for the executor and 65 for the others by\n * default.\n * - Each role gets `runCommand` with the `commands` whose `roles` name it, the executor by\n * default. A command asks the approver in any role, and its files are captured around it.\n * - The executor is told to finish only once the tests and checks pass, and the reviewer that\n * they passed, when the executor has a command; without one, both are told the tests were\n * not run, and the reviewer judges from the code whether they would pass.\n *\n * One run of the orchestrator is one run of the workspace's history, every round and retry\n * included, and its `AgentResponse` names it. Every role follows what the user undoes.\n * Asynchronous because opening the workspace is.\n *\n * @throws {InvalidAgentConfigError} for an unknown provider, or a workspace with no root.\n * @throws {UnrecoverableError} when a role cannot be set up, for example because\n * `OPENAI_API_KEY` is missing, or when no program can read the repository.\n */\nexport async function createLLMOrchestrator({\n\tprovider,\n\tautoApprove,\n\tsystemPrompts = {},\n\tworkspace = { roots: [{ path: process.cwd(), access: 'read' }] },\n\tmaxSteps,\n\tcompactAt,\n\tcommands,\n\tsummarizeUndone = true,\n\tlogger = new Logger(),\n}: CreateLLMOrchestratorOptions = {}): Promise<Agent> {\n\tconst roles = orchestratorRolesFor(provider);\n\tconst steps = perRole('maxSteps', maxSteps, defaultRoleSteps, {\n\t\tunit: 'steps',\n\t\texample: '{ \"executor\": 120 }',\n\t});\n\tconst compaction = perRole('compactAt', compactAt, defaultRoleCompaction, {\n\t\tunit: 'tokens',\n\t\texample: '{ \"executor\": 200000 }',\n\t});\n\tconst opened = await openWorkspace(workspace, logger);\n\tconst guidance = workspaceGuidance(workspace.roots);\n\tconst summarizer = summarizeUndone ? summarizerFor(roles.planner.provider, logger) : undefined;\n\tconst shared = { history: opened.store, ...(summarizer ? { summarizer } : {}), logger };\n\t// A caller's own prompt replaces our instructions and guidance, not the workspace: the model\n\t// needs the roots to name a file, as `createLLMAgent` gives them.\n\tconst prompt = (role: CommandRole, own: string | undefined, instructions: string) =>\n\t\town === undefined\n\t\t\t? withGuidance(instructions, `${guidance}\\n${repositoryGuidance[role]}`)\n\t\t\t: withGuidance(own, guidance);\n\t// Every command, checked once, before any role takes its share.\n\tif (commands !== undefined) validateCommands(commands);\n\tconst reader = async (\n\t\t{ provider, model, reasoningEffort }: Role,\n\t\trole: CommandRole,\n\t\tsystemPrompt: string,\n\t) =>\n\t\tbuildLLMAgent(provider, {\n\t\t\tmodel,\n\t\t\treasoningEffort,\n\t\t\tsystemPrompt,\n\t\t\ttools: await readingTools(opened, role, commands),\n\t\t\tmaxSteps: steps[role],\n\t\t\tcompactAt: compaction[role],\n\t\t\t...shared,\n\t\t});\n\tconst { executor } = roles;\n\tconst executorToolset = await executorTools(\n\t\texecutor.provider,\n\t\topened,\n\t\tcommands && commandsFor(commands, 'executor'),\n\t);\n\t// The executor and the reviewer are told whether the tests were run, which only an executor\n\t// with a command can do.\n\tconst executorRuns = executorToolset.some(tool => tool.name === 'runCommand');\n\n\treturn new OrchestratorAgent({\n\t\tplannerAgent: await reader(\n\t\t\troles.planner,\n\t\t\t'planner',\n\t\t\tprompt('planner', systemPrompts.planner, plannerInstructions),\n\t\t),\n\t\texecutorAgent: buildLLMAgent(executor.provider, {\n\t\t\tmodel: executor.model,\n\t\t\treasoningEffort: executor.reasoningEffort,\n\t\t\tsystemPrompt: prompt(\n\t\t\t\t'executor',\n\t\t\t\tsystemPrompts.executor,\n\t\t\t\texecutorRuns ? executorInstructions : executorInstructionsWithoutCommands,\n\t\t\t),\n\t\t\ttools: executorToolset,\n\t\t\tmaxSteps: steps.executor,\n\t\t\tcompactAt: compaction.executor,\n\t\t\tautoApprove,\n\t\t\t...shared,\n\t\t}),\n\t\treviewerAgent: await reader(\n\t\t\troles.reviewer,\n\t\t\t'reviewer',\n\t\t\tprompt(\n\t\t\t\t'reviewer',\n\t\t\t\tsystemPrompts.reviewer,\n\t\t\t\texecutorRuns ? reviewerInstructions : reviewerInstructionsWithoutCommands,\n\t\t\t),\n\t\t),\n\t\treviewerDecisionValidator: new ReviewerDecisionValidator(),\n\t\tlogger,\n\t});\n}\n"]}
|
|
1
|
+
{"version":3,"file":"agentLLMFactory.js","sourceRoot":"","sources":["../../../src/factory/infrastructure/agentLLMFactory.ts"],"names":[],"mappings":"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","sourcesContent":["import { resolve } from 'node:path';\nimport type { Agent } from '#src/agent/domain/agent';\nimport type { HistoryReader } from '#src/engines/domain/model/historyFollower';\nimport { LLMAgent } from '#src/engines/domain/model/llmAgent';\nimport type { LLMClient } from '#src/llm/domain/llm';\nimport {\n\tClaudeLLMClient,\n\tclaudeLLMModels,\n\tclaudeLLMReasoningEfforts,\n} from '#src/llm/infrastructure/claudeLLMClient';\nimport {\n\tOpenAILLMClient,\n\topenAIModels,\n\topenAIReasoningEfforts,\n} from '#src/llm/infrastructure/openAILLMClient';\nimport {\n\texecutorInstructions,\n\texecutorInstructionsWithoutCommands,\n\tOrchestratorAgent,\n\tplannerInstructions,\n\treviewerInstructions,\n\treviewerInstructionsWithoutCommands,\n} from '#src/orchestration/domain/model/orchestratorAgent';\nimport { ReviewerDecisionValidator } from '#src/orchestration/infrastructure/model/reviewerDecisionValidator';\nimport type { RecoveryStore } from '#src/recovery/domain/recoveryStore';\nimport { RetryingAgent } from '#src/retry/domain/model/retryingAgent';\nimport { InvalidAgentConfigError } from '#src/shared/domain/errors';\nimport { isOneOf } from '#src/shared/domain/isOneOf';\nimport type { ILogger } from '#src/shared/domain/logger';\nimport { Logger } from '#src/shared/infrastructure/logger';\nimport type { WorkspaceRoot } from '#src/tools/domain/accessPolicy';\nimport {\n\ttype CommandRole,\n\ttype Commands,\n\tcommandsFor,\n\ttype Formatter,\n\tvalidateCommands,\n\tvalidateFormatter,\n} from '#src/tools/domain/commands';\nimport type { AgentTool } from '#src/tools/domain/preparedCall';\nimport type { Tool } from '#src/tools/domain/tool';\nimport {\n\tcommandToolFor,\n\ttype FileTool,\n\tfileToolsFor,\n\ttype OpenedWorkspace,\n\topenStore,\n\topenWorkspace,\n\tstateDirectoryOf,\n\ttype WorkspaceOptions,\n} from '#src/tools/infrastructure/fileTools';\nimport { createExploreTool } from '#src/tools/infrastructure/exploreTool';\nimport { createShowChangesTool } from '#src/tools/infrastructure/showChangesTool';\nimport { createTrackedReadFile } from '#src/tools/infrastructure/trackedReadFile';\nimport { createWorkspaceTools } from '#src/tools/infrastructure/workspaceTools';\nimport { isAgentProvider, orchestratorRolesFor, type Role } from './agentFactory.ts';\nimport {\n\tagentProviders,\n\ttype AgentModel,\n\ttype AgentProvider,\n\ttype CreateOrchestratorOptions,\n\ttype ReasoningEffort,\n} from './types.ts';\n\n/** The models each provider's model API accepts. */\nexport const llmModels = {\n\tanthropic: claudeLLMModels,\n\topenai: openAIModels,\n} as const satisfies Readonly<Record<AgentProvider, readonly AgentModel[]>>;\n\n/** The reasoning efforts each provider's model API accepts. */\nexport const llmReasoningEfforts = {\n\tanthropic: claudeLLMReasoningEfforts,\n\topenai: openAIReasoningEfforts,\n} as const satisfies Readonly<Record<AgentProvider, readonly ReasoningEffort[]>>;\n\n/** Narrows a model to one accepted by the selected provider's model API. */\nexport function isLLMModelOf<Provider extends AgentProvider>(\n\tprovider: Provider,\n\tvalue: string,\n): value is (typeof llmModels)[Provider][number] {\n\treturn isOneOf(llmModels[provider], value);\n}\n\n/** Narrows a reasoning effort to one accepted by the selected provider's model API. */\nexport function isLLMReasoningEffortOf<Provider extends AgentProvider>(\n\tprovider: Provider,\n\tvalue: string,\n): value is (typeof llmReasoningEfforts)[Provider][number] {\n\treturn isOneOf(llmReasoningEfforts[provider], value);\n}\n\nexport interface CreateLLMAgentOptions {\n\t/**\n\t * Defaults to `'sonnet'` on Anthropic and `'gpt-5.6-luna'` on OpenAI. The provider's client\n\t * rejects a model it does not support.\n\t */\n\tmodel?: AgentModel;\n\t/** Defaults to `'high'`. The provider's client rejects an effort it does not support. */\n\treasoningEffort?: ReasoningEffort;\n\t/** Sent as the model's instructions on every call. */\n\tsystemPrompt: string;\n\t/** The tools the model may call: the consumer's own, and those of `createFileTools`. Defaults to none. */\n\ttools?: (Tool | FileTool)[];\n\t/**\n\t * The folders the agent works on, the same as given to `createFileTools`. With it, the agent\n\t * follows the workspace's history: after the user undoes runs, its next run starts from the\n\t * conversation as it was, and is told what was undone. The system prompt is followed by the\n\t * roots and how to name a file in them.\n\t */\n\tworkspace?: WorkspaceOptions;\n\t/**\n\t * With `workspace`, a summary of what was undone, from the provider's cheap model, in place\n\t * of the undone prompts. That is Claude Haiku without thinking, or `gpt-5.6-luna` at high\n\t * effort, and the call is billed with the run's tokens. Defaults to `true`.\n\t */\n\tsummarizeUndone?: boolean;\n\t/**\n\t * How many calls to the model one run may make before it fails. Defaults to 65, with no upper\n\t * bound. A run that asks for exactly the same tool calls 5 times in a row fails before that.\n\t */\n\tmaxSteps?: number;\n\t/**\n\t * The input tokens at which the provider compacts the conversation on its side: it replaces\n\t * every turn before that call with its own summary, which only its model reads back. The\n\t * conversation keeps every original turn. Without it, the conversation is never compacted.\n\t * At least 50,000 on Anthropic, where Claude Haiku cannot compact.\n\t */\n\tcompactAt?: number;\n\t/**\n\t * Runs every tool call without asking, `destructive` ones included, for CI where no one can\n\t * answer. Without it, a `destructive` call goes to the run's `approve`, or is denied.\n\t */\n\tautoApprove?: boolean;\n\t/** Defaults to warnings on stderr. */\n\tlogger?: ILogger;\n}\n\n/** A limit per role of `createLLMOrchestrator`; a role left out keeps its default. */\nexport type RoleSteps = Partial<Record<OrchestratorRole, number>>;\n\n/** A number of tokens per role of `createLLMOrchestrator`; a role left out keeps its default. */\nexport type RoleTokens = Partial<Record<OrchestratorRole, number>>;\n\n// The same three roles a command can be given to.\ntype OrchestratorRole = CommandRole;\n\n// The executor reads, edits and checks, so it needs more calls than the roles that only read.\nconst defaultRoleSteps: Record<OrchestratorRole, number> = {\n\tplanner: 65,\n\texecutor: 80,\n\treviewer: 65,\n};\n\n// No role compacts unless asked to.\nconst defaultRoleCompaction: Record<OrchestratorRole, number | undefined> = {\n\tplanner: undefined,\n\texecutor: undefined,\n\treviewer: undefined,\n};\n\n/**\n * Each role's value of the option `name`, from one number for all of them or a number per role.\n * Each number is checked by whatever takes it.\n *\n * @throws {InvalidAgentConfigError} for a value that is neither, and for a role that does not\n * exist, which is a typo that would otherwise leave that role on its default without a word.\n */\nfunction perRole<T extends number | undefined>(\n\tname: string,\n\tvalue: unknown,\n\tdefaults: Record<OrchestratorRole, T>,\n\t{ unit, example }: { unit: string; example: string },\n): Record<OrchestratorRole, T | number> {\n\tif (value === undefined) return defaults;\n\tif (typeof value === 'number') return { planner: value, executor: value, reviewer: value };\n\t// Untyped input, such as the CLI's configuration file, can hold `null` or a list here.\n\tif (typeof value !== 'object' || value === null || Array.isArray(value)) {\n\t\tthrow new InvalidAgentConfigError(\n\t\t\t`${name} must be a number or an object of ${unit} per role, such as ${example}`,\n\t\t);\n\t}\n\tconst unknown = Object.keys(value).filter(role => !(role in defaults));\n\tif (unknown.length > 0) {\n\t\tthrow new InvalidAgentConfigError(\n\t\t\t`${name} names roles that do not exist: ${unknown.join(', ')}; expected planner, executor or reviewer`,\n\t\t);\n\t}\n\t// A role given as `undefined`, which untyped input can do, keeps its default.\n\tconst given = Object.entries(value as Record<string, unknown>).filter(\n\t\t([, each]) => each !== undefined,\n\t);\n\treturn { ...defaults, ...Object.fromEntries(given) };\n}\n\nexport interface CreateLLMOrchestratorOptions extends CreateOrchestratorOptions {\n\t/**\n\t * The folders the agents work on. The executor changes files only in a `write` root, every\n\t * change recorded in the workspace's history. Defaults to the current working directory,\n\t * read only.\n\t */\n\tworkspace?: WorkspaceOptions;\n\t/**\n\t * How many calls to the model one run of each role may make: one number for every role, or a\n\t * number per role. Defaults to 80 for the executor and 65 for the planner and the reviewer,\n\t * with no upper bound. A run that asks for exactly the same tool calls 5 times in a row fails\n\t * before its limit.\n\t */\n\tmaxSteps?: number | RoleSteps;\n\t/**\n\t * The input tokens at which each role's provider compacts its conversation, as\n\t * `CreateLLMAgentOptions.compactAt`: one number for every role, or a number per role. Without\n\t * it, no role compacts.\n\t */\n\tcompactAt?: number | RoleTokens;\n\t/**\n\t * Commands the roles may run, such as the project's tests, through `runCommand`. Each goes to\n\t * the roles its `roles` names, the executor by default. See `createFileTools`. Each needs a\n\t * `write` root to run in.\n\t */\n\tcommands?: Commands;\n\t/**\n\t * A program the executor's edits run on each file they create or change, such as the\n\t * project's formatter. See `createFileTools`. It needs a `write` root to run in.\n\t */\n\tformatter?: Formatter;\n\t/**\n\t * A summary of what the user undid, from the planner's provider's cheap model, for every\n\t * role. See `CreateLLMAgentOptions.summarizeUndone`. Defaults to `true`.\n\t */\n\tsummarizeUndone?: boolean;\n}\n\nconst defaultLLMAgentModels = {\n\tanthropic: 'sonnet',\n\topenai: 'gpt-5.6-luna',\n} as const satisfies Record<AgentProvider, AgentModel>;\n\nfunction unknownProvider(provider: never): InvalidAgentConfigError {\n\treturn new InvalidAgentConfigError(\n\t\t`\"${String(provider)}\" is not an agent provider; expected one of: ${agentProviders.join(', ')}`,\n\t);\n}\n\nfunction llmClientFor(\n\tprovider: AgentProvider,\n\t{\n\t\tmodel,\n\t\treasoningEffort,\n\t\tcompactAt,\n\t\tsystemPrompt,\n\t\tlogger,\n\t}: {\n\t\tmodel?: AgentModel;\n\t\treasoningEffort?: ReasoningEffort;\n\t\tcompactAt?: number | undefined;\n\t\tsystemPrompt: string;\n\t\tlogger: ILogger;\n\t},\n): LLMClient {\n\tswitch (provider) {\n\t\tcase 'anthropic':\n\t\t\treturn new ClaudeLLMClient({\n\t\t\t\tmodel: model ?? defaultLLMAgentModels.anthropic,\n\t\t\t\treasoningEffort,\n\t\t\t\tcompactAt,\n\t\t\t\tsystemPrompt,\n\t\t\t\tlogger,\n\t\t\t});\n\t\tcase 'openai':\n\t\t\treturn new OpenAILLMClient({\n\t\t\t\tmodel: model ?? defaultLLMAgentModels.openai,\n\t\t\t\treasoningEffort,\n\t\t\t\tcompactAt,\n\t\t\t\tsystemPrompt,\n\t\t\t\tlogger,\n\t\t\t});\n\t\tdefault:\n\t\t\t// Reachable from untyped input.\n\t\t\tthrow unknownProvider(provider);\n\t}\n}\n\n/** The provider's cheap model, for the summary of what was undone. */\nfunction summarizerFor(provider: AgentProvider, logger: ILogger): LLMClient {\n\treturn provider === 'anthropic'\n\t\t? new ClaudeLLMClient({ model: 'haiku', systemPrompt: '', logger })\n\t\t: new OpenAILLMClient({\n\t\t\t\tmodel: 'gpt-5.6-luna',\n\t\t\t\treasoningEffort: 'high',\n\t\t\t\tsystemPrompt: '',\n\t\t\t\tlogger,\n\t\t\t});\n}\n\n/**\n * The workspace's history, opened on first use, so a factory that builds an agent stays\n * synchronous. Every agent and every file tool of one workspace reads the same files on disk.\n */\nfunction historyOf(workspace: WorkspaceOptions): HistoryReader {\n\t// Checked now: an agent with no root to follow is a setup error, not a first-run one.\n\tif (workspace.roots.length === 0) {\n\t\tthrow new InvalidAgentConfigError('A workspace needs at least one root');\n\t}\n\tlet store: Promise<RecoveryStore> | undefined;\n\tconst open = () => {\n\t\tstore ??= stateDirectoryOf(workspace)\n\t\t\t.then(directory => openStore(workspace, directory))\n\t\t\t.catch((error: unknown) => {\n\t\t\t\t// Not kept: the next run tries again, as one whose disk was full for a moment.\n\t\t\t\tstore = undefined;\n\t\t\t\tthrow error;\n\t\t\t});\n\t\treturn store;\n\t};\n\treturn {\n\t\tlistRuns: async () => (await open()).listRuns(),\n\t\tlistRevisions: async () => (await open()).listRevisions(),\n\t\treadRun: async runId => (await open()).readRun(runId),\n\t};\n}\n\n/**\n * What the model is told about the workspace after its instructions: the roots, which it may\n * change, and how to name a file. A host path in an error sends the model looking for it, so\n * the real root is given here once.\n */\nfunction workspaceGuidance(roots: WorkspaceRoot[]): string {\n\tconst [first, ...others] = roots.map(root => ({ ...root, path: resolve(root.path) }));\n\tif (!first) return '';\n\tconst described = [first, ...others].map(\n\t\t({ path, access }) => `- ${path}${access === 'write' ? '' : ' (read only)'}`,\n\t);\n\treturn [\n\t\t'The workspace:',\n\t\t...described,\n\t\t`Name a file by its path relative to ${first.path}, such as src/index.ts, and a file in another folder of the workspace by its full path. Read a file before you change it.`,\n\t].join('\\n');\n}\n\nfunction withGuidance(systemPrompt: string, guidance: string): string {\n\treturn guidance === '' ? systemPrompt : `${systemPrompt}\\n\\n${guidance}`;\n}\n\n/** An `LLMAgent` on `provider`, wrapped in the harness's retry policy. */\nfunction buildLLMAgent(\n\tprovider: AgentProvider,\n\t{\n\t\tmodel,\n\t\treasoningEffort,\n\t\tsystemPrompt,\n\t\ttools,\n\t\thistory,\n\t\tsummarizer,\n\t\tmaxSteps,\n\t\tcompactAt,\n\t\tautoApprove,\n\t\tlogger,\n\t}: {\n\t\tmodel?: AgentModel;\n\t\treasoningEffort?: ReasoningEffort;\n\t\tsystemPrompt: string;\n\t\ttools: AgentTool[];\n\t\thistory?: HistoryReader;\n\t\tsummarizer?: LLMClient;\n\t\tmaxSteps?: number;\n\t\tcompactAt?: number | undefined;\n\t\tautoApprove?: boolean;\n\t\tlogger: ILogger;\n\t},\n): Agent {\n\tconst llmClient = llmClientFor(provider, {\n\t\tmodel,\n\t\treasoningEffort,\n\t\tcompactAt,\n\t\tsystemPrompt,\n\t\tlogger,\n\t});\n\t// LLMAgent records nothing from a failed call, so the original prompt is the whole story.\n\treturn new RetryingAgent({\n\t\tinner: new LLMAgent({\n\t\t\tllmClient,\n\t\t\ttools,\n\t\t\tautoApprove,\n\t\t\tlogger,\n\t\t\t...(history ? { history } : {}),\n\t\t\t...(summarizer ? { summarizer } : {}),\n\t\t\t...(maxSteps === undefined ? {} : { maxSteps }),\n\t\t}),\n\t\tlogger,\n\t\tnoteFailures: false,\n\t});\n}\n\n/**\n * Builds an agent whose conversation MiKode owns, on a provider's model API, already wrapped in\n * the harness's retry policy. It needs the provider's API key and bills per token, where\n * `createAgent` uses the Agent SDK's login. Each tool judges the risk of each call; a\n * `destructive` one runs only when the run's `approve` allows it, or with `autoApprove`.\n *\n * @throws {InvalidAgentConfigError} for an unknown provider, a model or reasoning effort its\n * client does not support, or two tools that share a name.\n * @throws {UnrecoverableError} when the client cannot be set up, for example because\n * `OPENAI_API_KEY` is missing. The Anthropic SDK resolves credentials lazily, so a missing\n * `ANTHROPIC_API_KEY` fails the first run instead, with an `UnrecoverableError`.\n */\nexport function createLLMAgent(\n\tprovider: AgentProvider,\n\t{\n\t\tmodel,\n\t\treasoningEffort,\n\t\tsystemPrompt,\n\t\ttools = [],\n\t\tworkspace,\n\t\tsummarizeUndone = true,\n\t\tmaxSteps,\n\t\tcompactAt,\n\t\tautoApprove,\n\t\tlogger = new Logger(),\n\t}: CreateLLMAgentOptions,\n): Agent {\n\treturn buildLLMAgent(provider, {\n\t\tmodel,\n\t\treasoningEffort,\n\t\tsystemPrompt: workspace\n\t\t\t? withGuidance(systemPrompt, workspaceGuidance(workspace.roots))\n\t\t\t: systemPrompt,\n\t\t// A file tool is a harness tool under its public name.\n\t\ttools: tools as AgentTool[],\n\t\t...(workspace ? { history: historyOf(workspace) } : {}),\n\t\t...(workspace && summarizeUndone ? { summarizer: summarizerFor(provider, logger) } : {}),\n\t\tmaxSteps,\n\t\tcompactAt,\n\t\tautoApprove,\n\t\tlogger,\n\t});\n}\n\n/**\n * The file tools for an agent from `createLLMAgent` on `provider`, over `workspace`, in the\n * provider's own format:\n *\n * - OpenAI: `listFiles`, `searchText`, `readFile` and its native `apply_patch`;\n * - Anthropic: `listFiles`, `searchText`, its native text editor, whose `view` reads, and\n * `delete_file`.\n *\n * Every one goes through the workspace's access policy: a path outside the roots, a secret,\n * a file `.gitignore` excludes or the harness protects is refused. A change is made only in a\n * `write` root, only from a version the agent read, and is recorded in the workspace's history,\n * so it can be undone. Without a `write` root the tools only read. Give the agent the same\n * `workspace`, so it follows what the history undoes.\n *\n * With `commands`, they include `runCommand`, which runs one of them by name, with only the\n * parameters it declares and never through a shell. Each run asks the approver, showing the\n * program exactly as it will run, and gets a small environment without the provider keys. The\n * files of the `write` roots are captured before it and compared after, so what it changed is\n * in the history too. It is trusted execution, not a sandbox: a command can reach whatever\n * this user can. Not supported on Windows yet.\n *\n * With `formatter`, every file an edit creates or changes is formatted right after, its path\n * the formatter's last argument, and the edit returns the file as the formatter left it. Like a\n * command it runs without a shell, in a small environment, and what it changes in that file is\n * in the history. When it fails, the file stays as the edit wrote it, and the model is told\n * why. Not supported on Windows yet.\n *\n * @throws {InvalidAgentConfigError} for an unknown provider, a workspace with no root, or an\n * invalid command or formatter, including one whose `cwd` is not in a `write` root, or any on\n * Windows.\n * @throws {UnrecoverableError} when no program can read the first root.\n */\nexport async function createFileTools(\n\tprovider: AgentProvider,\n\tworkspace: WorkspaceOptions,\n\t{\n\t\tlogger = new Logger(),\n\t\tcommands,\n\t\tformatter,\n\t}: { logger?: ILogger; commands?: Commands; formatter?: Formatter } = {},\n): Promise<FileTool[]> {\n\t// Reachable from untyped input.\n\tif (!isAgentProvider(provider)) throw unknownProvider(provider);\n\tif (commands !== undefined) validateCommands(commands);\n\tif (formatter !== undefined) validateFormatter(formatter);\n\tconst opened = await openWorkspace(workspace, logger, formatter ? { formatter } : {});\n\treturn (await executorTools(provider, opened, commands)) as unknown as FileTool[];\n}\n\n/** The executor's file tools, and `runCommand` when there are commands. */\nasync function executorTools(\n\tprovider: AgentProvider,\n\tworkspace: OpenedWorkspace,\n\tcommands: Commands | undefined,\n): Promise<AgentTool[]> {\n\tconst tools = fileToolsFor(provider, workspace);\n\t// Every command may belong to other roles, leaving none for this one.\n\tif (commands === undefined || Object.keys(commands).length === 0) return tools;\n\treturn [...tools, await commandToolFor(workspace, commands)];\n}\n\n/** What an explorer finds and reads with: no edits, no changes, no commands. */\nfunction explorerTools({ read, policy }: OpenedWorkspace): AgentTool[] {\n\tconst finding = createWorkspaceTools(read).filter(tool => tool.name !== 'readFile');\n\treturn [...finding, createTrackedReadFile(policy)];\n}\n\n/**\n * What the planner and the reviewer read with: no edits, the current task's changes, `explore`\n * with `explorer`, and the commands given to their role.\n */\nasync function readingTools(\n\tworkspace: OpenedWorkspace,\n\trole: CommandRole,\n\tcommands: Commands | undefined,\n\texplorer: () => Agent,\n): Promise<AgentTool[]> {\n\tconst { policy, store } = workspace;\n\tconst tools = [\n\t\t...explorerTools(workspace),\n\t\tcreateShowChangesTool({ store, policy }),\n\t\tcreateExploreTool(explorer),\n\t];\n\tconst own = commands && commandsFor(commands, role);\n\tif (!own || Object.keys(own).length === 0) return tools;\n\treturn [...tools, await commandToolFor(workspace, own)];\n}\n\nconst batching =\n\t'When you know you need several files, or several searches, ask for all of them in one answer rather than one at a time: each answer is a step, and steps are limited.';\n\nconst exploring =\n\t'To learn an area of the repository, send explorers: ask `explore` the independent questions you have in one call, and read yourself only what you must see exactly.';\n\n// Each provider's cheaper model, which only finds and reads. Haiku, which names Claude Haiku 4.5,\n// takes no reasoning effort.\nconst explorerRoles = {\n\tanthropic: { model: 'haiku' },\n\topenai: { model: 'gpt-5.6-luna', reasoningEffort: 'medium' },\n} as const satisfies Record<\n\tAgentProvider,\n\t{ model: AgentModel; reasoningEffort?: ReasoningEffort }\n>;\n\nconst explorerSteps = 30;\n\nconst explorerInstructions = `You explore a repository for another agent, which is planning or reviewing a change and asked you one question. Find what it asks with listFiles, searchText and readFile, reading only what the question needs. Answer with what you found: the files and line numbers, what that code does, and short excerpts where the exact text matters. Say what you looked for and could not find. Do not propose changes or a plan, and do not copy whole files. ${batching}`;\n\n// How each role finds its way, sent after the workspace's roots, so it stays short. Only the\n// planner learns the repository; the executor and the reviewer work from its plan, so a plan\n// that leaves them guessing is the planner's to fix, not theirs to make up for.\nconst repositoryGuidance: Record<CommandRole, string> = {\n\tplanner: `Start with AGENTS.md, which says where the architecture and the decisions are, and read only the parts your task needs. Name in the plan every file to change and what to change in it, so the executor needs nothing else. ${exploring} ${batching}`,\n\texecutor: `Work from the plan: read the files it names, and only what you need to change them. Do not study the repository's architecture or decisions; the plan already applied them. ${batching}`,\n\treviewer: `Work from the plan and the changes: read what they touch, and only what you need to judge them. ${exploring} ${batching}`,\n};\n\n/**\n * Builds the planner → executor → reviewer workflow with every role on the model APIs, each\n * holding its role's instructions, or `systemPrompts`, as its system prompt, followed by the\n * workspace's roots.\n *\n * - The planner and the reviewer read: `listFiles`, `searchText`, `readFile`, `showChanges`,\n * the current task's changes as far as each may read them, and `explore`, which sends up to 4\n * read-only explorers at once on the role's provider's cheaper model, each a new agent that\n * starts from its question.\n * - The executor gets its provider's file tools (see `createFileTools`), and changes files only\n * in a `write` root; `autoApprove` reaches only it.\n * - Each role has its own step limit: `maxSteps`, 80 for the executor and 65 for the others by\n * default.\n * - Each role gets `runCommand` with the `commands` whose `roles` name it, the executor by\n * default. A command asks the approver in any role, and its files are captured around it.\n * - The executor is told to finish only once the tests and checks pass, and the reviewer that\n * they passed, when the executor has a command; without one, both are told the tests were\n * not run, and the reviewer judges from the code whether they would pass.\n *\n * One run of the orchestrator is one run of the workspace's history, every round and retry\n * included, and its `AgentResponse` names it. Every role follows what the user undoes.\n * Asynchronous because opening the workspace is.\n *\n * @throws {InvalidAgentConfigError} for an unknown provider, or a workspace with no root.\n * @throws {UnrecoverableError} when a role cannot be set up, for example because\n * `OPENAI_API_KEY` is missing, or when no program can read the repository.\n */\nexport async function createLLMOrchestrator({\n\tprovider,\n\tautoApprove,\n\tsystemPrompts = {},\n\tworkspace = { roots: [{ path: process.cwd(), access: 'read' }] },\n\tmaxSteps,\n\tcompactAt,\n\tcommands,\n\tformatter,\n\tsummarizeUndone = true,\n\tlogger = new Logger(),\n}: CreateLLMOrchestratorOptions = {}): Promise<Agent> {\n\tconst roles = orchestratorRolesFor(provider);\n\tconst steps = perRole('maxSteps', maxSteps, defaultRoleSteps, {\n\t\tunit: 'steps',\n\t\texample: '{ \"executor\": 120 }',\n\t});\n\tconst compaction = perRole('compactAt', compactAt, defaultRoleCompaction, {\n\t\tunit: 'tokens',\n\t\texample: '{ \"executor\": 200000 }',\n\t});\n\tif (formatter !== undefined) validateFormatter(formatter);\n\tconst opened = await openWorkspace(workspace, logger, formatter ? { formatter } : {});\n\tconst guidance = workspaceGuidance(workspace.roots);\n\tconst summarizer = summarizeUndone ? summarizerFor(roles.planner.provider, logger) : undefined;\n\tconst shared = { history: opened.store, ...(summarizer ? { summarizer } : {}), logger };\n\t// A caller's own prompt replaces our instructions and guidance, not the workspace: the model\n\t// needs the roots to name a file, as `createLLMAgent` gives them.\n\tconst prompt = (role: CommandRole, own: string | undefined, instructions: string) =>\n\t\town === undefined\n\t\t\t? withGuidance(instructions, `${guidance}\\n${repositoryGuidance[role]}`)\n\t\t\t: withGuidance(own, guidance);\n\t// Every command, checked once, before any role takes its share.\n\tif (commands !== undefined) validateCommands(commands);\n\t// A new explorer for each question, on the role's provider: it starts from the question alone.\n\tconst explorerToolset = explorerTools(opened);\n\tconst explorer = (provider: AgentProvider) => () =>\n\t\tbuildLLMAgent(provider, {\n\t\t\t...explorerRoles[provider],\n\t\t\tsystemPrompt: withGuidance(explorerInstructions, guidance),\n\t\t\ttools: explorerToolset,\n\t\t\tmaxSteps: explorerSteps,\n\t\t\tlogger,\n\t\t});\n\tconst reader = async (\n\t\t{ provider, model, reasoningEffort }: Role,\n\t\trole: CommandRole,\n\t\tsystemPrompt: string,\n\t) =>\n\t\tbuildLLMAgent(provider, {\n\t\t\tmodel,\n\t\t\treasoningEffort,\n\t\t\tsystemPrompt,\n\t\t\ttools: await readingTools(opened, role, commands, explorer(provider)),\n\t\t\tmaxSteps: steps[role],\n\t\t\tcompactAt: compaction[role],\n\t\t\t...shared,\n\t\t});\n\tconst { executor } = roles;\n\tconst executorToolset = await executorTools(\n\t\texecutor.provider,\n\t\topened,\n\t\tcommands && commandsFor(commands, 'executor'),\n\t);\n\t// The executor and the reviewer are told whether the tests were run, which only an executor\n\t// with a command can do.\n\tconst executorRuns = executorToolset.some(tool => tool.name === 'runCommand');\n\n\treturn new OrchestratorAgent({\n\t\tplannerAgent: await reader(\n\t\t\troles.planner,\n\t\t\t'planner',\n\t\t\tprompt('planner', systemPrompts.planner, plannerInstructions),\n\t\t),\n\t\texecutorAgent: buildLLMAgent(executor.provider, {\n\t\t\tmodel: executor.model,\n\t\t\treasoningEffort: executor.reasoningEffort,\n\t\t\tsystemPrompt: prompt(\n\t\t\t\t'executor',\n\t\t\t\tsystemPrompts.executor,\n\t\t\t\texecutorRuns ? executorInstructions : executorInstructionsWithoutCommands,\n\t\t\t),\n\t\t\ttools: executorToolset,\n\t\t\tmaxSteps: steps.executor,\n\t\t\tcompactAt: compaction.executor,\n\t\t\tautoApprove,\n\t\t\t...shared,\n\t\t}),\n\t\treviewerAgent: await reader(\n\t\t\troles.reviewer,\n\t\t\t'reviewer',\n\t\t\tprompt(\n\t\t\t\t'reviewer',\n\t\t\t\tsystemPrompts.reviewer,\n\t\t\t\texecutorRuns ? reviewerInstructions : reviewerInstructionsWithoutCommands,\n\t\t\t),\n\t\t),\n\t\treviewerDecisionValidator: new ReviewerDecisionValidator(),\n\t\tlogger,\n\t});\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export { createAgent, createOrchestrator, agentModels, agentReasoningEfforts, is
|
|
|
3
3
|
export { createFileTools, createLLMAgent, createLLMOrchestrator, isLLMModelOf, isLLMReasoningEffortOf, llmModels, llmReasoningEfforts, type CreateLLMAgentOptions, type CreateLLMOrchestratorOptions, type RoleSteps, type RoleTokens, } from './factory/infrastructure/agentLLMFactory.ts';
|
|
4
4
|
export type { FileTool, WorkspaceOptions } from './tools/infrastructure/fileTools.ts';
|
|
5
5
|
export type { WorkspaceRoot } from './tools/domain/accessPolicy.ts';
|
|
6
|
-
export type { CommandArgument, CommandDefinition, CommandParameter, CommandRole, Commands, } from './tools/domain/commands.ts';
|
|
6
|
+
export type { CommandArgument, CommandDefinition, CommandParameter, CommandRole, Commands, Formatter, } from './tools/domain/commands.ts';
|
|
7
7
|
export type { SecretRules } from './tools/infrastructure/rootsAccessPolicy.ts';
|
|
8
8
|
export { agentProviders, type AgentModel, type AgentProvider, type CreateOrchestratorOptions, type ReasoningEffort, } from './factory/infrastructure/types.ts';
|
|
9
9
|
export { type Agent, type AgentResponse, type Callback, type ProgressAgent, type ProgressEvent, type StepContext, type RunOptions, } from './agent/domain/agent.ts';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACN,WAAW,EACX,kBAAkB,EAClB,WAAW,EACX,qBAAqB,EACrB,eAAe,EACf,SAAS,EACT,mBAAmB,GAEnB,MAAM,0CAA0C,CAAC;AAClD,OAAO,EACN,eAAe,EACf,cAAc,EACd,qBAAqB,EACrB,YAAY,EACZ,sBAAsB,EACtB,SAAS,EACT,mBAAmB,GAKnB,MAAM,6CAA6C,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACN,WAAW,EACX,kBAAkB,EAClB,WAAW,EACX,qBAAqB,EACrB,eAAe,EACf,SAAS,EACT,mBAAmB,GAEnB,MAAM,0CAA0C,CAAC;AAClD,OAAO,EACN,eAAe,EACf,cAAc,EACd,qBAAqB,EACrB,YAAY,EACZ,sBAAsB,EACtB,SAAS,EACT,mBAAmB,GAKnB,MAAM,6CAA6C,CAAC;AAYrD,OAAO,EACN,cAAc,GAKd,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAQN,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACN,uBAAuB,EACvB,gBAAgB,EAChB,kBAAkB,GAClB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAE/D,OAAO,EACN,iBAAiB,GAOjB,MAAM,4BAA4B,CAAC;AAKpC,OAAO,EAAE,UAAU,EAAE,MAAM,sCAAsC,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,2CAA2C,CAAC;AAC5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,0CAA0C,CAAC;AAChF,OAAO,EAAE,aAAa,EAAE,MAAM,4CAA4C,CAAC;AAE3E,OAAO,EACN,YAAY,EACZ,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,GAGnB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACpF,OAAO,EAAE,cAAc,EAAE,MAAM,6CAA6C,CAAC;AAC7E,OAAO,EACN,oBAAoB,GASpB,MAAM,+BAA+B,CAAC","sourcesContent":["export type { ILogger } from './shared/domain/logger.ts';\nexport {\n\tcreateAgent,\n\tcreateOrchestrator,\n\tagentModels,\n\tagentReasoningEfforts,\n\tisAgentProvider,\n\tisModelOf,\n\tisReasoningEffortOf,\n\ttype CreateAgentOptions,\n} from './factory/infrastructure/agentFactory.ts';\nexport {\n\tcreateFileTools,\n\tcreateLLMAgent,\n\tcreateLLMOrchestrator,\n\tisLLMModelOf,\n\tisLLMReasoningEffortOf,\n\tllmModels,\n\tllmReasoningEfforts,\n\ttype CreateLLMAgentOptions,\n\ttype CreateLLMOrchestratorOptions,\n\ttype RoleSteps,\n\ttype RoleTokens,\n} from './factory/infrastructure/agentLLMFactory.ts';\nexport type { FileTool, WorkspaceOptions } from './tools/infrastructure/fileTools.ts';\nexport type { WorkspaceRoot } from './tools/domain/accessPolicy.ts';\nexport type {\n\tCommandArgument,\n\tCommandDefinition,\n\tCommandParameter,\n\tCommandRole,\n\tCommands,\n\tFormatter,\n} from './tools/domain/commands.ts';\nexport type { SecretRules } from './tools/infrastructure/rootsAccessPolicy.ts';\nexport {\n\tagentProviders,\n\ttype AgentModel,\n\ttype AgentProvider,\n\ttype CreateOrchestratorOptions,\n\ttype ReasoningEffort,\n} from './factory/infrastructure/types.ts';\nexport {\n\ttype Agent,\n\ttype AgentResponse,\n\ttype Callback,\n\ttype ProgressAgent,\n\ttype ProgressEvent,\n\ttype StepContext,\n\ttype RunOptions,\n} from './agent/domain/agent.ts';\nexport {\n\tInvalidAgentConfigError,\n\tRecoverableError,\n\tUnrecoverableError,\n} from './shared/domain/errors.ts';\nexport { isAbortError } from './shared/domain/isAbortError.ts';\nexport type { Tokens } from './shared/domain/tokens.ts';\nexport {\n\trememberApprovals,\n\ttype ApprovalDecision,\n\ttype ApprovalRequest,\n\ttype Approver,\n\ttype CommandLine,\n\ttype RememberableDecision,\n\ttype ToolRisk,\n} from './agent/domain/approval.ts';\nexport type { Asker, QuestionOption, UserQuestion } from './agent/domain/question.ts';\nexport type { JSONSchema, ToolDefinition } from './llm/domain/tool.ts';\nexport type { Tool, ToolSummarizer } from './tools/domain/tool.ts';\nexport type { TextMatch, Workspace } from './tools/domain/workspace.ts';\nexport { defineTool } from './tools/infrastructure/defineTool.ts';\nexport { createWorkspace } from './tools/infrastructure/createWorkspace.ts';\nexport { createWorkspaceTools } from './tools/infrastructure/workspaceTools.ts';\nexport { createHistory } from './recovery/infrastructure/createHistory.ts';\nexport type { History, HistoryRun, Move } from './recovery/domain/history.ts';\nexport {\n\thistoryStart,\n\tNothingToMoveError,\n\tWorkspaceBusyError,\n\tWorkspaceMovedError,\n\ttype MoveOptions,\n\ttype RunStatus,\n} from './recovery/domain/recoveryStore.ts';\nexport { HistoryExpiredError, UnknownRunError } from './recovery/domain/runTree.ts';\nexport { createWorkflow } from './factory/infrastructure/workflowFactory.ts';\nexport {\n\tdefineWorkflowStages,\n\ttype CreateWorkflowOptions,\n\ttype Workflow,\n\ttype WorkflowResult,\n\ttype WorkflowStage,\n\ttype WorkflowStageDefinition,\n\ttype WorkflowStageName,\n\ttype WorkflowStageReport,\n\ttype WorkflowTransition,\n} from './workflow/domain/workflow.ts';\nexport type { Validator } from './shared/domain/validator.ts';\n"]}
|
|
@@ -69,6 +69,27 @@ export interface CommandDefinition {
|
|
|
69
69
|
}
|
|
70
70
|
/** The commands an agent may run, by the name the model calls each one. */
|
|
71
71
|
export type Commands = Record<string, CommandDefinition>;
|
|
72
|
+
/**
|
|
73
|
+
* A program the edit tools run on each file they create or change, such as the project's
|
|
74
|
+
* formatter, so the model reads and keeps editing the file as the formatter leaves it. It runs
|
|
75
|
+
* as a command does: without a shell, in a small environment, with its changes in the history.
|
|
76
|
+
*/
|
|
77
|
+
export interface Formatter {
|
|
78
|
+
/** The program: a name looked up in its `PATH`, or a path. A relative path is from `cwd`. */
|
|
79
|
+
executable: string;
|
|
80
|
+
/**
|
|
81
|
+
* Its arguments, before the file's path, which is always the last one and relative to `cwd`:
|
|
82
|
+
* `["exec", "prettier", "--write", "--ignore-unknown"]` with `pnpm`.
|
|
83
|
+
*/
|
|
84
|
+
args?: string[];
|
|
85
|
+
/** Where it runs, as `CommandDefinition.cwd`. Defaults to the first root. */
|
|
86
|
+
cwd?: string;
|
|
87
|
+
/** Variables set for it, as `CommandDefinition.env`. */
|
|
88
|
+
env?: Record<string, string>;
|
|
89
|
+
/** How long it may run on one file before it is stopped. Defaults to 1 minute. */
|
|
90
|
+
timeoutMs?: number;
|
|
91
|
+
}
|
|
92
|
+
export declare const defaultFormatterTimeoutMs: number;
|
|
72
93
|
export declare const defaultCommandTimeoutMs: number;
|
|
73
94
|
export declare const defaultCommandOutputBytes: number;
|
|
74
95
|
/** A parameter's value as the model sent it, with `paths` already checked and resolved. */
|
|
@@ -80,6 +101,12 @@ export type ParameterValue = string[] | string | null;
|
|
|
80
101
|
* @throws {InvalidAgentConfigError} for the first thing that is wrong.
|
|
81
102
|
*/
|
|
82
103
|
export declare function validateCommands(commands: Commands): void;
|
|
104
|
+
/**
|
|
105
|
+
* Checks the formatter before an agent is built, as `validateCommands` checks a command.
|
|
106
|
+
*
|
|
107
|
+
* @throws {InvalidAgentConfigError} for the first thing that is wrong.
|
|
108
|
+
*/
|
|
109
|
+
export declare function validateFormatter(formatter: Formatter): void;
|
|
83
110
|
/** The commands `role` may run: those that name it, or, without `roles`, the executor. */
|
|
84
111
|
export declare function commandsFor(commands: Commands, role: CommandRole): Commands;
|
|
85
112
|
/** The parameters of a command, in the order its arguments use them. */
|
|
@@ -2,6 +2,7 @@ import { InvalidAgentConfigError } from '#src/shared/domain/errors';
|
|
|
2
2
|
import { isOneOf } from '#src/shared/domain/isOneOf';
|
|
3
3
|
/** The roles of the model-backed orchestrator a command can be given to. */
|
|
4
4
|
export const commandRoles = ['planner', 'executor', 'reviewer'];
|
|
5
|
+
export const defaultFormatterTimeoutMs = 60 * 1000;
|
|
5
6
|
export const defaultCommandTimeoutMs = 10 * 60 * 1000;
|
|
6
7
|
export const defaultCommandOutputBytes = 16 * 1024;
|
|
7
8
|
// A test filter or a snapshot name, not a script.
|
|
@@ -38,22 +39,8 @@ export function validateCommands(commands) {
|
|
|
38
39
|
if (typeof description !== 'string' || description.trim() === '') {
|
|
39
40
|
fail('needs a description');
|
|
40
41
|
}
|
|
41
|
-
|
|
42
|
-
fail('needs an executable');
|
|
43
|
-
if (cwd !== undefined && (typeof cwd !== 'string' || !isArgument(cwd))) {
|
|
44
|
-
fail('has an invalid cwd');
|
|
45
|
-
}
|
|
46
|
-
checkLimit(timeoutMs, maxTimeoutMs, 'timeoutMs', fail);
|
|
42
|
+
checkProcess({ executable, cwd, timeoutMs, env }, fail);
|
|
47
43
|
checkLimit(outputBytes, maxOutputBytes, 'outputBytes', fail);
|
|
48
|
-
if (env !== undefined && !isRecord(env))
|
|
49
|
-
fail('needs env to map names to values');
|
|
50
|
-
for (const [variable, value] of Object.entries((env ?? {}))) {
|
|
51
|
-
if (!variablePattern.test(variable))
|
|
52
|
-
fail(`sets an invalid variable name "${variable}"`);
|
|
53
|
-
if (typeof value !== 'string' || value.includes('\0')) {
|
|
54
|
-
fail(`sets "${variable}" to a non-string`);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
44
|
if (roles !== undefined &&
|
|
58
45
|
(!Array.isArray(roles) ||
|
|
59
46
|
roles.length === 0 ||
|
|
@@ -77,6 +64,47 @@ export function validateCommands(commands) {
|
|
|
77
64
|
}
|
|
78
65
|
}
|
|
79
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Checks the formatter before an agent is built, as `validateCommands` checks a command.
|
|
69
|
+
*
|
|
70
|
+
* @throws {InvalidAgentConfigError} for the first thing that is wrong.
|
|
71
|
+
*/
|
|
72
|
+
export function validateFormatter(formatter) {
|
|
73
|
+
const fail = (problem) => {
|
|
74
|
+
throw new InvalidAgentConfigError(`The formatter ${problem}`);
|
|
75
|
+
};
|
|
76
|
+
// Unknown throughout: the configuration may come from a JSON file, whatever its type says.
|
|
77
|
+
if (!isRecord(formatter))
|
|
78
|
+
fail('must be an object');
|
|
79
|
+
const { executable, args, cwd, timeoutMs, env, ...rest } = formatter;
|
|
80
|
+
const unknown = Object.keys(rest);
|
|
81
|
+
if (unknown.length > 0)
|
|
82
|
+
fail(`has unknown settings: ${unknown.join(', ')}`);
|
|
83
|
+
checkProcess({ executable, cwd, timeoutMs, env }, fail);
|
|
84
|
+
if (args !== undefined &&
|
|
85
|
+
(!Array.isArray(args) ||
|
|
86
|
+
!args.every(argument => typeof argument === 'string' && isArgument(argument, { allowEmpty: true })))) {
|
|
87
|
+
fail('needs args to be a list of text without NUL characters');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/** What a command and the formatter both declare about the program they run. */
|
|
91
|
+
function checkProcess({ executable, cwd, timeoutMs, env, }, fail) {
|
|
92
|
+
if (typeof executable !== 'string' || !isArgument(executable))
|
|
93
|
+
fail('needs an executable');
|
|
94
|
+
if (cwd !== undefined && (typeof cwd !== 'string' || !isArgument(cwd))) {
|
|
95
|
+
fail('has an invalid cwd');
|
|
96
|
+
}
|
|
97
|
+
checkLimit(timeoutMs, maxTimeoutMs, 'timeoutMs', fail);
|
|
98
|
+
if (env !== undefined && !isRecord(env))
|
|
99
|
+
fail('needs env to map names to values');
|
|
100
|
+
for (const [variable, value] of Object.entries((env ?? {}))) {
|
|
101
|
+
if (!variablePattern.test(variable))
|
|
102
|
+
fail(`sets an invalid variable name "${variable}"`);
|
|
103
|
+
if (typeof value !== 'string' || value.includes('\0')) {
|
|
104
|
+
fail(`sets "${variable}" to a non-string`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
80
108
|
function isRecord(value) {
|
|
81
109
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
82
110
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../../../src/tools/domain/commands.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAsBrD,4EAA4E;AAC5E,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,SAAS,EAAE,UAAU,EAAE,UAAU,CAAU,CAAC;AA0CzE,MAAM,CAAC,MAAM,uBAAuB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AACtD,MAAM,CAAC,MAAM,yBAAyB,GAAG,EAAE,GAAG,IAAI,CAAC;AACnD,kDAAkD;AAClD,MAAM,aAAa,GAAG,IAAI,CAAC;AAC3B,MAAM,cAAc,GAAG,IAAI,GAAG,IAAI,CAAC;AACnC,MAAM,YAAY,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAEzC,MAAM,WAAW,GAAG,+BAA+B,CAAC;AACpD,MAAM,eAAe,GAAG,0BAA0B,CAAC;AACnD,iFAAiF;AACjF,MAAM,iBAAiB,GAAG,MAAM,CAAC;AAKjC;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAkB;IAClD,2FAA2F;IAC3F,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,MAAM,IAAI,uBAAuB,CAAC,4BAA4B,CAAC,CAAC;IACzF,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,QAAmC,CAAC,CAAC;IACpE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,uBAAuB,CAAC,0BAA0B,CAAC,CAAC;IACxF,KAAK,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,OAAO,EAAE,CAAC;QAC1C,MAAM,IAAI,GAAG,CAAC,OAAe,EAAE,EAAE;YAChC,MAAM,IAAI,uBAAuB,CAAC,YAAY,IAAI,KAAK,OAAO,EAAE,CAAC,CAAC;QACnE,CAAC,CAAC;QACF,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7B,IAAI,CAAC,sEAAsE,CAAC,CAAC;QAC9E,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC;YAAE,IAAI,CAAC,mBAAmB,CAAC,CAAC;QACrD,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,GAAG,EAAE,SAAS,EAAE,WAAW,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,GAC/E,UAAqC,CAAC;QACvC,IAAI,OAAO,WAAW,KAAK,QAAQ,IAAI,WAAW,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YAClE,IAAI,CAAC,qBAAqB,CAAC,CAAC;QAC7B,CAAC;QACD,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;YAAE,IAAI,CAAC,qBAAqB,CAAC,CAAC;QAC3F,IAAI,GAAG,KAAK,SAAS,IAAI,CAAC,OAAO,GAAG,KAAK,QAAQ,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YACxE,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAC5B,CAAC;QACD,UAAU,CAAC,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC;QACvD,UAAU,CAAC,WAAW,EAAE,cAAc,EAAE,aAAa,EAAE,IAAI,CAAC,CAAC;QAC7D,IAAI,GAAG,KAAK,SAAS,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,IAAI,CAAC,kCAAkC,CAAC,CAAC;QAClF,KAAK,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,IAAI,EAAE,CAA4B,CAAC,EAAE,CAAC;YACxF,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAAE,IAAI,CAAC,kCAAkC,QAAQ,GAAG,CAAC,CAAC;YACzF,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;gBACvD,IAAI,CAAC,SAAS,QAAQ,mBAAmB,CAAC,CAAC;YAC5C,CAAC;QACF,CAAC;QAED,IACC,KAAK,KAAK,SAAS;YACnB,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;gBACrB,KAAK,CAAC,MAAM,KAAK,CAAC;gBAClB,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,MAAM;gBACpC,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,IAAI,KAAK,QAAQ,IAAI,OAAO,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC,CAAC,EAC9E,CAAC;YACF,IAAI,CAAC,+BAA+B,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC3E,CAAC;QACD,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;YAAE,IAAI,CAAC,yBAAyB,CAAC,CAAC;QAChF,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/B,KAAK,MAAM,QAAQ,IAAI,CAAC,IAAI,IAAI,EAAE,CAAc,EAAE,CAAC;YAClD,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;gBAClC,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;oBACjD,IAAI,CAAC,sCAAsC,CAAC,CAAC;gBAC9C,CAAC;gBACD,SAAS;YACV,CAAC;YACD,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBAAE,IAAI,CAAC,sDAAsD,CAAC,CAAC;YACtF,cAAc,CAAC,QAA4B,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;QAC1D,CAAC;IACF,CAAC;AACF,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC/B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC7E,CAAC;AAED,SAAS,UAAU,CAClB,KAAc,EACd,GAAW,EACX,KAAa,EACb,IAAgC;IAEhC,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO;IAChC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,GAAG,GAAG,EAAE,CAAC;QACxF,IAAI,CAAC,SAAS,KAAK,mCAAmC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACtE,CAAC;AACF,CAAC;AAED,SAAS,cAAc,CACtB,SAA2B,EAC3B,IAAiB,EACjB,IAAgC;IAEhC,gFAAgF;IAChF,MAAM,IAAI,GAAY,SAAS,CAAC,IAAI,CAAC;IACrC,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,KAAK,iBAAiB,EAAE,CAAC;QACvF,IAAI,CACH,wCAAwC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,kBAAkB,EAAE,CACrG,CAAC;IACH,CAAC;IACD,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,IAAI,CAAC,uBAAuB,IAAI,SAAS,CAAC,CAAC;IAC/D,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACf,IAAI,OAAO,SAAS,CAAC,WAAW,KAAK,QAAQ,IAAI,SAAS,CAAC,WAAW,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACtF,IAAI,CAAC,0CAA0C,IAAI,GAAG,CAAC,CAAC;IACzD,CAAC;IACD,MAAM,EAAE,QAAQ,EAAE,GAAG,SAAmC,CAAC;IACzD,IAAI,QAAQ,KAAK,SAAS,IAAI,OAAO,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC7D,IAAI,CAAC,yDAAyD,IAAI,GAAG,CAAC,CAAC;IACxE,CAAC;IAED,QAAQ,SAAS,CAAC,IAAI,EAAE,CAAC;QACxB,KAAK,OAAO;YACX,OAAO;QACR,KAAK,QAAQ;YACZ,IACC,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC;gBAChC,SAAS,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;gBAC7B,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC,EAC/E,CAAC;gBACF,IAAI,CAAC,qBAAqB,IAAI,8BAA8B,CAAC,CAAC;YAC/D,CAAC;YACD,OAAO;QACR,KAAK,MAAM;YACV,IAAI,OAAO,SAAS,CAAC,OAAO,KAAK,QAAQ;gBAAE,IAAI,CAAC,iCAAiC,IAAI,GAAG,CAAC,CAAC;YAC1F,IAAI,CAAC;gBACJ,WAAW,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;YAChC,CAAC;YAAC,MAAM,CAAC;gBACR,IAAI,CAAC,+BAA+B,IAAI,0CAA0C,CAAC,CAAC;YACrF,CAAC;YACD,OAAO;QACR;YACC,IAAI,CAAC,oBAAoB,IAAI,sBAAsB,CAAC,CAAC;IACvD,CAAC;AACF,CAAC;AAED,SAAS,UAAU,CAAC,KAAa,EAAE,EAAE,UAAU,GAAG,KAAK,EAAE,GAAG,EAAE;IAC7D,OAAO,CAAC,UAAU,IAAI,KAAK,KAAK,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AAC9D,CAAC;AAED,SAAS,WAAW,CAAC,OAAe;IACnC,OAAO,IAAI,MAAM,CAAC,OAAO,OAAO,IAAI,EAAE,GAAG,CAAC,CAAC;AAC5C,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,WAAW,CAAC,QAAkB,EAAE,IAAiB;IAChE,OAAO,MAAM,CAAC,WAAW,CACxB,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,EAAE,EAAE,CAClD,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CACjD,CACD,CAAC;AACH,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,YAAY,CAAC,UAA6B;IACzD,OAAO,CAAC,UAAU,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC;AACjF,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAC7B,UAA6B,EAC7B,MAAsC;IAEtC,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,KAAK,MAAM,QAAQ,IAAI,UAAU,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;QAC9C,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAClC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACpB,SAAS;QACV,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;QAC5C,IAAI,CAAC,IAAI,CAAC,GAAG,cAAc,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;IAC/C,CAAC;IACD,OAAO,IAAI,CAAC;AACb,CAAC;AAED,SAAS,cAAc,CAAC,SAA2B,EAAE,KAAqB;IACzE,MAAM,EAAE,IAAI,EAAE,GAAG,SAAS,CAAC;IAC3B,IAAI,SAAS,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAChC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,IAAI,IAAI,yBAAyB,CAAC,CAAC;QAC9E,OAAO,KAAK,CAAC;IACd,CAAC;IACD,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACpB,IAAI,SAAS,CAAC,QAAQ,KAAK,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,IAAI,IAAI,eAAe,CAAC,CAAC;QAC1E,OAAO,EAAE,CAAC;IACX,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,IAAI,IAAI,mBAAmB,CAAC,CAAC;IAE5E,IAAI,SAAS,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACjC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YACvC,MAAM,IAAI,KAAK,CAAC,IAAI,IAAI,mBAAmB,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC3E,CAAC;QACD,OAAO,CAAC,KAAK,CAAC,CAAC;IAChB,CAAC;IAED,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,IAAI,IAAI,0BAA0B,CAAC,CAAC;IAC/E,IACC,KAAK,CAAC,MAAM,GAAG,aAAa;QAC5B,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;QACpB,CAAC,WAAW,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAC1C,CAAC;QACF,MAAM,IAAI,KAAK,CAAC,IAAI,IAAI,gBAAgB,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,CAAC,KAAK,CAAC,CAAC;AAChB,CAAC","sourcesContent":["import { InvalidAgentConfigError } from '#src/shared/domain/errors';\nimport { isOneOf } from '#src/shared/domain/isOneOf';\n\n/**\n * Something the model fills in a command's arguments. It never writes free arguments: each\n * value is checked against what the consumer declared here, and none can become a flag it did\n * not declare.\n *\n * - `paths`: files or folders inside the roots that the agent may read, each its own argument,\n * relative to the command's working directory. The model may give none.\n * - `choice`: one of `values`, as the consumer wrote them, so a value may be a flag.\n * - `text`: a value that matches `pattern` whole, and never starts with `-`.\n *\n * A `choice` or `text` that is not `required` may be left out, and then adds no argument.\n */\nexport type CommandParameter =\n\t| { type: 'paths'; name: string; description: string }\n\t| { type: 'choice'; name: string; description: string; values: string[]; required?: boolean }\n\t| { type: 'text'; name: string; description: string; pattern: string; required?: boolean };\n\n/** One argument of a command: fixed text, or the place where a parameter goes. */\nexport type CommandArgument = string | CommandParameter;\n\n/** The roles of the model-backed orchestrator a command can be given to. */\nexport const commandRoles = ['planner', 'executor', 'reviewer'] as const;\nexport type CommandRole = (typeof commandRoles)[number];\n\n/**\n * A command the consumer lets the agent run, such as the project's tests or its formatter. It\n * runs without a shell, so nothing in an argument is ever read as shell syntax.\n */\nexport interface CommandDefinition {\n\t/** What it does and when to run it, for the model. */\n\tdescription: string;\n\t/**\n\t * The program: a name looked up in the command's `PATH`, or a path. A relative path is from\n\t * `cwd`.\n\t */\n\texecutable: string;\n\targs?: CommandArgument[];\n\t/**\n\t * Where it runs, inside a `write` root: relative to the first root, or absolute. Defaults to\n\t * the first root.\n\t */\n\tcwd?: string;\n\t/**\n\t * Variables set for it, on top of the few it inherits: `PATH`, `HOME`, `TMPDIR`, `USER`,\n\t * `SHELL`, `LANG` and `LC_*`. Nothing else of this process's environment reaches it, so\n\t * neither do the provider keys.\n\t */\n\tenv?: Record<string, string>;\n\t/** How long it may run before its whole process group is stopped. Defaults to 10 minutes. */\n\ttimeoutMs?: number;\n\t/** How much of the end of its output the model reads. Defaults to 16 KB. */\n\toutputBytes?: number;\n\t/**\n\t * Which roles of `createLLMOrchestrator` may run it, such as the planner for a command that\n\t * reads an issue. Defaults to the executor only. Whoever runs it, the files are captured\n\t * around it, so what it changes is in the history. `createFileTools` offers every command.\n\t */\n\troles?: CommandRole[];\n}\n\n/** The commands an agent may run, by the name the model calls each one. */\nexport type Commands = Record<string, CommandDefinition>;\n\nexport const defaultCommandTimeoutMs = 10 * 60 * 1000;\nexport const defaultCommandOutputBytes = 16 * 1024;\n// A test filter or a snapshot name, not a script.\nconst maxTextLength = 1000;\nconst maxOutputBytes = 1024 * 1024;\nconst maxTimeoutMs = 24 * 60 * 60 * 1000;\n\nconst namePattern = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;\nconst variablePattern = /^[A-Za-z_][A-Za-z0-9_]*$/;\n// The tool's input names the command with this key, so no parameter may take it.\nconst reservedParameter = 'name';\n\n/** A parameter's value as the model sent it, with `paths` already checked and resolved. */\nexport type ParameterValue = string[] | string | null;\n\n/**\n * Checks every command before an agent is built, so a mistake in the configuration fails there\n * and never as a refused call in the middle of a run.\n *\n * @throws {InvalidAgentConfigError} for the first thing that is wrong.\n */\nexport function validateCommands(commands: Commands): void {\n\t// Unknown throughout: the configuration may come from a JSON file, whatever its type says.\n\tif (!isRecord(commands)) throw new InvalidAgentConfigError('Commands must be an object');\n\tconst entries = Object.entries(commands as Record<string, unknown>);\n\tif (entries.length === 0) throw new InvalidAgentConfigError('No command is configured');\n\tfor (const [name, definition] of entries) {\n\t\tconst fail = (problem: string) => {\n\t\t\tthrow new InvalidAgentConfigError(`Command \"${name}\" ${problem}`);\n\t\t};\n\t\tif (!namePattern.test(name)) {\n\t\t\tfail('needs a name of letters, digits, \"-\" and \"_\", starting with a letter');\n\t\t}\n\t\tif (!isRecord(definition)) fail('must be an object');\n\t\tconst { description, executable, cwd, timeoutMs, outputBytes, env, args, roles } =\n\t\t\tdefinition as Record<string, unknown>;\n\t\tif (typeof description !== 'string' || description.trim() === '') {\n\t\t\tfail('needs a description');\n\t\t}\n\t\tif (typeof executable !== 'string' || !isArgument(executable)) fail('needs an executable');\n\t\tif (cwd !== undefined && (typeof cwd !== 'string' || !isArgument(cwd))) {\n\t\t\tfail('has an invalid cwd');\n\t\t}\n\t\tcheckLimit(timeoutMs, maxTimeoutMs, 'timeoutMs', fail);\n\t\tcheckLimit(outputBytes, maxOutputBytes, 'outputBytes', fail);\n\t\tif (env !== undefined && !isRecord(env)) fail('needs env to map names to values');\n\t\tfor (const [variable, value] of Object.entries((env ?? {}) as Record<string, unknown>)) {\n\t\t\tif (!variablePattern.test(variable)) fail(`sets an invalid variable name \"${variable}\"`);\n\t\t\tif (typeof value !== 'string' || value.includes('\\0')) {\n\t\t\t\tfail(`sets \"${variable}\" to a non-string`);\n\t\t\t}\n\t\t}\n\n\t\tif (\n\t\t\troles !== undefined &&\n\t\t\t(!Array.isArray(roles) ||\n\t\t\t\troles.length === 0 ||\n\t\t\t\tnew Set(roles).size !== roles.length ||\n\t\t\t\t!roles.every(role => typeof role === 'string' && isOneOf(commandRoles, role)))\n\t\t) {\n\t\t\tfail(`needs roles to list some of ${commandRoles.join(', ')}, each once`);\n\t\t}\n\t\tif (args !== undefined && !Array.isArray(args)) fail('needs args to be a list');\n\t\tconst seen = new Set<string>();\n\t\tfor (const argument of (args ?? []) as unknown[]) {\n\t\t\tif (typeof argument === 'string') {\n\t\t\t\tif (!isArgument(argument, { allowEmpty: true })) {\n\t\t\t\t\tfail('has an argument with a NUL character');\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!isRecord(argument)) fail('has an argument that is neither text nor a parameter');\n\t\t\tcheckParameter(argument as CommandParameter, seen, fail);\n\t\t}\n\t}\n}\n\nfunction isRecord(value: unknown): boolean {\n\treturn typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction checkLimit(\n\tvalue: unknown,\n\tmax: number,\n\tfield: string,\n\tfail: (problem: string) => never,\n): void {\n\tif (value === undefined) return;\n\tif (typeof value !== 'number' || !Number.isInteger(value) || value <= 0 || value > max) {\n\t\tfail(`needs ${field} to be a whole number from 1 to ${String(max)}`);\n\t}\n}\n\nfunction checkParameter(\n\tparameter: CommandParameter,\n\tseen: Set<string>,\n\tfail: (problem: string) => never,\n): void {\n\t// Unknown: the configuration may come from a JSON file, whatever its type says.\n\tconst name: unknown = parameter.name;\n\tif (typeof name !== 'string' || !namePattern.test(name) || name === reservedParameter) {\n\t\tfail(\n\t\t\t`has a parameter with an invalid name ${typeof name === 'string' ? `\"${name}\"` : 'that is not text'}`,\n\t\t);\n\t}\n\tif (seen.has(name)) fail(`uses the parameter \"${name}\" twice`);\n\tseen.add(name);\n\tif (typeof parameter.description !== 'string' || parameter.description.trim() === '') {\n\t\tfail(`needs a description for the parameter \"${name}\"`);\n\t}\n\tconst { required } = parameter as { required?: unknown };\n\tif (required !== undefined && typeof required !== 'boolean') {\n\t\tfail(`needs required to be true or false for the parameter \"${name}\"`);\n\t}\n\n\tswitch (parameter.type) {\n\t\tcase 'paths':\n\t\t\treturn;\n\t\tcase 'choice':\n\t\t\tif (\n\t\t\t\t!Array.isArray(parameter.values) ||\n\t\t\t\tparameter.values.length === 0 ||\n\t\t\t\t!parameter.values.every(value => typeof value === 'string' && isArgument(value))\n\t\t\t) {\n\t\t\t\tfail(`needs the choice \"${name}\" to list at least one value`);\n\t\t\t}\n\t\t\treturn;\n\t\tcase 'text':\n\t\t\tif (typeof parameter.pattern !== 'string') fail(`needs a pattern for the text \"${name}\"`);\n\t\t\ttry {\n\t\t\t\ttextMatcher(parameter.pattern);\n\t\t\t} catch {\n\t\t\t\tfail(`has a pattern for the text \"${name}\" that is not a valid regular expression`);\n\t\t\t}\n\t\t\treturn;\n\t\tdefault:\n\t\t\tfail(`has a parameter \"${name}\" of an unknown type`);\n\t}\n}\n\nfunction isArgument(value: string, { allowEmpty = false } = {}): boolean {\n\treturn (allowEmpty || value !== '') && !value.includes('\\0');\n}\n\nfunction textMatcher(pattern: string): RegExp {\n\treturn new RegExp(`^(?:${pattern})$`, 'u');\n}\n\n/** The commands `role` may run: those that name it, or, without `roles`, the executor. */\nexport function commandsFor(commands: Commands, role: CommandRole): Commands {\n\treturn Object.fromEntries(\n\t\tObject.entries(commands).filter(([, definition]) =>\n\t\t\t(definition.roles ?? ['executor']).includes(role),\n\t\t),\n\t);\n}\n\n/** The parameters of a command, in the order its arguments use them. */\nexport function parametersOf(definition: CommandDefinition): CommandParameter[] {\n\treturn (definition.args ?? []).filter(argument => typeof argument !== 'string');\n}\n\n/**\n * The arguments a call runs with: the fixed ones as declared, and each parameter replaced by\n * the value the model gave, once checked.\n *\n * @throws {Error} worded for the model, when a value is not one the parameter takes.\n */\nexport function buildArguments(\n\tdefinition: CommandDefinition,\n\tvalues: Record<string, ParameterValue>,\n): string[] {\n\tconst args: string[] = [];\n\tfor (const argument of definition.args ?? []) {\n\t\tif (typeof argument === 'string') {\n\t\t\targs.push(argument);\n\t\t\tcontinue;\n\t\t}\n\t\tconst value = values[argument.name] ?? null;\n\t\targs.push(...valueArguments(argument, value));\n\t}\n\treturn args;\n}\n\nfunction valueArguments(parameter: CommandParameter, value: ParameterValue): string[] {\n\tconst { name } = parameter;\n\tif (parameter.type === 'paths') {\n\t\tif (!Array.isArray(value)) throw new Error(`\"${name}\" takes a list of paths`);\n\t\treturn value;\n\t}\n\tif (value === null) {\n\t\tif (parameter.required === true) throw new Error(`\"${name}\" is required`);\n\t\treturn [];\n\t}\n\tif (typeof value !== 'string') throw new Error(`\"${name}\" takes one value`);\n\n\tif (parameter.type === 'choice') {\n\t\tif (!parameter.values.includes(value)) {\n\t\t\tthrow new Error(`\"${name}\" takes one of: ${parameter.values.join(', ')}`);\n\t\t}\n\t\treturn [value];\n\t}\n\n\tif (value.startsWith('-')) throw new Error(`\"${name}\" may not start with \"-\"`);\n\tif (\n\t\tvalue.length > maxTextLength ||\n\t\tvalue.includes('\\0') ||\n\t\t!textMatcher(parameter.pattern).test(value)\n\t) {\n\t\tthrow new Error(`\"${name}\" must match ${parameter.pattern}`);\n\t}\n\treturn [value];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../../../src/tools/domain/commands.ts"],"names":[],"mappings":"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","sourcesContent":["import { InvalidAgentConfigError } from '#src/shared/domain/errors';\nimport { isOneOf } from '#src/shared/domain/isOneOf';\n\n/**\n * Something the model fills in a command's arguments. It never writes free arguments: each\n * value is checked against what the consumer declared here, and none can become a flag it did\n * not declare.\n *\n * - `paths`: files or folders inside the roots that the agent may read, each its own argument,\n * relative to the command's working directory. The model may give none.\n * - `choice`: one of `values`, as the consumer wrote them, so a value may be a flag.\n * - `text`: a value that matches `pattern` whole, and never starts with `-`.\n *\n * A `choice` or `text` that is not `required` may be left out, and then adds no argument.\n */\nexport type CommandParameter =\n\t| { type: 'paths'; name: string; description: string }\n\t| { type: 'choice'; name: string; description: string; values: string[]; required?: boolean }\n\t| { type: 'text'; name: string; description: string; pattern: string; required?: boolean };\n\n/** One argument of a command: fixed text, or the place where a parameter goes. */\nexport type CommandArgument = string | CommandParameter;\n\n/** The roles of the model-backed orchestrator a command can be given to. */\nexport const commandRoles = ['planner', 'executor', 'reviewer'] as const;\nexport type CommandRole = (typeof commandRoles)[number];\n\n/**\n * A command the consumer lets the agent run, such as the project's tests or its formatter. It\n * runs without a shell, so nothing in an argument is ever read as shell syntax.\n */\nexport interface CommandDefinition {\n\t/** What it does and when to run it, for the model. */\n\tdescription: string;\n\t/**\n\t * The program: a name looked up in the command's `PATH`, or a path. A relative path is from\n\t * `cwd`.\n\t */\n\texecutable: string;\n\targs?: CommandArgument[];\n\t/**\n\t * Where it runs, inside a `write` root: relative to the first root, or absolute. Defaults to\n\t * the first root.\n\t */\n\tcwd?: string;\n\t/**\n\t * Variables set for it, on top of the few it inherits: `PATH`, `HOME`, `TMPDIR`, `USER`,\n\t * `SHELL`, `LANG` and `LC_*`. Nothing else of this process's environment reaches it, so\n\t * neither do the provider keys.\n\t */\n\tenv?: Record<string, string>;\n\t/** How long it may run before its whole process group is stopped. Defaults to 10 minutes. */\n\ttimeoutMs?: number;\n\t/** How much of the end of its output the model reads. Defaults to 16 KB. */\n\toutputBytes?: number;\n\t/**\n\t * Which roles of `createLLMOrchestrator` may run it, such as the planner for a command that\n\t * reads an issue. Defaults to the executor only. Whoever runs it, the files are captured\n\t * around it, so what it changes is in the history. `createFileTools` offers every command.\n\t */\n\troles?: CommandRole[];\n}\n\n/** The commands an agent may run, by the name the model calls each one. */\nexport type Commands = Record<string, CommandDefinition>;\n\n/**\n * A program the edit tools run on each file they create or change, such as the project's\n * formatter, so the model reads and keeps editing the file as the formatter leaves it. It runs\n * as a command does: without a shell, in a small environment, with its changes in the history.\n */\nexport interface Formatter {\n\t/** The program: a name looked up in its `PATH`, or a path. A relative path is from `cwd`. */\n\texecutable: string;\n\t/**\n\t * Its arguments, before the file's path, which is always the last one and relative to `cwd`:\n\t * `[\"exec\", \"prettier\", \"--write\", \"--ignore-unknown\"]` with `pnpm`.\n\t */\n\targs?: string[];\n\t/** Where it runs, as `CommandDefinition.cwd`. Defaults to the first root. */\n\tcwd?: string;\n\t/** Variables set for it, as `CommandDefinition.env`. */\n\tenv?: Record<string, string>;\n\t/** How long it may run on one file before it is stopped. Defaults to 1 minute. */\n\ttimeoutMs?: number;\n}\n\nexport const defaultFormatterTimeoutMs = 60 * 1000;\n\nexport const defaultCommandTimeoutMs = 10 * 60 * 1000;\nexport const defaultCommandOutputBytes = 16 * 1024;\n// A test filter or a snapshot name, not a script.\nconst maxTextLength = 1000;\nconst maxOutputBytes = 1024 * 1024;\nconst maxTimeoutMs = 24 * 60 * 60 * 1000;\n\nconst namePattern = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;\nconst variablePattern = /^[A-Za-z_][A-Za-z0-9_]*$/;\n// The tool's input names the command with this key, so no parameter may take it.\nconst reservedParameter = 'name';\n\n/** A parameter's value as the model sent it, with `paths` already checked and resolved. */\nexport type ParameterValue = string[] | string | null;\n\n/**\n * Checks every command before an agent is built, so a mistake in the configuration fails there\n * and never as a refused call in the middle of a run.\n *\n * @throws {InvalidAgentConfigError} for the first thing that is wrong.\n */\nexport function validateCommands(commands: Commands): void {\n\t// Unknown throughout: the configuration may come from a JSON file, whatever its type says.\n\tif (!isRecord(commands)) throw new InvalidAgentConfigError('Commands must be an object');\n\tconst entries = Object.entries(commands as Record<string, unknown>);\n\tif (entries.length === 0) throw new InvalidAgentConfigError('No command is configured');\n\tfor (const [name, definition] of entries) {\n\t\tconst fail = (problem: string) => {\n\t\t\tthrow new InvalidAgentConfigError(`Command \"${name}\" ${problem}`);\n\t\t};\n\t\tif (!namePattern.test(name)) {\n\t\t\tfail('needs a name of letters, digits, \"-\" and \"_\", starting with a letter');\n\t\t}\n\t\tif (!isRecord(definition)) fail('must be an object');\n\t\tconst { description, executable, cwd, timeoutMs, outputBytes, env, args, roles } =\n\t\t\tdefinition as Record<string, unknown>;\n\t\tif (typeof description !== 'string' || description.trim() === '') {\n\t\t\tfail('needs a description');\n\t\t}\n\t\tcheckProcess({ executable, cwd, timeoutMs, env }, fail);\n\t\tcheckLimit(outputBytes, maxOutputBytes, 'outputBytes', fail);\n\n\t\tif (\n\t\t\troles !== undefined &&\n\t\t\t(!Array.isArray(roles) ||\n\t\t\t\troles.length === 0 ||\n\t\t\t\tnew Set(roles).size !== roles.length ||\n\t\t\t\t!roles.every(role => typeof role === 'string' && isOneOf(commandRoles, role)))\n\t\t) {\n\t\t\tfail(`needs roles to list some of ${commandRoles.join(', ')}, each once`);\n\t\t}\n\t\tif (args !== undefined && !Array.isArray(args)) fail('needs args to be a list');\n\t\tconst seen = new Set<string>();\n\t\tfor (const argument of (args ?? []) as unknown[]) {\n\t\t\tif (typeof argument === 'string') {\n\t\t\t\tif (!isArgument(argument, { allowEmpty: true })) {\n\t\t\t\t\tfail('has an argument with a NUL character');\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!isRecord(argument)) fail('has an argument that is neither text nor a parameter');\n\t\t\tcheckParameter(argument as CommandParameter, seen, fail);\n\t\t}\n\t}\n}\n\n/**\n * Checks the formatter before an agent is built, as `validateCommands` checks a command.\n *\n * @throws {InvalidAgentConfigError} for the first thing that is wrong.\n */\nexport function validateFormatter(formatter: Formatter): void {\n\tconst fail = (problem: string): never => {\n\t\tthrow new InvalidAgentConfigError(`The formatter ${problem}`);\n\t};\n\t// Unknown throughout: the configuration may come from a JSON file, whatever its type says.\n\tif (!isRecord(formatter)) fail('must be an object');\n\tconst { executable, args, cwd, timeoutMs, env, ...rest } = formatter as unknown as Record<\n\t\tstring,\n\t\tunknown\n\t>;\n\tconst unknown = Object.keys(rest);\n\tif (unknown.length > 0) fail(`has unknown settings: ${unknown.join(', ')}`);\n\tcheckProcess({ executable, cwd, timeoutMs, env }, fail);\n\tif (\n\t\targs !== undefined &&\n\t\t(!Array.isArray(args) ||\n\t\t\t!args.every(\n\t\t\t\targument => typeof argument === 'string' && isArgument(argument, { allowEmpty: true }),\n\t\t\t))\n\t) {\n\t\tfail('needs args to be a list of text without NUL characters');\n\t}\n}\n\n/** What a command and the formatter both declare about the program they run. */\nfunction checkProcess(\n\t{\n\t\texecutable,\n\t\tcwd,\n\t\ttimeoutMs,\n\t\tenv,\n\t}: { executable: unknown; cwd: unknown; timeoutMs: unknown; env: unknown },\n\tfail: (problem: string) => never,\n): void {\n\tif (typeof executable !== 'string' || !isArgument(executable)) fail('needs an executable');\n\tif (cwd !== undefined && (typeof cwd !== 'string' || !isArgument(cwd))) {\n\t\tfail('has an invalid cwd');\n\t}\n\tcheckLimit(timeoutMs, maxTimeoutMs, 'timeoutMs', fail);\n\tif (env !== undefined && !isRecord(env)) fail('needs env to map names to values');\n\tfor (const [variable, value] of Object.entries((env ?? {}) as Record<string, unknown>)) {\n\t\tif (!variablePattern.test(variable)) fail(`sets an invalid variable name \"${variable}\"`);\n\t\tif (typeof value !== 'string' || value.includes('\\0')) {\n\t\t\tfail(`sets \"${variable}\" to a non-string`);\n\t\t}\n\t}\n}\n\nfunction isRecord(value: unknown): boolean {\n\treturn typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction checkLimit(\n\tvalue: unknown,\n\tmax: number,\n\tfield: string,\n\tfail: (problem: string) => never,\n): void {\n\tif (value === undefined) return;\n\tif (typeof value !== 'number' || !Number.isInteger(value) || value <= 0 || value > max) {\n\t\tfail(`needs ${field} to be a whole number from 1 to ${String(max)}`);\n\t}\n}\n\nfunction checkParameter(\n\tparameter: CommandParameter,\n\tseen: Set<string>,\n\tfail: (problem: string) => never,\n): void {\n\t// Unknown: the configuration may come from a JSON file, whatever its type says.\n\tconst name: unknown = parameter.name;\n\tif (typeof name !== 'string' || !namePattern.test(name) || name === reservedParameter) {\n\t\tfail(\n\t\t\t`has a parameter with an invalid name ${typeof name === 'string' ? `\"${name}\"` : 'that is not text'}`,\n\t\t);\n\t}\n\tif (seen.has(name)) fail(`uses the parameter \"${name}\" twice`);\n\tseen.add(name);\n\tif (typeof parameter.description !== 'string' || parameter.description.trim() === '') {\n\t\tfail(`needs a description for the parameter \"${name}\"`);\n\t}\n\tconst { required } = parameter as { required?: unknown };\n\tif (required !== undefined && typeof required !== 'boolean') {\n\t\tfail(`needs required to be true or false for the parameter \"${name}\"`);\n\t}\n\n\tswitch (parameter.type) {\n\t\tcase 'paths':\n\t\t\treturn;\n\t\tcase 'choice':\n\t\t\tif (\n\t\t\t\t!Array.isArray(parameter.values) ||\n\t\t\t\tparameter.values.length === 0 ||\n\t\t\t\t!parameter.values.every(value => typeof value === 'string' && isArgument(value))\n\t\t\t) {\n\t\t\t\tfail(`needs the choice \"${name}\" to list at least one value`);\n\t\t\t}\n\t\t\treturn;\n\t\tcase 'text':\n\t\t\tif (typeof parameter.pattern !== 'string') fail(`needs a pattern for the text \"${name}\"`);\n\t\t\ttry {\n\t\t\t\ttextMatcher(parameter.pattern);\n\t\t\t} catch {\n\t\t\t\tfail(`has a pattern for the text \"${name}\" that is not a valid regular expression`);\n\t\t\t}\n\t\t\treturn;\n\t\tdefault:\n\t\t\tfail(`has a parameter \"${name}\" of an unknown type`);\n\t}\n}\n\nfunction isArgument(value: string, { allowEmpty = false } = {}): boolean {\n\treturn (allowEmpty || value !== '') && !value.includes('\\0');\n}\n\nfunction textMatcher(pattern: string): RegExp {\n\treturn new RegExp(`^(?:${pattern})$`, 'u');\n}\n\n/** The commands `role` may run: those that name it, or, without `roles`, the executor. */\nexport function commandsFor(commands: Commands, role: CommandRole): Commands {\n\treturn Object.fromEntries(\n\t\tObject.entries(commands).filter(([, definition]) =>\n\t\t\t(definition.roles ?? ['executor']).includes(role),\n\t\t),\n\t);\n}\n\n/** The parameters of a command, in the order its arguments use them. */\nexport function parametersOf(definition: CommandDefinition): CommandParameter[] {\n\treturn (definition.args ?? []).filter(argument => typeof argument !== 'string');\n}\n\n/**\n * The arguments a call runs with: the fixed ones as declared, and each parameter replaced by\n * the value the model gave, once checked.\n *\n * @throws {Error} worded for the model, when a value is not one the parameter takes.\n */\nexport function buildArguments(\n\tdefinition: CommandDefinition,\n\tvalues: Record<string, ParameterValue>,\n): string[] {\n\tconst args: string[] = [];\n\tfor (const argument of definition.args ?? []) {\n\t\tif (typeof argument === 'string') {\n\t\t\targs.push(argument);\n\t\t\tcontinue;\n\t\t}\n\t\tconst value = values[argument.name] ?? null;\n\t\targs.push(...valueArguments(argument, value));\n\t}\n\treturn args;\n}\n\nfunction valueArguments(parameter: CommandParameter, value: ParameterValue): string[] {\n\tconst { name } = parameter;\n\tif (parameter.type === 'paths') {\n\t\tif (!Array.isArray(value)) throw new Error(`\"${name}\" takes a list of paths`);\n\t\treturn value;\n\t}\n\tif (value === null) {\n\t\tif (parameter.required === true) throw new Error(`\"${name}\" is required`);\n\t\treturn [];\n\t}\n\tif (typeof value !== 'string') throw new Error(`\"${name}\" takes one value`);\n\n\tif (parameter.type === 'choice') {\n\t\tif (!parameter.values.includes(value)) {\n\t\t\tthrow new Error(`\"${name}\" takes one of: ${parameter.values.join(', ')}`);\n\t\t}\n\t\treturn [value];\n\t}\n\n\tif (value.startsWith('-')) throw new Error(`\"${name}\" may not start with \"-\"`);\n\tif (\n\t\tvalue.length > maxTextLength ||\n\t\tvalue.includes('\\0') ||\n\t\t!textMatcher(parameter.pattern).test(value)\n\t) {\n\t\tthrow new Error(`\"${name}\" must match ${parameter.pattern}`);\n\t}\n\treturn [value];\n}\n"]}
|