@mikode13/harness 5.4.0 → 6.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 +127 -3
- package/dist/agent/domain/agent.d.ts +30 -3
- package/dist/agent/domain/agent.js.map +1 -1
- package/dist/engines/domain/model/llmAgent.js +9 -8
- package/dist/engines/domain/model/llmAgent.js.map +1 -1
- package/dist/engines/domain/model/stepContext.d.ts +1 -1
- package/dist/engines/domain/model/stepContext.js +8 -1
- package/dist/engines/domain/model/stepContext.js.map +1 -1
- package/dist/factory/infrastructure/agentLLMFactory.d.ts +17 -2
- package/dist/factory/infrastructure/agentLLMFactory.js +57 -27
- package/dist/factory/infrastructure/agentLLMFactory.js.map +1 -1
- package/dist/factory/infrastructure/workflowFactory.d.ts +9 -0
- package/dist/factory/infrastructure/workflowFactory.js +42 -0
- package/dist/factory/infrastructure/workflowFactory.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/llm/domain/llm.d.ts +2 -2
- package/dist/llm/domain/llm.js.map +1 -1
- package/dist/llm/domain/message.d.ts +17 -1
- package/dist/llm/domain/message.js +14 -1
- package/dist/llm/domain/message.js.map +1 -1
- package/dist/llm/infrastructure/claudeLLMClient.d.ts +5 -2
- package/dist/llm/infrastructure/claudeLLMClient.js +61 -5
- package/dist/llm/infrastructure/claudeLLMClient.js.map +1 -1
- package/dist/llm/infrastructure/openAILLMClient.d.ts +5 -2
- package/dist/llm/infrastructure/openAILLMClient.js +25 -2
- package/dist/llm/infrastructure/openAILLMClient.js.map +1 -1
- package/dist/orchestration/domain/model/orchestratorAgent.d.ts +1 -1
- package/dist/orchestration/domain/model/orchestratorAgent.js.map +1 -1
- package/dist/orchestration/infrastructure/model/reviewerDecisionValidator.d.ts +1 -1
- package/dist/orchestration/infrastructure/model/reviewerDecisionValidator.js.map +1 -1
- package/dist/shared/domain/isAbortError.d.ts +1 -0
- package/dist/shared/domain/isAbortError.js +2 -1
- package/dist/shared/domain/isAbortError.js.map +1 -1
- package/dist/shared/domain/validator.d.ts +4 -0
- package/dist/shared/domain/validator.js.map +1 -0
- package/dist/tools/infrastructure/workspaceTools.d.ts +1 -0
- package/dist/tools/infrastructure/workspaceTools.js +32 -10
- package/dist/tools/infrastructure/workspaceTools.js.map +1 -1
- package/dist/workflow/domain/workflow.d.ts +100 -0
- package/dist/workflow/domain/workflow.js +12 -0
- package/dist/workflow/domain/workflow.js.map +1 -0
- package/dist/workflow/domain/workflowRunner.d.ts +54 -0
- package/dist/workflow/domain/workflowRunner.js +271 -0
- package/dist/workflow/domain/workflowRunner.js.map +1 -0
- package/package.json +1 -1
- package/dist/orchestration/domain/interface/validator.d.ts +0 -3
- package/dist/orchestration/domain/interface/validator.js.map +0 -1
- /package/dist/{orchestration/domain/interface → shared/domain}/validator.js +0 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { RunContext, runContextOf, withRunContext, } from '#src/agent/domain/runContext';
|
|
2
|
+
import { RecoverableError, UnrecoverableError, withSpentTokens } from '#src/shared/domain/errors';
|
|
3
|
+
import { isAbortError } from '#src/shared/domain/isAbortError';
|
|
4
|
+
import { classifyHostFailure, describeFailure, treatErrors, } from '#src/shared/domain/providerFailure';
|
|
5
|
+
import { addTokens } from '#src/shared/domain/tokens';
|
|
6
|
+
function addUsage(totals, response) {
|
|
7
|
+
if (response.tokens === undefined)
|
|
8
|
+
totals.unreported = true;
|
|
9
|
+
totals.tokens = addTokens(totals.tokens, response.tokens);
|
|
10
|
+
}
|
|
11
|
+
function knownTokens(totals) {
|
|
12
|
+
return totals.unreported ? undefined : totals.tokens;
|
|
13
|
+
}
|
|
14
|
+
function stripCodeFence(text) {
|
|
15
|
+
const match = /^```(?:json)?\s*([\s\S]*?)\s*```$/i.exec(text.trim());
|
|
16
|
+
return match?.[1] ?? text;
|
|
17
|
+
}
|
|
18
|
+
function parseJSON(text) {
|
|
19
|
+
try {
|
|
20
|
+
return { value: JSON.parse(stripCodeFence(text)) };
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function retryNotice(reason) {
|
|
27
|
+
return `\n\nYour previous response could not be used: ${reason} Respond with JSON only, matching the schema exactly, with no surrounding text and no markdown code fences.`;
|
|
28
|
+
}
|
|
29
|
+
function has(record, field) {
|
|
30
|
+
return Object.prototype.hasOwnProperty.call(record, field);
|
|
31
|
+
}
|
|
32
|
+
/** The types only bind TypeScript callers; a JavaScript callback can return anything. */
|
|
33
|
+
function isTransition(value) {
|
|
34
|
+
if (typeof value !== 'object' || value === null || !('type' in value))
|
|
35
|
+
return false;
|
|
36
|
+
switch (value.type) {
|
|
37
|
+
case 'go':
|
|
38
|
+
return 'stage' in value && typeof value.stage === 'string' && has(value, 'input');
|
|
39
|
+
case 'stop':
|
|
40
|
+
case 'complete':
|
|
41
|
+
return has(value, 'result');
|
|
42
|
+
default:
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Runs the stages the factory already checked: one stage at a time, each answer validated before
|
|
48
|
+
* its transition is taken, and only the transition's `input` reaching the next stage. Each stage
|
|
49
|
+
* creates its agent once per run, so a run never sees another run's conversations.
|
|
50
|
+
*/
|
|
51
|
+
export class WorkflowRunner {
|
|
52
|
+
stages;
|
|
53
|
+
initialStage;
|
|
54
|
+
maxAttempts;
|
|
55
|
+
maxTransitions;
|
|
56
|
+
logger;
|
|
57
|
+
constructor(options) {
|
|
58
|
+
this.initialStage = options.initialStage;
|
|
59
|
+
this.stages = options.stages;
|
|
60
|
+
this.maxAttempts = options.maxAttempts;
|
|
61
|
+
this.maxTransitions = options.maxTransitions;
|
|
62
|
+
this.logger = options.logger;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Every stage of one run shares one run context, so what their agents write is one run of the
|
|
66
|
+
* workspace's history. The workflow ends that context with the run, unless an outer agent owns it.
|
|
67
|
+
*/
|
|
68
|
+
async run(input, options) {
|
|
69
|
+
const start = Date.now();
|
|
70
|
+
// Per invocation, not per instance: one workflow can serve several runs at once.
|
|
71
|
+
const totals = { tokens: undefined, unreported: false };
|
|
72
|
+
const stages = [];
|
|
73
|
+
const inherited = runContextOf(options);
|
|
74
|
+
const context = inherited ?? new RunContext();
|
|
75
|
+
let end = 'failed';
|
|
76
|
+
let outcome;
|
|
77
|
+
try {
|
|
78
|
+
outcome = await this.runStages(input, inherited ? options : withRunContext(options, context), totals, stages, new Map());
|
|
79
|
+
end = 'completed';
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
if (isAbortError(error) || options.signal.aborted)
|
|
83
|
+
end = 'cancelled';
|
|
84
|
+
// A failing stage carries its own tokens; the stages before it are in the totals.
|
|
85
|
+
throw withSpentTokens(error, totals.tokens, totals.unreported);
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
if (!inherited)
|
|
89
|
+
await this.finishContext(context, end);
|
|
90
|
+
}
|
|
91
|
+
// Read once the run ended: a run that changed nothing left the history then.
|
|
92
|
+
const { runId } = context.historyRun;
|
|
93
|
+
return {
|
|
94
|
+
...outcome,
|
|
95
|
+
tokens: knownTokens(totals),
|
|
96
|
+
duration: (Date.now() - start) / 1000,
|
|
97
|
+
stages,
|
|
98
|
+
...(!inherited && runId ? { runId } : {}),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/** `agents` holds the agent each stage created in this run, so a stage the run comes back to goes on with it. */
|
|
102
|
+
async runStages(input, options, totals, reports, agents) {
|
|
103
|
+
let stage = this.stageNamed(this.initialStage);
|
|
104
|
+
let stageInput = input;
|
|
105
|
+
for (let transitions = 0;; transitions++) {
|
|
106
|
+
const started = Date.now();
|
|
107
|
+
const stageTotals = { tokens: undefined, unreported: false };
|
|
108
|
+
const agent = await this.agentOf(stage, agents);
|
|
109
|
+
const { output, attempts } = await this.answer(stage, agent, stageInput, options, totals, stageTotals);
|
|
110
|
+
const transition = await this.transition(stage, output, stageInput);
|
|
111
|
+
options.signal.throwIfAborted();
|
|
112
|
+
const tokens = knownTokens(stageTotals);
|
|
113
|
+
const nextStage = transition.type === 'go' ? transition.stage : undefined;
|
|
114
|
+
reports.push({
|
|
115
|
+
stage: stage.name,
|
|
116
|
+
input: stageInput,
|
|
117
|
+
output,
|
|
118
|
+
attempts,
|
|
119
|
+
duration: (Date.now() - started) / 1000,
|
|
120
|
+
...(tokens && { tokens }),
|
|
121
|
+
transition: transition.type,
|
|
122
|
+
...(nextStage !== undefined && { nextStage }),
|
|
123
|
+
});
|
|
124
|
+
this.emit(options, {
|
|
125
|
+
type: 'workflowTransition',
|
|
126
|
+
stage: stage.name,
|
|
127
|
+
transition: transition.type,
|
|
128
|
+
...(nextStage !== undefined && { nextStage }),
|
|
129
|
+
});
|
|
130
|
+
if (transition.type !== 'go') {
|
|
131
|
+
const status = transition.type === 'complete' ? 'completed' : 'stopped';
|
|
132
|
+
return { status, result: transition.result };
|
|
133
|
+
}
|
|
134
|
+
if (transitions + 1 > this.maxTransitions) {
|
|
135
|
+
throw new UnrecoverableError('Workflow transition limit exhausted', {
|
|
136
|
+
cause: `The run went to another stage more than ${String(this.maxTransitions)} times.`,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
stage = this.stageNamed(transition.stage, stage.name);
|
|
140
|
+
stageInput = transition.input;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/** The stage's agent in this run: created the first time the stage runs, then reused. */
|
|
144
|
+
async agentOf(stage, agents) {
|
|
145
|
+
const existing = agents.get(stage.name);
|
|
146
|
+
if (existing)
|
|
147
|
+
return existing;
|
|
148
|
+
let agent;
|
|
149
|
+
try {
|
|
150
|
+
agent = await stage.agent();
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
throw classifyHostFailure(error, `Workflow stage "${stage.name}" could not create its agent`);
|
|
154
|
+
}
|
|
155
|
+
agents.set(stage.name, agent);
|
|
156
|
+
return agent;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Asks the stage's agent until an answer is valid JSON its validator accepts, or attempts run
|
|
160
|
+
* out. Each answer's usage goes to the run's and the stage's totals; a failure carries the
|
|
161
|
+
* run's when `run` rethrows it.
|
|
162
|
+
*/
|
|
163
|
+
async answer(stage, agent, input, options, totals, stageTotals) {
|
|
164
|
+
const maxAttempts = stage.maxAttempts ?? this.maxAttempts;
|
|
165
|
+
options.signal.throwIfAborted();
|
|
166
|
+
let prompt;
|
|
167
|
+
try {
|
|
168
|
+
prompt = await stage.prompt(input);
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
throw classifyHostFailure(error, `Workflow stage "${stage.name}" could not build its prompt`);
|
|
172
|
+
}
|
|
173
|
+
let failureReason;
|
|
174
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
175
|
+
options.signal.throwIfAborted();
|
|
176
|
+
this.emit(options, { type: 'workflowStage', stage: stage.name, attempt });
|
|
177
|
+
const response = await this.runAgent(stage, agent, failureReason ? prompt + retryNotice(failureReason) : prompt, options);
|
|
178
|
+
addUsage(totals, response);
|
|
179
|
+
// An answer that arrives after a cancellation is not used.
|
|
180
|
+
options.signal.throwIfAborted();
|
|
181
|
+
addUsage(stageTotals, response);
|
|
182
|
+
const parsed = parseJSON(response.response);
|
|
183
|
+
if (parsed) {
|
|
184
|
+
let output;
|
|
185
|
+
try {
|
|
186
|
+
output = stage.validator.validate(parsed.value);
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
throw classifyHostFailure(error, `Workflow stage "${stage.name}" could not validate its answer`);
|
|
190
|
+
}
|
|
191
|
+
if (output !== undefined)
|
|
192
|
+
return { output, attempts: attempt };
|
|
193
|
+
failureReason = 'it did not match the schema.';
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
failureReason = 'it was not valid JSON.';
|
|
197
|
+
}
|
|
198
|
+
if (attempt < maxAttempts) {
|
|
199
|
+
this.warn(`Workflow stage "${stage.name}" answer ${String(attempt)}/${String(maxAttempts)} was unusable (${failureReason}); asking again`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
throw new UnrecoverableError('Workflow stage attempts exhausted', {
|
|
203
|
+
cause: `Stage "${stage.name}" gave no usable answer in ${String(maxAttempts)} attempts: the last one ${failureReason ?? ''}`,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
async runAgent(stage, agent, prompt, options) {
|
|
207
|
+
try {
|
|
208
|
+
return await agent.run(prompt, options);
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
// Whatever the agent threw once the run was cancelled, the run ends as cancelled.
|
|
212
|
+
options.signal.throwIfAborted();
|
|
213
|
+
if (isAbortError(error))
|
|
214
|
+
throw error;
|
|
215
|
+
if (error instanceof RecoverableError || error instanceof UnrecoverableError)
|
|
216
|
+
throw error;
|
|
217
|
+
throw new UnrecoverableError(`Workflow stage "${stage.name}" failed`, {
|
|
218
|
+
cause: describeFailure(error),
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
async transition(stage, output, input) {
|
|
223
|
+
let transition;
|
|
224
|
+
try {
|
|
225
|
+
transition = await stage.transition(output, input);
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
throw classifyHostFailure(error, `Workflow stage "${stage.name}" could not choose its transition`);
|
|
229
|
+
}
|
|
230
|
+
if (!isTransition(transition)) {
|
|
231
|
+
throw new UnrecoverableError('Workflow transition is invalid', {
|
|
232
|
+
cause: `Stage "${stage.name}" returned something that is not a transition.`,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
return transition;
|
|
236
|
+
}
|
|
237
|
+
stageNamed(name, from) {
|
|
238
|
+
const stage = this.stages.get(name);
|
|
239
|
+
if (!stage) {
|
|
240
|
+
throw new UnrecoverableError('Workflow stage does not exist', {
|
|
241
|
+
cause: from
|
|
242
|
+
? `Stage "${from}" selected unknown stage "${name}".`
|
|
243
|
+
: `The workflow has no stage "${name}".`,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
return stage;
|
|
247
|
+
}
|
|
248
|
+
/** The run's records end here, but its result stands: what it changed is already on disk. */
|
|
249
|
+
async finishContext(context, end) {
|
|
250
|
+
try {
|
|
251
|
+
await context.finish(end);
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
this.warn(`The run could not close its records: ${describeFailure(error)}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
emit(options, event) {
|
|
258
|
+
const { onProgress } = options;
|
|
259
|
+
if (!onProgress)
|
|
260
|
+
return;
|
|
261
|
+
treatErrors(() => {
|
|
262
|
+
onProgress(event);
|
|
263
|
+
}, classifyHostFailure, 'Workflow progress callback failed');
|
|
264
|
+
}
|
|
265
|
+
warn(message) {
|
|
266
|
+
treatErrors(() => {
|
|
267
|
+
this.logger.warn(message);
|
|
268
|
+
}, classifyHostFailure, 'Workflow logger failed while reporting a retry');
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
//# sourceMappingURL=workflowRunner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflowRunner.js","sourceRoot":"","sources":["../../../src/workflow/domain/workflowRunner.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Agent, AgentResponse, ProgressEvent, RunOptions } from '#src/agent/domain/agent';\nimport {\n\tRunContext,\n\trunContextOf,\n\twithRunContext,\n\ttype RunEnd,\n} from '#src/agent/domain/runContext';\nimport { RecoverableError, UnrecoverableError, withSpentTokens } from '#src/shared/domain/errors';\nimport { isAbortError } from '#src/shared/domain/isAbortError';\nimport type { ILogger } from '#src/shared/domain/logger';\nimport {\n\tclassifyHostFailure,\n\tdescribeFailure,\n\ttreatErrors,\n} from '#src/shared/domain/providerFailure';\nimport { addTokens, type Tokens } from '#src/shared/domain/tokens';\nimport type { Validator } from '#src/shared/domain/validator';\nimport type { Workflow, WorkflowResult, WorkflowStageReport } from './workflow.ts';\n\n/** A stage with its types erased: the factory checked them, and the runner only passes values on. */\nexport interface RunnableStage {\n\treadonly name: string;\n\tagent(): Agent | PromiseLike<Agent>;\n\tprompt(input: unknown): string | PromiseLike<string>;\n\treadonly validator: Validator<unknown>;\n\ttransition(output: unknown, input: unknown): unknown;\n\treadonly maxAttempts?: number;\n}\n\ntype Transition<Result> =\n\t{ type: 'go'; stage: string; input: unknown } | { type: 'stop' | 'complete'; result: Result };\n\ninterface Totals {\n\ttokens: Tokens | undefined;\n\tunreported: boolean;\n}\n\nfunction addUsage(totals: Totals, response: AgentResponse): void {\n\tif (response.tokens === undefined) totals.unreported = true;\n\ttotals.tokens = addTokens(totals.tokens, response.tokens);\n}\n\nfunction knownTokens(totals: Totals): Tokens | undefined {\n\treturn totals.unreported ? undefined : totals.tokens;\n}\n\nfunction stripCodeFence(text: string): string {\n\tconst match = /^```(?:json)?\\s*([\\s\\S]*?)\\s*```$/i.exec(text.trim());\n\treturn match?.[1] ?? text;\n}\n\nfunction parseJSON(text: string): { value: unknown } | undefined {\n\ttry {\n\t\treturn { value: JSON.parse(stripCodeFence(text)) as unknown };\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction retryNotice(reason: string): string {\n\treturn `\\n\\nYour previous response could not be used: ${reason} Respond with JSON only, matching the schema exactly, with no surrounding text and no markdown code fences.`;\n}\n\nfunction has(record: object, field: string): boolean {\n\treturn Object.prototype.hasOwnProperty.call(record, field);\n}\n\n/** The types only bind TypeScript callers; a JavaScript callback can return anything. */\nfunction isTransition<Result>(value: unknown): value is Transition<Result> {\n\tif (typeof value !== 'object' || value === null || !('type' in value)) return false;\n\tswitch (value.type) {\n\t\tcase 'go':\n\t\t\treturn 'stage' in value && typeof value.stage === 'string' && has(value, 'input');\n\t\tcase 'stop':\n\t\tcase 'complete':\n\t\t\treturn has(value, 'result');\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\n/**\n * Runs the stages the factory already checked: one stage at a time, each answer validated before\n * its transition is taken, and only the transition's `input` reaching the next stage. Each stage\n * creates its agent once per run, so a run never sees another run's conversations.\n */\nexport class WorkflowRunner<Input, Result> implements Workflow<Input, Result> {\n\tprivate readonly stages: ReadonlyMap<string, RunnableStage>;\n\tprivate readonly initialStage: string;\n\tprivate readonly maxAttempts: number;\n\tprivate readonly maxTransitions: number;\n\tprivate readonly logger: ILogger;\n\n\tconstructor(options: {\n\t\tinitialStage: string;\n\t\tstages: ReadonlyMap<string, RunnableStage>;\n\t\tmaxAttempts: number;\n\t\tmaxTransitions: number;\n\t\tlogger: ILogger;\n\t}) {\n\t\tthis.initialStage = options.initialStage;\n\t\tthis.stages = options.stages;\n\t\tthis.maxAttempts = options.maxAttempts;\n\t\tthis.maxTransitions = options.maxTransitions;\n\t\tthis.logger = options.logger;\n\t}\n\n\t/**\n\t * Every stage of one run shares one run context, so what their agents write is one run of the\n\t * workspace's history. The workflow ends that context with the run, unless an outer agent owns it.\n\t */\n\tasync run(input: Input, options: RunOptions): Promise<WorkflowResult<Result>> {\n\t\tconst start = Date.now();\n\t\t// Per invocation, not per instance: one workflow can serve several runs at once.\n\t\tconst totals: Totals = { tokens: undefined, unreported: false };\n\t\tconst stages: WorkflowStageReport[] = [];\n\t\tconst inherited = runContextOf(options);\n\t\tconst context = inherited ?? new RunContext();\n\t\tlet end: RunEnd = 'failed';\n\t\tlet outcome: { status: 'completed' | 'stopped'; result: Result };\n\n\t\ttry {\n\t\t\toutcome = await this.runStages(\n\t\t\t\tinput,\n\t\t\t\tinherited ? options : withRunContext(options, context),\n\t\t\t\ttotals,\n\t\t\t\tstages,\n\t\t\t\tnew Map(),\n\t\t\t);\n\t\t\tend = 'completed';\n\t\t} catch (error) {\n\t\t\tif (isAbortError(error) || options.signal.aborted) end = 'cancelled';\n\t\t\t// A failing stage carries its own tokens; the stages before it are in the totals.\n\t\t\tthrow withSpentTokens(error, totals.tokens, totals.unreported);\n\t\t} finally {\n\t\t\tif (!inherited) await this.finishContext(context, end);\n\t\t}\n\n\t\t// Read once the run ended: a run that changed nothing left the history then.\n\t\tconst { runId } = context.historyRun;\n\t\treturn {\n\t\t\t...outcome,\n\t\t\ttokens: knownTokens(totals),\n\t\t\tduration: (Date.now() - start) / 1000,\n\t\t\tstages,\n\t\t\t...(!inherited && runId ? { runId } : {}),\n\t\t};\n\t}\n\n\t/** `agents` holds the agent each stage created in this run, so a stage the run comes back to goes on with it. */\n\tprivate async runStages(\n\t\tinput: Input,\n\t\toptions: RunOptions,\n\t\ttotals: Totals,\n\t\treports: WorkflowStageReport[],\n\t\tagents: Map<string, Agent>,\n\t): Promise<{ status: 'completed' | 'stopped'; result: Result }> {\n\t\tlet stage = this.stageNamed(this.initialStage);\n\t\tlet stageInput: unknown = input;\n\n\t\tfor (let transitions = 0; ; transitions++) {\n\t\t\tconst started = Date.now();\n\t\t\tconst stageTotals: Totals = { tokens: undefined, unreported: false };\n\t\t\tconst agent = await this.agentOf(stage, agents);\n\t\t\tconst { output, attempts } = await this.answer(\n\t\t\t\tstage,\n\t\t\t\tagent,\n\t\t\t\tstageInput,\n\t\t\t\toptions,\n\t\t\t\ttotals,\n\t\t\t\tstageTotals,\n\t\t\t);\n\t\t\tconst transition = await this.transition(stage, output, stageInput);\n\t\t\toptions.signal.throwIfAborted();\n\n\t\t\tconst tokens = knownTokens(stageTotals);\n\t\t\tconst nextStage = transition.type === 'go' ? transition.stage : undefined;\n\t\t\treports.push({\n\t\t\t\tstage: stage.name,\n\t\t\t\tinput: stageInput,\n\t\t\t\toutput,\n\t\t\t\tattempts,\n\t\t\t\tduration: (Date.now() - started) / 1000,\n\t\t\t\t...(tokens && { tokens }),\n\t\t\t\ttransition: transition.type,\n\t\t\t\t...(nextStage !== undefined && { nextStage }),\n\t\t\t});\n\t\t\tthis.emit(options, {\n\t\t\t\ttype: 'workflowTransition',\n\t\t\t\tstage: stage.name,\n\t\t\t\ttransition: transition.type,\n\t\t\t\t...(nextStage !== undefined && { nextStage }),\n\t\t\t});\n\n\t\t\tif (transition.type !== 'go') {\n\t\t\t\tconst status = transition.type === 'complete' ? 'completed' : 'stopped';\n\t\t\t\treturn { status, result: transition.result };\n\t\t\t}\n\t\t\tif (transitions + 1 > this.maxTransitions) {\n\t\t\t\tthrow new UnrecoverableError('Workflow transition limit exhausted', {\n\t\t\t\t\tcause: `The run went to another stage more than ${String(this.maxTransitions)} times.`,\n\t\t\t\t});\n\t\t\t}\n\t\t\tstage = this.stageNamed(transition.stage, stage.name);\n\t\t\tstageInput = transition.input;\n\t\t}\n\t}\n\n\t/** The stage's agent in this run: created the first time the stage runs, then reused. */\n\tprivate async agentOf(stage: RunnableStage, agents: Map<string, Agent>): Promise<Agent> {\n\t\tconst existing = agents.get(stage.name);\n\t\tif (existing) return existing;\n\t\tlet agent: Agent;\n\t\ttry {\n\t\t\tagent = await stage.agent();\n\t\t} catch (error) {\n\t\t\tthrow classifyHostFailure(error, `Workflow stage \"${stage.name}\" could not create its agent`);\n\t\t}\n\t\tagents.set(stage.name, agent);\n\t\treturn agent;\n\t}\n\n\t/**\n\t * Asks the stage's agent until an answer is valid JSON its validator accepts, or attempts run\n\t * out. Each answer's usage goes to the run's and the stage's totals; a failure carries the\n\t * run's when `run` rethrows it.\n\t */\n\tprivate async answer(\n\t\tstage: RunnableStage,\n\t\tagent: Agent,\n\t\tinput: unknown,\n\t\toptions: RunOptions,\n\t\ttotals: Totals,\n\t\tstageTotals: Totals,\n\t): Promise<{ output: unknown; attempts: number }> {\n\t\tconst maxAttempts = stage.maxAttempts ?? this.maxAttempts;\n\t\toptions.signal.throwIfAborted();\n\t\tlet prompt: string;\n\t\ttry {\n\t\t\tprompt = await stage.prompt(input);\n\t\t} catch (error) {\n\t\t\tthrow classifyHostFailure(error, `Workflow stage \"${stage.name}\" could not build its prompt`);\n\t\t}\n\n\t\tlet failureReason: string | undefined;\n\t\tfor (let attempt = 1; attempt <= maxAttempts; attempt++) {\n\t\t\toptions.signal.throwIfAborted();\n\t\t\tthis.emit(options, { type: 'workflowStage', stage: stage.name, attempt });\n\t\t\tconst response = await this.runAgent(\n\t\t\t\tstage,\n\t\t\t\tagent,\n\t\t\t\tfailureReason ? prompt + retryNotice(failureReason) : prompt,\n\t\t\t\toptions,\n\t\t\t);\n\t\t\taddUsage(totals, response);\n\t\t\t// An answer that arrives after a cancellation is not used.\n\t\t\toptions.signal.throwIfAborted();\n\t\t\taddUsage(stageTotals, response);\n\n\t\t\tconst parsed = parseJSON(response.response);\n\t\t\tif (parsed) {\n\t\t\t\tlet output: unknown;\n\t\t\t\ttry {\n\t\t\t\t\toutput = stage.validator.validate(parsed.value);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthrow classifyHostFailure(\n\t\t\t\t\t\terror,\n\t\t\t\t\t\t`Workflow stage \"${stage.name}\" could not validate its answer`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (output !== undefined) return { output, attempts: attempt };\n\t\t\t\tfailureReason = 'it did not match the schema.';\n\t\t\t} else {\n\t\t\t\tfailureReason = 'it was not valid JSON.';\n\t\t\t}\n\n\t\t\tif (attempt < maxAttempts) {\n\t\t\t\tthis.warn(\n\t\t\t\t\t`Workflow stage \"${stage.name}\" answer ${String(attempt)}/${String(maxAttempts)} was unusable (${failureReason}); asking again`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tthrow new UnrecoverableError('Workflow stage attempts exhausted', {\n\t\t\tcause: `Stage \"${stage.name}\" gave no usable answer in ${String(maxAttempts)} attempts: the last one ${failureReason ?? ''}`,\n\t\t});\n\t}\n\n\tprivate async runAgent(\n\t\tstage: RunnableStage,\n\t\tagent: Agent,\n\t\tprompt: string,\n\t\toptions: RunOptions,\n\t): Promise<AgentResponse> {\n\t\ttry {\n\t\t\treturn await agent.run(prompt, options);\n\t\t} catch (error) {\n\t\t\t// Whatever the agent threw once the run was cancelled, the run ends as cancelled.\n\t\t\toptions.signal.throwIfAborted();\n\t\t\tif (isAbortError(error)) throw error;\n\t\t\tif (error instanceof RecoverableError || error instanceof UnrecoverableError) throw error;\n\t\t\tthrow new UnrecoverableError(`Workflow stage \"${stage.name}\" failed`, {\n\t\t\t\tcause: describeFailure(error),\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate async transition(\n\t\tstage: RunnableStage,\n\t\toutput: unknown,\n\t\tinput: unknown,\n\t): Promise<Transition<Result>> {\n\t\tlet transition: unknown;\n\t\ttry {\n\t\t\ttransition = await stage.transition(output, input);\n\t\t} catch (error) {\n\t\t\tthrow classifyHostFailure(\n\t\t\t\terror,\n\t\t\t\t`Workflow stage \"${stage.name}\" could not choose its transition`,\n\t\t\t);\n\t\t}\n\t\tif (!isTransition<Result>(transition)) {\n\t\t\tthrow new UnrecoverableError('Workflow transition is invalid', {\n\t\t\t\tcause: `Stage \"${stage.name}\" returned something that is not a transition.`,\n\t\t\t});\n\t\t}\n\t\treturn transition;\n\t}\n\n\tprivate stageNamed(name: string, from?: string): RunnableStage {\n\t\tconst stage = this.stages.get(name);\n\t\tif (!stage) {\n\t\t\tthrow new UnrecoverableError('Workflow stage does not exist', {\n\t\t\t\tcause: from\n\t\t\t\t\t? `Stage \"${from}\" selected unknown stage \"${name}\".`\n\t\t\t\t\t: `The workflow has no stage \"${name}\".`,\n\t\t\t});\n\t\t}\n\t\treturn stage;\n\t}\n\n\t/** The run's records end here, but its result stands: what it changed is already on disk. */\n\tprivate async finishContext(context: RunContext, end: RunEnd): Promise<void> {\n\t\ttry {\n\t\t\tawait context.finish(end);\n\t\t} catch (error) {\n\t\t\tthis.warn(`The run could not close its records: ${describeFailure(error)}`);\n\t\t}\n\t}\n\n\tprivate emit(options: RunOptions, event: ProgressEvent): void {\n\t\tconst { onProgress } = options;\n\t\tif (!onProgress) return;\n\t\ttreatErrors(\n\t\t\t() => {\n\t\t\t\tonProgress(event);\n\t\t\t},\n\t\t\tclassifyHostFailure,\n\t\t\t'Workflow progress callback failed',\n\t\t);\n\t}\n\n\tprivate warn(message: string): void {\n\t\ttreatErrors(\n\t\t\t() => {\n\t\t\t\tthis.logger.warn(message);\n\t\t\t},\n\t\t\tclassifyHostFailure,\n\t\t\t'Workflow logger failed while reporting a retry',\n\t\t);\n\t}\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"validator.js","sourceRoot":"","sources":["../../../../src/orchestration/domain/interface/validator.ts"],"names":[],"mappings":"","sourcesContent":["export interface Validator<T> {\n\tvalidate(input: unknown): T | undefined;\n}\n"]}
|
|
File without changes
|