@mikode13/harness 2.3.0 → 3.0.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 CHANGED
@@ -111,7 +111,7 @@ factories let you replace through their `logger` option:
111
111
  ```ts
112
112
  import { createAgent, UnrecoverableError, type ProgressEvent } from '@mikode13/harness';
113
113
 
114
- const agent = createAgent('claude', { model: 'sonnet' });
114
+ const agent = createAgent('anthropic', { model: 'sonnet' });
115
115
  const controller = new AbortController();
116
116
 
117
117
  const render = (event: ProgressEvent) => {
@@ -133,10 +133,13 @@ try {
133
133
  consumer's decision, not the harness's. `cli/progressEventFormatter.ts` is one
134
134
  terminal-shaped implementation to copy from. New event types can arrive in minor
135
135
  releases, so render the ones you know and ignore the rest. Swapping the agent for
136
- `createAgent('codex')`, or for `createOrchestrator()` and its planner → executor →
136
+ `createAgent('openai')`, or for `createOrchestrator()` and its planner → executor →
137
137
  reviewer workflow, changes nothing else in the snippet above.
138
- `createOrchestrator({ provider: 'claude' })` runs every role on one provider, for
138
+ `createOrchestrator({ provider: 'anthropic' })` runs every role on one provider, for
139
139
  example when the other one is out of quota.
140
+ `systemPrompts: { planner, executor, reviewer }` replaces a role's instructions;
141
+ a role left out keeps the harness's own, and the reviewer must still answer with
142
+ the JSON decision described on the option.
140
143
 
141
144
  Every agent a factory returns already retries recoverable failures. A model or
142
145
  reasoning effort the chosen provider does not support throws
@@ -1,16 +1,9 @@
1
1
  import type { Agent } from '#src/agent/domain/agent';
2
- import { type ClaudeModel, type ClaudeReasoningEffort } from '#src/engines/claude/infrastructure/model/claudeAgent';
3
- import { type CodexModel, type CodexReasoningEffort } from '#src/engines/codex/infrastructure/model/codexAgent';
4
2
  import type { ILogger } from '#src/shared/domain/logger';
5
- export declare const agentProviders: readonly ["claude", "codex"];
6
- export type AgentProvider = (typeof agentProviders)[number];
7
- /** Any provider's model. The engine the provider selects rejects one it does not support. */
8
- export type AgentModel = ClaudeModel | CodexModel;
9
- /** Any provider's reasoning effort. The engine the provider selects rejects one it does not support. */
10
- export type ReasoningEffort = ClaudeReasoningEffort | CodexReasoningEffort;
3
+ import { type AgentModel, type AgentProvider, type CreateOrchestratorOptions, type ReasoningEffort } from './types.ts';
11
4
  export declare function isAgentProvider(value: string): value is AgentProvider;
12
5
  export interface CreateAgentOptions {
13
- /** Defaults to `'opus'` on Claude and `'gpt-5.6-sol'` on Codex. */
6
+ /** Defaults to `'opus'` on Anthropic and `'gpt-5.6-sol'` on OpenAI. */
14
7
  model?: AgentModel;
15
8
  /** Defaults to `'high'`. */
16
9
  reasoningEffort?: ReasoningEffort;
@@ -20,7 +13,8 @@ export interface CreateAgentOptions {
20
13
  logger?: ILogger;
21
14
  }
22
15
  /**
23
- * Builds the agent for a provider, already wrapped in the harness's retry policy.
16
+ * Builds the agent for a provider on its Agent SDK — Claude Code for Anthropic, Codex for
17
+ * OpenAI — already wrapped in the harness's retry policy.
24
18
  *
25
19
  * @throws {InvalidAgentConfigError} for an unknown provider, or a model or reasoning effort
26
20
  * the provider does not support.
@@ -28,47 +22,22 @@ export interface CreateAgentOptions {
28
22
  * is missing.
29
23
  */
30
24
  export declare function createAgent(provider: AgentProvider, { model, reasoningEffort, autoApprove, logger }?: CreateAgentOptions): Agent;
31
- export interface CreateOrchestratorOptions {
32
- /**
33
- * Runs every role on one provider, for example when the other one is out of quota. By
34
- * default Codex plans and executes, and Claude reviews.
35
- */
36
- provider?: AgentProvider;
37
- /** Maps to each provider's permission-bypass mode. Defaults to `false`. */
38
- autoApprove?: boolean;
39
- /** Defaults to warnings on stderr. */
40
- logger?: ILogger;
25
+ export interface Role {
26
+ provider: AgentProvider;
27
+ model: AgentModel;
28
+ reasoningEffort: ReasoningEffort;
41
29
  }
42
30
  /**
43
- * Builds the planner → executor → reviewer workflow, each role an agent from `createAgent`.
31
+ * The roles for `provider`, or the default mix without one.
44
32
  *
45
33
  * @throws {InvalidAgentConfigError} for an unknown provider.
46
- * @throws {UnrecoverableError} when a Codex role cannot be set up, for example because its CLI
47
- * binary is missing.
48
34
  */
49
- export declare function createOrchestrator({ provider, autoApprove, logger, }?: CreateOrchestratorOptions): Agent;
50
- export declare const llmProviders: readonly ["claude", "openai"];
51
- export type LLMProvider = (typeof llmProviders)[number];
52
- export interface CreateLLMAgentOptions {
53
- /**
54
- * Defaults to `'claude-sonnet-5'` on Claude and `'gpt-5.6-luna'` on OpenAI. The provider's
55
- * client rejects a model it does not support.
56
- */
57
- model?: string;
58
- /** Sent as the model's instructions on every call. */
59
- systemPrompt: string;
60
- /** Defaults to warnings on stderr. */
61
- logger?: ILogger;
62
- }
35
+ export declare function orchestratorRolesFor(provider: AgentProvider | undefined): Record<'planner' | 'executor' | 'reviewer', Role>;
63
36
  /**
64
- * Builds an agent whose conversation MiKode owns, on a provider's model API, already wrapped in
65
- * the harness's retry policy. Internal while #23 reaches parity with the Agent SDK engines: it
66
- * is not exported from `src/index.ts`. It runs no tools, so it has no `autoApprove`.
37
+ * Builds the planner → executor → reviewer workflow, each role an agent from `createAgent`.
67
38
  *
68
- * @throws {InvalidAgentConfigError} for an unknown provider, or a model its client does not
69
- * support.
70
- * @throws {UnrecoverableError} when the client cannot be set up, for example because
71
- * `OPENAI_API_KEY` is missing. The Anthropic SDK resolves credentials lazily, so a missing
72
- * `ANTHROPIC_API_KEY` fails the first run instead, with an `UnrecoverableError`.
39
+ * @throws {InvalidAgentConfigError} for an unknown provider.
40
+ * @throws {UnrecoverableError} when an OpenAI role cannot be set up, for example because the
41
+ * Codex CLI binary is missing.
73
42
  */
74
- export declare function createLLMAgent(provider: LLMProvider, { model, systemPrompt, logger }: CreateLLMAgentOptions): Agent;
43
+ export declare function createOrchestrator({ provider, autoApprove, systemPrompts, logger, }?: CreateOrchestratorOptions): Agent;
@@ -1,24 +1,23 @@
1
1
  import { InvalidAgentConfigError } from '#src/agent/domain/errors';
2
- import { ClaudeAgent, } from '#src/engines/claude/infrastructure/model/claudeAgent';
3
- import { CodexAgent, } from '#src/engines/codex/infrastructure/model/codexAgent';
4
- import { LLMAgent } from '#src/engines/domain/model/llmAgent';
5
- import { ClaudeLLMClient } from '#src/llm/infrastructure/claudeLLMClient';
6
- import { OpenAILLMClient } from '#src/llm/infrastructure/openAILLMClient';
7
- import { OrchestratorAgent } from '#src/orchestration/domain/model/orchestratorAgent';
2
+ import { ClaudeAgent } from '#src/engines/claude/infrastructure/model/claudeAgent';
3
+ import { CodexAgent } from '#src/engines/codex/infrastructure/model/codexAgent';
4
+ import { InstructedAgent } from '#src/orchestration/domain/model/instructedAgent';
5
+ import { executorInstructions, OrchestratorAgent, plannerInstructions, reviewerInstructions, } from '#src/orchestration/domain/model/orchestratorAgent';
8
6
  import { ReviewerDecisionValidator } from '#src/orchestration/infrastructure/model/reviewerDecisionValidator';
9
7
  import { RetryingAgent } from '#src/retry/domain/model/retryingAgent';
10
8
  import { isOneOf } from '#src/shared/domain/isOneOf';
11
9
  import { Logger } from '#src/shared/infrastructure/logger';
12
- export const agentProviders = ['claude', 'codex'];
10
+ import { agentProviders, } from "./types.js";
13
11
  export function isAgentProvider(value) {
14
12
  return isOneOf(agentProviders, value);
15
13
  }
16
14
  const defaultModels = {
17
- claude: 'opus',
18
- codex: 'gpt-5.6-sol',
15
+ anthropic: 'opus',
16
+ openai: 'gpt-5.6-sol',
19
17
  };
20
18
  /**
21
- * Builds the agent for a provider, already wrapped in the harness's retry policy.
19
+ * Builds the agent for a provider on its Agent SDK — Claude Code for Anthropic, Codex for
20
+ * OpenAI — already wrapped in the harness's retry policy.
22
21
  *
23
22
  * @throws {InvalidAgentConfigError} for an unknown provider, or a model or reasoning effort
24
23
  * the provider does not support.
@@ -28,17 +27,17 @@ const defaultModels = {
28
27
  export function createAgent(provider, { model, reasoningEffort, autoApprove, logger = new Logger() } = {}) {
29
28
  let engine;
30
29
  switch (provider) {
31
- case 'claude':
30
+ case 'anthropic':
32
31
  engine = new ClaudeAgent({
33
- model: model ?? defaultModels.claude,
32
+ model: model ?? defaultModels.anthropic,
34
33
  reasoningEffort,
35
34
  autoApprove,
36
35
  logger,
37
36
  });
38
37
  break;
39
- case 'codex':
38
+ case 'openai':
40
39
  engine = new CodexAgent({
41
- model: model ?? defaultModels.codex,
40
+ model: model ?? defaultModels.openai,
42
41
  reasoningEffort,
43
42
  autoApprove,
44
43
  logger,
@@ -52,85 +51,55 @@ export function createAgent(provider, { model, reasoningEffort, autoApprove, log
52
51
  }
53
52
  return new RetryingAgent({ inner: engine, logger });
54
53
  }
55
- // A stronger model plans and reviews; a cheaper one executes the plan.
54
+ // A stronger model plans and reviews; a cheaper one executes the plan. Every model and effort
55
+ // here is one both the Agent SDKs and the model APIs accept, so either path can run a role.
56
56
  const orchestratorRoles = {
57
57
  default: {
58
- planner: { provider: 'codex', model: 'gpt-5.6-sol', reasoningEffort: 'high' },
59
- executor: { provider: 'codex', model: 'gpt-5.6-luna', reasoningEffort: 'xhigh' },
60
- reviewer: { provider: 'claude', model: 'opus', reasoningEffort: 'high' },
58
+ planner: { provider: 'openai', model: 'gpt-5.6-sol', reasoningEffort: 'high' },
59
+ executor: { provider: 'openai', model: 'gpt-5.6-luna', reasoningEffort: 'xhigh' },
60
+ reviewer: { provider: 'anthropic', model: 'opus', reasoningEffort: 'high' },
61
61
  },
62
- claude: {
63
- planner: { provider: 'claude', model: 'opus', reasoningEffort: 'high' },
64
- executor: { provider: 'claude', model: 'sonnet', reasoningEffort: 'xhigh' },
65
- reviewer: { provider: 'claude', model: 'opus', reasoningEffort: 'high' },
62
+ anthropic: {
63
+ planner: { provider: 'anthropic', model: 'opus', reasoningEffort: 'high' },
64
+ executor: { provider: 'anthropic', model: 'sonnet', reasoningEffort: 'xhigh' },
65
+ reviewer: { provider: 'anthropic', model: 'opus', reasoningEffort: 'high' },
66
66
  },
67
- codex: {
68
- planner: { provider: 'codex', model: 'gpt-5.6-sol', reasoningEffort: 'high' },
69
- executor: { provider: 'codex', model: 'gpt-5.6-luna', reasoningEffort: 'xhigh' },
70
- reviewer: { provider: 'codex', model: 'gpt-5.6-sol', reasoningEffort: 'high' },
67
+ openai: {
68
+ planner: { provider: 'openai', model: 'gpt-5.6-sol', reasoningEffort: 'high' },
69
+ executor: { provider: 'openai', model: 'gpt-5.6-luna', reasoningEffort: 'xhigh' },
70
+ reviewer: { provider: 'openai', model: 'gpt-5.6-sol', reasoningEffort: 'high' },
71
71
  },
72
72
  };
73
73
  /**
74
- * Builds the planner → executor → reviewer workflow, each role an agent from `createAgent`.
74
+ * The roles for `provider`, or the default mix without one.
75
75
  *
76
76
  * @throws {InvalidAgentConfigError} for an unknown provider.
77
- * @throws {UnrecoverableError} when a Codex role cannot be set up, for example because its CLI
78
- * binary is missing.
79
77
  */
80
- export function createOrchestrator({ provider, autoApprove, logger = new Logger(), } = {}) {
78
+ export function orchestratorRolesFor(provider) {
81
79
  if (provider !== undefined && !isAgentProvider(provider)) {
82
80
  throw new InvalidAgentConfigError(`"${String(provider)}" is not an agent provider; expected one of: ${agentProviders.join(', ')}`);
83
81
  }
84
- const roles = orchestratorRoles[provider ?? 'default'];
85
- const agentFor = ({ provider, model, reasoningEffort }) => createAgent(provider, { model, reasoningEffort, autoApprove, logger });
86
- return new OrchestratorAgent({
87
- plannerAgent: agentFor(roles.planner),
88
- executorAgent: agentFor(roles.executor),
89
- reviewerAgent: agentFor(roles.reviewer),
90
- reviewerDecisionValidator: new ReviewerDecisionValidator(),
91
- logger,
92
- });
82
+ return orchestratorRoles[provider ?? 'default'];
93
83
  }
94
- export const llmProviders = ['claude', 'openai'];
95
- const defaultLLMAgentModels = {
96
- claude: 'claude-sonnet-5',
97
- openai: 'gpt-5.6-luna',
98
- };
99
84
  /**
100
- * Builds an agent whose conversation MiKode owns, on a provider's model API, already wrapped in
101
- * the harness's retry policy. Internal while #23 reaches parity with the Agent SDK engines: it
102
- * is not exported from `src/index.ts`. It runs no tools, so it has no `autoApprove`.
85
+ * Builds the planner → executor → reviewer workflow, each role an agent from `createAgent`.
103
86
  *
104
- * @throws {InvalidAgentConfigError} for an unknown provider, or a model its client does not
105
- * support.
106
- * @throws {UnrecoverableError} when the client cannot be set up, for example because
107
- * `OPENAI_API_KEY` is missing. The Anthropic SDK resolves credentials lazily, so a missing
108
- * `ANTHROPIC_API_KEY` fails the first run instead, with an `UnrecoverableError`.
87
+ * @throws {InvalidAgentConfigError} for an unknown provider.
88
+ * @throws {UnrecoverableError} when an OpenAI role cannot be set up, for example because the
89
+ * Codex CLI binary is missing.
109
90
  */
110
- export function createLLMAgent(provider, { model, systemPrompt, logger = new Logger() }) {
111
- let llmClient;
112
- switch (provider) {
113
- case 'claude':
114
- llmClient = new ClaudeLLMClient({
115
- model: model ?? defaultLLMAgentModels.claude,
116
- systemPrompt,
117
- logger,
118
- });
119
- break;
120
- case 'openai':
121
- llmClient = new OpenAILLMClient({
122
- model: model ?? defaultLLMAgentModels.openai,
123
- systemPrompt,
124
- logger,
125
- });
126
- break;
127
- default: {
128
- // Reachable from untyped input.
129
- const unknownProvider = provider;
130
- throw new InvalidAgentConfigError(`"${String(unknownProvider)}" is not an LLM provider; expected one of: ${llmProviders.join(', ')}`);
131
- }
132
- }
133
- // LLMAgent records nothing from a failed call, so the original prompt is the whole story.
134
- return new RetryingAgent({ inner: new LLMAgent({ llmClient }), logger, noteFailures: false });
91
+ export function createOrchestrator({ provider, autoApprove, systemPrompts = {}, logger = new Logger(), } = {}) {
92
+ const roles = orchestratorRolesFor(provider);
93
+ const agentFor = ({ provider, model, reasoningEffort }, instructions) => new InstructedAgent({
94
+ inner: createAgent(provider, { model, reasoningEffort, autoApprove, logger }),
95
+ instructions,
96
+ });
97
+ return new OrchestratorAgent({
98
+ plannerAgent: agentFor(roles.planner, systemPrompts.planner ?? plannerInstructions),
99
+ executorAgent: agentFor(roles.executor, systemPrompts.executor ?? executorInstructions),
100
+ reviewerAgent: agentFor(roles.reviewer, systemPrompts.reviewer ?? reviewerInstructions),
101
+ reviewerDecisionValidator: new ReviewerDecisionValidator(),
102
+ logger,
103
+ });
135
104
  }
136
105
  //# sourceMappingURL=agentFactory.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"agentFactory.js","sourceRoot":"","sources":["../../../src/factory/infrastructure/agentFactory.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Agent } from '#src/agent/domain/agent';\nimport { InvalidAgentConfigError } from '#src/agent/domain/errors';\nimport {\n\tClaudeAgent,\n\ttype ClaudeModel,\n\ttype ClaudeReasoningEffort,\n} from '#src/engines/claude/infrastructure/model/claudeAgent';\nimport {\n\tCodexAgent,\n\ttype CodexModel,\n\ttype CodexReasoningEffort,\n} from '#src/engines/codex/infrastructure/model/codexAgent';\nimport { LLMAgent } from '#src/engines/domain/model/llmAgent';\nimport type { LLMClient } from '#src/llm/domain/llm';\nimport { ClaudeLLMClient, type ClaudeLLMModel } from '#src/llm/infrastructure/claudeLLMClient';\nimport { OpenAILLMClient, type OpenAIModel } from '#src/llm/infrastructure/openAILLMClient';\nimport { OrchestratorAgent } from '#src/orchestration/domain/model/orchestratorAgent';\nimport { ReviewerDecisionValidator } from '#src/orchestration/infrastructure/model/reviewerDecisionValidator';\nimport { RetryingAgent } from '#src/retry/domain/model/retryingAgent';\nimport { isOneOf } from '#src/shared/domain/isOneOf';\nimport type { ILogger } from '#src/shared/domain/logger';\nimport { Logger } from '#src/shared/infrastructure/logger';\n\nexport const agentProviders = ['claude', 'codex'] as const;\nexport type AgentProvider = (typeof agentProviders)[number];\n\n/** Any provider's model. The engine the provider selects rejects one it does not support. */\nexport type AgentModel = ClaudeModel | CodexModel;\n/** Any provider's reasoning effort. The engine the provider selects rejects one it does not support. */\nexport type ReasoningEffort = ClaudeReasoningEffort | CodexReasoningEffort;\n\nexport function isAgentProvider(value: string): value is AgentProvider {\n\treturn isOneOf(agentProviders, value);\n}\n\nexport interface CreateAgentOptions {\n\t/** Defaults to `'opus'` on Claude and `'gpt-5.6-sol'` on Codex. */\n\tmodel?: AgentModel;\n\t/** Defaults to `'high'`. */\n\treasoningEffort?: ReasoningEffort;\n\t/** Maps to the provider's permission-bypass mode. Defaults to `false`. */\n\tautoApprove?: boolean;\n\t/** Defaults to warnings on stderr. */\n\tlogger?: ILogger;\n}\n\nconst defaultModels = {\n\tclaude: 'opus',\n\tcodex: 'gpt-5.6-sol',\n} as const satisfies Record<AgentProvider, AgentModel>;\n\n/**\n * Builds the agent for a provider, already wrapped in the harness's retry policy.\n *\n * @throws {InvalidAgentConfigError} for an unknown provider, or a model or reasoning effort\n * the provider does not support.\n * @throws {UnrecoverableError} when Codex cannot be set up, for example because its CLI binary\n * is missing.\n */\nexport function createAgent(\n\tprovider: AgentProvider,\n\t{ model, reasoningEffort, autoApprove, logger = new Logger() }: CreateAgentOptions = {},\n): Agent {\n\tlet engine: Agent;\n\n\tswitch (provider) {\n\t\tcase 'claude':\n\t\t\tengine = new ClaudeAgent({\n\t\t\t\tmodel: model ?? defaultModels.claude,\n\t\t\t\treasoningEffort,\n\t\t\t\tautoApprove,\n\t\t\t\tlogger,\n\t\t\t});\n\t\t\tbreak;\n\t\tcase 'codex':\n\t\t\tengine = new CodexAgent({\n\t\t\t\tmodel: model ?? defaultModels.codex,\n\t\t\t\treasoningEffort,\n\t\t\t\tautoApprove,\n\t\t\t\tlogger,\n\t\t\t});\n\t\t\tbreak;\n\t\tdefault: {\n\t\t\t// Reachable from untyped input that skipped `isAgentProvider`.\n\t\t\tconst unknownProvider: never = provider;\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t`\"${String(unknownProvider)}\" is not an agent provider; expected one of: ${agentProviders.join(', ')}`,\n\t\t\t);\n\t\t}\n\t}\n\n\treturn new RetryingAgent({ inner: engine, logger });\n}\n\nexport interface CreateOrchestratorOptions {\n\t/**\n\t * Runs every role on one provider, for example when the other one is out of quota. By\n\t * default Codex plans and executes, and Claude reviews.\n\t */\n\tprovider?: AgentProvider;\n\t/** Maps to each provider's permission-bypass mode. Defaults to `false`. */\n\tautoApprove?: boolean;\n\t/** Defaults to warnings on stderr. */\n\tlogger?: ILogger;\n}\n\ninterface Role {\n\tprovider: AgentProvider;\n\tmodel: AgentModel;\n\treasoningEffort: ReasoningEffort;\n}\n\n// A stronger model plans and reviews; a cheaper one executes the plan.\nconst orchestratorRoles = {\n\tdefault: {\n\t\tplanner: { provider: 'codex', model: 'gpt-5.6-sol', reasoningEffort: 'high' },\n\t\texecutor: { provider: 'codex', model: 'gpt-5.6-luna', reasoningEffort: 'xhigh' },\n\t\treviewer: { provider: 'claude', model: 'opus', reasoningEffort: 'high' },\n\t},\n\tclaude: {\n\t\tplanner: { provider: 'claude', model: 'opus', reasoningEffort: 'high' },\n\t\texecutor: { provider: 'claude', model: 'sonnet', reasoningEffort: 'xhigh' },\n\t\treviewer: { provider: 'claude', model: 'opus', reasoningEffort: 'high' },\n\t},\n\tcodex: {\n\t\tplanner: { provider: 'codex', model: 'gpt-5.6-sol', reasoningEffort: 'high' },\n\t\texecutor: { provider: 'codex', model: 'gpt-5.6-luna', reasoningEffort: 'xhigh' },\n\t\treviewer: { provider: 'codex', model: 'gpt-5.6-sol', reasoningEffort: 'high' },\n\t},\n} as const satisfies Record<\n\tAgentProvider | 'default',\n\tRecord<'planner' | 'executor' | 'reviewer', Role>\n>;\n\n/**\n * Builds the planner → executor → reviewer workflow, each role an agent from `createAgent`.\n *\n * @throws {InvalidAgentConfigError} for an unknown provider.\n * @throws {UnrecoverableError} when a Codex role cannot be set up, for example because its CLI\n * binary is missing.\n */\nexport function createOrchestrator({\n\tprovider,\n\tautoApprove,\n\tlogger = new Logger(),\n}: CreateOrchestratorOptions = {}): Agent {\n\tif (provider !== undefined && !isAgentProvider(provider)) {\n\t\tthrow new InvalidAgentConfigError(\n\t\t\t`\"${String(provider)}\" is not an agent provider; expected one of: ${agentProviders.join(', ')}`,\n\t\t);\n\t}\n\n\tconst roles = orchestratorRoles[provider ?? 'default'];\n\tconst agentFor = ({ provider, model, reasoningEffort }: Role) =>\n\t\tcreateAgent(provider, { model, reasoningEffort, autoApprove, logger });\n\n\treturn new OrchestratorAgent({\n\t\tplannerAgent: agentFor(roles.planner),\n\t\texecutorAgent: agentFor(roles.executor),\n\t\treviewerAgent: agentFor(roles.reviewer),\n\t\treviewerDecisionValidator: new ReviewerDecisionValidator(),\n\t\tlogger,\n\t});\n}\n\nexport const llmProviders = ['claude', 'openai'] as const;\nexport type LLMProvider = (typeof llmProviders)[number];\n\nexport interface CreateLLMAgentOptions {\n\t/**\n\t * Defaults to `'claude-sonnet-5'` on Claude and `'gpt-5.6-luna'` on OpenAI. The provider's\n\t * client rejects a model it does not support.\n\t */\n\tmodel?: string;\n\t/** Sent as the model's instructions on every call. */\n\tsystemPrompt: string;\n\t/** Defaults to warnings on stderr. */\n\tlogger?: ILogger;\n}\n\nconst defaultLLMAgentModels = {\n\tclaude: 'claude-sonnet-5' satisfies ClaudeLLMModel,\n\topenai: 'gpt-5.6-luna' satisfies OpenAIModel,\n} as const satisfies Record<LLMProvider, string>;\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. Internal while #23 reaches parity with the Agent SDK engines: it\n * is not exported from `src/index.ts`. It runs no tools, so it has no `autoApprove`.\n *\n * @throws {InvalidAgentConfigError} for an unknown provider, or a model its client does not\n * support.\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: LLMProvider,\n\t{ model, systemPrompt, logger = new Logger() }: CreateLLMAgentOptions,\n): Agent {\n\tlet llmClient: LLMClient;\n\n\tswitch (provider) {\n\t\tcase 'claude':\n\t\t\tllmClient = new ClaudeLLMClient({\n\t\t\t\tmodel: model ?? defaultLLMAgentModels.claude,\n\t\t\t\tsystemPrompt,\n\t\t\t\tlogger,\n\t\t\t});\n\t\t\tbreak;\n\t\tcase 'openai':\n\t\t\tllmClient = new OpenAILLMClient({\n\t\t\t\tmodel: model ?? defaultLLMAgentModels.openai,\n\t\t\t\tsystemPrompt,\n\t\t\t\tlogger,\n\t\t\t});\n\t\t\tbreak;\n\t\tdefault: {\n\t\t\t// Reachable from untyped input.\n\t\t\tconst unknownProvider: never = provider;\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t`\"${String(unknownProvider)}\" is not an LLM provider; expected one of: ${llmProviders.join(', ')}`,\n\t\t\t);\n\t\t}\n\t}\n\n\t// LLMAgent records nothing from a failed call, so the original prompt is the whole story.\n\treturn new RetryingAgent({ inner: new LLMAgent({ llmClient }), logger, noteFailures: false });\n}\n"]}
1
+ {"version":3,"file":"agentFactory.js","sourceRoot":"","sources":["../../../src/factory/infrastructure/agentFactory.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Agent } from '#src/agent/domain/agent';\nimport { InvalidAgentConfigError } from '#src/agent/domain/errors';\nimport { ClaudeAgent } from '#src/engines/claude/infrastructure/model/claudeAgent';\nimport { CodexAgent } from '#src/engines/codex/infrastructure/model/codexAgent';\nimport { InstructedAgent } from '#src/orchestration/domain/model/instructedAgent';\nimport {\n\texecutorInstructions,\n\tOrchestratorAgent,\n\tplannerInstructions,\n\treviewerInstructions,\n} from '#src/orchestration/domain/model/orchestratorAgent';\nimport { ReviewerDecisionValidator } from '#src/orchestration/infrastructure/model/reviewerDecisionValidator';\nimport { RetryingAgent } from '#src/retry/domain/model/retryingAgent';\nimport { isOneOf } from '#src/shared/domain/isOneOf';\nimport type { ILogger } from '#src/shared/domain/logger';\nimport { Logger } from '#src/shared/infrastructure/logger';\nimport {\n\tagentProviders,\n\ttype AgentModel,\n\ttype AgentProvider,\n\ttype CreateOrchestratorOptions,\n\ttype ReasoningEffort,\n} from './types.ts';\n\nexport function isAgentProvider(value: string): value is AgentProvider {\n\treturn isOneOf(agentProviders, value);\n}\n\nexport interface CreateAgentOptions {\n\t/** Defaults to `'opus'` on Anthropic and `'gpt-5.6-sol'` on OpenAI. */\n\tmodel?: AgentModel;\n\t/** Defaults to `'high'`. */\n\treasoningEffort?: ReasoningEffort;\n\t/** Maps to the provider's permission-bypass mode. Defaults to `false`. */\n\tautoApprove?: boolean;\n\t/** Defaults to warnings on stderr. */\n\tlogger?: ILogger;\n}\n\nconst defaultModels = {\n\tanthropic: 'opus',\n\topenai: 'gpt-5.6-sol',\n} as const satisfies Record<AgentProvider, AgentModel>;\n\n/**\n * Builds the agent for a provider on its Agent SDK — Claude Code for Anthropic, Codex for\n * OpenAI — already wrapped in the harness's retry policy.\n *\n * @throws {InvalidAgentConfigError} for an unknown provider, or a model or reasoning effort\n * the provider does not support.\n * @throws {UnrecoverableError} when Codex cannot be set up, for example because its CLI binary\n * is missing.\n */\nexport function createAgent(\n\tprovider: AgentProvider,\n\t{ model, reasoningEffort, autoApprove, logger = new Logger() }: CreateAgentOptions = {},\n): Agent {\n\tlet engine: Agent;\n\n\tswitch (provider) {\n\t\tcase 'anthropic':\n\t\t\tengine = new ClaudeAgent({\n\t\t\t\tmodel: model ?? defaultModels.anthropic,\n\t\t\t\treasoningEffort,\n\t\t\t\tautoApprove,\n\t\t\t\tlogger,\n\t\t\t});\n\t\t\tbreak;\n\t\tcase 'openai':\n\t\t\tengine = new CodexAgent({\n\t\t\t\tmodel: model ?? defaultModels.openai,\n\t\t\t\treasoningEffort,\n\t\t\t\tautoApprove,\n\t\t\t\tlogger,\n\t\t\t});\n\t\t\tbreak;\n\t\tdefault: {\n\t\t\t// Reachable from untyped input that skipped `isAgentProvider`.\n\t\t\tconst unknownProvider: never = provider;\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t`\"${String(unknownProvider)}\" is not an agent provider; expected one of: ${agentProviders.join(', ')}`,\n\t\t\t);\n\t\t}\n\t}\n\n\treturn new RetryingAgent({ inner: engine, logger });\n}\n\nexport interface Role {\n\tprovider: AgentProvider;\n\tmodel: AgentModel;\n\treasoningEffort: ReasoningEffort;\n}\n\n// A stronger model plans and reviews; a cheaper one executes the plan. Every model and effort\n// here is one both the Agent SDKs and the model APIs accept, so either path can run a role.\nconst orchestratorRoles = {\n\tdefault: {\n\t\tplanner: { provider: 'openai', model: 'gpt-5.6-sol', reasoningEffort: 'high' },\n\t\texecutor: { provider: 'openai', model: 'gpt-5.6-luna', reasoningEffort: 'xhigh' },\n\t\treviewer: { provider: 'anthropic', model: 'opus', reasoningEffort: 'high' },\n\t},\n\tanthropic: {\n\t\tplanner: { provider: 'anthropic', model: 'opus', reasoningEffort: 'high' },\n\t\texecutor: { provider: 'anthropic', model: 'sonnet', reasoningEffort: 'xhigh' },\n\t\treviewer: { provider: 'anthropic', model: 'opus', reasoningEffort: 'high' },\n\t},\n\topenai: {\n\t\tplanner: { provider: 'openai', model: 'gpt-5.6-sol', reasoningEffort: 'high' },\n\t\texecutor: { provider: 'openai', model: 'gpt-5.6-luna', reasoningEffort: 'xhigh' },\n\t\treviewer: { provider: 'openai', model: 'gpt-5.6-sol', reasoningEffort: 'high' },\n\t},\n} as const satisfies Record<\n\tAgentProvider | 'default',\n\tRecord<'planner' | 'executor' | 'reviewer', Role>\n>;\n\n/**\n * The roles for `provider`, or the default mix without one.\n *\n * @throws {InvalidAgentConfigError} for an unknown provider.\n */\nexport function orchestratorRolesFor(\n\tprovider: AgentProvider | undefined,\n): Record<'planner' | 'executor' | 'reviewer', Role> {\n\tif (provider !== undefined && !isAgentProvider(provider)) {\n\t\tthrow new InvalidAgentConfigError(\n\t\t\t`\"${String(provider)}\" is not an agent provider; expected one of: ${agentProviders.join(', ')}`,\n\t\t);\n\t}\n\n\treturn orchestratorRoles[provider ?? 'default'];\n}\n\n/**\n * Builds the planner → executor → reviewer workflow, each role an agent from `createAgent`.\n *\n * @throws {InvalidAgentConfigError} for an unknown provider.\n * @throws {UnrecoverableError} when an OpenAI role cannot be set up, for example because the\n * Codex CLI binary is missing.\n */\nexport function createOrchestrator({\n\tprovider,\n\tautoApprove,\n\tsystemPrompts = {},\n\tlogger = new Logger(),\n}: CreateOrchestratorOptions = {}): Agent {\n\tconst roles = orchestratorRolesFor(provider);\n\tconst agentFor = ({ provider, model, reasoningEffort }: Role, instructions: string) =>\n\t\tnew InstructedAgent({\n\t\t\tinner: createAgent(provider, { model, reasoningEffort, autoApprove, logger }),\n\t\t\tinstructions,\n\t\t});\n\n\treturn new OrchestratorAgent({\n\t\tplannerAgent: agentFor(roles.planner, systemPrompts.planner ?? plannerInstructions),\n\t\texecutorAgent: agentFor(roles.executor, systemPrompts.executor ?? executorInstructions),\n\t\treviewerAgent: agentFor(roles.reviewer, systemPrompts.reviewer ?? reviewerInstructions),\n\t\treviewerDecisionValidator: new ReviewerDecisionValidator(),\n\t\tlogger,\n\t});\n}\n"]}
@@ -0,0 +1,44 @@
1
+ import type { Agent } from '#src/agent/domain/agent';
2
+ import type { ILogger } from '#src/shared/domain/logger';
3
+ import type { Tool } from '#src/tools/domain/tool';
4
+ import { type AgentModel, type AgentProvider, type CreateOrchestratorOptions, type ReasoningEffort } from './types.ts';
5
+ export interface CreateLLMAgentOptions {
6
+ /**
7
+ * Defaults to `'sonnet'` on Anthropic and `'gpt-5.6-luna'` on OpenAI. The provider's client
8
+ * rejects a model it does not support.
9
+ */
10
+ model?: AgentModel;
11
+ /** Defaults to `'high'`. The provider's client rejects an effort it does not support. */
12
+ reasoningEffort?: ReasoningEffort;
13
+ /** Sent as the model's instructions on every call. */
14
+ systemPrompt: string;
15
+ /** The tools the model may call. Defaults to none. */
16
+ tools?: Tool[];
17
+ /** Defaults to warnings on stderr. */
18
+ logger?: ILogger;
19
+ }
20
+ /**
21
+ * Builds an agent whose conversation MiKode owns, on a provider's model API, already wrapped in
22
+ * the harness's retry policy. Internal while #23 reaches parity with the Agent SDK engines: it
23
+ * is not exported from `src/index.ts`. Its tools only read, so it has no `autoApprove`.
24
+ *
25
+ * @throws {InvalidAgentConfigError} for an unknown provider, or a model or reasoning effort its
26
+ * client does not support.
27
+ * @throws {UnrecoverableError} when the client cannot be set up, for example because
28
+ * `OPENAI_API_KEY` is missing. The Anthropic SDK resolves credentials lazily, so a missing
29
+ * `ANTHROPIC_API_KEY` fails the first run instead, with an `UnrecoverableError`.
30
+ */
31
+ export declare function createLLMAgent(provider: AgentProvider, { model, reasoningEffort, systemPrompt, tools, logger }: CreateLLMAgentOptions): Agent;
32
+ /**
33
+ * Builds the planner → executor → reviewer workflow with the planner and reviewer on the model
34
+ * APIs, from `createLLMAgent`, each holding its role's instructions, or `systemPrompts`, as its
35
+ * system prompt, with the read-only repository tools for the current working directory. The
36
+ * executor stays on its Agent SDK, from `createAgent`, until an agent of ours can change files; `autoApprove` reaches
37
+ * only it. Asynchronous because finding the program that reads the repository is.
38
+ *
39
+ * @throws {InvalidAgentConfigError} for an unknown provider.
40
+ * @throws {UnrecoverableError} when a role cannot be set up, for example because
41
+ * `OPENAI_API_KEY` or the Codex CLI binary is missing, or when no program can read the
42
+ * repository.
43
+ */
44
+ export declare function createLLMOrchestrator({ provider, autoApprove, systemPrompts, logger, }?: CreateOrchestratorOptions): Promise<Agent>;
@@ -0,0 +1,98 @@
1
+ import { InvalidAgentConfigError } from '#src/agent/domain/errors';
2
+ import { LLMAgent } from '#src/engines/domain/model/llmAgent';
3
+ import { ClaudeLLMClient } from '#src/llm/infrastructure/claudeLLMClient';
4
+ import { OpenAILLMClient } from '#src/llm/infrastructure/openAILLMClient';
5
+ import { InstructedAgent } from '#src/orchestration/domain/model/instructedAgent';
6
+ import { executorInstructions, OrchestratorAgent, plannerInstructions, reviewerInstructions, } from '#src/orchestration/domain/model/orchestratorAgent';
7
+ import { ReviewerDecisionValidator } from '#src/orchestration/infrastructure/model/reviewerDecisionValidator';
8
+ import { RetryingAgent } from '#src/retry/domain/model/retryingAgent';
9
+ import { Logger } from '#src/shared/infrastructure/logger';
10
+ import { createWorkspace } from '#src/tools/infrastructure/createWorkspace';
11
+ import { createWorkspaceTools } from '#src/tools/infrastructure/workspaceTools';
12
+ import { createAgent, orchestratorRolesFor } from "./agentFactory.js";
13
+ import { agentProviders, } from "./types.js";
14
+ const defaultLLMAgentModels = {
15
+ anthropic: 'sonnet',
16
+ openai: 'gpt-5.6-luna',
17
+ };
18
+ /**
19
+ * Builds an agent whose conversation MiKode owns, on a provider's model API, already wrapped in
20
+ * the harness's retry policy. Internal while #23 reaches parity with the Agent SDK engines: it
21
+ * is not exported from `src/index.ts`. Its tools only read, so it has no `autoApprove`.
22
+ *
23
+ * @throws {InvalidAgentConfigError} for an unknown provider, or a model or reasoning effort its
24
+ * client does not support.
25
+ * @throws {UnrecoverableError} when the client cannot be set up, for example because
26
+ * `OPENAI_API_KEY` is missing. The Anthropic SDK resolves credentials lazily, so a missing
27
+ * `ANTHROPIC_API_KEY` fails the first run instead, with an `UnrecoverableError`.
28
+ */
29
+ export function createLLMAgent(provider, { model, reasoningEffort, systemPrompt, tools, logger = new Logger() }) {
30
+ let llmClient;
31
+ switch (provider) {
32
+ case 'anthropic':
33
+ llmClient = new ClaudeLLMClient({
34
+ model: model ?? defaultLLMAgentModels.anthropic,
35
+ reasoningEffort,
36
+ systemPrompt,
37
+ logger,
38
+ });
39
+ break;
40
+ case 'openai':
41
+ llmClient = new OpenAILLMClient({
42
+ model: model ?? defaultLLMAgentModels.openai,
43
+ reasoningEffort,
44
+ systemPrompt,
45
+ logger,
46
+ });
47
+ break;
48
+ default: {
49
+ // Reachable from untyped input.
50
+ const unknownProvider = provider;
51
+ throw new InvalidAgentConfigError(`"${String(unknownProvider)}" is not an agent provider; expected one of: ${agentProviders.join(', ')}`);
52
+ }
53
+ }
54
+ // LLMAgent records nothing from a failed call, so the original prompt is the whole story.
55
+ return new RetryingAgent({
56
+ inner: new LLMAgent({ llmClient, tools }),
57
+ logger,
58
+ noteFailures: false,
59
+ });
60
+ }
61
+ // Sent on every call after the role's instructions, so it stays short: it only says how to find
62
+ // one's way around the repository without reading all of it.
63
+ const repositoryGuidance = 'Paths are relative to the repository root. Start with AGENTS.md and the architecture document it links to learn where things live, then read only the files your task needs.';
64
+ /**
65
+ * Builds the planner → executor → reviewer workflow with the planner and reviewer on the model
66
+ * APIs, from `createLLMAgent`, each holding its role's instructions, or `systemPrompts`, as its
67
+ * system prompt, with the read-only repository tools for the current working directory. The
68
+ * executor stays on its Agent SDK, from `createAgent`, until an agent of ours can change files; `autoApprove` reaches
69
+ * only it. Asynchronous because finding the program that reads the repository is.
70
+ *
71
+ * @throws {InvalidAgentConfigError} for an unknown provider.
72
+ * @throws {UnrecoverableError} when a role cannot be set up, for example because
73
+ * `OPENAI_API_KEY` or the Codex CLI binary is missing, or when no program can read the
74
+ * repository.
75
+ */
76
+ export async function createLLMOrchestrator({ provider, autoApprove, systemPrompts = {}, logger = new Logger(), } = {}) {
77
+ const roles = orchestratorRolesFor(provider);
78
+ const tools = createWorkspaceTools(await createWorkspace({ root: process.cwd(), logger }));
79
+ const llmAgentFor = ({ provider, model, reasoningEffort }, systemPrompt) => createLLMAgent(provider, { model, reasoningEffort, systemPrompt, tools, logger });
80
+ // A caller's own prompt replaces the whole of ours, guidance included.
81
+ const withGuidance = (instructions) => `${instructions}\n\n${repositoryGuidance}`;
82
+ return new OrchestratorAgent({
83
+ plannerAgent: llmAgentFor(roles.planner, systemPrompts.planner ?? withGuidance(plannerInstructions)),
84
+ executorAgent: new InstructedAgent({
85
+ inner: createAgent(roles.executor.provider, {
86
+ model: roles.executor.model,
87
+ reasoningEffort: roles.executor.reasoningEffort,
88
+ autoApprove,
89
+ logger,
90
+ }),
91
+ instructions: systemPrompts.executor ?? executorInstructions,
92
+ }),
93
+ reviewerAgent: llmAgentFor(roles.reviewer, systemPrompts.reviewer ?? withGuidance(reviewerInstructions)),
94
+ reviewerDecisionValidator: new ReviewerDecisionValidator(),
95
+ logger,
96
+ });
97
+ }
98
+ //# sourceMappingURL=agentLLMFactory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agentLLMFactory.js","sourceRoot":"","sources":["../../../src/factory/infrastructure/agentLLMFactory.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Agent } from '#src/agent/domain/agent';\nimport { InvalidAgentConfigError } from '#src/agent/domain/errors';\nimport { LLMAgent } from '#src/engines/domain/model/llmAgent';\nimport type { LLMClient } from '#src/llm/domain/llm';\nimport { ClaudeLLMClient } from '#src/llm/infrastructure/claudeLLMClient';\nimport { OpenAILLMClient } from '#src/llm/infrastructure/openAILLMClient';\nimport { InstructedAgent } from '#src/orchestration/domain/model/instructedAgent';\nimport {\n\texecutorInstructions,\n\tOrchestratorAgent,\n\tplannerInstructions,\n\treviewerInstructions,\n} from '#src/orchestration/domain/model/orchestratorAgent';\nimport { ReviewerDecisionValidator } from '#src/orchestration/infrastructure/model/reviewerDecisionValidator';\nimport { RetryingAgent } from '#src/retry/domain/model/retryingAgent';\nimport type { ILogger } from '#src/shared/domain/logger';\nimport { Logger } from '#src/shared/infrastructure/logger';\nimport type { Tool } from '#src/tools/domain/tool';\nimport { createWorkspace } from '#src/tools/infrastructure/createWorkspace';\nimport { createWorkspaceTools } from '#src/tools/infrastructure/workspaceTools';\nimport { createAgent, 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\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. Defaults to none. */\n\ttools?: Tool[];\n\t/** Defaults to warnings on stderr. */\n\tlogger?: ILogger;\n}\n\nconst defaultLLMAgentModels = {\n\tanthropic: 'sonnet',\n\topenai: 'gpt-5.6-luna',\n} as const satisfies Record<AgentProvider, AgentModel>;\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. Internal while #23 reaches parity with the Agent SDK engines: it\n * is not exported from `src/index.ts`. Its tools only read, so it has no `autoApprove`.\n *\n * @throws {InvalidAgentConfigError} for an unknown provider, or a model or reasoning effort its\n * client does not support.\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{ model, reasoningEffort, systemPrompt, tools, logger = new Logger() }: CreateLLMAgentOptions,\n): Agent {\n\tlet llmClient: LLMClient;\n\n\tswitch (provider) {\n\t\tcase 'anthropic':\n\t\t\tllmClient = new ClaudeLLMClient({\n\t\t\t\tmodel: model ?? defaultLLMAgentModels.anthropic,\n\t\t\t\treasoningEffort,\n\t\t\t\tsystemPrompt,\n\t\t\t\tlogger,\n\t\t\t});\n\t\t\tbreak;\n\t\tcase 'openai':\n\t\t\tllmClient = new OpenAILLMClient({\n\t\t\t\tmodel: model ?? defaultLLMAgentModels.openai,\n\t\t\t\treasoningEffort,\n\t\t\t\tsystemPrompt,\n\t\t\t\tlogger,\n\t\t\t});\n\t\t\tbreak;\n\t\tdefault: {\n\t\t\t// Reachable from untyped input.\n\t\t\tconst unknownProvider: never = provider;\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t`\"${String(unknownProvider)}\" is not an agent provider; expected one of: ${agentProviders.join(', ')}`,\n\t\t\t);\n\t\t}\n\t}\n\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({ llmClient, tools }),\n\t\tlogger,\n\t\tnoteFailures: false,\n\t});\n}\n\n// Sent on every call after the role's instructions, so it stays short: it only says how to find\n// one's way around the repository without reading all of it.\nconst repositoryGuidance =\n\t'Paths are relative to the repository root. Start with AGENTS.md and the architecture document it links to learn where things live, then read only the files your task needs.';\n\n/**\n * Builds the planner → executor → reviewer workflow with the planner and reviewer on the model\n * APIs, from `createLLMAgent`, each holding its role's instructions, or `systemPrompts`, as its\n * system prompt, with the read-only repository tools for the current working directory. The\n * executor stays on its Agent SDK, from `createAgent`, until an agent of ours can change files; `autoApprove` reaches\n * only it. Asynchronous because finding the program that reads the repository is.\n *\n * @throws {InvalidAgentConfigError} for an unknown provider.\n * @throws {UnrecoverableError} when a role cannot be set up, for example because\n * `OPENAI_API_KEY` or the Codex CLI binary is missing, or when no program can read the\n * repository.\n */\nexport async function createLLMOrchestrator({\n\tprovider,\n\tautoApprove,\n\tsystemPrompts = {},\n\tlogger = new Logger(),\n}: CreateOrchestratorOptions = {}): Promise<Agent> {\n\tconst roles = orchestratorRolesFor(provider);\n\tconst tools = createWorkspaceTools(await createWorkspace({ root: process.cwd(), logger }));\n\tconst llmAgentFor = ({ provider, model, reasoningEffort }: Role, systemPrompt: string) =>\n\t\tcreateLLMAgent(provider, { model, reasoningEffort, systemPrompt, tools, logger });\n\t// A caller's own prompt replaces the whole of ours, guidance included.\n\tconst withGuidance = (instructions: string) => `${instructions}\\n\\n${repositoryGuidance}`;\n\n\treturn new OrchestratorAgent({\n\t\tplannerAgent: llmAgentFor(\n\t\t\troles.planner,\n\t\t\tsystemPrompts.planner ?? withGuidance(plannerInstructions),\n\t\t),\n\t\texecutorAgent: new InstructedAgent({\n\t\t\tinner: createAgent(roles.executor.provider, {\n\t\t\t\tmodel: roles.executor.model,\n\t\t\t\treasoningEffort: roles.executor.reasoningEffort,\n\t\t\t\tautoApprove,\n\t\t\t\tlogger,\n\t\t\t}),\n\t\t\tinstructions: systemPrompts.executor ?? executorInstructions,\n\t\t}),\n\t\treviewerAgent: llmAgentFor(\n\t\t\troles.reviewer,\n\t\t\tsystemPrompts.reviewer ?? withGuidance(reviewerInstructions),\n\t\t),\n\t\treviewerDecisionValidator: new ReviewerDecisionValidator(),\n\t\tlogger,\n\t});\n}\n"]}
@@ -0,0 +1,33 @@
1
+ import type { ClaudeModel, ClaudeReasoningEffort } from '#src/engines/claude/infrastructure/model/claudeAgent';
2
+ import type { CodexModel, CodexReasoningEffort } from '#src/engines/codex/infrastructure/model/codexAgent';
3
+ import type { ILogger } from '#src/shared/domain/logger';
4
+ /** Named after the company, not the product: one provider can be reached by an SDK or an API. */
5
+ export declare const agentProviders: readonly ["anthropic", "openai"];
6
+ export type AgentProvider = (typeof agentProviders)[number];
7
+ /** Any provider's model. The agent the provider selects rejects one it does not support. */
8
+ export type AgentModel = ClaudeModel | CodexModel;
9
+ /** Any provider's reasoning effort. The agent the provider selects rejects one it does not support. */
10
+ export type ReasoningEffort = ClaudeReasoningEffort | CodexReasoningEffort;
11
+ export interface CreateOrchestratorOptions {
12
+ /**
13
+ * Runs every role on one provider, for example when the other one is out of quota. By
14
+ * default OpenAI plans and executes, and Anthropic reviews.
15
+ */
16
+ provider?: AgentProvider;
17
+ /** Maps to each provider's permission-bypass mode. Defaults to `false`. */
18
+ autoApprove?: boolean;
19
+ /**
20
+ * Replaces a role's instructions, word for word; a role left out keeps the harness's own. A
21
+ * role on a model API receives them as its system prompt, and one on an Agent SDK, which
22
+ * takes none, at the head of every prompt. The reviewer must still answer with the JSON
23
+ * decision the workflow parses: `{"decision":"approved"}`, or
24
+ * `{"decision":"rejected","feedback":"..."}`.
25
+ */
26
+ systemPrompts?: {
27
+ planner?: string;
28
+ executor?: string;
29
+ reviewer?: string;
30
+ };
31
+ /** Defaults to warnings on stderr. */
32
+ logger?: ILogger;
33
+ }
@@ -0,0 +1,3 @@
1
+ /** Named after the company, not the product: one provider can be reached by an SDK or an API. */
2
+ export const agentProviders = ['anthropic', 'openai'];
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/factory/infrastructure/types.ts"],"names":[],"mappings":"AAUA,iGAAiG;AACjG,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,WAAW,EAAE,QAAQ,CAAU,CAAC","sourcesContent":["import type {\n\tClaudeModel,\n\tClaudeReasoningEffort,\n} from '#src/engines/claude/infrastructure/model/claudeAgent';\nimport type {\n\tCodexModel,\n\tCodexReasoningEffort,\n} from '#src/engines/codex/infrastructure/model/codexAgent';\nimport type { ILogger } from '#src/shared/domain/logger';\n\n/** Named after the company, not the product: one provider can be reached by an SDK or an API. */\nexport const agentProviders = ['anthropic', 'openai'] as const;\nexport type AgentProvider = (typeof agentProviders)[number];\n\n/** Any provider's model. The agent the provider selects rejects one it does not support. */\nexport type AgentModel = ClaudeModel | CodexModel;\n/** Any provider's reasoning effort. The agent the provider selects rejects one it does not support. */\nexport type ReasoningEffort = ClaudeReasoningEffort | CodexReasoningEffort;\n\nexport interface CreateOrchestratorOptions {\n\t/**\n\t * Runs every role on one provider, for example when the other one is out of quota. By\n\t * default OpenAI plans and executes, and Anthropic reviews.\n\t */\n\tprovider?: AgentProvider;\n\t/** Maps to each provider's permission-bypass mode. Defaults to `false`. */\n\tautoApprove?: boolean;\n\t/**\n\t * Replaces a role's instructions, word for word; a role left out keeps the harness's own. A\n\t * role on a model API receives them as its system prompt, and one on an Agent SDK, which\n\t * takes none, at the head of every prompt. The reviewer must still answer with the JSON\n\t * decision the workflow parses: `{\"decision\":\"approved\"}`, or\n\t * `{\"decision\":\"rejected\",\"feedback\":\"...\"}`.\n\t */\n\tsystemPrompts?: { planner?: string; executor?: string; reviewer?: string };\n\t/** Defaults to warnings on stderr. */\n\tlogger?: ILogger;\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export type { ILogger } from './shared/domain/logger.ts';
2
- export { agentProviders, createAgent, createOrchestrator, isAgentProvider, type AgentModel, type AgentProvider, type CreateAgentOptions, type CreateOrchestratorOptions, type ReasoningEffort, } from './factory/infrastructure/agentFactory.ts';
2
+ export { createAgent, createOrchestrator, isAgentProvider, type CreateAgentOptions, } from './factory/infrastructure/agentFactory.ts';
3
+ export { agentProviders, type AgentModel, type AgentProvider, type CreateOrchestratorOptions, type ReasoningEffort, } from './factory/infrastructure/types.ts';
3
4
  export { type Agent, type AgentResponse, type Callback, type ProgressEvent, } from './agent/domain/agent.ts';
4
5
  export { InvalidAgentConfigError, RecoverableError, UnrecoverableError, } from './agent/domain/errors.ts';
5
6
  export { isAbortError } from './shared/domain/isAbortError.ts';
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
- export { agentProviders, createAgent, createOrchestrator, isAgentProvider, } from "./factory/infrastructure/agentFactory.js";
1
+ export { createAgent, createOrchestrator, isAgentProvider, } from "./factory/infrastructure/agentFactory.js";
2
+ export { agentProviders, } from "./factory/infrastructure/types.js";
2
3
  export {} from "./agent/domain/agent.js";
3
4
  export { InvalidAgentConfigError, RecoverableError, UnrecoverableError, } from "./agent/domain/errors.js";
4
5
  export { isAbortError } from "./shared/domain/isAbortError.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACN,cAAc,EACd,WAAW,EACX,kBAAkB,EAClB,eAAe,GAMf,MAAM,0CAA0C,CAAC;AAClD,OAAO,EAKN,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACN,uBAAuB,EACvB,gBAAgB,EAChB,kBAAkB,GAClB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC","sourcesContent":["export type { ILogger } from './shared/domain/logger.ts';\nexport {\n\tagentProviders,\n\tcreateAgent,\n\tcreateOrchestrator,\n\tisAgentProvider,\n\ttype AgentModel,\n\ttype AgentProvider,\n\ttype CreateAgentOptions,\n\ttype CreateOrchestratorOptions,\n\ttype ReasoningEffort,\n} from './factory/infrastructure/agentFactory.ts';\nexport {\n\ttype Agent,\n\ttype AgentResponse,\n\ttype Callback,\n\ttype ProgressEvent,\n} from './agent/domain/agent.ts';\nexport {\n\tInvalidAgentConfigError,\n\tRecoverableError,\n\tUnrecoverableError,\n} from './agent/domain/errors.ts';\nexport { isAbortError } from './shared/domain/isAbortError.ts';\nexport type { Tokens } from './shared/domain/tokens.ts';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACN,WAAW,EACX,kBAAkB,EAClB,eAAe,GAEf,MAAM,0CAA0C,CAAC;AAClD,OAAO,EACN,cAAc,GAKd,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAKN,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACN,uBAAuB,EACvB,gBAAgB,EAChB,kBAAkB,GAClB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC","sourcesContent":["export type { ILogger } from './shared/domain/logger.ts';\nexport {\n\tcreateAgent,\n\tcreateOrchestrator,\n\tisAgentProvider,\n\ttype CreateAgentOptions,\n} from './factory/infrastructure/agentFactory.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 ProgressEvent,\n} from './agent/domain/agent.ts';\nexport {\n\tInvalidAgentConfigError,\n\tRecoverableError,\n\tUnrecoverableError,\n} from './agent/domain/errors.ts';\nexport { isAbortError } from './shared/domain/isAbortError.ts';\nexport type { Tokens } from './shared/domain/tokens.ts';\n"]}
@@ -2,8 +2,10 @@ import type { ILogger } from '#src/shared/domain/logger';
2
2
  import type { LLMClient, LLMResponse } from '../domain/llm.ts';
3
3
  import type { Message } from '../domain/message.ts';
4
4
  import type { ToolDefinition } from '../domain/tool.ts';
5
- export declare const claudeLLMModels: readonly ["claude-opus-5-5", "claude-fable-5-1", "claude-sonnet-5"];
5
+ export declare const claudeLLMModels: readonly ["opus", "fable", "sonnet"];
6
6
  export type ClaudeLLMModel = (typeof claudeLLMModels)[number];
7
+ export declare const claudeLLMReasoningEfforts: readonly ["low", "medium", "high", "xhigh", "max"];
8
+ export type ClaudeLLMReasoningEffort = (typeof claudeLLMReasoningEfforts)[number];
7
9
  /**
8
10
  * A stateless `LLMClient` on the Anthropic Messages API, which keeps nothing between calls:
9
11
  * every call carries the whole context, so the conversation is MiKode's, not the provider's.
@@ -11,10 +13,12 @@ export type ClaudeLLMModel = (typeof claudeLLMModels)[number];
11
13
  export declare class ClaudeLLMClient implements LLMClient {
12
14
  private readonly client;
13
15
  private readonly model;
16
+ private readonly reasoningEffort;
14
17
  private readonly systemPrompt;
15
18
  private readonly logger;
16
- constructor({ model, systemPrompt, logger, }: {
19
+ constructor({ model, reasoningEffort, systemPrompt, logger, }: {
17
20
  model: string;
21
+ reasoningEffort?: string;
18
22
  systemPrompt: string;
19
23
  logger: ILogger;
20
24
  });
@@ -3,8 +3,22 @@ import { InvalidAgentConfigError, UnrecoverableError, withSpentTokens, } from '#
3
3
  import { classifyHostFailure, classifyLocalFailure, classifyProviderFailure, describeFailure, treatErrors, } from '#src/agent/domain/providerFailure';
4
4
  import { isOneOf } from '#src/shared/domain/isOneOf';
5
5
  import { MaxContextError } from "../domain/errors.js";
6
- // The Messages API takes full model IDs, not the Agent SDK's aliases, so this list is its own.
7
- export const claudeLLMModels = ['claude-opus-5-5', 'claude-fable-5-1', 'claude-sonnet-5'];
6
+ // The Messages API takes full model IDs. Callers name a model by the Agent SDK's alias, so both
7
+ // paths share one vocabulary; this table pins the ID each alias means here, and moves by hand
8
+ // when Anthropic releases a newer model. Haiku is left out: it has no adaptive thinking.
9
+ export const claudeLLMModels = ['opus', 'fable', 'sonnet'];
10
+ const claudeLLMModelIds = {
11
+ opus: 'claude-opus-5-5',
12
+ fable: 'claude-fable-5-1',
13
+ sonnet: 'claude-sonnet-5',
14
+ };
15
+ export const claudeLLMReasoningEfforts = [
16
+ 'low',
17
+ 'medium',
18
+ 'high',
19
+ 'xhigh',
20
+ 'max',
21
+ ];
8
22
  // Adaptive thinking spends from this budget too. Above roughly 21,000 the SDK refuses a request
9
23
  // that is not streamed, because it could outlast its ten-minute timeout.
10
24
  const maxOutputTokens = 16_000;
@@ -198,13 +212,18 @@ function classifyClaudeFailure(error) {
198
212
  export class ClaudeLLMClient {
199
213
  client;
200
214
  model;
215
+ reasoningEffort;
201
216
  systemPrompt;
202
217
  logger;
203
- // `model` is untyped on purpose: this adapter is the one that knows what Claude supports.
204
- constructor({ model, systemPrompt, logger, }) {
218
+ // `model` and `reasoningEffort` are untyped on purpose: this adapter is the one that knows
219
+ // what Claude supports.
220
+ constructor({ model, reasoningEffort = 'high', systemPrompt, logger, }) {
205
221
  if (!isOneOf(claudeLLMModels, model)) {
206
222
  throw new InvalidAgentConfigError(`"${model}" is not a Claude API model; expected one of: ${claudeLLMModels.join(', ')}`);
207
223
  }
224
+ if (!isOneOf(claudeLLMReasoningEfforts, reasoningEffort)) {
225
+ throw new InvalidAgentConfigError(`"${reasoningEffort}" is not a Claude API reasoning effort; expected one of: ${claudeLLMReasoningEfforts.join(', ')}`);
226
+ }
208
227
  try {
209
228
  // The SDK's default retries stay on, as OpenAI's do. See "The provider SDKs keep their
210
229
  // transport retries under RetryingAgent" in decisions.md.
@@ -217,7 +236,8 @@ export class ClaudeLLMClient {
217
236
  cause: describeFailure(error),
218
237
  });
219
238
  }
220
- this.model = model;
239
+ this.model = claudeLLMModelIds[model];
240
+ this.reasoningEffort = reasoningEffort;
221
241
  this.systemPrompt = systemPrompt;
222
242
  this.logger = logger;
223
243
  }
@@ -232,6 +252,7 @@ export class ClaudeLLMClient {
232
252
  // Left out when empty, so a request without tools stays as it always was.
233
253
  ...(tools.length > 0 && { tools: tools.map(toClaudeTool) }),
234
254
  thinking: { type: 'adaptive', display: 'summarized' },
255
+ output_config: { effort: this.reasoningEffort },
235
256
  // Anthropic caches only on request. This marks the last block, so the next call
236
257
  // reads everything before it from the cache instead of paying for it again.
237
258
  cache_control: { type: 'ephemeral' },
@@ -1 +1 @@
1
- {"version":3,"file":"claudeLLMClient.js","sourceRoot":"","sources":["../../../src/llm/infrastructure/claudeLLMClient.ts"],"names":[],"mappings":"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","sourcesContent":["import Anthropic, { AnthropicError, APIError } from '@anthropic-ai/sdk';\nimport type {\n\tContentBlock,\n\tContentBlockParam,\n\tMessage as AnthropicMessage,\n\tMessageParam,\n\tRedactedThinkingBlockParam,\n\tThinkingBlockParam,\n\tTool as ClaudeTool,\n\tUsage,\n} from '@anthropic-ai/sdk/resources/messages';\nimport {\n\tInvalidAgentConfigError,\n\tUnrecoverableError,\n\twithSpentTokens,\n} from '#src/agent/domain/errors';\nimport {\n\tclassifyHostFailure,\n\tclassifyLocalFailure,\n\tclassifyProviderFailure,\n\tdescribeFailure,\n\ttreatErrors,\n} from '#src/agent/domain/providerFailure';\nimport { isOneOf } from '#src/shared/domain/isOneOf';\nimport type { ILogger } from '#src/shared/domain/logger';\nimport type { Tokens } from '#src/shared/domain/tokens';\nimport { MaxContextError } from '../domain/errors.ts';\nimport type { LLMClient, LLMResponse, StopReason } from '../domain/llm.ts';\nimport type { Message, MessagePart } from '../domain/message.ts';\nimport type { ToolDefinition } from '../domain/tool.ts';\n\n// The Messages API takes full model IDs, not the Agent SDK's aliases, so this list is its own.\nexport const claudeLLMModels = ['claude-opus-5-5', 'claude-fable-5-1', 'claude-sonnet-5'] as const;\nexport type ClaudeLLMModel = (typeof claudeLLMModels)[number];\n\n// Adaptive thinking spends from this budget too. Above roughly 21,000 the SDK refuses a request\n// that is not streamed, because it could outlast its ten-minute timeout.\nconst maxOutputTokens = 16_000;\n\n// The request itself is wrong or not allowed, so sending it again cannot succeed.\nconst unrecoverableStatuses = [400, 401, 403, 404, 413, 422];\n\n// The account is out of credit, or its key cannot make the request. Resending cannot fix\n// either, whatever status the error arrives with.\nconst unrecoverableErrorTypes = ['billing_error', 'authentication_error', 'permission_error'];\n\n// This client's label on the thinking blocks it keeps for replay; see `ProviderDataPart`.\nconst claudeSource = 'claude';\n\n/**\n * Anthropic rejects a `tool_use` whose input is not an object, with a 400 on every later call.\n * Another provider's call can hold anything else, such as OpenAI arguments that did not parse;\n * its result already told the model the call failed, so sending it as empty loses nothing.\n */\nfunction toClaudeInputObject(input: unknown): unknown {\n\treturn typeof input === 'object' && input !== null && !Array.isArray(input) ? input : {};\n}\n\n/** Thinking crosses back only as the signed block this client kept, never as its text. */\nfunction toClaudeBlocks(part: MessagePart): ContentBlockParam[] {\n\tswitch (part.type) {\n\t\tcase 'text':\n\t\t\t// The API rejects an empty text block.\n\t\t\treturn part.text ? [{ type: 'text', text: part.text }] : [];\n\t\tcase 'toolCall':\n\t\t\treturn [\n\t\t\t\t{ type: 'tool_use', id: part.id, name: part.name, input: toClaudeInputObject(part.input) },\n\t\t\t];\n\t\tcase 'toolResult':\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'tool_result',\n\t\t\t\t\ttool_use_id: part.callId,\n\t\t\t\t\tcontent: part.output,\n\t\t\t\t\tis_error: part.isError,\n\t\t\t\t},\n\t\t\t];\n\t\tcase 'providerData':\n\t\t\t// Another provider's block would be rejected; losing it only costs that provider's reasoning.\n\t\t\treturn part.source === claudeSource\n\t\t\t\t? [part.data as ThinkingBlockParam | RedactedThinkingBlockParam]\n\t\t\t\t: [];\n\t\tcase 'reasoning':\n\t\t\t// Unsigned text cannot be replayed as thinking. The API accepts a turn without it.\n\t\t\treturn [];\n\t}\n}\n\n/**\n * Tool results travel in a user turn, as the API requires. A message left with no blocks is\n * left out whole; the API joins the consecutive user turns that leaves.\n */\nfunction toClaudeInput(message: Message): MessageParam[] {\n\tconst content = message.content.flatMap(toClaudeBlocks);\n\tif (content.length === 0) {\n\t\treturn [];\n\t}\n\n\treturn [{ role: message.role === 'tool' ? 'user' : message.role, content }];\n}\n\n/**\n * Strict: the API constrains the model to the schema, so its input always parses and matches.\n * A schema outside what strict mode supports fails the request with a 400.\n */\nfunction toClaudeTool({ name, description, inputSchema }: ToolDefinition): ClaudeTool {\n\treturn { name, description, input_schema: inputSchema, strict: true };\n}\n\nfunction describeBlock(block: ContentBlock, logger: ILogger): MessagePart[] {\n\tswitch (block.type) {\n\t\tcase 'text':\n\t\t\treturn [{ type: 'text', text: block.text }];\n\t\tcase 'thinking': {\n\t\t\t// Kept signed for replay; the summarized text is only for narration.\n\t\t\tconst signed: ThinkingBlockParam = {\n\t\t\t\ttype: 'thinking',\n\t\t\t\tthinking: block.thinking,\n\t\t\t\tsignature: block.signature,\n\t\t\t};\n\t\t\tconst replay: MessagePart = { type: 'providerData', source: claudeSource, data: signed };\n\t\t\treturn block.thinking ? [{ type: 'reasoning', text: block.thinking }, replay] : [replay];\n\t\t}\n\t\tcase 'redacted_thinking': {\n\t\t\t// Encrypted by design: nothing to narrate, but it still goes back.\n\t\t\tconst redacted: RedactedThinkingBlockParam = { type: 'redacted_thinking', data: block.data };\n\t\t\treturn [{ type: 'providerData', source: claudeSource, data: redacted }];\n\t\t}\n\t\tcase 'tool_use':\n\t\t\treturn [{ type: 'toolCall', id: block.id, name: block.name, input: block.input }];\n\t\tdefault:\n\t\t\t// Server tools are never offered, so any other block is output this client does not expect.\n\t\t\ttreatErrors(\n\t\t\t\t() => {\n\t\t\t\t\tlogger.warn(block, 'Claude returned a content block the client does not map');\n\t\t\t\t},\n\t\t\t\tclassifyHostFailure,\n\t\t\t\t'Claude client logger failed while mapping a response',\n\t\t\t);\n\t\t\treturn [];\n\t}\n}\n\nfunction toStopReason(response: AnthropicMessage): StopReason {\n\tswitch (response.stop_reason) {\n\t\t// `tool_use` is a complete answer that asks for tools: running them is the agent's call.\n\t\tcase 'end_turn':\n\t\tcase 'tool_use':\n\t\t\treturn 'completed';\n\t\tcase 'max_tokens':\n\t\t\treturn 'truncated';\n\t\tcase 'refusal':\n\t\t\treturn 'refused';\n\t\tcase 'model_context_window_exceeded':\n\t\t\tthrow new MaxContextError('The context no longer fits the Claude model', {\n\t\t\t\tcause: 'The response stopped at the model context window.',\n\t\t\t});\n\t\tdefault:\n\t\t\t// `pause_turn` and `stop_sequence` need server tools or stop sequences, and this\n\t\t\t// client sends neither.\n\t\t\tthrow new UnrecoverableError('The Claude response stopped unexpectedly', {\n\t\t\t\tcause: `The response stopped with \"${response.stop_reason ?? 'unknown'}\".`,\n\t\t\t});\n\t}\n}\n\n/** Unlike OpenAI, Anthropic counts cache reads and writes apart from `input_tokens`. */\nfunction toTokens(usage: Usage | undefined, logger: ILogger): Tokens | undefined {\n\tif (!usage) {\n\t\t// Zeros would report a call that may have been billed as free.\n\t\ttreatErrors(\n\t\t\t() => {\n\t\t\t\tlogger.warn('Claude returned a response without usage; it cannot be accounted for');\n\t\t\t},\n\t\t\tclassifyHostFailure,\n\t\t\t'Claude client logger failed while reading usage',\n\t\t);\n\t\treturn undefined;\n\t}\n\n\treturn {\n\t\tinputTokens: usage.input_tokens,\n\t\treadCacheTokens: usage.cache_read_input_tokens ?? 0,\n\t\twrittenCacheTokens: usage.cache_creation_input_tokens ?? 0,\n\t\toutputTokens: usage.output_tokens,\n\t};\n}\n\nfunction toLLMResponse(response: AnthropicMessage, logger: ILogger): LLMResponse {\n\tlet usage: Tokens | undefined;\n\n\ttry {\n\t\t// Inside the try: it warns when the usage is missing, and that warning can throw too.\n\t\tusage = toTokens(response.usage, logger);\n\t\treturn {\n\t\t\tmessage: {\n\t\t\t\trole: 'assistant',\n\t\t\t\tcontent: response.content.flatMap(block => describeBlock(block, logger)),\n\t\t\t},\n\t\t\tusage,\n\t\t\tstopReason: toStopReason(response),\n\t\t};\n\t} catch (error) {\n\t\t// The call was billed whether the response failed or a logger did while mapping it, and\n\t\t// an answer without usage leaves the run's count unknown.\n\t\tthrow withSpentTokens(error, usage, !response.usage);\n\t}\n}\n\n/**\n * The SDK resolves credentials lazily, so a missing key surfaces on the first call, as a plain\n * `Error` the SDK gives no class of its own. An `AnthropicError` that is not an `APIError` never\n * carries an answer from the API: on this client's path it is the SDK refusing to make the\n * call, such as an unresolved profile or a request too large to send without streaming.\n */\nfunction isLocalSDKFailure(error: unknown): boolean {\n\treturn (\n\t\t(error instanceof AnthropicError && !(error instanceof APIError)) ||\n\t\t(error instanceof Error && error.message.startsWith('Could not resolve authentication method'))\n\t);\n}\n\nfunction classifyClaudeFailure(error: unknown): Error {\n\tif (isLocalSDKFailure(error)) {\n\t\t// The API never answered, and calling again would fail the same way.\n\t\treturn new UnrecoverableError('The Claude client cannot send the request', {\n\t\t\tcause: describeFailure(error),\n\t\t});\n\t}\n\tif (error instanceof APIError) {\n\t\t// Anthropic gives an overlong prompt no code of its own, only a 400 with this message.\n\t\tif (error.status === 400 && error.message.includes('prompt is too long')) {\n\t\t\treturn new MaxContextError('The context no longer fits the Claude model', {\n\t\t\t\tcause: describeFailure(error),\n\t\t\t});\n\t\t}\n\t\tif (\n\t\t\tunrecoverableErrorTypes.some(type => type === error.type) ||\n\t\t\tunrecoverableStatuses.some(status => status === error.status)\n\t\t) {\n\t\t\treturn new UnrecoverableError('Claude rejected the request', {\n\t\t\t\tcause: describeFailure(error),\n\t\t\t});\n\t\t}\n\t}\n\n\t// A rate limit, an overload (529), a 5xx or a network failure: a later attempt can succeed.\n\treturn classifyProviderFailure(error, 'The Claude request failed');\n}\n\n/**\n * A stateless `LLMClient` on the Anthropic Messages API, which keeps nothing between calls:\n * every call carries the whole context, so the conversation is MiKode's, not the provider's.\n */\nexport class ClaudeLLMClient implements LLMClient {\n\tprivate readonly client: Anthropic;\n\tprivate readonly model: ClaudeLLMModel;\n\tprivate readonly systemPrompt: string;\n\tprivate readonly logger: ILogger;\n\n\t// `model` is untyped on purpose: this adapter is the one that knows what Claude supports.\n\tconstructor({\n\t\tmodel,\n\t\tsystemPrompt,\n\t\tlogger,\n\t}: {\n\t\tmodel: string;\n\t\tsystemPrompt: string;\n\t\tlogger: ILogger;\n\t}) {\n\t\tif (!isOneOf(claudeLLMModels, model)) {\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t`\"${model}\" is not a Claude API model; expected one of: ${claudeLLMModels.join(', ')}`,\n\t\t\t);\n\t\t}\n\n\t\ttry {\n\t\t\t// The SDK's default retries stay on, as OpenAI's do. See \"The provider SDKs keep their\n\t\t\t// transport retries under RetryingAgent\" in decisions.md.\n\t\t\tthis.client = new Anthropic();\n\t\t} catch (error) {\n\t\t\t// Conflicting credential options, for example: building it again cannot fix them. A\n\t\t\t// missing key does not fail here but on the first call; see `isLocalSDKFailure`.\n\t\t\tthrow new UnrecoverableError('The Claude client could not be set up', {\n\t\t\t\tcause: describeFailure(error),\n\t\t\t});\n\t\t}\n\n\t\tthis.model = model;\n\t\tthis.systemPrompt = systemPrompt;\n\t\tthis.logger = logger;\n\t}\n\n\tasync send(\n\t\t{ context, tools }: { context: Message[]; tools: ToolDefinition[] },\n\t\tsignal: AbortSignal,\n\t): Promise<LLMResponse> {\n\t\tlet response: AnthropicMessage;\n\n\t\ttry {\n\t\t\tresponse = await this.client.messages.create(\n\t\t\t\t{\n\t\t\t\t\tmodel: this.model,\n\t\t\t\t\tmax_tokens: maxOutputTokens,\n\t\t\t\t\tsystem: this.systemPrompt,\n\t\t\t\t\tmessages: context.flatMap(toClaudeInput),\n\t\t\t\t\t// Left out when empty, so a request without tools stays as it always was.\n\t\t\t\t\t...(tools.length > 0 && { tools: tools.map(toClaudeTool) }),\n\t\t\t\t\tthinking: { type: 'adaptive', display: 'summarized' },\n\t\t\t\t\t// Anthropic caches only on request. This marks the last block, so the next call\n\t\t\t\t\t// reads everything before it from the cache instead of paying for it again.\n\t\t\t\t\tcache_control: { type: 'ephemeral' },\n\t\t\t\t},\n\t\t\t\t{ signal },\n\t\t\t);\n\t\t} catch (error) {\n\t\t\t// The SDK's abort error is named 'Error', so it would pass for a provider failure.\n\t\t\tsignal.throwIfAborted();\n\t\t\tthrow classifyClaudeFailure(error);\n\t\t}\n\n\t\treturn treatErrors(\n\t\t\t() => toLLMResponse(response, this.logger),\n\t\t\tclassifyLocalFailure,\n\t\t\t'Claude response mapping failed',\n\t\t);\n\t}\n}\n"]}
1
+ {"version":3,"file":"claudeLLMClient.js","sourceRoot":"","sources":["../../../src/llm/infrastructure/claudeLLMClient.ts"],"names":[],"mappings":"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","sourcesContent":["import Anthropic, { AnthropicError, APIError } from '@anthropic-ai/sdk';\nimport type {\n\tContentBlock,\n\tContentBlockParam,\n\tMessage as AnthropicMessage,\n\tMessageParam,\n\tOutputConfig,\n\tRedactedThinkingBlockParam,\n\tThinkingBlockParam,\n\tTool as ClaudeTool,\n\tUsage,\n} from '@anthropic-ai/sdk/resources/messages';\nimport {\n\tInvalidAgentConfigError,\n\tUnrecoverableError,\n\twithSpentTokens,\n} from '#src/agent/domain/errors';\nimport {\n\tclassifyHostFailure,\n\tclassifyLocalFailure,\n\tclassifyProviderFailure,\n\tdescribeFailure,\n\ttreatErrors,\n} from '#src/agent/domain/providerFailure';\nimport { isOneOf } from '#src/shared/domain/isOneOf';\nimport type { ILogger } from '#src/shared/domain/logger';\nimport type { Tokens } from '#src/shared/domain/tokens';\nimport { MaxContextError } from '../domain/errors.ts';\nimport type { LLMClient, LLMResponse, StopReason } from '../domain/llm.ts';\nimport type { Message, MessagePart } from '../domain/message.ts';\nimport type { ToolDefinition } from '../domain/tool.ts';\n\n// The Messages API takes full model IDs. Callers name a model by the Agent SDK's alias, so both\n// paths share one vocabulary; this table pins the ID each alias means here, and moves by hand\n// when Anthropic releases a newer model. Haiku is left out: it has no adaptive thinking.\nexport const claudeLLMModels = ['opus', 'fable', 'sonnet'] as const;\nexport type ClaudeLLMModel = (typeof claudeLLMModels)[number];\n\nconst claudeLLMModelIds = {\n\topus: 'claude-opus-5-5',\n\tfable: 'claude-fable-5-1',\n\tsonnet: 'claude-sonnet-5',\n} as const satisfies Record<ClaudeLLMModel, string>;\n\nexport const claudeLLMReasoningEfforts = [\n\t'low',\n\t'medium',\n\t'high',\n\t'xhigh',\n\t'max',\n] as const satisfies readonly NonNullable<OutputConfig['effort']>[];\nexport type ClaudeLLMReasoningEffort = (typeof claudeLLMReasoningEfforts)[number];\n\n// Adaptive thinking spends from this budget too. Above roughly 21,000 the SDK refuses a request\n// that is not streamed, because it could outlast its ten-minute timeout.\nconst maxOutputTokens = 16_000;\n\n// The request itself is wrong or not allowed, so sending it again cannot succeed.\nconst unrecoverableStatuses = [400, 401, 403, 404, 413, 422];\n\n// The account is out of credit, or its key cannot make the request. Resending cannot fix\n// either, whatever status the error arrives with.\nconst unrecoverableErrorTypes = ['billing_error', 'authentication_error', 'permission_error'];\n\n// This client's label on the thinking blocks it keeps for replay; see `ProviderDataPart`.\nconst claudeSource = 'claude';\n\n/**\n * Anthropic rejects a `tool_use` whose input is not an object, with a 400 on every later call.\n * Another provider's call can hold anything else, such as OpenAI arguments that did not parse;\n * its result already told the model the call failed, so sending it as empty loses nothing.\n */\nfunction toClaudeInputObject(input: unknown): unknown {\n\treturn typeof input === 'object' && input !== null && !Array.isArray(input) ? input : {};\n}\n\n/** Thinking crosses back only as the signed block this client kept, never as its text. */\nfunction toClaudeBlocks(part: MessagePart): ContentBlockParam[] {\n\tswitch (part.type) {\n\t\tcase 'text':\n\t\t\t// The API rejects an empty text block.\n\t\t\treturn part.text ? [{ type: 'text', text: part.text }] : [];\n\t\tcase 'toolCall':\n\t\t\treturn [\n\t\t\t\t{ type: 'tool_use', id: part.id, name: part.name, input: toClaudeInputObject(part.input) },\n\t\t\t];\n\t\tcase 'toolResult':\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'tool_result',\n\t\t\t\t\ttool_use_id: part.callId,\n\t\t\t\t\tcontent: part.output,\n\t\t\t\t\tis_error: part.isError,\n\t\t\t\t},\n\t\t\t];\n\t\tcase 'providerData':\n\t\t\t// Another provider's block would be rejected; losing it only costs that provider's reasoning.\n\t\t\treturn part.source === claudeSource\n\t\t\t\t? [part.data as ThinkingBlockParam | RedactedThinkingBlockParam]\n\t\t\t\t: [];\n\t\tcase 'reasoning':\n\t\t\t// Unsigned text cannot be replayed as thinking. The API accepts a turn without it.\n\t\t\treturn [];\n\t}\n}\n\n/**\n * Tool results travel in a user turn, as the API requires. A message left with no blocks is\n * left out whole; the API joins the consecutive user turns that leaves.\n */\nfunction toClaudeInput(message: Message): MessageParam[] {\n\tconst content = message.content.flatMap(toClaudeBlocks);\n\tif (content.length === 0) {\n\t\treturn [];\n\t}\n\n\treturn [{ role: message.role === 'tool' ? 'user' : message.role, content }];\n}\n\n/**\n * Strict: the API constrains the model to the schema, so its input always parses and matches.\n * A schema outside what strict mode supports fails the request with a 400.\n */\nfunction toClaudeTool({ name, description, inputSchema }: ToolDefinition): ClaudeTool {\n\treturn { name, description, input_schema: inputSchema, strict: true };\n}\n\nfunction describeBlock(block: ContentBlock, logger: ILogger): MessagePart[] {\n\tswitch (block.type) {\n\t\tcase 'text':\n\t\t\treturn [{ type: 'text', text: block.text }];\n\t\tcase 'thinking': {\n\t\t\t// Kept signed for replay; the summarized text is only for narration.\n\t\t\tconst signed: ThinkingBlockParam = {\n\t\t\t\ttype: 'thinking',\n\t\t\t\tthinking: block.thinking,\n\t\t\t\tsignature: block.signature,\n\t\t\t};\n\t\t\tconst replay: MessagePart = { type: 'providerData', source: claudeSource, data: signed };\n\t\t\treturn block.thinking ? [{ type: 'reasoning', text: block.thinking }, replay] : [replay];\n\t\t}\n\t\tcase 'redacted_thinking': {\n\t\t\t// Encrypted by design: nothing to narrate, but it still goes back.\n\t\t\tconst redacted: RedactedThinkingBlockParam = { type: 'redacted_thinking', data: block.data };\n\t\t\treturn [{ type: 'providerData', source: claudeSource, data: redacted }];\n\t\t}\n\t\tcase 'tool_use':\n\t\t\treturn [{ type: 'toolCall', id: block.id, name: block.name, input: block.input }];\n\t\tdefault:\n\t\t\t// Server tools are never offered, so any other block is output this client does not expect.\n\t\t\ttreatErrors(\n\t\t\t\t() => {\n\t\t\t\t\tlogger.warn(block, 'Claude returned a content block the client does not map');\n\t\t\t\t},\n\t\t\t\tclassifyHostFailure,\n\t\t\t\t'Claude client logger failed while mapping a response',\n\t\t\t);\n\t\t\treturn [];\n\t}\n}\n\nfunction toStopReason(response: AnthropicMessage): StopReason {\n\tswitch (response.stop_reason) {\n\t\t// `tool_use` is a complete answer that asks for tools: running them is the agent's call.\n\t\tcase 'end_turn':\n\t\tcase 'tool_use':\n\t\t\treturn 'completed';\n\t\tcase 'max_tokens':\n\t\t\treturn 'truncated';\n\t\tcase 'refusal':\n\t\t\treturn 'refused';\n\t\tcase 'model_context_window_exceeded':\n\t\t\tthrow new MaxContextError('The context no longer fits the Claude model', {\n\t\t\t\tcause: 'The response stopped at the model context window.',\n\t\t\t});\n\t\tdefault:\n\t\t\t// `pause_turn` and `stop_sequence` need server tools or stop sequences, and this\n\t\t\t// client sends neither.\n\t\t\tthrow new UnrecoverableError('The Claude response stopped unexpectedly', {\n\t\t\t\tcause: `The response stopped with \"${response.stop_reason ?? 'unknown'}\".`,\n\t\t\t});\n\t}\n}\n\n/** Unlike OpenAI, Anthropic counts cache reads and writes apart from `input_tokens`. */\nfunction toTokens(usage: Usage | undefined, logger: ILogger): Tokens | undefined {\n\tif (!usage) {\n\t\t// Zeros would report a call that may have been billed as free.\n\t\ttreatErrors(\n\t\t\t() => {\n\t\t\t\tlogger.warn('Claude returned a response without usage; it cannot be accounted for');\n\t\t\t},\n\t\t\tclassifyHostFailure,\n\t\t\t'Claude client logger failed while reading usage',\n\t\t);\n\t\treturn undefined;\n\t}\n\n\treturn {\n\t\tinputTokens: usage.input_tokens,\n\t\treadCacheTokens: usage.cache_read_input_tokens ?? 0,\n\t\twrittenCacheTokens: usage.cache_creation_input_tokens ?? 0,\n\t\toutputTokens: usage.output_tokens,\n\t};\n}\n\nfunction toLLMResponse(response: AnthropicMessage, logger: ILogger): LLMResponse {\n\tlet usage: Tokens | undefined;\n\n\ttry {\n\t\t// Inside the try: it warns when the usage is missing, and that warning can throw too.\n\t\tusage = toTokens(response.usage, logger);\n\t\treturn {\n\t\t\tmessage: {\n\t\t\t\trole: 'assistant',\n\t\t\t\tcontent: response.content.flatMap(block => describeBlock(block, logger)),\n\t\t\t},\n\t\t\tusage,\n\t\t\tstopReason: toStopReason(response),\n\t\t};\n\t} catch (error) {\n\t\t// The call was billed whether the response failed or a logger did while mapping it, and\n\t\t// an answer without usage leaves the run's count unknown.\n\t\tthrow withSpentTokens(error, usage, !response.usage);\n\t}\n}\n\n/**\n * The SDK resolves credentials lazily, so a missing key surfaces on the first call, as a plain\n * `Error` the SDK gives no class of its own. An `AnthropicError` that is not an `APIError` never\n * carries an answer from the API: on this client's path it is the SDK refusing to make the\n * call, such as an unresolved profile or a request too large to send without streaming.\n */\nfunction isLocalSDKFailure(error: unknown): boolean {\n\treturn (\n\t\t(error instanceof AnthropicError && !(error instanceof APIError)) ||\n\t\t(error instanceof Error && error.message.startsWith('Could not resolve authentication method'))\n\t);\n}\n\nfunction classifyClaudeFailure(error: unknown): Error {\n\tif (isLocalSDKFailure(error)) {\n\t\t// The API never answered, and calling again would fail the same way.\n\t\treturn new UnrecoverableError('The Claude client cannot send the request', {\n\t\t\tcause: describeFailure(error),\n\t\t});\n\t}\n\tif (error instanceof APIError) {\n\t\t// Anthropic gives an overlong prompt no code of its own, only a 400 with this message.\n\t\tif (error.status === 400 && error.message.includes('prompt is too long')) {\n\t\t\treturn new MaxContextError('The context no longer fits the Claude model', {\n\t\t\t\tcause: describeFailure(error),\n\t\t\t});\n\t\t}\n\t\tif (\n\t\t\tunrecoverableErrorTypes.some(type => type === error.type) ||\n\t\t\tunrecoverableStatuses.some(status => status === error.status)\n\t\t) {\n\t\t\treturn new UnrecoverableError('Claude rejected the request', {\n\t\t\t\tcause: describeFailure(error),\n\t\t\t});\n\t\t}\n\t}\n\n\t// A rate limit, an overload (529), a 5xx or a network failure: a later attempt can succeed.\n\treturn classifyProviderFailure(error, 'The Claude request failed');\n}\n\n/**\n * A stateless `LLMClient` on the Anthropic Messages API, which keeps nothing between calls:\n * every call carries the whole context, so the conversation is MiKode's, not the provider's.\n */\nexport class ClaudeLLMClient implements LLMClient {\n\tprivate readonly client: Anthropic;\n\tprivate readonly model: (typeof claudeLLMModelIds)[ClaudeLLMModel];\n\tprivate readonly reasoningEffort: ClaudeLLMReasoningEffort;\n\tprivate readonly systemPrompt: string;\n\tprivate readonly logger: ILogger;\n\n\t// `model` and `reasoningEffort` are untyped on purpose: this adapter is the one that knows\n\t// what Claude supports.\n\tconstructor({\n\t\tmodel,\n\t\treasoningEffort = 'high',\n\t\tsystemPrompt,\n\t\tlogger,\n\t}: {\n\t\tmodel: string;\n\t\treasoningEffort?: string;\n\t\tsystemPrompt: string;\n\t\tlogger: ILogger;\n\t}) {\n\t\tif (!isOneOf(claudeLLMModels, model)) {\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t`\"${model}\" is not a Claude API model; expected one of: ${claudeLLMModels.join(', ')}`,\n\t\t\t);\n\t\t}\n\t\tif (!isOneOf(claudeLLMReasoningEfforts, reasoningEffort)) {\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t`\"${reasoningEffort}\" is not a Claude API reasoning effort; expected one of: ${claudeLLMReasoningEfforts.join(', ')}`,\n\t\t\t);\n\t\t}\n\n\t\ttry {\n\t\t\t// The SDK's default retries stay on, as OpenAI's do. See \"The provider SDKs keep their\n\t\t\t// transport retries under RetryingAgent\" in decisions.md.\n\t\t\tthis.client = new Anthropic();\n\t\t} catch (error) {\n\t\t\t// Conflicting credential options, for example: building it again cannot fix them. A\n\t\t\t// missing key does not fail here but on the first call; see `isLocalSDKFailure`.\n\t\t\tthrow new UnrecoverableError('The Claude client could not be set up', {\n\t\t\t\tcause: describeFailure(error),\n\t\t\t});\n\t\t}\n\n\t\tthis.model = claudeLLMModelIds[model];\n\t\tthis.reasoningEffort = reasoningEffort;\n\t\tthis.systemPrompt = systemPrompt;\n\t\tthis.logger = logger;\n\t}\n\n\tasync send(\n\t\t{ context, tools }: { context: Message[]; tools: ToolDefinition[] },\n\t\tsignal: AbortSignal,\n\t): Promise<LLMResponse> {\n\t\tlet response: AnthropicMessage;\n\n\t\ttry {\n\t\t\tresponse = await this.client.messages.create(\n\t\t\t\t{\n\t\t\t\t\tmodel: this.model,\n\t\t\t\t\tmax_tokens: maxOutputTokens,\n\t\t\t\t\tsystem: this.systemPrompt,\n\t\t\t\t\tmessages: context.flatMap(toClaudeInput),\n\t\t\t\t\t// Left out when empty, so a request without tools stays as it always was.\n\t\t\t\t\t...(tools.length > 0 && { tools: tools.map(toClaudeTool) }),\n\t\t\t\t\tthinking: { type: 'adaptive', display: 'summarized' },\n\t\t\t\t\toutput_config: { effort: this.reasoningEffort },\n\t\t\t\t\t// Anthropic caches only on request. This marks the last block, so the next call\n\t\t\t\t\t// reads everything before it from the cache instead of paying for it again.\n\t\t\t\t\tcache_control: { type: 'ephemeral' },\n\t\t\t\t},\n\t\t\t\t{ signal },\n\t\t\t);\n\t\t} catch (error) {\n\t\t\t// The SDK's abort error is named 'Error', so it would pass for a provider failure.\n\t\t\tsignal.throwIfAborted();\n\t\t\tthrow classifyClaudeFailure(error);\n\t\t}\n\n\t\treturn treatErrors(\n\t\t\t() => toLLMResponse(response, this.logger),\n\t\t\tclassifyLocalFailure,\n\t\t\t'Claude response mapping failed',\n\t\t);\n\t}\n}\n"]}
@@ -4,6 +4,8 @@ import type { Message } from '../domain/message.ts';
4
4
  import type { ToolDefinition } from '../domain/tool.ts';
5
5
  export declare const openAIModels: readonly ["gpt-6-astra", "gpt-5.6-sol", "gpt-5.6-luna", "gpt-5.6-terra"];
6
6
  export type OpenAIModel = (typeof openAIModels)[number];
7
+ export declare const openAIReasoningEfforts: readonly ["low", "medium", "high", "xhigh", "max"];
8
+ export type OpenAIReasoningEffort = (typeof openAIReasoningEfforts)[number];
7
9
  /**
8
10
  * A stateless `LLMClient` on the OpenAI Responses API. With `store: false` OpenAI keeps
9
11
  * nothing between calls, so every call carries the whole context: the conversation is
@@ -12,10 +14,12 @@ export type OpenAIModel = (typeof openAIModels)[number];
12
14
  export declare class OpenAILLMClient implements LLMClient {
13
15
  private readonly client;
14
16
  private readonly model;
17
+ private readonly reasoningEffort;
15
18
  private readonly systemPrompt;
16
19
  private readonly logger;
17
- constructor({ model, systemPrompt, logger, }: {
20
+ constructor({ model, reasoningEffort, systemPrompt, logger, }: {
18
21
  model: string;
22
+ reasoningEffort?: string;
19
23
  systemPrompt: string;
20
24
  logger: ILogger;
21
25
  });
@@ -10,6 +10,14 @@ export const openAIModels = [
10
10
  'gpt-5.6-luna',
11
11
  'gpt-5.6-terra',
12
12
  ];
13
+ // The efforts both the Responses API and Codex's model catalog list for the models above.
14
+ export const openAIReasoningEfforts = [
15
+ 'low',
16
+ 'medium',
17
+ 'high',
18
+ 'xhigh',
19
+ 'max',
20
+ ];
13
21
  // The request itself is wrong or not allowed, so sending it again cannot succeed.
14
22
  const unrecoverableStatuses = [400, 401, 403, 404, 422];
15
23
  // Out of credit or over a spend limit. These arrive as a 429 like a rate limit, but the account
@@ -200,13 +208,18 @@ function classifyOpenAIFailure(error) {
200
208
  export class OpenAILLMClient {
201
209
  client;
202
210
  model;
211
+ reasoningEffort;
203
212
  systemPrompt;
204
213
  logger;
205
- // `model` is untyped on purpose: this adapter is the one that knows what OpenAI supports.
206
- constructor({ model, systemPrompt, logger, }) {
214
+ // `model` and `reasoningEffort` are untyped on purpose: this adapter is the one that knows
215
+ // what OpenAI supports.
216
+ constructor({ model, reasoningEffort = 'high', systemPrompt, logger, }) {
207
217
  if (!isOneOf(openAIModels, model)) {
208
218
  throw new InvalidAgentConfigError(`"${model}" is not an OpenAI model; expected one of: ${openAIModels.join(', ')}`);
209
219
  }
220
+ if (!isOneOf(openAIReasoningEfforts, reasoningEffort)) {
221
+ throw new InvalidAgentConfigError(`"${reasoningEffort}" is not an OpenAI reasoning effort; expected one of: ${openAIReasoningEfforts.join(', ')}`);
222
+ }
210
223
  try {
211
224
  // The SDK's default retries stay on: they back off and honour `Retry-After`, which
212
225
  // `RetryingAgent` does not. See "The provider SDKs keep their transport retries" in decisions.md.
@@ -219,6 +232,7 @@ export class OpenAILLMClient {
219
232
  });
220
233
  }
221
234
  this.model = model;
235
+ this.reasoningEffort = reasoningEffort;
222
236
  this.systemPrompt = systemPrompt;
223
237
  this.logger = logger;
224
238
  }
@@ -232,7 +246,7 @@ export class OpenAILLMClient {
232
246
  // Left out when empty, so a request without tools stays as it always was.
233
247
  ...(tools.length > 0 && { tools: tools.map(toOpenAITool) }),
234
248
  store: false,
235
- reasoning: { summary: 'auto' },
249
+ reasoning: { effort: this.reasoningEffort, summary: 'auto' },
236
250
  // With `store: false` this is the only way to send the reasoning back.
237
251
  include: ['reasoning.encrypted_content'],
238
252
  }, { signal });
@@ -1 +1 @@
1
- {"version":3,"file":"openAILLMClient.js","sourceRoot":"","sources":["../../../src/llm/infrastructure/openAILLMClient.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAS1C,OAAO,EACN,uBAAuB,EACvB,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,GACf,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACN,mBAAmB,EACnB,oBAAoB,EACpB,uBAAuB,EACvB,eAAe,EACf,WAAW,GACX,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAGrD,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAKtD,+EAA+E;AAC/E,MAAM,CAAC,MAAM,YAAY,GAAG;IAC3B,aAAa;IACb,aAAa;IACb,cAAc;IACd,eAAe;CACN,CAAC;AAGX,kFAAkF;AAClF,MAAM,qBAAqB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAExD,gGAAgG;AAChG,mDAAmD;AACnD,MAAM,mBAAmB,GAAG;IAC3B,oBAAoB;IACpB,0BAA0B;IAC1B,8BAA8B;CAC9B,CAAC;AAEF,gFAAgF;AAChF,MAAM,2BAA2B,GAAG,CAAC,cAAc,EAAE,qBAAqB,CAAC,CAAC;AAE5E,0FAA0F;AAC1F,MAAM,YAAY,GAAG,QAAQ,CAAC;AAE9B,yFAAyF;AACzF,MAAM,gBAAgB,GAAG,wBAAwB,CAAC;AAElD;;;;GAIG;AACH,SAAS,aAAa,CAAC,IAAiB,EAAE,IAA0B;IACnE,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QACnB,KAAK,MAAM;YACV,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACxD,KAAK,UAAU;YACd,OAAO;gBACN;oBACC,IAAI,EAAE,eAAe;oBACrB,OAAO,EAAE,IAAI,CAAC,EAAE;oBAChB,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,+EAA+E;oBAC/E,SAAS,EAAE,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC;iBACnF;aACD,CAAC;QACH,KAAK,YAAY;YAChB,OAAO;gBACN;oBACC,IAAI,EAAE,sBAAsB;oBAC5B,OAAO,EAAE,IAAI,CAAC,MAAM;oBACpB,8FAA8F;oBAC9F,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,gBAAgB,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM;iBACxE;aACD,CAAC;QACH,KAAK,cAAc;YAClB,6FAA6F;YAC7F,OAAO,IAAI,CAAC,MAAM,KAAK,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAA6B,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACjF,KAAK,WAAW;YACf,OAAO,EAAE,CAAC;IACZ,CAAC;AACF,CAAC;AAED,SAAS,aAAa,CAAC,OAAgB;IACtC,0DAA0D;IAC1D,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC;IACjE,OAAO,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;AACnE,CAAC;AAED;;;GAGG;AACH,SAAS,YAAY,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,WAAW,EAAkB;IACvE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AACvF,CAAC;AAED;;;;GAIG;AACH,SAAS,cAAc,CAAC,UAAkB;IACzC,IAAI,CAAC;QACJ,OAAO,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,UAAU,CAAC;IACnB,CAAC;AACF,CAAC;AAED,SAAS,YAAY,CAAC,IAAwB,EAAE,MAAe;IAC9D,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QACnB,KAAK,SAAS;YACb,4DAA4D;YAC5D,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CACrC,OAAO,CAAC,IAAI,KAAK,aAAa,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CACrF,CAAC;QACH,KAAK,WAAW,CAAC,CAAC,CAAC;YAClB,kFAAkF;YAClF,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAClE,MAAM,KAAK,GAAkB,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACvE,oFAAoF;YACpF,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;gBAC5B,MAAM,SAAS,GAA0B;oBACxC,IAAI,EAAE,WAAW;oBACjB,EAAE,EAAE,IAAI,CAAC,EAAE;oBACX,OAAO,EAAE,IAAI,CAAC,OAAO;oBACrB,iBAAiB,EAAE,IAAI,CAAC,iBAAiB;iBACzC,CAAC;gBACF,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;YAC7E,CAAC;YACD,OAAO,KAAK,CAAC;QACd,CAAC;QACD,KAAK,eAAe;YACnB,OAAO;gBACN;oBACC,IAAI,EAAE,UAAU;oBAChB,EAAE,EAAE,IAAI,CAAC,OAAO;oBAChB,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,KAAK,EAAE,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC;iBACrC;aACD,CAAC;QACH;YACC,0FAA0F;YAC1F,WAAW,CACV,GAAG,EAAE;gBACJ,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,wDAAwD,CAAC,CAAC;YAC7E,CAAC,EACD,mBAAmB,EACnB,sDAAsD,CACtD,CAAC;YACF,OAAO,EAAE,CAAC;IACZ,CAAC;AACF,CAAC;AAED,SAAS,YAAY,CAAC,QAAkB;IACvC,IAAI,QAAQ,CAAC,MAAM,KAAK,YAAY,EAAE,CAAC;QACtC,OAAO,QAAQ,CAAC,kBAAkB,EAAE,MAAM,KAAK,gBAAgB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC;IAC3F,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;QACrC,MAAM,KAAK,GACV,QAAQ,CAAC,KAAK,EAAE,OAAO;YACvB,mCAAmC,QAAQ,CAAC,MAAM,IAAI,SAAS,IAAI,CAAC;QACrE,uFAAuF;QACvF,IAAI,QAAQ,CAAC,KAAK,IAAI,2BAA2B,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACjF,MAAM,IAAI,gBAAgB,CAAC,4BAA4B,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QACrE,CAAC;QACD,MAAM,IAAI,kBAAkB,CAAC,sCAAsC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IACjF,CAAC;IAED,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,CACnC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAC3F,CAAC;IACF,OAAO,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC;AAC1C,CAAC;AAED,6EAA6E;AAC7E,SAAS,QAAQ,CAAC,KAAgC,EAAE,MAAe;IAClE,IAAI,CAAC,KAAK,EAAE,CAAC;QACZ,+DAA+D;QAC/D,WAAW,CACV,GAAG,EAAE;YACJ,MAAM,CAAC,IAAI,CAAC,sEAAsE,CAAC,CAAC;QACrF,CAAC,EACD,mBAAmB,EACnB,iDAAiD,CACjD,CAAC;QACF,OAAO,SAAS,CAAC;IAClB,CAAC;IAED,MAAM,EAAE,aAAa,EAAE,kBAAkB,EAAE,GAAG,KAAK,CAAC,oBAAoB,CAAC;IACzE,OAAO;QACN,WAAW,EAAE,KAAK,CAAC,YAAY,GAAG,aAAa,GAAG,kBAAkB;QACpE,eAAe,EAAE,aAAa;QAC9B,kBAAkB,EAAE,kBAAkB;QACtC,YAAY,EAAE,KAAK,CAAC,aAAa;KACjC,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,QAAkB,EAAE,MAAe;IACzD,IAAI,KAAyB,CAAC;IAE9B,IAAI,CAAC;QACJ,sFAAsF;QACtF,KAAK,GAAG,QAAQ,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QACzC,OAAO;YACN,OAAO,EAAE;gBACR,IAAI,EAAE,WAAW;gBACjB,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;aACpE;YACD,KAAK;YACL,UAAU,EAAE,YAAY,CAAC,QAAQ,CAAC;SAClC,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,wFAAwF;QACxF,0DAA0D;QAC1D,MAAM,eAAe,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACtD,CAAC;AACF,CAAC;AAED,SAAS,qBAAqB,CAAC,KAAc;IAC5C,IAAI,KAAK,YAAY,QAAQ,EAAE,CAAC;QAC/B,IAAI,KAAK,CAAC,IAAI,KAAK,yBAAyB,EAAE,CAAC;YAC9C,OAAO,IAAI,eAAe,CAAC,6CAA6C,EAAE;gBACzE,KAAK,EAAE,eAAe,CAAC,KAAK,CAAC;aAC7B,CAAC,CAAC;QACJ,CAAC;QACD,IACC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,CAAC;YACrD,qBAAqB,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,CAAC,EAC5D,CAAC;YACF,OAAO,IAAI,kBAAkB,CAAC,6BAA6B,EAAE;gBAC5D,KAAK,EAAE,eAAe,CAAC,KAAK,CAAC;aAC7B,CAAC,CAAC;QACJ,CAAC;IACF,CAAC;IAED,OAAO,uBAAuB,CAAC,KAAK,EAAE,2BAA2B,CAAC,CAAC;AACpE,CAAC;AAED;;;;GAIG;AACH,MAAM,OAAO,eAAe;IACV,MAAM,CAAS;IACf,KAAK,CAAc;IACnB,YAAY,CAAS;IACrB,MAAM,CAAU;IAEjC,0FAA0F;IAC1F,YAAY,EACX,KAAK,EACL,YAAY,EACZ,MAAM,GAKN;QACA,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC;YACnC,MAAM,IAAI,uBAAuB,CAChC,IAAI,KAAK,8CAA8C,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAChF,CAAC;QACH,CAAC;QAED,IAAI,CAAC;YACJ,mFAAmF;YACnF,kGAAkG;YAClG,IAAI,CAAC,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;QAC5B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,2EAA2E;YAC3E,MAAM,IAAI,kBAAkB,CAAC,uCAAuC,EAAE;gBACrE,KAAK,EAAE,eAAe,CAAC,KAAK,CAAC;aAC7B,CAAC,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,IAAI,CACT,EAAE,OAAO,EAAE,KAAK,EAAmD,EACnE,MAAmB;QAEnB,IAAI,QAAkB,CAAC;QAEvB,IAAI,CAAC;YACJ,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAC5C;gBACC,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,YAAY,EAAE,IAAI,CAAC,YAAY;gBAC/B,KAAK,EAAE,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC;gBACrC,0EAA0E;gBAC1E,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC;gBAC3D,KAAK,EAAE,KAAK;gBACZ,SAAS,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE;gBAC9B,uEAAuE;gBACvE,OAAO,EAAE,CAAC,6BAA6B,CAAC;aACxC,EACD,EAAE,MAAM,EAAE,CACV,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,mFAAmF;YACnF,MAAM,CAAC,cAAc,EAAE,CAAC;YACxB,MAAM,qBAAqB,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC;QAED,OAAO,WAAW,CACjB,GAAG,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,EAC1C,oBAAoB,EACpB,gCAAgC,CAChC,CAAC;IACH,CAAC;CACD","sourcesContent":["import OpenAI, { APIError } from 'openai';\nimport type {\n\tFunctionTool,\n\tResponse,\n\tResponseInputItem,\n\tResponseOutputItem,\n\tResponseReasoningItem,\n\tResponseUsage,\n} from 'openai/resources/responses/responses';\nimport {\n\tInvalidAgentConfigError,\n\tRecoverableError,\n\tUnrecoverableError,\n\twithSpentTokens,\n} from '#src/agent/domain/errors';\nimport {\n\tclassifyHostFailure,\n\tclassifyLocalFailure,\n\tclassifyProviderFailure,\n\tdescribeFailure,\n\ttreatErrors,\n} from '#src/agent/domain/providerFailure';\nimport { isOneOf } from '#src/shared/domain/isOneOf';\nimport type { ILogger } from '#src/shared/domain/logger';\nimport type { Tokens } from '#src/shared/domain/tokens';\nimport { MaxContextError } from '../domain/errors.ts';\nimport type { LLMClient, LLMResponse, StopReason } from '../domain/llm.ts';\nimport type { Message, MessagePart } from '../domain/message.ts';\nimport type { ToolDefinition } from '../domain/tool.ts';\n\n// The SDK types `model` as a plain string, so this list is maintained by hand.\nexport const openAIModels = [\n\t'gpt-6-astra',\n\t'gpt-5.6-sol',\n\t'gpt-5.6-luna',\n\t'gpt-5.6-terra',\n] as const;\nexport type OpenAIModel = (typeof openAIModels)[number];\n\n// The request itself is wrong or not allowed, so sending it again cannot succeed.\nconst unrecoverableStatuses = [400, 401, 403, 404, 422];\n\n// Out of credit or over a spend limit. These arrive as a 429 like a rate limit, but the account\n// stays blocked however often the request is sent.\nconst exhaustedQuotaCodes = [\n\t'insufficient_quota',\n\t'credit_balance_exhausted',\n\t'project_spend_limit_exceeded',\n];\n\n// The codes OpenAI gives a failed response that a new attempt can succeed past.\nconst transientResponseErrorCodes = ['server_error', 'rate_limit_exceeded'];\n\n// This client's label on the reasoning items it keeps for replay; see `ProviderDataPart`.\nconst openAISource = 'openai';\n\n// Marks a failed call's output, which would otherwise read the same as a successful one.\nconst failedToolPrefix = 'The tool call failed: ';\n\n/**\n * OpenAI takes a flat list of items, so every part becomes its own, in order. Reasoning crosses\n * back only as the encrypted item this client kept, which OpenAI requires beside the tool calls\n * that followed it; a reasoning part is its summary, kept for narration.\n */\nfunction toOpenAIItems(part: MessagePart, role: 'user' | 'assistant'): ResponseInputItem[] {\n\tswitch (part.type) {\n\t\tcase 'text':\n\t\t\treturn part.text ? [{ role, content: part.text }] : [];\n\t\tcase 'toolCall':\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'function_call',\n\t\t\t\t\tcall_id: part.id,\n\t\t\t\t\tname: part.name,\n\t\t\t\t\t// A string is arguments that did not parse, sent back as the model wrote them.\n\t\t\t\t\targuments: typeof part.input === 'string' ? part.input : JSON.stringify(part.input),\n\t\t\t\t},\n\t\t\t];\n\t\tcase 'toolResult':\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'function_call_output',\n\t\t\t\t\tcall_id: part.callId,\n\t\t\t\t\t// OpenAI has no error flag, so the text says it; the conversation keeps the output as it was.\n\t\t\t\t\toutput: part.isError ? `${failedToolPrefix}${part.output}` : part.output,\n\t\t\t\t},\n\t\t\t];\n\t\tcase 'providerData':\n\t\t\t// Another provider's item would be rejected; losing it only costs that provider's reasoning.\n\t\t\treturn part.source === openAISource ? [part.data as ResponseReasoningItem] : [];\n\t\tcase 'reasoning':\n\t\t\treturn [];\n\t}\n}\n\nfunction toOpenAIInput(message: Message): ResponseInputItem[] {\n\t// A tool message holds only results, which carry no role.\n\tconst role = message.role === 'assistant' ? 'assistant' : 'user';\n\treturn message.content.flatMap(part => toOpenAIItems(part, role));\n}\n\n/**\n * Strict: the API constrains the model to the schema, so its arguments always parse and match.\n * A schema outside what strict mode supports fails the request with a 400.\n */\nfunction toOpenAITool({ name, description, inputSchema }: ToolDefinition): FunctionTool {\n\treturn { type: 'function', name, description, parameters: inputSchema, strict: true };\n}\n\n/**\n * Strict mode makes arguments that do not parse rare: a response cut short mid-call ends as\n * `truncated` before any tool runs. Should one arrive, the tool gets the raw string and its\n * rejection lets the model correct itself, instead of the run failing.\n */\nfunction parseArguments(serialized: string): unknown {\n\ttry {\n\t\treturn JSON.parse(serialized);\n\t} catch {\n\t\treturn serialized;\n\t}\n}\n\nfunction describeItem(item: ResponseOutputItem, logger: ILogger): MessagePart[] {\n\tswitch (item.type) {\n\t\tcase 'message':\n\t\t\t// A refusal is not an answer: it only sets the stop reason.\n\t\t\treturn item.content.flatMap(content =>\n\t\t\t\tcontent.type === 'output_text' ? [{ type: 'text' as const, text: content.text }] : [],\n\t\t\t);\n\t\tcase 'reasoning': {\n\t\t\t// The summary is the readable part; the encrypted item is what OpenAI reads back.\n\t\t\tconst text = item.summary.map(summary => summary.text).join('\\n');\n\t\t\tconst parts: MessagePart[] = text ? [{ type: 'reasoning', text }] : [];\n\t\t\t// Without its encrypted content the item cannot be replayed: OpenAI stores nothing.\n\t\t\tif (item.encrypted_content) {\n\t\t\t\tconst encrypted: ResponseReasoningItem = {\n\t\t\t\t\ttype: 'reasoning',\n\t\t\t\t\tid: item.id,\n\t\t\t\t\tsummary: item.summary,\n\t\t\t\t\tencrypted_content: item.encrypted_content,\n\t\t\t\t};\n\t\t\t\tparts.push({ type: 'providerData', source: openAISource, data: encrypted });\n\t\t\t}\n\t\t\treturn parts;\n\t\t}\n\t\tcase 'function_call':\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'toolCall',\n\t\t\t\t\tid: item.call_id,\n\t\t\t\t\tname: item.name,\n\t\t\t\t\tinput: parseArguments(item.arguments),\n\t\t\t\t},\n\t\t\t];\n\t\tdefault:\n\t\t\t// No built-in tools are offered, so any other item is output this client does not expect.\n\t\t\ttreatErrors(\n\t\t\t\t() => {\n\t\t\t\t\tlogger.warn(item, 'OpenAI returned an output item the client does not map');\n\t\t\t\t},\n\t\t\t\tclassifyHostFailure,\n\t\t\t\t'OpenAI client logger failed while mapping a response',\n\t\t\t);\n\t\t\treturn [];\n\t}\n}\n\nfunction toStopReason(response: Response): StopReason {\n\tif (response.status === 'incomplete') {\n\t\treturn response.incomplete_details?.reason === 'content_filter' ? 'refused' : 'truncated';\n\t}\n\tif (response.status !== 'completed') {\n\t\tconst cause =\n\t\t\tresponse.error?.message ??\n\t\t\t`The response ended with status \"${response.status ?? 'unknown'}\".`;\n\t\t// The same failure is retried when it arrives as an SDK error, so it must be here too.\n\t\tif (response.error && transientResponseErrorCodes.includes(response.error.code)) {\n\t\t\tthrow new RecoverableError('The OpenAI response failed', { cause });\n\t\t}\n\t\tthrow new UnrecoverableError('The OpenAI response did not complete', { cause });\n\t}\n\n\tconst refused = response.output.some(\n\t\titem => item.type === 'message' && item.content.some(content => content.type === 'refusal'),\n\t);\n\treturn refused ? 'refused' : 'completed';\n}\n\n/** OpenAI counts both cache reads and cache writes inside `input_tokens`. */\nfunction toTokens(usage: ResponseUsage | undefined, logger: ILogger): Tokens | undefined {\n\tif (!usage) {\n\t\t// Zeros would report a call that may have been billed as free.\n\t\ttreatErrors(\n\t\t\t() => {\n\t\t\t\tlogger.warn('OpenAI returned a response without usage; it cannot be accounted for');\n\t\t\t},\n\t\t\tclassifyHostFailure,\n\t\t\t'OpenAI client logger failed while reading usage',\n\t\t);\n\t\treturn undefined;\n\t}\n\n\tconst { cached_tokens, cache_write_tokens } = usage.input_tokens_details;\n\treturn {\n\t\tinputTokens: usage.input_tokens - cached_tokens - cache_write_tokens,\n\t\treadCacheTokens: cached_tokens,\n\t\twrittenCacheTokens: cache_write_tokens,\n\t\toutputTokens: usage.output_tokens,\n\t};\n}\n\nfunction toLLMResponse(response: Response, logger: ILogger): LLMResponse {\n\tlet usage: Tokens | undefined;\n\n\ttry {\n\t\t// Inside the try: it warns when the usage is missing, and that warning can throw too.\n\t\tusage = toTokens(response.usage, logger);\n\t\treturn {\n\t\t\tmessage: {\n\t\t\t\trole: 'assistant',\n\t\t\t\tcontent: response.output.flatMap(item => describeItem(item, logger)),\n\t\t\t},\n\t\t\tusage,\n\t\t\tstopReason: toStopReason(response),\n\t\t};\n\t} catch (error) {\n\t\t// The call was billed whether the response failed or a logger did while mapping it, and\n\t\t// an answer without usage leaves the run's count unknown.\n\t\tthrow withSpentTokens(error, usage, !response.usage);\n\t}\n}\n\nfunction classifyOpenAIFailure(error: unknown): Error {\n\tif (error instanceof APIError) {\n\t\tif (error.code === 'context_length_exceeded') {\n\t\t\treturn new MaxContextError('The context no longer fits the OpenAI model', {\n\t\t\t\tcause: describeFailure(error),\n\t\t\t});\n\t\t}\n\t\tif (\n\t\t\texhaustedQuotaCodes.some(code => code === error.code) ||\n\t\t\tunrecoverableStatuses.some(status => status === error.status)\n\t\t) {\n\t\t\treturn new UnrecoverableError('OpenAI rejected the request', {\n\t\t\t\tcause: describeFailure(error),\n\t\t\t});\n\t\t}\n\t}\n\n\treturn classifyProviderFailure(error, 'The OpenAI request failed');\n}\n\n/**\n * A stateless `LLMClient` on the OpenAI Responses API. With `store: false` OpenAI keeps\n * nothing between calls, so every call carries the whole context: the conversation is\n * MiKode's, not the provider's.\n */\nexport class OpenAILLMClient implements LLMClient {\n\tprivate readonly client: OpenAI;\n\tprivate readonly model: OpenAIModel;\n\tprivate readonly systemPrompt: string;\n\tprivate readonly logger: ILogger;\n\n\t// `model` is untyped on purpose: this adapter is the one that knows what OpenAI supports.\n\tconstructor({\n\t\tmodel,\n\t\tsystemPrompt,\n\t\tlogger,\n\t}: {\n\t\tmodel: string;\n\t\tsystemPrompt: string;\n\t\tlogger: ILogger;\n\t}) {\n\t\tif (!isOneOf(openAIModels, model)) {\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t`\"${model}\" is not an OpenAI model; expected one of: ${openAIModels.join(', ')}`,\n\t\t\t);\n\t\t}\n\n\t\ttry {\n\t\t\t// The SDK's default retries stay on: they back off and honour `Retry-After`, which\n\t\t\t// `RetryingAgent` does not. See \"The provider SDKs keep their transport retries\" in decisions.md.\n\t\t\tthis.client = new OpenAI();\n\t\t} catch (error) {\n\t\t\t// A missing API key, for example: building the client again cannot fix it.\n\t\t\tthrow new UnrecoverableError('The OpenAI client could not be set up', {\n\t\t\t\tcause: describeFailure(error),\n\t\t\t});\n\t\t}\n\n\t\tthis.model = model;\n\t\tthis.systemPrompt = systemPrompt;\n\t\tthis.logger = logger;\n\t}\n\n\tasync send(\n\t\t{ context, tools }: { context: Message[]; tools: ToolDefinition[] },\n\t\tsignal: AbortSignal,\n\t): Promise<LLMResponse> {\n\t\tlet response: Response;\n\n\t\ttry {\n\t\t\tresponse = await this.client.responses.create(\n\t\t\t\t{\n\t\t\t\t\tmodel: this.model,\n\t\t\t\t\tinstructions: this.systemPrompt,\n\t\t\t\t\tinput: context.flatMap(toOpenAIInput),\n\t\t\t\t\t// Left out when empty, so a request without tools stays as it always was.\n\t\t\t\t\t...(tools.length > 0 && { tools: tools.map(toOpenAITool) }),\n\t\t\t\t\tstore: false,\n\t\t\t\t\treasoning: { summary: 'auto' },\n\t\t\t\t\t// With `store: false` this is the only way to send the reasoning back.\n\t\t\t\t\tinclude: ['reasoning.encrypted_content'],\n\t\t\t\t},\n\t\t\t\t{ signal },\n\t\t\t);\n\t\t} catch (error) {\n\t\t\t// The SDK's abort error is named 'Error', so it would pass for a provider failure.\n\t\t\tsignal.throwIfAborted();\n\t\t\tthrow classifyOpenAIFailure(error);\n\t\t}\n\n\t\treturn treatErrors(\n\t\t\t() => toLLMResponse(response, this.logger),\n\t\t\tclassifyLocalFailure,\n\t\t\t'OpenAI response mapping failed',\n\t\t);\n\t}\n}\n"]}
1
+ {"version":3,"file":"openAILLMClient.js","sourceRoot":"","sources":["../../../src/llm/infrastructure/openAILLMClient.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAU1C,OAAO,EACN,uBAAuB,EACvB,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,GACf,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACN,mBAAmB,EACnB,oBAAoB,EACpB,uBAAuB,EACvB,eAAe,EACf,WAAW,GACX,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAGrD,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAKtD,+EAA+E;AAC/E,MAAM,CAAC,MAAM,YAAY,GAAG;IAC3B,aAAa;IACb,aAAa;IACb,cAAc;IACd,eAAe;CACN,CAAC;AAGX,0FAA0F;AAC1F,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACrC,KAAK;IACL,QAAQ;IACR,MAAM;IACN,OAAO;IACP,KAAK;CACsD,CAAC;AAG7D,kFAAkF;AAClF,MAAM,qBAAqB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAExD,gGAAgG;AAChG,mDAAmD;AACnD,MAAM,mBAAmB,GAAG;IAC3B,oBAAoB;IACpB,0BAA0B;IAC1B,8BAA8B;CAC9B,CAAC;AAEF,gFAAgF;AAChF,MAAM,2BAA2B,GAAG,CAAC,cAAc,EAAE,qBAAqB,CAAC,CAAC;AAE5E,0FAA0F;AAC1F,MAAM,YAAY,GAAG,QAAQ,CAAC;AAE9B,yFAAyF;AACzF,MAAM,gBAAgB,GAAG,wBAAwB,CAAC;AAElD;;;;GAIG;AACH,SAAS,aAAa,CAAC,IAAiB,EAAE,IAA0B;IACnE,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QACnB,KAAK,MAAM;YACV,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACxD,KAAK,UAAU;YACd,OAAO;gBACN;oBACC,IAAI,EAAE,eAAe;oBACrB,OAAO,EAAE,IAAI,CAAC,EAAE;oBAChB,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,+EAA+E;oBAC/E,SAAS,EAAE,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC;iBACnF;aACD,CAAC;QACH,KAAK,YAAY;YAChB,OAAO;gBACN;oBACC,IAAI,EAAE,sBAAsB;oBAC5B,OAAO,EAAE,IAAI,CAAC,MAAM;oBACpB,8FAA8F;oBAC9F,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,gBAAgB,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM;iBACxE;aACD,CAAC;QACH,KAAK,cAAc;YAClB,6FAA6F;YAC7F,OAAO,IAAI,CAAC,MAAM,KAAK,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAA6B,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACjF,KAAK,WAAW;YACf,OAAO,EAAE,CAAC;IACZ,CAAC;AACF,CAAC;AAED,SAAS,aAAa,CAAC,OAAgB;IACtC,0DAA0D;IAC1D,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC;IACjE,OAAO,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;AACnE,CAAC;AAED;;;GAGG;AACH,SAAS,YAAY,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,WAAW,EAAkB;IACvE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AACvF,CAAC;AAED;;;;GAIG;AACH,SAAS,cAAc,CAAC,UAAkB;IACzC,IAAI,CAAC;QACJ,OAAO,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,UAAU,CAAC;IACnB,CAAC;AACF,CAAC;AAED,SAAS,YAAY,CAAC,IAAwB,EAAE,MAAe;IAC9D,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QACnB,KAAK,SAAS;YACb,4DAA4D;YAC5D,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CACrC,OAAO,CAAC,IAAI,KAAK,aAAa,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CACrF,CAAC;QACH,KAAK,WAAW,CAAC,CAAC,CAAC;YAClB,kFAAkF;YAClF,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAClE,MAAM,KAAK,GAAkB,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACvE,oFAAoF;YACpF,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;gBAC5B,MAAM,SAAS,GAA0B;oBACxC,IAAI,EAAE,WAAW;oBACjB,EAAE,EAAE,IAAI,CAAC,EAAE;oBACX,OAAO,EAAE,IAAI,CAAC,OAAO;oBACrB,iBAAiB,EAAE,IAAI,CAAC,iBAAiB;iBACzC,CAAC;gBACF,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;YAC7E,CAAC;YACD,OAAO,KAAK,CAAC;QACd,CAAC;QACD,KAAK,eAAe;YACnB,OAAO;gBACN;oBACC,IAAI,EAAE,UAAU;oBAChB,EAAE,EAAE,IAAI,CAAC,OAAO;oBAChB,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,KAAK,EAAE,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC;iBACrC;aACD,CAAC;QACH;YACC,0FAA0F;YAC1F,WAAW,CACV,GAAG,EAAE;gBACJ,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,wDAAwD,CAAC,CAAC;YAC7E,CAAC,EACD,mBAAmB,EACnB,sDAAsD,CACtD,CAAC;YACF,OAAO,EAAE,CAAC;IACZ,CAAC;AACF,CAAC;AAED,SAAS,YAAY,CAAC,QAAkB;IACvC,IAAI,QAAQ,CAAC,MAAM,KAAK,YAAY,EAAE,CAAC;QACtC,OAAO,QAAQ,CAAC,kBAAkB,EAAE,MAAM,KAAK,gBAAgB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC;IAC3F,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;QACrC,MAAM,KAAK,GACV,QAAQ,CAAC,KAAK,EAAE,OAAO;YACvB,mCAAmC,QAAQ,CAAC,MAAM,IAAI,SAAS,IAAI,CAAC;QACrE,uFAAuF;QACvF,IAAI,QAAQ,CAAC,KAAK,IAAI,2BAA2B,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACjF,MAAM,IAAI,gBAAgB,CAAC,4BAA4B,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QACrE,CAAC;QACD,MAAM,IAAI,kBAAkB,CAAC,sCAAsC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IACjF,CAAC;IAED,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,CACnC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAC3F,CAAC;IACF,OAAO,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC;AAC1C,CAAC;AAED,6EAA6E;AAC7E,SAAS,QAAQ,CAAC,KAAgC,EAAE,MAAe;IAClE,IAAI,CAAC,KAAK,EAAE,CAAC;QACZ,+DAA+D;QAC/D,WAAW,CACV,GAAG,EAAE;YACJ,MAAM,CAAC,IAAI,CAAC,sEAAsE,CAAC,CAAC;QACrF,CAAC,EACD,mBAAmB,EACnB,iDAAiD,CACjD,CAAC;QACF,OAAO,SAAS,CAAC;IAClB,CAAC;IAED,MAAM,EAAE,aAAa,EAAE,kBAAkB,EAAE,GAAG,KAAK,CAAC,oBAAoB,CAAC;IACzE,OAAO;QACN,WAAW,EAAE,KAAK,CAAC,YAAY,GAAG,aAAa,GAAG,kBAAkB;QACpE,eAAe,EAAE,aAAa;QAC9B,kBAAkB,EAAE,kBAAkB;QACtC,YAAY,EAAE,KAAK,CAAC,aAAa;KACjC,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,QAAkB,EAAE,MAAe;IACzD,IAAI,KAAyB,CAAC;IAE9B,IAAI,CAAC;QACJ,sFAAsF;QACtF,KAAK,GAAG,QAAQ,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QACzC,OAAO;YACN,OAAO,EAAE;gBACR,IAAI,EAAE,WAAW;gBACjB,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;aACpE;YACD,KAAK;YACL,UAAU,EAAE,YAAY,CAAC,QAAQ,CAAC;SAClC,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,wFAAwF;QACxF,0DAA0D;QAC1D,MAAM,eAAe,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACtD,CAAC;AACF,CAAC;AAED,SAAS,qBAAqB,CAAC,KAAc;IAC5C,IAAI,KAAK,YAAY,QAAQ,EAAE,CAAC;QAC/B,IAAI,KAAK,CAAC,IAAI,KAAK,yBAAyB,EAAE,CAAC;YAC9C,OAAO,IAAI,eAAe,CAAC,6CAA6C,EAAE;gBACzE,KAAK,EAAE,eAAe,CAAC,KAAK,CAAC;aAC7B,CAAC,CAAC;QACJ,CAAC;QACD,IACC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,CAAC;YACrD,qBAAqB,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,CAAC,EAC5D,CAAC;YACF,OAAO,IAAI,kBAAkB,CAAC,6BAA6B,EAAE;gBAC5D,KAAK,EAAE,eAAe,CAAC,KAAK,CAAC;aAC7B,CAAC,CAAC;QACJ,CAAC;IACF,CAAC;IAED,OAAO,uBAAuB,CAAC,KAAK,EAAE,2BAA2B,CAAC,CAAC;AACpE,CAAC;AAED;;;;GAIG;AACH,MAAM,OAAO,eAAe;IACV,MAAM,CAAS;IACf,KAAK,CAAc;IACnB,eAAe,CAAwB;IACvC,YAAY,CAAS;IACrB,MAAM,CAAU;IAEjC,2FAA2F;IAC3F,wBAAwB;IACxB,YAAY,EACX,KAAK,EACL,eAAe,GAAG,MAAM,EACxB,YAAY,EACZ,MAAM,GAMN;QACA,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC;YACnC,MAAM,IAAI,uBAAuB,CAChC,IAAI,KAAK,8CAA8C,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAChF,CAAC;QACH,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,sBAAsB,EAAE,eAAe,CAAC,EAAE,CAAC;YACvD,MAAM,IAAI,uBAAuB,CAChC,IAAI,eAAe,yDAAyD,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC/G,CAAC;QACH,CAAC;QAED,IAAI,CAAC;YACJ,mFAAmF;YACnF,kGAAkG;YAClG,IAAI,CAAC,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;QAC5B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,2EAA2E;YAC3E,MAAM,IAAI,kBAAkB,CAAC,uCAAuC,EAAE;gBACrE,KAAK,EAAE,eAAe,CAAC,KAAK,CAAC;aAC7B,CAAC,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;QACvC,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,IAAI,CACT,EAAE,OAAO,EAAE,KAAK,EAAmD,EACnE,MAAmB;QAEnB,IAAI,QAAkB,CAAC;QAEvB,IAAI,CAAC;YACJ,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAC5C;gBACC,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,YAAY,EAAE,IAAI,CAAC,YAAY;gBAC/B,KAAK,EAAE,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC;gBACrC,0EAA0E;gBAC1E,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC;gBAC3D,KAAK,EAAE,KAAK;gBACZ,SAAS,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE;gBAC5D,uEAAuE;gBACvE,OAAO,EAAE,CAAC,6BAA6B,CAAC;aACxC,EACD,EAAE,MAAM,EAAE,CACV,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,mFAAmF;YACnF,MAAM,CAAC,cAAc,EAAE,CAAC;YACxB,MAAM,qBAAqB,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC;QAED,OAAO,WAAW,CACjB,GAAG,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,EAC1C,oBAAoB,EACpB,gCAAgC,CAChC,CAAC;IACH,CAAC;CACD","sourcesContent":["import OpenAI, { APIError } from 'openai';\nimport type { ReasoningEffort } from 'openai/resources/shared';\nimport type {\n\tFunctionTool,\n\tResponse,\n\tResponseInputItem,\n\tResponseOutputItem,\n\tResponseReasoningItem,\n\tResponseUsage,\n} from 'openai/resources/responses/responses';\nimport {\n\tInvalidAgentConfigError,\n\tRecoverableError,\n\tUnrecoverableError,\n\twithSpentTokens,\n} from '#src/agent/domain/errors';\nimport {\n\tclassifyHostFailure,\n\tclassifyLocalFailure,\n\tclassifyProviderFailure,\n\tdescribeFailure,\n\ttreatErrors,\n} from '#src/agent/domain/providerFailure';\nimport { isOneOf } from '#src/shared/domain/isOneOf';\nimport type { ILogger } from '#src/shared/domain/logger';\nimport type { Tokens } from '#src/shared/domain/tokens';\nimport { MaxContextError } from '../domain/errors.ts';\nimport type { LLMClient, LLMResponse, StopReason } from '../domain/llm.ts';\nimport type { Message, MessagePart } from '../domain/message.ts';\nimport type { ToolDefinition } from '../domain/tool.ts';\n\n// The SDK types `model` as a plain string, so this list is maintained by hand.\nexport const openAIModels = [\n\t'gpt-6-astra',\n\t'gpt-5.6-sol',\n\t'gpt-5.6-luna',\n\t'gpt-5.6-terra',\n] as const;\nexport type OpenAIModel = (typeof openAIModels)[number];\n\n// The efforts both the Responses API and Codex's model catalog list for the models above.\nexport const openAIReasoningEfforts = [\n\t'low',\n\t'medium',\n\t'high',\n\t'xhigh',\n\t'max',\n] as const satisfies readonly NonNullable<ReasoningEffort>[];\nexport type OpenAIReasoningEffort = (typeof openAIReasoningEfforts)[number];\n\n// The request itself is wrong or not allowed, so sending it again cannot succeed.\nconst unrecoverableStatuses = [400, 401, 403, 404, 422];\n\n// Out of credit or over a spend limit. These arrive as a 429 like a rate limit, but the account\n// stays blocked however often the request is sent.\nconst exhaustedQuotaCodes = [\n\t'insufficient_quota',\n\t'credit_balance_exhausted',\n\t'project_spend_limit_exceeded',\n];\n\n// The codes OpenAI gives a failed response that a new attempt can succeed past.\nconst transientResponseErrorCodes = ['server_error', 'rate_limit_exceeded'];\n\n// This client's label on the reasoning items it keeps for replay; see `ProviderDataPart`.\nconst openAISource = 'openai';\n\n// Marks a failed call's output, which would otherwise read the same as a successful one.\nconst failedToolPrefix = 'The tool call failed: ';\n\n/**\n * OpenAI takes a flat list of items, so every part becomes its own, in order. Reasoning crosses\n * back only as the encrypted item this client kept, which OpenAI requires beside the tool calls\n * that followed it; a reasoning part is its summary, kept for narration.\n */\nfunction toOpenAIItems(part: MessagePart, role: 'user' | 'assistant'): ResponseInputItem[] {\n\tswitch (part.type) {\n\t\tcase 'text':\n\t\t\treturn part.text ? [{ role, content: part.text }] : [];\n\t\tcase 'toolCall':\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'function_call',\n\t\t\t\t\tcall_id: part.id,\n\t\t\t\t\tname: part.name,\n\t\t\t\t\t// A string is arguments that did not parse, sent back as the model wrote them.\n\t\t\t\t\targuments: typeof part.input === 'string' ? part.input : JSON.stringify(part.input),\n\t\t\t\t},\n\t\t\t];\n\t\tcase 'toolResult':\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'function_call_output',\n\t\t\t\t\tcall_id: part.callId,\n\t\t\t\t\t// OpenAI has no error flag, so the text says it; the conversation keeps the output as it was.\n\t\t\t\t\toutput: part.isError ? `${failedToolPrefix}${part.output}` : part.output,\n\t\t\t\t},\n\t\t\t];\n\t\tcase 'providerData':\n\t\t\t// Another provider's item would be rejected; losing it only costs that provider's reasoning.\n\t\t\treturn part.source === openAISource ? [part.data as ResponseReasoningItem] : [];\n\t\tcase 'reasoning':\n\t\t\treturn [];\n\t}\n}\n\nfunction toOpenAIInput(message: Message): ResponseInputItem[] {\n\t// A tool message holds only results, which carry no role.\n\tconst role = message.role === 'assistant' ? 'assistant' : 'user';\n\treturn message.content.flatMap(part => toOpenAIItems(part, role));\n}\n\n/**\n * Strict: the API constrains the model to the schema, so its arguments always parse and match.\n * A schema outside what strict mode supports fails the request with a 400.\n */\nfunction toOpenAITool({ name, description, inputSchema }: ToolDefinition): FunctionTool {\n\treturn { type: 'function', name, description, parameters: inputSchema, strict: true };\n}\n\n/**\n * Strict mode makes arguments that do not parse rare: a response cut short mid-call ends as\n * `truncated` before any tool runs. Should one arrive, the tool gets the raw string and its\n * rejection lets the model correct itself, instead of the run failing.\n */\nfunction parseArguments(serialized: string): unknown {\n\ttry {\n\t\treturn JSON.parse(serialized);\n\t} catch {\n\t\treturn serialized;\n\t}\n}\n\nfunction describeItem(item: ResponseOutputItem, logger: ILogger): MessagePart[] {\n\tswitch (item.type) {\n\t\tcase 'message':\n\t\t\t// A refusal is not an answer: it only sets the stop reason.\n\t\t\treturn item.content.flatMap(content =>\n\t\t\t\tcontent.type === 'output_text' ? [{ type: 'text' as const, text: content.text }] : [],\n\t\t\t);\n\t\tcase 'reasoning': {\n\t\t\t// The summary is the readable part; the encrypted item is what OpenAI reads back.\n\t\t\tconst text = item.summary.map(summary => summary.text).join('\\n');\n\t\t\tconst parts: MessagePart[] = text ? [{ type: 'reasoning', text }] : [];\n\t\t\t// Without its encrypted content the item cannot be replayed: OpenAI stores nothing.\n\t\t\tif (item.encrypted_content) {\n\t\t\t\tconst encrypted: ResponseReasoningItem = {\n\t\t\t\t\ttype: 'reasoning',\n\t\t\t\t\tid: item.id,\n\t\t\t\t\tsummary: item.summary,\n\t\t\t\t\tencrypted_content: item.encrypted_content,\n\t\t\t\t};\n\t\t\t\tparts.push({ type: 'providerData', source: openAISource, data: encrypted });\n\t\t\t}\n\t\t\treturn parts;\n\t\t}\n\t\tcase 'function_call':\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'toolCall',\n\t\t\t\t\tid: item.call_id,\n\t\t\t\t\tname: item.name,\n\t\t\t\t\tinput: parseArguments(item.arguments),\n\t\t\t\t},\n\t\t\t];\n\t\tdefault:\n\t\t\t// No built-in tools are offered, so any other item is output this client does not expect.\n\t\t\ttreatErrors(\n\t\t\t\t() => {\n\t\t\t\t\tlogger.warn(item, 'OpenAI returned an output item the client does not map');\n\t\t\t\t},\n\t\t\t\tclassifyHostFailure,\n\t\t\t\t'OpenAI client logger failed while mapping a response',\n\t\t\t);\n\t\t\treturn [];\n\t}\n}\n\nfunction toStopReason(response: Response): StopReason {\n\tif (response.status === 'incomplete') {\n\t\treturn response.incomplete_details?.reason === 'content_filter' ? 'refused' : 'truncated';\n\t}\n\tif (response.status !== 'completed') {\n\t\tconst cause =\n\t\t\tresponse.error?.message ??\n\t\t\t`The response ended with status \"${response.status ?? 'unknown'}\".`;\n\t\t// The same failure is retried when it arrives as an SDK error, so it must be here too.\n\t\tif (response.error && transientResponseErrorCodes.includes(response.error.code)) {\n\t\t\tthrow new RecoverableError('The OpenAI response failed', { cause });\n\t\t}\n\t\tthrow new UnrecoverableError('The OpenAI response did not complete', { cause });\n\t}\n\n\tconst refused = response.output.some(\n\t\titem => item.type === 'message' && item.content.some(content => content.type === 'refusal'),\n\t);\n\treturn refused ? 'refused' : 'completed';\n}\n\n/** OpenAI counts both cache reads and cache writes inside `input_tokens`. */\nfunction toTokens(usage: ResponseUsage | undefined, logger: ILogger): Tokens | undefined {\n\tif (!usage) {\n\t\t// Zeros would report a call that may have been billed as free.\n\t\ttreatErrors(\n\t\t\t() => {\n\t\t\t\tlogger.warn('OpenAI returned a response without usage; it cannot be accounted for');\n\t\t\t},\n\t\t\tclassifyHostFailure,\n\t\t\t'OpenAI client logger failed while reading usage',\n\t\t);\n\t\treturn undefined;\n\t}\n\n\tconst { cached_tokens, cache_write_tokens } = usage.input_tokens_details;\n\treturn {\n\t\tinputTokens: usage.input_tokens - cached_tokens - cache_write_tokens,\n\t\treadCacheTokens: cached_tokens,\n\t\twrittenCacheTokens: cache_write_tokens,\n\t\toutputTokens: usage.output_tokens,\n\t};\n}\n\nfunction toLLMResponse(response: Response, logger: ILogger): LLMResponse {\n\tlet usage: Tokens | undefined;\n\n\ttry {\n\t\t// Inside the try: it warns when the usage is missing, and that warning can throw too.\n\t\tusage = toTokens(response.usage, logger);\n\t\treturn {\n\t\t\tmessage: {\n\t\t\t\trole: 'assistant',\n\t\t\t\tcontent: response.output.flatMap(item => describeItem(item, logger)),\n\t\t\t},\n\t\t\tusage,\n\t\t\tstopReason: toStopReason(response),\n\t\t};\n\t} catch (error) {\n\t\t// The call was billed whether the response failed or a logger did while mapping it, and\n\t\t// an answer without usage leaves the run's count unknown.\n\t\tthrow withSpentTokens(error, usage, !response.usage);\n\t}\n}\n\nfunction classifyOpenAIFailure(error: unknown): Error {\n\tif (error instanceof APIError) {\n\t\tif (error.code === 'context_length_exceeded') {\n\t\t\treturn new MaxContextError('The context no longer fits the OpenAI model', {\n\t\t\t\tcause: describeFailure(error),\n\t\t\t});\n\t\t}\n\t\tif (\n\t\t\texhaustedQuotaCodes.some(code => code === error.code) ||\n\t\t\tunrecoverableStatuses.some(status => status === error.status)\n\t\t) {\n\t\t\treturn new UnrecoverableError('OpenAI rejected the request', {\n\t\t\t\tcause: describeFailure(error),\n\t\t\t});\n\t\t}\n\t}\n\n\treturn classifyProviderFailure(error, 'The OpenAI request failed');\n}\n\n/**\n * A stateless `LLMClient` on the OpenAI Responses API. With `store: false` OpenAI keeps\n * nothing between calls, so every call carries the whole context: the conversation is\n * MiKode's, not the provider's.\n */\nexport class OpenAILLMClient implements LLMClient {\n\tprivate readonly client: OpenAI;\n\tprivate readonly model: OpenAIModel;\n\tprivate readonly reasoningEffort: OpenAIReasoningEffort;\n\tprivate readonly systemPrompt: string;\n\tprivate readonly logger: ILogger;\n\n\t// `model` and `reasoningEffort` are untyped on purpose: this adapter is the one that knows\n\t// what OpenAI supports.\n\tconstructor({\n\t\tmodel,\n\t\treasoningEffort = 'high',\n\t\tsystemPrompt,\n\t\tlogger,\n\t}: {\n\t\tmodel: string;\n\t\treasoningEffort?: string;\n\t\tsystemPrompt: string;\n\t\tlogger: ILogger;\n\t}) {\n\t\tif (!isOneOf(openAIModels, model)) {\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t`\"${model}\" is not an OpenAI model; expected one of: ${openAIModels.join(', ')}`,\n\t\t\t);\n\t\t}\n\t\tif (!isOneOf(openAIReasoningEfforts, reasoningEffort)) {\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t`\"${reasoningEffort}\" is not an OpenAI reasoning effort; expected one of: ${openAIReasoningEfforts.join(', ')}`,\n\t\t\t);\n\t\t}\n\n\t\ttry {\n\t\t\t// The SDK's default retries stay on: they back off and honour `Retry-After`, which\n\t\t\t// `RetryingAgent` does not. See \"The provider SDKs keep their transport retries\" in decisions.md.\n\t\t\tthis.client = new OpenAI();\n\t\t} catch (error) {\n\t\t\t// A missing API key, for example: building the client again cannot fix it.\n\t\t\tthrow new UnrecoverableError('The OpenAI client could not be set up', {\n\t\t\t\tcause: describeFailure(error),\n\t\t\t});\n\t\t}\n\n\t\tthis.model = model;\n\t\tthis.reasoningEffort = reasoningEffort;\n\t\tthis.systemPrompt = systemPrompt;\n\t\tthis.logger = logger;\n\t}\n\n\tasync send(\n\t\t{ context, tools }: { context: Message[]; tools: ToolDefinition[] },\n\t\tsignal: AbortSignal,\n\t): Promise<LLMResponse> {\n\t\tlet response: Response;\n\n\t\ttry {\n\t\t\tresponse = await this.client.responses.create(\n\t\t\t\t{\n\t\t\t\t\tmodel: this.model,\n\t\t\t\t\tinstructions: this.systemPrompt,\n\t\t\t\t\tinput: context.flatMap(toOpenAIInput),\n\t\t\t\t\t// Left out when empty, so a request without tools stays as it always was.\n\t\t\t\t\t...(tools.length > 0 && { tools: tools.map(toOpenAITool) }),\n\t\t\t\t\tstore: false,\n\t\t\t\t\treasoning: { effort: this.reasoningEffort, summary: 'auto' },\n\t\t\t\t\t// With `store: false` this is the only way to send the reasoning back.\n\t\t\t\t\tinclude: ['reasoning.encrypted_content'],\n\t\t\t\t},\n\t\t\t\t{ signal },\n\t\t\t);\n\t\t} catch (error) {\n\t\t\t// The SDK's abort error is named 'Error', so it would pass for a provider failure.\n\t\t\tsignal.throwIfAborted();\n\t\t\tthrow classifyOpenAIFailure(error);\n\t\t}\n\n\t\treturn treatErrors(\n\t\t\t() => toLLMResponse(response, this.logger),\n\t\t\tclassifyLocalFailure,\n\t\t\t'OpenAI response mapping failed',\n\t\t);\n\t}\n}\n"]}
@@ -0,0 +1,15 @@
1
+ import type { Agent, AgentResponse, Callback } from '#src/agent/domain/agent';
2
+ /**
3
+ * Gives an agent without a system prompt its role's instructions at the head of every prompt.
4
+ * An agent with one, such as `LLMAgent`, holds them there instead, so they are not resent
5
+ * inside each prompt.
6
+ */
7
+ export declare class InstructedAgent implements Agent {
8
+ private readonly inner;
9
+ private readonly instructions;
10
+ constructor({ inner, instructions }: {
11
+ inner: Agent;
12
+ instructions: string;
13
+ });
14
+ run(prompt: string, signal: AbortSignal, callback: Callback): Promise<AgentResponse>;
15
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Gives an agent without a system prompt its role's instructions at the head of every prompt.
3
+ * An agent with one, such as `LLMAgent`, holds them there instead, so they are not resent
4
+ * inside each prompt.
5
+ */
6
+ export class InstructedAgent {
7
+ inner;
8
+ instructions;
9
+ constructor({ inner, instructions }) {
10
+ this.inner = inner;
11
+ this.instructions = instructions;
12
+ }
13
+ run(prompt, signal, callback) {
14
+ return this.inner.run(`${this.instructions}\n\n${prompt}`, signal, callback);
15
+ }
16
+ }
17
+ //# sourceMappingURL=instructedAgent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instructedAgent.js","sourceRoot":"","sources":["../../../../src/orchestration/domain/model/instructedAgent.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,OAAO,eAAe;IACV,KAAK,CAAQ;IACb,YAAY,CAAS;IAEtC,YAAY,EAAE,KAAK,EAAE,YAAY,EAA0C;QAC1E,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;IAClC,CAAC;IAED,GAAG,CAAC,MAAc,EAAE,MAAmB,EAAE,QAAkB;QAC1D,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,YAAY,OAAO,MAAM,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC9E,CAAC;CACD","sourcesContent":["import type { Agent, AgentResponse, Callback } from '#src/agent/domain/agent';\n\n/**\n * Gives an agent without a system prompt its role's instructions at the head of every prompt.\n * An agent with one, such as `LLMAgent`, holds them there instead, so they are not resent\n * inside each prompt.\n */\nexport class InstructedAgent implements Agent {\n\tprivate readonly inner: Agent;\n\tprivate readonly instructions: string;\n\n\tconstructor({ inner, instructions }: { inner: Agent; instructions: string }) {\n\t\tthis.inner = inner;\n\t\tthis.instructions = instructions;\n\t}\n\n\trun(prompt: string, signal: AbortSignal, callback: Callback): Promise<AgentResponse> {\n\t\treturn this.inner.run(`${this.instructions}\\n\\n${prompt}`, signal, callback);\n\t}\n}\n"]}
@@ -2,6 +2,9 @@ import type { Agent, AgentResponse, Callback } from '#src/agent/domain/agent';
2
2
  import type { ReviewerDecision } from './reviewerDecision.ts';
3
3
  import type { Validator } from '../interface/validator.ts';
4
4
  import type { ILogger } from '#src/shared/domain/logger';
5
+ export declare const plannerInstructions = "You are the planner agent. Read the relevant repository code and create a concise, engineering-grade plan for the executor. Cover the requested behaviour, structural issues, affected files or symbols, API contracts and boundaries, important failure paths, and meaningful tests. Keep the plan focused on the current request and do not require an architecture redesign yet.";
6
+ export declare const executorInstructions = "You are the executor agent. Read the relevant repository code and implement the original user request according to the planner's current plan. Preserve contracts and boundaries, handle important failure paths, keep the change focused, and add or update meaningful tests. Inspect and change the code; do not merely describe what should be done.";
7
+ export declare const reviewerInstructions = "You are the reviewer agent. Independently inspect the repository, the current diff, and relevant surrounding code; do not rely on the executor's narrative. Evaluate the original request first; plan compliance is secondary and provides supporting context. Check correctness and logic errors, important failure paths, unused or artificial abstractions, races or shared state, API contract breaks, boundary violations, regressions, scope, and test quality. If it is correct, respond with JSON only: {\"decision\":\"approved\"}. If it is not correct, respond with JSON only: {\"decision\":\"rejected\",\"feedback\":\"list concrete, prioritized findings and the required direction for each\"}. The feedback must contain actionable engineering findings, not a general summary.";
5
8
  export declare class OrchestratorAgent implements Agent {
6
9
  private plannerAgent;
7
10
  private executorAgent;
@@ -1,20 +1,22 @@
1
1
  import { addTokens } from '#src/shared/domain/tokens';
2
2
  import { RecoverableError, UnrecoverableError, withSpentTokens } from '#src/agent/domain/errors';
3
3
  import { classifyHostFailure, treatErrors } from '#src/agent/domain/providerFailure';
4
+ // Each role's standing instructions, kept apart from the data of a round so an agent with a
5
+ // system prompt can hold them there instead of receiving them again in every prompt. An agent
6
+ // without one gets them at the head of each prompt; see `InstructedAgent`.
7
+ export const plannerInstructions = 'You are the planner agent. Read the relevant repository code and create a concise, engineering-grade plan for the executor. Cover the requested behaviour, structural issues, affected files or symbols, API contracts and boundaries, important failure paths, and meaningful tests. Keep the plan focused on the current request and do not require an architecture redesign yet.';
8
+ export const executorInstructions = "You are the executor agent. Read the relevant repository code and implement the original user request according to the planner's current plan. Preserve contracts and boundaries, handle important failure paths, keep the change focused, and add or update meaningful tests. Inspect and change the code; do not merely describe what should be done.";
9
+ export const reviewerInstructions = 'You are the reviewer agent. Independently inspect the repository, the current diff, and relevant surrounding code; do not rely on the executor\'s narrative. Evaluate the original request first; plan compliance is secondary and provides supporting context. Check correctness and logic errors, important failure paths, unused or artificial abstractions, races or shared state, API contract breaks, boundary violations, regressions, scope, and test quality. If it is correct, respond with JSON only: {"decision":"approved"}. If it is not correct, respond with JSON only: {"decision":"rejected","feedback":"list concrete, prioritized findings and the required direction for each"}. The feedback must contain actionable engineering findings, not a general summary.';
4
10
  const getPlannerPrompt = (userPrompt, previousFailureReason) => {
5
11
  const feedback = previousFailureReason
6
12
  ? `\n\nFeedback from the previous attempt:\n---\n${previousFailureReason}\n---`
7
13
  : '';
8
- return `You are the planner agent. Read the relevant repository code and create a concise, engineering-grade plan for the executor. Cover the requested behaviour, structural issues, affected files or symbols, API contracts and boundaries, important failure paths, and meaningful tests. Keep the plan focused on the current request and do not require an architecture redesign yet.
9
-
10
- Original user request:
14
+ return `Original user request:
11
15
  ---
12
16
  ${userPrompt}
13
17
  ---${feedback}`;
14
18
  };
15
- const getExecutorPrompt = (userPrompt, plannerPrompt) => `You are the executor agent. Read the relevant repository code and implement the original user request according to the planner's current plan. Preserve contracts and boundaries, handle important failure paths, keep the change focused, and add or update meaningful tests. Inspect and change the code; do not merely describe what should be done.
16
-
17
- Original user request:
19
+ const getExecutorPrompt = (userPrompt, plannerPrompt) => `Original user request:
18
20
  ---
19
21
  ${userPrompt}
20
22
  ---
@@ -27,9 +29,7 @@ const getReviewerPrompt = (userPrompt, plannerPrompt, executorResult, parseFailu
27
29
  const retryNotice = parseFailureReason
28
30
  ? `\n\nYour previous response could not be used: ${parseFailureReason} Respond with JSON only, matching the schema exactly, with no surrounding text and no markdown code fences.`
29
31
  : '';
30
- return `You are the reviewer agent. Independently inspect the repository, the current diff, and relevant surrounding code; do not rely on the executor's narrative. Evaluate the original request first; plan compliance is secondary and provides supporting context. Check correctness and logic errors, important failure paths, unused or artificial abstractions, races or shared state, API contract breaks, boundary violations, regressions, scope, and test quality. If it is correct, respond with JSON only: {"decision":"approved"}. If it is not correct, respond with JSON only: {"decision":"rejected","feedback":"list concrete, prioritized findings and the required direction for each"}. The feedback must contain actionable engineering findings, not a general summary.
31
-
32
- Original user request:
32
+ return `Original user request:
33
33
  ---
34
34
  ${userPrompt}
35
35
  ---
@@ -1 +1 @@
1
- {"version":3,"file":"orchestratorAgent.js","sourceRoot":"","sources":["../../../../src/orchestration/domain/model/orchestratorAgent.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Agent, AgentResponse, Callback } from '#src/agent/domain/agent';\nimport { addTokens, type Tokens } from '#src/shared/domain/tokens';\nimport { RecoverableError, UnrecoverableError, withSpentTokens } from '#src/agent/domain/errors';\nimport type { ReviewerDecision } from './reviewerDecision.ts';\nimport type { Validator } from '../interface/validator.ts';\nimport type { ILogger } from '#src/shared/domain/logger';\nimport { classifyHostFailure, treatErrors } from '#src/agent/domain/providerFailure';\n\nconst getPlannerPrompt = (userPrompt: string, previousFailureReason?: string) => {\n\tconst feedback = previousFailureReason\n\t\t? `\\n\\nFeedback from the previous attempt:\\n---\\n${previousFailureReason}\\n---`\n\t\t: '';\n\n\treturn `You are the planner agent. Read the relevant repository code and create a concise, engineering-grade plan for the executor. Cover the requested behaviour, structural issues, affected files or symbols, API contracts and boundaries, important failure paths, and meaningful tests. Keep the plan focused on the current request and do not require an architecture redesign yet.\n\nOriginal user request:\n---\n${userPrompt}\n---${feedback}`;\n};\n\nconst getExecutorPrompt = (userPrompt: string, plannerPrompt: string) =>\n\t`You are the executor agent. Read the relevant repository code and implement the original user request according to the planner's current plan. Preserve contracts and boundaries, handle important failure paths, keep the change focused, and add or update meaningful tests. Inspect and change the code; do not merely describe what should be done.\n\nOriginal user request:\n---\n${userPrompt}\n---\n\nCurrent implementation plan:\n---\n${plannerPrompt}\n---`;\n\nconst getReviewerPrompt = (\n\tuserPrompt: string,\n\tplannerPrompt: string,\n\texecutorResult: string,\n\tparseFailureReason?: string,\n) => {\n\tconst retryNotice = parseFailureReason\n\t\t? `\\n\\nYour previous response could not be used: ${parseFailureReason} Respond with JSON only, matching the schema exactly, with no surrounding text and no markdown code fences.`\n\t\t: '';\n\n\treturn `You are the reviewer agent. Independently inspect the repository, the current diff, and relevant surrounding code; do not rely on the executor's narrative. Evaluate the original request first; plan compliance is secondary and provides supporting context. Check correctness and logic errors, important failure paths, unused or artificial abstractions, races or shared state, API contract breaks, boundary violations, regressions, scope, and test quality. If it is correct, respond with JSON only: {\"decision\":\"approved\"}. If it is not correct, respond with JSON only: {\"decision\":\"rejected\",\"feedback\":\"list concrete, prioritized findings and the required direction for each\"}. The feedback must contain actionable engineering findings, not a general summary.\n\nOriginal user request:\n---\n${userPrompt}\n---\n\nPlanner's current plan:\n---\n${plannerPrompt}\n---\n\nExecutor response (context only; verify it independently):\n---\n${executorResult}\n---${retryNotice}`;\n};\n\ninterface RunTotals {\n\ttokens: Tokens | undefined;\n\t/** A role answered without usage, so the run's total cannot be known. */\n\tunreported: boolean;\n}\n\n// Every role's response counts, even an empty one that starts another round: it was billed.\nfunction addToTotals(totals: RunTotals, response: AgentResponse): void {\n\tif (!response.tokens) totals.unreported = true;\n\ttotals.tokens = addTokens(totals.tokens, response.tokens);\n}\n\n/** Unknown rather than partial: a sum missing a billed call must not look complete. */\nfunction runTokens(totals: RunTotals): Tokens | undefined {\n\treturn totals.unreported ? undefined : totals.tokens;\n}\n\nfunction stripCodeFence(text: string): string {\n\tconst match = /^```(?:json)?\\s*([\\s\\S]*?)\\s*```$/.exec(text.trim());\n\treturn match?.[1] ?? text;\n}\n\nfunction parseReviewerDecision(\n\tresponse: AgentResponse,\n\treviewerDecisionValidator: Validator<ReviewerDecision>,\n): ReviewerDecision {\n\tif (!response.response) {\n\t\tthrow new RecoverableError(\"There's no decision from the reviewer, something went wrong\", {\n\t\t\tcause: 'Reviewer decision is missing.',\n\t\t});\n\t}\n\n\tlet parsedResponse: unknown;\n\ttry {\n\t\tparsedResponse = JSON.parse(stripCodeFence(response.response));\n\t} catch {\n\t\tthrow new RecoverableError('Reviewer returned an invalid decision', {\n\t\t\tcause: 'Reviewer response must be valid JSON.',\n\t\t});\n\t}\n\n\tconst decision = reviewerDecisionValidator.validate(parsedResponse);\n\tif (!decision) {\n\t\tthrow new RecoverableError('Reviewer returned an invalid decision', {\n\t\t\tcause: 'Reviewer response did not match the decision schema.',\n\t\t});\n\t}\n\n\treturn decision;\n}\n\nexport class OrchestratorAgent implements Agent {\n\tprivate plannerAgent: Agent;\n\tprivate executorAgent: Agent;\n\tprivate reviewerAgent: Agent;\n\tprivate reviewerDecisionValidator: Validator<ReviewerDecision>;\n\tprivate maxAttempts: number;\n\tprivate logger: ILogger;\n\n\tconstructor({\n\t\tplannerAgent,\n\t\texecutorAgent,\n\t\treviewerAgent,\n\t\treviewerDecisionValidator,\n\t\tmaxAttempts = 3,\n\t\tlogger,\n\t}: {\n\t\tplannerAgent: Agent;\n\t\texecutorAgent: Agent;\n\t\treviewerAgent: Agent;\n\t\treviewerDecisionValidator: Validator<ReviewerDecision>;\n\t\tmaxAttempts?: number;\n\t\tlogger: ILogger;\n\t}) {\n\t\tif (!Number.isInteger(maxAttempts) || maxAttempts < 1) {\n\t\t\tthrow new RangeError('maxAttempts must be a positive integer');\n\t\t}\n\n\t\tthis.plannerAgent = plannerAgent;\n\t\tthis.executorAgent = executorAgent;\n\t\tthis.reviewerAgent = reviewerAgent;\n\t\tthis.reviewerDecisionValidator = reviewerDecisionValidator;\n\t\tthis.maxAttempts = maxAttempts;\n\t\tthis.logger = logger;\n\t}\n\n\tasync run(prompt: string, signal: AbortSignal, callback: Callback): Promise<AgentResponse> {\n\t\tconst start = Date.now();\n\t\t// Per invocation, not per instance: the CLI keeps one orchestrator for a whole session.\n\t\tconst totals: RunTotals = { tokens: undefined, unreported: false };\n\n\t\ttry {\n\t\t\tawait this.runRounds(prompt, signal, callback, totals);\n\t\t} catch (error) {\n\t\t\t// A failing role carries its own tokens; the roles before it are in the totals.\n\t\t\tthrow withSpentTokens(error, totals.tokens, totals.unreported);\n\t\t}\n\n\t\treturn {\n\t\t\tresponse: 'All job has finished',\n\t\t\ttokens: runTokens(totals),\n\t\t\t// The whole run's wall clock, so it matches what the consumer waited.\n\t\t\tduration: (Date.now() - start) / 1000,\n\t\t};\n\t}\n\n\tprivate async runRounds(\n\t\tprompt: string,\n\t\tsignal: AbortSignal,\n\t\tcallback: Callback,\n\t\ttotals: RunTotals,\n\t): Promise<void> {\n\t\tlet lastFailureReason: string | undefined;\n\n\t\tfor (let attempt = 1; attempt <= this.maxAttempts; attempt++) {\n\t\t\tconst isLastAttempt = attempt === this.maxAttempts;\n\n\t\t\tconst plannerResponse = await this.plannerAgent.run(\n\t\t\t\tgetPlannerPrompt(prompt, lastFailureReason),\n\t\t\t\tsignal,\n\t\t\t\tcallback,\n\t\t\t);\n\t\t\taddToTotals(totals, plannerResponse);\n\n\t\t\tif (!plannerResponse.response) {\n\t\t\t\tlastFailureReason = 'The planner produced no response.';\n\t\t\t\tif (isLastAttempt) {\n\t\t\t\t\tthrow new UnrecoverableError('Max attempts exhausted', { cause: lastFailureReason });\n\t\t\t\t}\n\t\t\t\tthis.warn(\n\t\t\t\t\t`Attempt ${String(attempt)}/${String(this.maxAttempts)}: the planner produced no response; starting another round`,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst executorResponse = await this.executorAgent.run(\n\t\t\t\tgetExecutorPrompt(prompt, plannerResponse.response),\n\t\t\t\tsignal,\n\t\t\t\tcallback,\n\t\t\t);\n\t\t\taddToTotals(totals, executorResponse);\n\n\t\t\tif (!executorResponse.response) {\n\t\t\t\tlastFailureReason = 'The executor produced no response.';\n\t\t\t\tif (isLastAttempt) {\n\t\t\t\t\tthrow new UnrecoverableError('Max attempts exhausted', { cause: lastFailureReason });\n\t\t\t\t}\n\n\t\t\t\tthis.warn(\n\t\t\t\t\t`Attempt ${String(attempt)}/${String(this.maxAttempts)}: the executor produced no response; starting another round`,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst reviewerDecision = await this.getReviewerDecision(\n\t\t\t\tprompt,\n\t\t\t\tplannerResponse.response,\n\t\t\t\texecutorResponse.response,\n\t\t\t\tsignal,\n\t\t\t\tcallback,\n\t\t\t\ttotals,\n\t\t\t);\n\n\t\t\tif (reviewerDecision.decision === 'approved') return;\n\n\t\t\tlastFailureReason = reviewerDecision.feedback;\n\t\t\tif (isLastAttempt) {\n\t\t\t\tthrow new UnrecoverableError('Max attempts exhausted', { cause: lastFailureReason });\n\t\t\t}\n\n\t\t\tthis.warn(\n\t\t\t\t`Attempt ${String(attempt)}/${String(this.maxAttempts)}: the reviewer rejected the round; starting another with its feedback`,\n\t\t\t\tlastFailureReason,\n\t\t\t);\n\t\t}\n\n\t\tthrow new UnrecoverableError('Max attempts exhausted', {\n\t\t\tcause: lastFailureReason ?? 'Unknown failure.',\n\t\t});\n\t}\n\n\t// Retries only the reviewer call on a malformed decision, instead of redoing\n\t// planning/execution — a bad or unparsable reviewer response is not evidence\n\t// the plan or the implementation were wrong.\n\tprivate async getReviewerDecision(\n\t\tuserPrompt: string,\n\t\tplannerPrompt: string,\n\t\texecutorResult: string,\n\t\tsignal: AbortSignal,\n\t\tcallback: Callback,\n\t\ttotals: RunTotals,\n\t): Promise<ReviewerDecision> {\n\t\tlet parseFailureReason: string | undefined;\n\n\t\tfor (let attempt = 1; attempt <= this.maxAttempts; attempt++) {\n\t\t\tconst reviewerResponse = await this.reviewerAgent.run(\n\t\t\t\tgetReviewerPrompt(userPrompt, plannerPrompt, executorResult, parseFailureReason),\n\t\t\t\tsignal,\n\t\t\t\tcallback,\n\t\t\t);\n\n\t\t\taddToTotals(totals, reviewerResponse);\n\n\t\t\ttry {\n\t\t\t\treturn parseReviewerDecision(reviewerResponse, this.reviewerDecisionValidator);\n\t\t\t} catch (error) {\n\t\t\t\tif (!(error instanceof RecoverableError)) throw error;\n\n\t\t\t\tparseFailureReason = error.cause;\n\t\t\t\tif (attempt === this.maxAttempts) {\n\t\t\t\t\tthrow new UnrecoverableError('Max attempts exhausted', { cause: parseFailureReason });\n\t\t\t\t}\n\n\t\t\t\tthis.warn(\n\t\t\t\t\t`Reviewer decision ${String(attempt)}/${String(this.maxAttempts)} was unusable; asking the reviewer again`,\n\t\t\t\t\tparseFailureReason,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tthrow new UnrecoverableError('Max attempts exhausted', {\n\t\t\tcause: parseFailureReason ?? 'Unknown failure.',\n\t\t});\n\t}\n\n\tprivate warn(...args: unknown[]): void {\n\t\ttreatErrors(\n\t\t\t() => {\n\t\t\t\tthis.logger.warn(...args);\n\t\t\t},\n\t\t\tclassifyHostFailure,\n\t\t\t'Orchestrator logger failed while reporting a retry',\n\t\t);\n\t}\n}\n"]}
1
+ {"version":3,"file":"orchestratorAgent.js","sourceRoot":"","sources":["../../../../src/orchestration/domain/model/orchestratorAgent.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Agent, AgentResponse, Callback } from '#src/agent/domain/agent';\nimport { addTokens, type Tokens } from '#src/shared/domain/tokens';\nimport { RecoverableError, UnrecoverableError, withSpentTokens } from '#src/agent/domain/errors';\nimport type { ReviewerDecision } from './reviewerDecision.ts';\nimport type { Validator } from '../interface/validator.ts';\nimport type { ILogger } from '#src/shared/domain/logger';\nimport { classifyHostFailure, treatErrors } from '#src/agent/domain/providerFailure';\n\n// Each role's standing instructions, kept apart from the data of a round so an agent with a\n// system prompt can hold them there instead of receiving them again in every prompt. An agent\n// without one gets them at the head of each prompt; see `InstructedAgent`.\nexport const plannerInstructions =\n\t'You are the planner agent. Read the relevant repository code and create a concise, engineering-grade plan for the executor. Cover the requested behaviour, structural issues, affected files or symbols, API contracts and boundaries, important failure paths, and meaningful tests. Keep the plan focused on the current request and do not require an architecture redesign yet.';\n\nexport const executorInstructions =\n\t\"You are the executor agent. Read the relevant repository code and implement the original user request according to the planner's current plan. Preserve contracts and boundaries, handle important failure paths, keep the change focused, and add or update meaningful tests. Inspect and change the code; do not merely describe what should be done.\";\n\nexport const reviewerInstructions =\n\t'You are the reviewer agent. Independently inspect the repository, the current diff, and relevant surrounding code; do not rely on the executor\\'s narrative. Evaluate the original request first; plan compliance is secondary and provides supporting context. Check correctness and logic errors, important failure paths, unused or artificial abstractions, races or shared state, API contract breaks, boundary violations, regressions, scope, and test quality. If it is correct, respond with JSON only: {\"decision\":\"approved\"}. If it is not correct, respond with JSON only: {\"decision\":\"rejected\",\"feedback\":\"list concrete, prioritized findings and the required direction for each\"}. The feedback must contain actionable engineering findings, not a general summary.';\n\nconst getPlannerPrompt = (userPrompt: string, previousFailureReason?: string) => {\n\tconst feedback = previousFailureReason\n\t\t? `\\n\\nFeedback from the previous attempt:\\n---\\n${previousFailureReason}\\n---`\n\t\t: '';\n\n\treturn `Original user request:\n---\n${userPrompt}\n---${feedback}`;\n};\n\nconst getExecutorPrompt = (userPrompt: string, plannerPrompt: string) =>\n\t`Original user request:\n---\n${userPrompt}\n---\n\nCurrent implementation plan:\n---\n${plannerPrompt}\n---`;\n\nconst getReviewerPrompt = (\n\tuserPrompt: string,\n\tplannerPrompt: string,\n\texecutorResult: string,\n\tparseFailureReason?: string,\n) => {\n\tconst retryNotice = parseFailureReason\n\t\t? `\\n\\nYour previous response could not be used: ${parseFailureReason} Respond with JSON only, matching the schema exactly, with no surrounding text and no markdown code fences.`\n\t\t: '';\n\n\treturn `Original user request:\n---\n${userPrompt}\n---\n\nPlanner's current plan:\n---\n${plannerPrompt}\n---\n\nExecutor response (context only; verify it independently):\n---\n${executorResult}\n---${retryNotice}`;\n};\n\ninterface RunTotals {\n\ttokens: Tokens | undefined;\n\t/** A role answered without usage, so the run's total cannot be known. */\n\tunreported: boolean;\n}\n\n// Every role's response counts, even an empty one that starts another round: it was billed.\nfunction addToTotals(totals: RunTotals, response: AgentResponse): void {\n\tif (!response.tokens) totals.unreported = true;\n\ttotals.tokens = addTokens(totals.tokens, response.tokens);\n}\n\n/** Unknown rather than partial: a sum missing a billed call must not look complete. */\nfunction runTokens(totals: RunTotals): Tokens | undefined {\n\treturn totals.unreported ? undefined : totals.tokens;\n}\n\nfunction stripCodeFence(text: string): string {\n\tconst match = /^```(?:json)?\\s*([\\s\\S]*?)\\s*```$/.exec(text.trim());\n\treturn match?.[1] ?? text;\n}\n\nfunction parseReviewerDecision(\n\tresponse: AgentResponse,\n\treviewerDecisionValidator: Validator<ReviewerDecision>,\n): ReviewerDecision {\n\tif (!response.response) {\n\t\tthrow new RecoverableError(\"There's no decision from the reviewer, something went wrong\", {\n\t\t\tcause: 'Reviewer decision is missing.',\n\t\t});\n\t}\n\n\tlet parsedResponse: unknown;\n\ttry {\n\t\tparsedResponse = JSON.parse(stripCodeFence(response.response));\n\t} catch {\n\t\tthrow new RecoverableError('Reviewer returned an invalid decision', {\n\t\t\tcause: 'Reviewer response must be valid JSON.',\n\t\t});\n\t}\n\n\tconst decision = reviewerDecisionValidator.validate(parsedResponse);\n\tif (!decision) {\n\t\tthrow new RecoverableError('Reviewer returned an invalid decision', {\n\t\t\tcause: 'Reviewer response did not match the decision schema.',\n\t\t});\n\t}\n\n\treturn decision;\n}\n\nexport class OrchestratorAgent implements Agent {\n\tprivate plannerAgent: Agent;\n\tprivate executorAgent: Agent;\n\tprivate reviewerAgent: Agent;\n\tprivate reviewerDecisionValidator: Validator<ReviewerDecision>;\n\tprivate maxAttempts: number;\n\tprivate logger: ILogger;\n\n\tconstructor({\n\t\tplannerAgent,\n\t\texecutorAgent,\n\t\treviewerAgent,\n\t\treviewerDecisionValidator,\n\t\tmaxAttempts = 3,\n\t\tlogger,\n\t}: {\n\t\tplannerAgent: Agent;\n\t\texecutorAgent: Agent;\n\t\treviewerAgent: Agent;\n\t\treviewerDecisionValidator: Validator<ReviewerDecision>;\n\t\tmaxAttempts?: number;\n\t\tlogger: ILogger;\n\t}) {\n\t\tif (!Number.isInteger(maxAttempts) || maxAttempts < 1) {\n\t\t\tthrow new RangeError('maxAttempts must be a positive integer');\n\t\t}\n\n\t\tthis.plannerAgent = plannerAgent;\n\t\tthis.executorAgent = executorAgent;\n\t\tthis.reviewerAgent = reviewerAgent;\n\t\tthis.reviewerDecisionValidator = reviewerDecisionValidator;\n\t\tthis.maxAttempts = maxAttempts;\n\t\tthis.logger = logger;\n\t}\n\n\tasync run(prompt: string, signal: AbortSignal, callback: Callback): Promise<AgentResponse> {\n\t\tconst start = Date.now();\n\t\t// Per invocation, not per instance: the CLI keeps one orchestrator for a whole session.\n\t\tconst totals: RunTotals = { tokens: undefined, unreported: false };\n\n\t\ttry {\n\t\t\tawait this.runRounds(prompt, signal, callback, totals);\n\t\t} catch (error) {\n\t\t\t// A failing role carries its own tokens; the roles before it are in the totals.\n\t\t\tthrow withSpentTokens(error, totals.tokens, totals.unreported);\n\t\t}\n\n\t\treturn {\n\t\t\tresponse: 'All job has finished',\n\t\t\ttokens: runTokens(totals),\n\t\t\t// The whole run's wall clock, so it matches what the consumer waited.\n\t\t\tduration: (Date.now() - start) / 1000,\n\t\t};\n\t}\n\n\tprivate async runRounds(\n\t\tprompt: string,\n\t\tsignal: AbortSignal,\n\t\tcallback: Callback,\n\t\ttotals: RunTotals,\n\t): Promise<void> {\n\t\tlet lastFailureReason: string | undefined;\n\n\t\tfor (let attempt = 1; attempt <= this.maxAttempts; attempt++) {\n\t\t\tconst isLastAttempt = attempt === this.maxAttempts;\n\n\t\t\tconst plannerResponse = await this.plannerAgent.run(\n\t\t\t\tgetPlannerPrompt(prompt, lastFailureReason),\n\t\t\t\tsignal,\n\t\t\t\tcallback,\n\t\t\t);\n\t\t\taddToTotals(totals, plannerResponse);\n\n\t\t\tif (!plannerResponse.response) {\n\t\t\t\tlastFailureReason = 'The planner produced no response.';\n\t\t\t\tif (isLastAttempt) {\n\t\t\t\t\tthrow new UnrecoverableError('Max attempts exhausted', { cause: lastFailureReason });\n\t\t\t\t}\n\t\t\t\tthis.warn(\n\t\t\t\t\t`Attempt ${String(attempt)}/${String(this.maxAttempts)}: the planner produced no response; starting another round`,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst executorResponse = await this.executorAgent.run(\n\t\t\t\tgetExecutorPrompt(prompt, plannerResponse.response),\n\t\t\t\tsignal,\n\t\t\t\tcallback,\n\t\t\t);\n\t\t\taddToTotals(totals, executorResponse);\n\n\t\t\tif (!executorResponse.response) {\n\t\t\t\tlastFailureReason = 'The executor produced no response.';\n\t\t\t\tif (isLastAttempt) {\n\t\t\t\t\tthrow new UnrecoverableError('Max attempts exhausted', { cause: lastFailureReason });\n\t\t\t\t}\n\n\t\t\t\tthis.warn(\n\t\t\t\t\t`Attempt ${String(attempt)}/${String(this.maxAttempts)}: the executor produced no response; starting another round`,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst reviewerDecision = await this.getReviewerDecision(\n\t\t\t\tprompt,\n\t\t\t\tplannerResponse.response,\n\t\t\t\texecutorResponse.response,\n\t\t\t\tsignal,\n\t\t\t\tcallback,\n\t\t\t\ttotals,\n\t\t\t);\n\n\t\t\tif (reviewerDecision.decision === 'approved') return;\n\n\t\t\tlastFailureReason = reviewerDecision.feedback;\n\t\t\tif (isLastAttempt) {\n\t\t\t\tthrow new UnrecoverableError('Max attempts exhausted', { cause: lastFailureReason });\n\t\t\t}\n\n\t\t\tthis.warn(\n\t\t\t\t`Attempt ${String(attempt)}/${String(this.maxAttempts)}: the reviewer rejected the round; starting another with its feedback`,\n\t\t\t\tlastFailureReason,\n\t\t\t);\n\t\t}\n\n\t\tthrow new UnrecoverableError('Max attempts exhausted', {\n\t\t\tcause: lastFailureReason ?? 'Unknown failure.',\n\t\t});\n\t}\n\n\t// Retries only the reviewer call on a malformed decision, instead of redoing\n\t// planning/execution — a bad or unparsable reviewer response is not evidence\n\t// the plan or the implementation were wrong.\n\tprivate async getReviewerDecision(\n\t\tuserPrompt: string,\n\t\tplannerPrompt: string,\n\t\texecutorResult: string,\n\t\tsignal: AbortSignal,\n\t\tcallback: Callback,\n\t\ttotals: RunTotals,\n\t): Promise<ReviewerDecision> {\n\t\tlet parseFailureReason: string | undefined;\n\n\t\tfor (let attempt = 1; attempt <= this.maxAttempts; attempt++) {\n\t\t\tconst reviewerResponse = await this.reviewerAgent.run(\n\t\t\t\tgetReviewerPrompt(userPrompt, plannerPrompt, executorResult, parseFailureReason),\n\t\t\t\tsignal,\n\t\t\t\tcallback,\n\t\t\t);\n\n\t\t\taddToTotals(totals, reviewerResponse);\n\n\t\t\ttry {\n\t\t\t\treturn parseReviewerDecision(reviewerResponse, this.reviewerDecisionValidator);\n\t\t\t} catch (error) {\n\t\t\t\tif (!(error instanceof RecoverableError)) throw error;\n\n\t\t\t\tparseFailureReason = error.cause;\n\t\t\t\tif (attempt === this.maxAttempts) {\n\t\t\t\t\tthrow new UnrecoverableError('Max attempts exhausted', { cause: parseFailureReason });\n\t\t\t\t}\n\n\t\t\t\tthis.warn(\n\t\t\t\t\t`Reviewer decision ${String(attempt)}/${String(this.maxAttempts)} was unusable; asking the reviewer again`,\n\t\t\t\t\tparseFailureReason,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tthrow new UnrecoverableError('Max attempts exhausted', {\n\t\t\tcause: parseFailureReason ?? 'Unknown failure.',\n\t\t});\n\t}\n\n\tprivate warn(...args: unknown[]): void {\n\t\ttreatErrors(\n\t\t\t() => {\n\t\t\t\tthis.logger.warn(...args);\n\t\t\t},\n\t\t\tclassifyHostFailure,\n\t\t\t'Orchestrator logger failed while reporting a retry',\n\t\t);\n\t}\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikode13/harness",
3
- "version": "2.3.0",
3
+ "version": "3.0.0",
4
4
  "description": "Test/agent harness utility",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",