@mikode13/harness 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/agent/domain/agent.d.ts +8 -1
- package/dist/agent/domain/agent.js.map +1 -1
- package/dist/agent/domain/errors.d.ts +1 -1
- package/dist/agent/domain/errors.js +1 -1
- package/dist/agent/domain/errors.js.map +1 -1
- package/dist/agent/domain/providerFailure.js +1 -1
- package/dist/agent/domain/providerFailure.js.map +1 -1
- package/dist/engines/claude/infrastructure/model/claudeAgent.d.ts +2 -2
- package/dist/engines/claude/infrastructure/model/claudeAgent.js +3 -3
- package/dist/engines/claude/infrastructure/model/claudeAgent.js.map +1 -1
- package/dist/engines/codex/infrastructure/model/codexAgent.d.ts +2 -2
- package/dist/engines/codex/infrastructure/model/codexAgent.js +4 -4
- package/dist/engines/codex/infrastructure/model/codexAgent.js.map +1 -1
- package/dist/engines/domain/model/llmAgent.d.ts +23 -6
- package/dist/engines/domain/model/llmAgent.js +157 -31
- package/dist/engines/domain/model/llmAgent.js.map +1 -1
- package/dist/factory/infrastructure/agentFactory.d.ts +19 -13
- package/dist/factory/infrastructure/agentFactory.js +48 -23
- package/dist/factory/infrastructure/agentFactory.js.map +1 -1
- package/dist/llm/domain/conversation.d.ts +4 -3
- package/dist/llm/domain/conversation.js +5 -4
- package/dist/llm/domain/conversation.js.map +1 -1
- package/dist/llm/domain/errors.d.ts +1 -1
- package/dist/llm/domain/errors.js +1 -1
- package/dist/llm/domain/errors.js.map +1 -1
- package/dist/llm/domain/llm.d.ts +6 -2
- package/dist/llm/domain/llm.js.map +1 -1
- package/dist/llm/domain/message.d.ts +30 -4
- package/dist/llm/domain/message.js.map +1 -1
- package/dist/llm/domain/tool.d.ts +17 -0
- package/dist/llm/domain/tool.js +2 -0
- package/dist/llm/domain/tool.js.map +1 -0
- package/dist/llm/infrastructure/claudeLLMClient.d.ts +25 -0
- package/dist/llm/infrastructure/claudeLLMClient.js +248 -0
- package/dist/llm/infrastructure/claudeLLMClient.js.map +1 -0
- package/dist/llm/infrastructure/openAILLMClient.d.ts +6 -2
- package/dist/llm/infrastructure/openAILLMClient.js +92 -16
- package/dist/llm/infrastructure/openAILLMClient.js.map +1 -1
- package/dist/orchestration/domain/model/orchestratorAgent.d.ts +2 -2
- package/dist/orchestration/domain/model/orchestratorAgent.js +3 -3
- package/dist/orchestration/domain/model/orchestratorAgent.js.map +1 -1
- package/dist/retry/domain/model/retryingAgent.d.ts +2 -2
- package/dist/retry/domain/model/retryingAgent.js +4 -4
- package/dist/retry/domain/model/retryingAgent.js.map +1 -1
- package/package.json +8 -2
|
@@ -1,30 +1,76 @@
|
|
|
1
|
-
import { UnrecoverableError, withSpentTokens } from
|
|
2
|
-
import { classifyHostFailure, classifyProviderFailure, treatErrors, } from
|
|
3
|
-
import { Conversation } from
|
|
1
|
+
import { InvalidAgentConfigError, RecoverableError, UnrecoverableError, withSpentTokens, } from '#src/agent/domain/errors';
|
|
2
|
+
import { classifyHostFailure, classifyProviderFailure, describeFailure, treatErrors, } from '#src/agent/domain/providerFailure';
|
|
3
|
+
import { Conversation } from '#src/llm/domain/conversation';
|
|
4
|
+
import { addTokens } from '#src/shared/domain/tokens';
|
|
4
5
|
function describePart(part) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
switch (part.type) {
|
|
7
|
+
case 'text':
|
|
8
|
+
return { type: 'agentMessage', message: part.text };
|
|
9
|
+
case 'reasoning':
|
|
10
|
+
return { type: 'reasoning', message: part.text };
|
|
11
|
+
case 'toolCall':
|
|
12
|
+
return { type: 'tool', id: part.id, name: part.name, status: 'in_progress' };
|
|
13
|
+
case 'toolResult':
|
|
14
|
+
return {
|
|
15
|
+
type: 'tool',
|
|
16
|
+
id: part.callId,
|
|
17
|
+
name: part.name,
|
|
18
|
+
status: part.isError ? 'error' : 'completed',
|
|
19
|
+
};
|
|
20
|
+
case 'providerData':
|
|
21
|
+
// Opaque by design: its readable side, if any, arrives as a reasoning part.
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function narrate(part, callback) {
|
|
26
|
+
const event = describePart(part);
|
|
27
|
+
if (!event) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
treatErrors(() => {
|
|
31
|
+
callback(event);
|
|
32
|
+
}, classifyHostFailure, 'LLM agent progress callback failed');
|
|
8
33
|
}
|
|
9
34
|
/**
|
|
10
35
|
* An agent whose conversation is owned by MiKode: every run sends the whole context to a
|
|
11
|
-
* stateless `LLMClient`.
|
|
12
|
-
*
|
|
36
|
+
* stateless `LLMClient`. Within a run the model may call the agent's tools; the agent runs them
|
|
37
|
+
* and sends their results back until the model answers without calling one. Turn boundaries
|
|
38
|
+
* are the consumer's to emit, as with the other engines; this agent only narrates what the
|
|
39
|
+
* model produced and what its tools did.
|
|
13
40
|
*/
|
|
14
41
|
export class LLMAgent {
|
|
15
42
|
llmClient;
|
|
16
43
|
conversation;
|
|
17
|
-
|
|
44
|
+
tools;
|
|
45
|
+
toolDefinitions;
|
|
46
|
+
maxSteps;
|
|
47
|
+
constructor({ llmClient, messages, tools = [], maxSteps = 25, }) {
|
|
48
|
+
if (!Number.isInteger(maxSteps) || maxSteps < 1) {
|
|
49
|
+
throw new InvalidAgentConfigError(`maxSteps must be a positive integer; got ${String(maxSteps)}`);
|
|
50
|
+
}
|
|
18
51
|
this.llmClient = llmClient;
|
|
19
52
|
this.conversation = new Conversation(messages);
|
|
53
|
+
this.maxSteps = maxSteps;
|
|
54
|
+
this.tools = new Map(tools.map(tool => [tool.name, tool]));
|
|
55
|
+
if (this.tools.size !== tools.length) {
|
|
56
|
+
// The model calls tools by name, so a second one with the same name could never run.
|
|
57
|
+
throw new InvalidAgentConfigError('Two tools share a name; each name must be unique');
|
|
58
|
+
}
|
|
59
|
+
// The client only describes the tools to the model; running them stays with the agent.
|
|
60
|
+
this.toolDefinitions = tools.map(({ name, description, inputSchema }) => ({
|
|
61
|
+
name,
|
|
62
|
+
description,
|
|
63
|
+
inputSchema,
|
|
64
|
+
}));
|
|
20
65
|
}
|
|
21
|
-
async
|
|
22
|
-
const start = Date.now();
|
|
23
|
-
const userMessage = { role: 'user', content: [{ type: 'text', text: prompt }] };
|
|
66
|
+
async llmCall(runMessages, signal) {
|
|
24
67
|
let response;
|
|
25
68
|
try {
|
|
26
|
-
//
|
|
27
|
-
response = await this.llmClient.send(
|
|
69
|
+
// Sent as a copy, so what the run records is what the agent built, whatever the client does.
|
|
70
|
+
response = await this.llmClient.send({
|
|
71
|
+
context: [...this.conversation.getContext(), ...structuredClone(runMessages)],
|
|
72
|
+
tools: this.toolDefinitions,
|
|
73
|
+
}, signal);
|
|
28
74
|
}
|
|
29
75
|
catch (error) {
|
|
30
76
|
throw classifyProviderFailure(error, 'The LLM call failed');
|
|
@@ -37,27 +83,107 @@ export class LLMAgent {
|
|
|
37
83
|
usageUnreported: !response.usage,
|
|
38
84
|
});
|
|
39
85
|
}
|
|
40
|
-
|
|
86
|
+
return response;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Always answers the call: a missing tool or a failing one becomes an error result, so the
|
|
90
|
+
* model can correct itself. Only a cancellation escapes, whatever error the tool turned it into.
|
|
91
|
+
*/
|
|
92
|
+
async runTool(call, signal) {
|
|
93
|
+
const result = { type: 'toolResult', callId: call.id, name: call.name };
|
|
94
|
+
const tool = this.tools.get(call.name);
|
|
95
|
+
if (!tool) {
|
|
96
|
+
return {
|
|
97
|
+
...result,
|
|
98
|
+
output: `There is no tool named "${call.name}". Available tools: ${[...this.tools.keys()].join(', ') || 'none'}.`,
|
|
99
|
+
isError: true,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
// Checked again at the last moment: the consumer may cancel on the announcement itself.
|
|
103
|
+
signal.throwIfAborted();
|
|
41
104
|
try {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
105
|
+
return { ...result, output: await tool.execute(call.input, signal), isError: false };
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
signal.throwIfAborted();
|
|
109
|
+
return { ...result, output: describeFailure(error), isError: true };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/** One at a time and in order, so their effects and their narration never interleave. */
|
|
113
|
+
async runTools(calls, signal, callback) {
|
|
114
|
+
const results = [];
|
|
115
|
+
for (const call of calls) {
|
|
116
|
+
// A call before this one may have ignored the cancellation; this one must not start,
|
|
117
|
+
// nor be announced as running.
|
|
118
|
+
signal.throwIfAborted();
|
|
119
|
+
// Announced only as its turn comes, so a consumer never shows a call as running early.
|
|
120
|
+
narrate(call, callback);
|
|
121
|
+
const result = await this.runTool(call, signal);
|
|
122
|
+
narrate(result, callback);
|
|
123
|
+
results.push(result);
|
|
124
|
+
}
|
|
125
|
+
return { role: 'tool', content: results };
|
|
126
|
+
}
|
|
127
|
+
async run(prompt, signal, callback) {
|
|
128
|
+
const start = Date.now();
|
|
129
|
+
const runMessages = [{ role: 'user', content: [{ type: 'text', text: prompt }] }];
|
|
130
|
+
let tokens;
|
|
131
|
+
// Once one call went unreported the run's total is unknown, however many report later.
|
|
132
|
+
let unreported = false;
|
|
133
|
+
let toolRan = false;
|
|
134
|
+
try {
|
|
135
|
+
for (let step = 1; step <= this.maxSteps; step++) {
|
|
136
|
+
const response = await this.llmCall(runMessages, signal);
|
|
137
|
+
tokens = addTokens(tokens, response.usage);
|
|
138
|
+
unreported ||= !response.usage;
|
|
139
|
+
runMessages.push(response.message);
|
|
140
|
+
// Tool calls are narrated as they start, in `runTools`, and never if they do not run.
|
|
141
|
+
for (const part of response.message.content) {
|
|
142
|
+
if (part.type !== 'toolCall')
|
|
143
|
+
narrate(part, callback);
|
|
144
|
+
}
|
|
145
|
+
const calls = response.message.content.filter(part => part.type === 'toolCall');
|
|
146
|
+
if (calls.length === 0) {
|
|
147
|
+
// Recorded only now, so a failed run leaves no tool call without its result.
|
|
148
|
+
this.conversation.addRun(runMessages);
|
|
149
|
+
return {
|
|
150
|
+
// Only the final answer: text from earlier steps was narrated as it came.
|
|
151
|
+
response: response.message.content
|
|
152
|
+
.filter(part => part.type === 'text')
|
|
153
|
+
.map(part => part.text)
|
|
154
|
+
.join('\n'),
|
|
155
|
+
tokens: unreported ? undefined : tokens,
|
|
156
|
+
duration: (Date.now() - start) / 1000,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
// On the last step no call is left to send the results to, so the tools do not run.
|
|
160
|
+
if (step < this.maxSteps) {
|
|
161
|
+
// Only a call to a tool the agent has reaches `execute`; a missing one has no
|
|
162
|
+
// effect a retry could repeat.
|
|
163
|
+
toolRan ||= calls.some(call => this.tools.has(call.name));
|
|
164
|
+
runMessages.push(await this.runTools(calls, signal, callback));
|
|
165
|
+
}
|
|
46
166
|
}
|
|
167
|
+
throw new UnrecoverableError('The LLM did not finish within its step limit', {
|
|
168
|
+
cause: `The model was still calling tools after ${String(this.maxSteps)} steps.`,
|
|
169
|
+
});
|
|
47
170
|
}
|
|
48
171
|
catch (error) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
172
|
+
let failure = error;
|
|
173
|
+
if (toolRan && error instanceof RecoverableError) {
|
|
174
|
+
// RetryingAgent would run the whole prompt again, and with it every tool this run
|
|
175
|
+
// already executed, so a failure after one ran must not invite a retry.
|
|
176
|
+
const unrecoverable = new UnrecoverableError(error.message, {
|
|
177
|
+
cause: error.cause,
|
|
178
|
+
tokens: error.tokens,
|
|
179
|
+
usageUnreported: error.usageUnreported,
|
|
180
|
+
});
|
|
181
|
+
unrecoverable.stack = error.stack;
|
|
182
|
+
failure = unrecoverable;
|
|
183
|
+
}
|
|
184
|
+
// The run spent the tokens of every step before the one that failed.
|
|
185
|
+
throw withSpentTokens(failure, tokens, unreported);
|
|
186
|
+
}
|
|
61
187
|
}
|
|
62
188
|
}
|
|
63
189
|
//# sourceMappingURL=llmAgent.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"llmAgent.js","sourceRoot":"","sources":["../../../../src/engines/domain/model/llmAgent.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Agent, AgentResponse, Callback, ProgressEvent } from '../../../agent/domain/agent.ts';\nimport { UnrecoverableError, withSpentTokens } from '../../../agent/domain/errors.ts';\nimport {\n\tclassifyHostFailure,\n\tclassifyProviderFailure,\n\ttreatErrors,\n} from '../../../agent/domain/providerFailure.ts';\nimport { Conversation } from '../../../llm/domain/conversation.ts';\nimport type { LLMClient, LLMResponse } from '../../../llm/domain/llm.ts';\nimport type { Message, MessagePart } from '../../../llm/domain/message.ts';\n\nfunction describePart(part: MessagePart): ProgressEvent {\n\treturn part.type === 'text'\n\t\t? { type: 'agentMessage', message: part.text }\n\t\t: { type: 'reasoning', message: part.text };\n}\n\n/**\n * An agent whose conversation is owned by MiKode: every run sends the whole context to a\n * stateless `LLMClient`. Turn boundaries are the consumer's to emit, as with the other\n * engines; this agent only narrates what the model produced.\n */\nexport class LLMAgent implements Agent {\n\tprivate readonly llmClient: LLMClient;\n\tprivate readonly conversation: Conversation;\n\n\tconstructor({ llmClient, messages }: { llmClient: LLMClient; messages?: Message[] }) {\n\t\tthis.llmClient = llmClient;\n\t\tthis.conversation = new Conversation(messages);\n\t}\n\n\tasync run(prompt: string, signal: AbortSignal, callback: Callback): Promise<AgentResponse> {\n\t\tconst start = Date.now();\n\t\tconst userMessage: Message = { role: 'user', content: [{ type: 'text', text: prompt }] };\n\n\t\tlet response: LLMResponse;\n\t\ttry {\n\t\t\t// The prompt is sent as a copy so the one recorded below is the one the agent built.\n\t\t\tresponse = await this.llmClient.send(\n\t\t\t\t[...this.conversation.getContext(), structuredClone(userMessage)],\n\t\t\t\tsignal,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tthrow classifyProviderFailure(error, 'The LLM call failed');\n\t\t}\n\n\t\tif (response.stopReason !== 'completed') {\n\t\t\tthrow new UnrecoverableError('The LLM stopped before completing its answer', {\n\t\t\t\tcause: `The model stopped with \"${response.stopReason}\".`,\n\t\t\t\t// The call was billed even though its answer is unusable.\n\t\t\t\ttokens: response.usage,\n\t\t\t\tusageUnreported: !response.usage,\n\t\t\t});\n\t\t}\n\n\t\tthis.conversation.addExchange(userMessage, response.message);\n\n\t\ttry {\n\t\t\tfor (const part of response.message.content) {\n\t\t\t\ttreatErrors(\n\t\t\t\t\t() => {\n\t\t\t\t\t\tcallback(describePart(part));\n\t\t\t\t\t},\n\t\t\t\t\tclassifyHostFailure,\n\t\t\t\t\t'LLM agent progress callback failed',\n\t\t\t\t);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\t// The answer already arrived, so the run spent its tokens whatever the consumer did.\n\t\t\tthrow withSpentTokens(error, response.usage, !response.usage);\n\t\t}\n\n\t\tconst text = response.message.content\n\t\t\t.filter(part => part.type === 'text')\n\t\t\t.map(part => part.text)\n\t\t\t.join('\\n');\n\n\t\treturn {\n\t\t\tresponse: text,\n\t\t\ttokens: response.usage,\n\t\t\tduration: (Date.now() - start) / 1000,\n\t\t};\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"llmAgent.js","sourceRoot":"","sources":["../../../../src/engines/domain/model/llmAgent.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Agent, AgentResponse, Callback, ProgressEvent } from '#src/agent/domain/agent';\nimport {\n\tInvalidAgentConfigError,\n\tRecoverableError,\n\tUnrecoverableError,\n\twithSpentTokens,\n} from '#src/agent/domain/errors';\nimport {\n\tclassifyHostFailure,\n\tclassifyProviderFailure,\n\tdescribeFailure,\n\ttreatErrors,\n} from '#src/agent/domain/providerFailure';\nimport { Conversation } from '#src/llm/domain/conversation';\nimport type { LLMClient, LLMResponse } from '#src/llm/domain/llm';\nimport type { Message, MessagePart, ToolCallPart, ToolResultPart } from '#src/llm/domain/message';\nimport type { Tool, ToolDefinition } from '#src/llm/domain/tool';\nimport { addTokens, type Tokens } from '#src/shared/domain/tokens';\n\nfunction describePart(part: MessagePart): ProgressEvent | undefined {\n\tswitch (part.type) {\n\t\tcase 'text':\n\t\t\treturn { type: 'agentMessage', message: part.text };\n\t\tcase 'reasoning':\n\t\t\treturn { type: 'reasoning', message: part.text };\n\t\tcase 'toolCall':\n\t\t\treturn { type: 'tool', id: part.id, name: part.name, status: 'in_progress' };\n\t\tcase 'toolResult':\n\t\t\treturn {\n\t\t\t\ttype: 'tool',\n\t\t\t\tid: part.callId,\n\t\t\t\tname: part.name,\n\t\t\t\tstatus: part.isError ? 'error' : 'completed',\n\t\t\t};\n\t\tcase 'providerData':\n\t\t\t// Opaque by design: its readable side, if any, arrives as a reasoning part.\n\t\t\treturn undefined;\n\t}\n}\n\nfunction narrate(part: MessagePart, callback: Callback): void {\n\tconst event = describePart(part);\n\tif (!event) {\n\t\treturn;\n\t}\n\n\ttreatErrors(\n\t\t() => {\n\t\t\tcallback(event);\n\t\t},\n\t\tclassifyHostFailure,\n\t\t'LLM agent progress callback failed',\n\t);\n}\n\n/**\n * An agent whose conversation is owned by MiKode: every run sends the whole context to a\n * stateless `LLMClient`. Within a run the model may call the agent's tools; the agent runs them\n * and sends their results back until the model answers without calling one. Turn boundaries\n * are the consumer's to emit, as with the other engines; this agent only narrates what the\n * model produced and what its tools did.\n */\nexport class LLMAgent implements Agent {\n\tprivate readonly llmClient: LLMClient;\n\tprivate readonly conversation: Conversation;\n\tprivate readonly tools: Map<string, Tool>;\n\tprivate readonly toolDefinitions: ToolDefinition[];\n\tprivate readonly maxSteps: number;\n\n\tconstructor({\n\t\tllmClient,\n\t\tmessages,\n\t\ttools = [],\n\t\tmaxSteps = 25,\n\t}: {\n\t\tllmClient: LLMClient;\n\t\tmessages?: Message[];\n\t\ttools?: Tool[];\n\t\t/** How many calls to the model one run may make before it fails. */\n\t\tmaxSteps?: number;\n\t}) {\n\t\tif (!Number.isInteger(maxSteps) || maxSteps < 1) {\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t`maxSteps must be a positive integer; got ${String(maxSteps)}`,\n\t\t\t);\n\t\t}\n\n\t\tthis.llmClient = llmClient;\n\t\tthis.conversation = new Conversation(messages);\n\t\tthis.maxSteps = maxSteps;\n\t\tthis.tools = new Map(tools.map(tool => [tool.name, tool]));\n\t\tif (this.tools.size !== tools.length) {\n\t\t\t// The model calls tools by name, so a second one with the same name could never run.\n\t\t\tthrow new InvalidAgentConfigError('Two tools share a name; each name must be unique');\n\t\t}\n\t\t// The client only describes the tools to the model; running them stays with the agent.\n\t\tthis.toolDefinitions = tools.map(({ name, description, inputSchema }) => ({\n\t\t\tname,\n\t\t\tdescription,\n\t\t\tinputSchema,\n\t\t}));\n\t}\n\n\tprivate async llmCall(runMessages: Message[], signal: AbortSignal): Promise<LLMResponse> {\n\t\tlet response: LLMResponse;\n\t\ttry {\n\t\t\t// Sent as a copy, so what the run records is what the agent built, whatever the client does.\n\t\t\tresponse = await this.llmClient.send(\n\t\t\t\t{\n\t\t\t\t\tcontext: [...this.conversation.getContext(), ...structuredClone(runMessages)],\n\t\t\t\t\ttools: this.toolDefinitions,\n\t\t\t\t},\n\t\t\t\tsignal,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tthrow classifyProviderFailure(error, 'The LLM call failed');\n\t\t}\n\n\t\tif (response.stopReason !== 'completed') {\n\t\t\tthrow new UnrecoverableError('The LLM stopped before completing its answer', {\n\t\t\t\tcause: `The model stopped with \"${response.stopReason}\".`,\n\t\t\t\t// The call was billed even though its answer is unusable.\n\t\t\t\ttokens: response.usage,\n\t\t\t\tusageUnreported: !response.usage,\n\t\t\t});\n\t\t}\n\n\t\treturn response;\n\t}\n\n\t/**\n\t * Always answers the call: a missing tool or a failing one becomes an error result, so the\n\t * model can correct itself. Only a cancellation escapes, whatever error the tool turned it into.\n\t */\n\tprivate async runTool(call: ToolCallPart, signal: AbortSignal): Promise<ToolResultPart> {\n\t\tconst result = { type: 'toolResult' as const, callId: call.id, name: call.name };\n\t\tconst tool = this.tools.get(call.name);\n\t\tif (!tool) {\n\t\t\treturn {\n\t\t\t\t...result,\n\t\t\t\toutput: `There is no tool named \"${call.name}\". Available tools: ${[...this.tools.keys()].join(', ') || 'none'}.`,\n\t\t\t\tisError: true,\n\t\t\t};\n\t\t}\n\n\t\t// Checked again at the last moment: the consumer may cancel on the announcement itself.\n\t\tsignal.throwIfAborted();\n\t\ttry {\n\t\t\treturn { ...result, output: await tool.execute(call.input, signal), isError: false };\n\t\t} catch (error) {\n\t\t\tsignal.throwIfAborted();\n\t\t\treturn { ...result, output: describeFailure(error), isError: true };\n\t\t}\n\t}\n\n\t/** One at a time and in order, so their effects and their narration never interleave. */\n\tprivate async runTools(\n\t\tcalls: ToolCallPart[],\n\t\tsignal: AbortSignal,\n\t\tcallback: Callback,\n\t): Promise<Message> {\n\t\tconst results: MessagePart[] = [];\n\t\tfor (const call of calls) {\n\t\t\t// A call before this one may have ignored the cancellation; this one must not start,\n\t\t\t// nor be announced as running.\n\t\t\tsignal.throwIfAborted();\n\t\t\t// Announced only as its turn comes, so a consumer never shows a call as running early.\n\t\t\tnarrate(call, callback);\n\t\t\tconst result = await this.runTool(call, signal);\n\t\t\tnarrate(result, callback);\n\t\t\tresults.push(result);\n\t\t}\n\n\t\treturn { role: 'tool', content: results };\n\t}\n\n\tasync run(prompt: string, signal: AbortSignal, callback: Callback): Promise<AgentResponse> {\n\t\tconst start = Date.now();\n\t\tconst runMessages: Message[] = [{ role: 'user', content: [{ type: 'text', text: prompt }] }];\n\t\tlet tokens: Tokens | undefined;\n\t\t// Once one call went unreported the run's total is unknown, however many report later.\n\t\tlet unreported = false;\n\t\tlet toolRan = false;\n\n\t\ttry {\n\t\t\tfor (let step = 1; step <= this.maxSteps; step++) {\n\t\t\t\tconst response = await this.llmCall(runMessages, signal);\n\t\t\t\ttokens = addTokens(tokens, response.usage);\n\t\t\t\tunreported ||= !response.usage;\n\t\t\t\trunMessages.push(response.message);\n\n\t\t\t\t// Tool calls are narrated as they start, in `runTools`, and never if they do not run.\n\t\t\t\tfor (const part of response.message.content) {\n\t\t\t\t\tif (part.type !== 'toolCall') narrate(part, callback);\n\t\t\t\t}\n\n\t\t\t\tconst calls = response.message.content.filter(part => part.type === 'toolCall');\n\t\t\t\tif (calls.length === 0) {\n\t\t\t\t\t// Recorded only now, so a failed run leaves no tool call without its result.\n\t\t\t\t\tthis.conversation.addRun(runMessages);\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\t// Only the final answer: text from earlier steps was narrated as it came.\n\t\t\t\t\t\tresponse: response.message.content\n\t\t\t\t\t\t\t.filter(part => part.type === 'text')\n\t\t\t\t\t\t\t.map(part => part.text)\n\t\t\t\t\t\t\t.join('\\n'),\n\t\t\t\t\t\ttokens: unreported ? undefined : tokens,\n\t\t\t\t\t\tduration: (Date.now() - start) / 1000,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\t// On the last step no call is left to send the results to, so the tools do not run.\n\t\t\t\tif (step < this.maxSteps) {\n\t\t\t\t\t// Only a call to a tool the agent has reaches `execute`; a missing one has no\n\t\t\t\t\t// effect a retry could repeat.\n\t\t\t\t\ttoolRan ||= calls.some(call => this.tools.has(call.name));\n\t\t\t\t\trunMessages.push(await this.runTools(calls, signal, callback));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthrow new UnrecoverableError('The LLM did not finish within its step limit', {\n\t\t\t\tcause: `The model was still calling tools after ${String(this.maxSteps)} steps.`,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tlet failure = error;\n\t\t\tif (toolRan && error instanceof RecoverableError) {\n\t\t\t\t// RetryingAgent would run the whole prompt again, and with it every tool this run\n\t\t\t\t// already executed, so a failure after one ran must not invite a retry.\n\t\t\t\tconst unrecoverable = new UnrecoverableError(error.message, {\n\t\t\t\t\tcause: error.cause,\n\t\t\t\t\ttokens: error.tokens,\n\t\t\t\t\tusageUnreported: error.usageUnreported,\n\t\t\t\t});\n\t\t\t\tunrecoverable.stack = error.stack;\n\t\t\t\tfailure = unrecoverable;\n\t\t\t}\n\n\t\t\t// The run spent the tokens of every step before the one that failed.\n\t\t\tthrow withSpentTokens(failure, tokens, unreported);\n\t\t}\n\t}\n}\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { Agent } from '
|
|
2
|
-
import { type ClaudeModel, type ClaudeReasoningEffort } from '
|
|
3
|
-
import { type CodexModel, type CodexReasoningEffort } from '
|
|
4
|
-
import type { ILogger } from '
|
|
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
|
+
import type { ILogger } from '#src/shared/domain/logger';
|
|
5
5
|
export declare const agentProviders: readonly ["claude", "codex"];
|
|
6
6
|
export type AgentProvider = (typeof agentProviders)[number];
|
|
7
7
|
/** Any provider's model. The engine the provider selects rejects one it does not support. */
|
|
@@ -47,8 +47,13 @@ export interface CreateOrchestratorOptions {
|
|
|
47
47
|
* binary is missing.
|
|
48
48
|
*/
|
|
49
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];
|
|
50
52
|
export interface CreateLLMAgentOptions {
|
|
51
|
-
/**
|
|
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
|
+
*/
|
|
52
57
|
model?: string;
|
|
53
58
|
/** Sent as the model's instructions on every call. */
|
|
54
59
|
systemPrompt: string;
|
|
@@ -56,13 +61,14 @@ export interface CreateLLMAgentOptions {
|
|
|
56
61
|
logger?: ILogger;
|
|
57
62
|
}
|
|
58
63
|
/**
|
|
59
|
-
* Builds an agent whose conversation MiKode owns, on
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* `LLMClient` so far. It runs no tools, so it has no `autoApprove`.
|
|
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`.
|
|
63
67
|
*
|
|
64
|
-
* @throws {InvalidAgentConfigError} for a model
|
|
65
|
-
*
|
|
66
|
-
*
|
|
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`.
|
|
67
73
|
*/
|
|
68
|
-
export declare function createLLMAgent({ model, systemPrompt, logger
|
|
74
|
+
export declare function createLLMAgent(provider: LLMProvider, { model, systemPrompt, logger }: CreateLLMAgentOptions): Agent;
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { InvalidAgentConfigError } from
|
|
2
|
-
import { ClaudeAgent, } from
|
|
3
|
-
import { CodexAgent, } from
|
|
4
|
-
import { LLMAgent } from
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
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';
|
|
8
|
+
import { ReviewerDecisionValidator } from '#src/orchestration/infrastructure/model/reviewerDecisionValidator';
|
|
9
|
+
import { RetryingAgent } from '#src/retry/domain/model/retryingAgent';
|
|
10
|
+
import { isOneOf } from '#src/shared/domain/isOneOf';
|
|
11
|
+
import { Logger } from '#src/shared/infrastructure/logger';
|
|
11
12
|
export const agentProviders = ['claude', 'codex'];
|
|
12
13
|
export function isAgentProvider(value) {
|
|
13
14
|
return isOneOf(agentProviders, value);
|
|
@@ -90,22 +91,46 @@ export function createOrchestrator({ provider, autoApprove, logger = new Logger(
|
|
|
90
91
|
logger,
|
|
91
92
|
});
|
|
92
93
|
}
|
|
93
|
-
const
|
|
94
|
+
export const llmProviders = ['claude', 'openai'];
|
|
95
|
+
const defaultLLMAgentModels = {
|
|
96
|
+
claude: 'claude-sonnet-5',
|
|
97
|
+
openai: 'gpt-5.6-luna',
|
|
98
|
+
};
|
|
94
99
|
/**
|
|
95
|
-
* Builds an agent whose conversation MiKode owns, on
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
* `LLMClient` so far. It runs no tools, so it has no `autoApprove`.
|
|
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`.
|
|
99
103
|
*
|
|
100
|
-
* @throws {InvalidAgentConfigError} for a model
|
|
101
|
-
*
|
|
102
|
-
*
|
|
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`.
|
|
103
109
|
*/
|
|
104
|
-
export function createLLMAgent({ model
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
+
}
|
|
108
133
|
// LLMAgent records nothing from a failed call, so the original prompt is the whole story.
|
|
109
|
-
return new RetryingAgent({ inner:
|
|
134
|
+
return new RetryingAgent({ inner: new LLMAgent({ llmClient }), logger, noteFailures: false });
|
|
110
135
|
}
|
|
111
136
|
//# 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 '../../agent/domain/agent.ts';\nimport { InvalidAgentConfigError } from '../../agent/domain/errors.ts';\nimport {\n\tClaudeAgent,\n\ttype ClaudeModel,\n\ttype ClaudeReasoningEffort,\n} from '../../engines/claude/infrastructure/model/claudeAgent.ts';\nimport {\n\tCodexAgent,\n\ttype CodexModel,\n\ttype CodexReasoningEffort,\n} from '../../engines/codex/infrastructure/model/codexAgent.ts';\nimport { LLMAgent } from '../../engines/domain/model/llmAgent.ts';\nimport { OpenAILLMClient, type OpenAIModel } from '../../llm/infrastructure/openAILLMClient.ts';\nimport { OrchestratorAgent } from '../../orchestration/domain/model/orchestratorAgent.ts';\nimport { ReviewerDecisionValidator } from '../../orchestration/infrastructure/model/reviewerDecisionValidator.ts';\nimport { RetryingAgent } from '../../retry/domain/model/retryingAgent.ts';\nimport { isOneOf } from '../../shared/domain/isOneOf.ts';\nimport type { ILogger } from '../../shared/domain/logger.ts';\nimport { Logger } from '../../shared/infrastructure/logger.ts';\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 interface CreateLLMAgentOptions {\n\t/** Defaults to `'gpt-5.6-luna'`. The OpenAI client rejects a model it does not support. */\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 defaultLLMAgentModel = 'gpt-5.6-luna' satisfies OpenAIModel;\n\n/**\n * Builds an agent whose conversation MiKode owns, on the OpenAI Responses API, already wrapped\n * in the harness's retry policy. Internal while #23 reaches parity with the Agent SDK engines:\n * it is not exported from `src/index.ts`, and it takes no provider because OpenAI is the only\n * `LLMClient` so far. It runs no tools, so it has no `autoApprove`.\n *\n * @throws {InvalidAgentConfigError} for a model the OpenAI client does not support.\n * @throws {UnrecoverableError} when the OpenAI client cannot be set up, for example because\n * `OPENAI_API_KEY` is missing.\n */\nexport function createLLMAgent({\n\tmodel = defaultLLMAgentModel,\n\tsystemPrompt,\n\tlogger = new Logger(),\n}: CreateLLMAgentOptions): Agent {\n\tconst engine = new LLMAgent({\n\t\tllmClient: new OpenAILLMClient({ model, systemPrompt, logger }),\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: engine, 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 {\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"]}
|
|
@@ -8,10 +8,11 @@ export declare class Conversation {
|
|
|
8
8
|
private readonly messages;
|
|
9
9
|
constructor(messages?: Message[]);
|
|
10
10
|
/**
|
|
11
|
-
* Records a
|
|
12
|
-
* records nothing, so a retry
|
|
11
|
+
* Records a whole run at once, once it succeeded: the prompt, every step the model took and
|
|
12
|
+
* every tool result. A failed run records nothing, so a retry cannot repeat its prompt, and
|
|
13
|
+
* no tool call is ever kept without its result.
|
|
13
14
|
*/
|
|
14
|
-
|
|
15
|
+
addRun(messages: Message[]): void;
|
|
15
16
|
/**
|
|
16
17
|
* A deep copy, like everything the conversation takes in: a client that rewrites the
|
|
17
18
|
* messages it was sent cannot change what the next turn sends.
|
|
@@ -9,11 +9,12 @@ export class Conversation {
|
|
|
9
9
|
this.messages = structuredClone(messages);
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
* Records a
|
|
13
|
-
* records nothing, so a retry
|
|
12
|
+
* Records a whole run at once, once it succeeded: the prompt, every step the model took and
|
|
13
|
+
* every tool result. A failed run records nothing, so a retry cannot repeat its prompt, and
|
|
14
|
+
* no tool call is ever kept without its result.
|
|
14
15
|
*/
|
|
15
|
-
|
|
16
|
-
this.messages.push(structuredClone(
|
|
16
|
+
addRun(messages) {
|
|
17
|
+
this.messages.push(...structuredClone(messages));
|
|
17
18
|
}
|
|
18
19
|
/**
|
|
19
20
|
* A deep copy, like everything the conversation takes in: a client that rewrites the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversation.js","sourceRoot":"","sources":["../../../src/llm/domain/conversation.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,OAAO,YAAY;IACP,QAAQ,CAAY;IAErC,YAAY,WAAsB,EAAE;QACnC,IAAI,CAAC,QAAQ,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;IAC3C,CAAC;IAED
|
|
1
|
+
{"version":3,"file":"conversation.js","sourceRoot":"","sources":["../../../src/llm/domain/conversation.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,OAAO,YAAY;IACP,QAAQ,CAAY;IAErC,YAAY,WAAsB,EAAE;QACnC,IAAI,CAAC,QAAQ,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;IAC3C,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,QAAmB;QACzB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC;IAClD,CAAC;IAED;;;OAGG;IACH,UAAU;QACT,OAAO,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACvC,CAAC;CACD","sourcesContent":["import type { Message } from './message.ts';\n\n/**\n * What one agent remembers across its runs. The LLM never sees this object, only the\n * context built from it, so how the context is derived (compaction, later) can change\n * without touching either side.\n */\nexport class Conversation {\n\tprivate readonly messages: Message[];\n\n\tconstructor(messages: Message[] = []) {\n\t\tthis.messages = structuredClone(messages);\n\t}\n\n\t/**\n\t * Records a whole run at once, once it succeeded: the prompt, every step the model took and\n\t * every tool result. A failed run records nothing, so a retry cannot repeat its prompt, and\n\t * no tool call is ever kept without its result.\n\t */\n\taddRun(messages: Message[]): void {\n\t\tthis.messages.push(...structuredClone(messages));\n\t}\n\n\t/**\n\t * A deep copy, like everything the conversation takes in: a client that rewrites the\n\t * messages it was sent cannot change what the next turn sends.\n\t */\n\tgetContext(): Message[] {\n\t\treturn structuredClone(this.messages);\n\t}\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { UnrecoverableError } from '
|
|
1
|
+
import { UnrecoverableError } from '#src/agent/domain/errors';
|
|
2
2
|
/** The context no longer fits the model's window; the agent must compact before sending again. */
|
|
3
3
|
export declare class MaxContextError extends UnrecoverableError {
|
|
4
4
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { UnrecoverableError } from
|
|
1
|
+
import { UnrecoverableError } from '#src/agent/domain/errors';
|
|
2
2
|
/** The context no longer fits the model's window; the agent must compact before sending again. */
|
|
3
3
|
export class MaxContextError extends UnrecoverableError {
|
|
4
4
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/llm/domain/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/llm/domain/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAE9D,kGAAkG;AAClG,MAAM,OAAO,eAAgB,SAAQ,kBAAkB;CAAG","sourcesContent":["import { UnrecoverableError } from '#src/agent/domain/errors';\n\n/** The context no longer fits the model's window; the agent must compact before sending again. */\nexport class MaxContextError extends UnrecoverableError {}\n"]}
|
package/dist/llm/domain/llm.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { Tokens } from '
|
|
1
|
+
import type { Tokens } from '#src/shared/domain/tokens';
|
|
2
2
|
import type { Message } from './message.ts';
|
|
3
|
+
import type { ToolDefinition } from './tool.ts';
|
|
3
4
|
export type StopReason = 'completed' | 'truncated' | 'refused';
|
|
4
5
|
export interface LLMResponse {
|
|
5
6
|
message: Message & {
|
|
@@ -14,5 +15,8 @@ export interface LLMResponse {
|
|
|
14
15
|
stopReason: StopReason;
|
|
15
16
|
}
|
|
16
17
|
export interface LLMClient {
|
|
17
|
-
send(context
|
|
18
|
+
send({ context, tools }: {
|
|
19
|
+
context: Message[];
|
|
20
|
+
tools: ToolDefinition[];
|
|
21
|
+
}, signal: AbortSignal): Promise<LLMResponse>;
|
|
18
22
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"llm.js","sourceRoot":"","sources":["../../../src/llm/domain/llm.ts"],"names":[],"mappings":"","sourcesContent":["import type { Tokens } from '
|
|
1
|
+
{"version":3,"file":"llm.js","sourceRoot":"","sources":["../../../src/llm/domain/llm.ts"],"names":[],"mappings":"","sourcesContent":["import type { Tokens } from '#src/shared/domain/tokens';\nimport type { Message } from './message.ts';\nimport type { ToolDefinition } from './tool.ts';\n\nexport type StopReason = 'completed' | 'truncated' | 'refused';\n\nexport interface LLMResponse {\n\tmessage: Message & { role: 'assistant' };\n\t/**\n\t * Missing when the provider did not report it. The call was made and may have been billed,\n\t * so the agent keeps the answer and only its tokens become unknown; a failure raised from\n\t * such a response is marked `usageUnreported`.\n\t */\n\tusage: Tokens | undefined;\n\tstopReason: StopReason;\n}\n\nexport interface LLMClient {\n\tsend(\n\t\t{ context, tools }: { context: Message[]; tools: ToolDefinition[] },\n\t\tsignal: AbortSignal,\n\t): Promise<LLMResponse>;\n}\n"]}
|
|
@@ -1,11 +1,37 @@
|
|
|
1
|
-
export type MessagePart =
|
|
1
|
+
export type MessagePart = TextPart | ReasoningPart | ToolCallPart | ToolResultPart | ProviderDataPart;
|
|
2
|
+
export interface TextPart {
|
|
2
3
|
type: 'text';
|
|
3
4
|
text: string;
|
|
4
|
-
}
|
|
5
|
+
}
|
|
6
|
+
export interface ReasoningPart {
|
|
5
7
|
type: 'reasoning';
|
|
6
8
|
text: string;
|
|
7
|
-
}
|
|
9
|
+
}
|
|
10
|
+
export interface ToolCallPart {
|
|
11
|
+
id: string;
|
|
12
|
+
type: 'toolCall';
|
|
13
|
+
name: string;
|
|
14
|
+
input: unknown;
|
|
15
|
+
}
|
|
16
|
+
export interface ToolResultPart {
|
|
17
|
+
callId: string;
|
|
18
|
+
type: 'toolResult';
|
|
19
|
+
name: string;
|
|
20
|
+
output: string;
|
|
21
|
+
isError: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A block only the client that produced it can read back, such as signed or encrypted
|
|
25
|
+
* reasoning, kept whole so that client can send it again on the next call. `source` is the
|
|
26
|
+
* client's own label and the domain gives it no meaning: every other client leaves the part out.
|
|
27
|
+
* `data` must stay plain JSON, since the conversation is copied and will be persisted.
|
|
28
|
+
*/
|
|
29
|
+
export interface ProviderDataPart {
|
|
30
|
+
type: 'providerData';
|
|
31
|
+
source: string;
|
|
32
|
+
data: unknown;
|
|
33
|
+
}
|
|
8
34
|
export interface Message {
|
|
9
|
-
role: 'user' | 'assistant';
|
|
35
|
+
role: 'user' | 'assistant' | 'tool';
|
|
10
36
|
content: MessagePart[];
|
|
11
37
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message.js","sourceRoot":"","sources":["../../../src/llm/domain/message.ts"],"names":[],"mappings":"","sourcesContent":["export type MessagePart
|
|
1
|
+
{"version":3,"file":"message.js","sourceRoot":"","sources":["../../../src/llm/domain/message.ts"],"names":[],"mappings":"","sourcesContent":["export type MessagePart =\n\tTextPart | ReasoningPart | ToolCallPart | ToolResultPart | ProviderDataPart;\n\nexport interface TextPart {\n\ttype: 'text';\n\ttext: string;\n}\n\nexport interface ReasoningPart {\n\ttype: 'reasoning';\n\ttext: string;\n}\n\nexport interface ToolCallPart {\n\tid: string;\n\ttype: 'toolCall';\n\tname: string;\n\tinput: unknown;\n}\n\nexport interface ToolResultPart {\n\tcallId: string;\n\ttype: 'toolResult';\n\tname: string;\n\toutput: string;\n\tisError: boolean;\n}\n\n/**\n * A block only the client that produced it can read back, such as signed or encrypted\n * reasoning, kept whole so that client can send it again on the next call. `source` is the\n * client's own label and the domain gives it no meaning: every other client leaves the part out.\n * `data` must stay plain JSON, since the conversation is copied and will be persisted.\n */\nexport interface ProviderDataPart {\n\ttype: 'providerData';\n\tsource: string;\n\tdata: unknown;\n}\n\nexport interface Message {\n\trole: 'user' | 'assistant' | 'tool';\n\tcontent: MessagePart[];\n}\n"]}
|