agentic-api 2.0.491 → 2.0.592
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 +37 -34
- package/dist/src/agents/job.runner.d.ts +130 -0
- package/dist/src/agents/job.runner.js +339 -0
- package/dist/src/agents/reducer.core.d.ts +11 -1
- package/dist/src/agents/reducer.core.js +76 -86
- package/dist/src/agents/reducer.d.ts +1 -0
- package/dist/src/agents/reducer.factory.d.ts +46 -0
- package/dist/src/agents/reducer.factory.js +154 -0
- package/dist/src/agents/reducer.js +1 -0
- package/dist/src/agents/simulator.d.ts +26 -1
- package/dist/src/agents/simulator.dashboard.d.ts +140 -0
- package/dist/src/agents/simulator.dashboard.js +344 -0
- package/dist/src/agents/simulator.js +56 -0
- package/dist/src/agents/simulator.types.d.ts +38 -6
- package/dist/src/agents/simulator.utils.d.ts +22 -1
- package/dist/src/agents/simulator.utils.js +27 -0
- package/dist/src/execute/helpers.js +2 -2
- package/dist/src/execute/modelconfig.d.ts +21 -11
- package/dist/src/execute/modelconfig.js +29 -13
- package/dist/src/execute/responses.js +8 -7
- package/dist/src/index.d.ts +6 -1
- package/dist/src/index.js +21 -1
- package/dist/src/llm/config.d.ts +25 -0
- package/dist/src/llm/config.js +38 -0
- package/dist/src/llm/index.d.ts +48 -0
- package/dist/src/llm/index.js +115 -0
- package/dist/src/llm/openai.d.ts +6 -0
- package/dist/src/llm/openai.js +154 -0
- package/dist/src/llm/pricing.d.ts +26 -0
- package/dist/src/llm/pricing.js +129 -0
- package/dist/src/llm/xai.d.ts +17 -0
- package/dist/src/llm/xai.js +90 -0
- package/dist/src/pricing.llm.d.ts +3 -15
- package/dist/src/pricing.llm.js +10 -251
- package/dist/src/prompts.d.ts +0 -1
- package/dist/src/prompts.js +51 -118
- package/dist/src/rag/embeddings.d.ts +5 -1
- package/dist/src/rag/embeddings.js +15 -5
- package/dist/src/rag/parser.js +1 -1
- package/dist/src/rag/rag.manager.d.ts +44 -6
- package/dist/src/rag/rag.manager.js +138 -49
- package/dist/src/rag/types.d.ts +2 -0
- package/dist/src/rag/usecase.js +8 -11
- package/dist/src/rules/git/git.health.js +59 -4
- package/dist/src/rules/git/repo.d.ts +11 -4
- package/dist/src/rules/git/repo.js +64 -18
- package/dist/src/rules/git/repo.pr.d.ts +8 -0
- package/dist/src/rules/git/repo.pr.js +45 -1
- package/dist/src/rules/git/repo.tools.d.ts +5 -1
- package/dist/src/rules/git/repo.tools.js +54 -7
- package/dist/src/rules/types.d.ts +14 -0
- package/dist/src/rules/utils.matter.d.ts +0 -20
- package/dist/src/rules/utils.matter.js +42 -74
- package/dist/src/scrapper.js +2 -2
- package/dist/src/utils.d.ts +0 -8
- package/dist/src/utils.js +1 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -66,15 +66,24 @@ npm install @agentic-api
|
|
|
66
66
|
|
|
67
67
|
## 💡 Quick Start
|
|
68
68
|
|
|
69
|
+
### Configuration `.env`
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Provider LLM (openai | xai)
|
|
73
|
+
LLM_PROVIDER=openai
|
|
74
|
+
|
|
75
|
+
# Clés API
|
|
76
|
+
OPENAI_API_KEY=sk-... # Requis pour OpenAI + embeddings + whisper
|
|
77
|
+
XAI_API_KEY=xai-... # Requis si LLM_PROVIDER=xai
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Usage
|
|
81
|
+
|
|
69
82
|
```typescript
|
|
70
|
-
import
|
|
71
|
-
import { executeAgentSet } from '@agentic-api';
|
|
72
|
-
import { AgenticContext } from '@agentic-api';
|
|
73
|
-
import { AgentStateGraph } from '@agentic-api';
|
|
83
|
+
import { llmInstance, executeAgentSet, AgenticContext, AgentStateGraph } from '@agentic-api';
|
|
74
84
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
});
|
|
85
|
+
// Initialiser le LLM (utilise LLM_PROVIDER depuis .env)
|
|
86
|
+
llmInstance();
|
|
78
87
|
|
|
79
88
|
// Create context with user information
|
|
80
89
|
const context: AgenticContext = {
|
|
@@ -357,41 +366,35 @@ const structuredResult = await mapper.reduce(config, structuredCallback, {
|
|
|
357
366
|
|
|
358
367
|
Advanced testing framework for agent behavior validation with scenario-based simulations.
|
|
359
368
|
|
|
360
|
-
- **
|
|
361
|
-
- **
|
|
362
|
-
- **Automatic Validation**: Built-in
|
|
369
|
+
- **Clean API**: Separated `scenario` (context) and `testCase` (test parameters)
|
|
370
|
+
- **Oneshot by Default**: `maxExchanges=1` for simple single-response tests
|
|
371
|
+
- **Automatic Tool Validation**: Built-in validation with `expectedTools`
|
|
363
372
|
- **Exchange Limiting**: Control simulation length with configurable exchange limits
|
|
364
373
|
|
|
365
374
|
📖 **[Complete Agent Simulator Documentation →](./docs/README-AGENT-SIMULATOR.md)**
|
|
366
375
|
|
|
367
376
|
```typescript
|
|
368
|
-
import { AgentSimulator,
|
|
369
|
-
|
|
370
|
-
// Define test scenario
|
|
371
|
-
const scenario: SimulationScenario = {
|
|
372
|
-
testGoals: "Verify that the agent can help with haiku creation",
|
|
373
|
-
testEnd: "Agent provides a complete haiku poem",
|
|
374
|
-
testPersona: "A poetry enthusiast seeking creative assistance",
|
|
375
|
-
testQuery: "I want to write a haiku about nature. Can you help me?",
|
|
376
|
-
testResult: "Agent successfully guides haiku creation process",
|
|
377
|
-
testError: "Agent refuses to help or provides incorrect format"
|
|
378
|
-
};
|
|
377
|
+
import { AgentSimulator, PERSONA_PATIENT } from '@agentic-api';
|
|
379
378
|
|
|
380
379
|
// Configure simulator
|
|
381
380
|
const simulator = new AgentSimulator({
|
|
382
381
|
agents: [haikuAgent, welcomeAgent],
|
|
383
382
|
start: "welcome",
|
|
384
|
-
verbose: true
|
|
385
|
-
instructionEx: "Focus on creative writing assistance"
|
|
383
|
+
verbose: true
|
|
386
384
|
});
|
|
387
385
|
|
|
388
|
-
//
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
386
|
+
// Define test scenario (context)
|
|
387
|
+
const scenario = {
|
|
388
|
+
goals: "Verify that the agent can help with haiku creation. Agent provides a complete haiku poem.",
|
|
389
|
+
persona: PERSONA_PATIENT
|
|
390
|
+
// result defaults to '{"success": boolean, "explain": string, "error": string}'
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
// Run test case
|
|
394
|
+
const result = await simulator.testCase(scenario, {
|
|
395
|
+
query: "I want to write a haiku about nature. Can you help me?",
|
|
396
|
+
maxExchanges: 5, // defaults to 1 (oneshot)
|
|
397
|
+
expectedTools: { 'transferAgents': { gte: 1 } } // defaults to {}
|
|
395
398
|
});
|
|
396
399
|
|
|
397
400
|
// Validate results
|
|
@@ -406,10 +409,10 @@ if (!result.success) {
|
|
|
406
409
|
|
|
407
410
|
### Simulation Features
|
|
408
411
|
|
|
409
|
-
- **
|
|
410
|
-
- **
|
|
411
|
-
- **
|
|
412
|
-
- **
|
|
412
|
+
- **Separated Concerns**: `scenario` for context, `testCase` for test parameters
|
|
413
|
+
- **Sensible Defaults**: `maxExchanges=1`, `expectedTools={}`, default result format
|
|
414
|
+
- **Persona Simulation**: Built-in personas (PERSONA_PATIENT, PERSONA_PRESSE, PERSONA_ENERVE)
|
|
415
|
+
- **Tool Validation**: Automatic validation with `equal`, `gte`, `lte` constraints
|
|
413
416
|
- **Execution Metadata**: Access to token usage, actions, and performance metrics
|
|
414
417
|
|
|
415
418
|
## 📋 Rules Management System
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
export type JobStatus = 'planned' | 'running' | 'done' | 'failed';
|
|
2
|
+
export type TaskStatus = 'todo' | 'doing' | 'done' | 'failed';
|
|
3
|
+
export interface TaskSpec {
|
|
4
|
+
id: string;
|
|
5
|
+
title: string;
|
|
6
|
+
type?: string;
|
|
7
|
+
dependsOn?: string[];
|
|
8
|
+
input?: any;
|
|
9
|
+
acceptance?: string[];
|
|
10
|
+
}
|
|
11
|
+
export interface TaskResult {
|
|
12
|
+
taskId: string;
|
|
13
|
+
ok: boolean;
|
|
14
|
+
summary: string;
|
|
15
|
+
data?: any;
|
|
16
|
+
artifacts?: {
|
|
17
|
+
kind: string;
|
|
18
|
+
ref: string;
|
|
19
|
+
meta?: any;
|
|
20
|
+
}[];
|
|
21
|
+
error?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface JobPlan {
|
|
24
|
+
jobId: string;
|
|
25
|
+
goal: string;
|
|
26
|
+
beneficiary?: string;
|
|
27
|
+
tasks: TaskSpec[];
|
|
28
|
+
}
|
|
29
|
+
export interface ReducedJobMemory {
|
|
30
|
+
memory: string;
|
|
31
|
+
index: Record<string, any>;
|
|
32
|
+
statusLine?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface JobRunnerState {
|
|
35
|
+
jobId: string;
|
|
36
|
+
plan?: JobPlan;
|
|
37
|
+
jobStatus: JobStatus;
|
|
38
|
+
taskStatus: Record<string, TaskStatus>;
|
|
39
|
+
lastMemory: ReducedJobMemory | null;
|
|
40
|
+
lastError?: string;
|
|
41
|
+
}
|
|
42
|
+
export type PlannerFn = (input: any, seedMemory?: ReducedJobMemory | null) => Promise<JobPlan>;
|
|
43
|
+
export type ExecutorFn = (task: TaskSpec, memory: ReducedJobMemory | null) => Promise<TaskResult>;
|
|
44
|
+
export type ReducerFn = (previous: ReducedJobMemory | null, task: TaskSpec, result: TaskResult) => Promise<ReducedJobMemory>;
|
|
45
|
+
export type JobEventType = 'job_created' | 'plan_ready' | 'task_started' | 'task_done' | 'memory_reduced' | 'job_done' | 'job_failed';
|
|
46
|
+
export interface JobEvent<T = any> {
|
|
47
|
+
type: JobEventType;
|
|
48
|
+
at: string;
|
|
49
|
+
payload?: T;
|
|
50
|
+
}
|
|
51
|
+
export interface JobRunnerConfig {
|
|
52
|
+
planner: PlannerFn;
|
|
53
|
+
executor: ExecutorFn;
|
|
54
|
+
reducer: ReducerFn;
|
|
55
|
+
/**
|
|
56
|
+
* Directory where snapshots and events are persisted.
|
|
57
|
+
* If not provided, persistence is disabled.
|
|
58
|
+
*/
|
|
59
|
+
storeDir?: string;
|
|
60
|
+
/**
|
|
61
|
+
* Max attempts per task when schema validation fails or executor throws.
|
|
62
|
+
* (Logical failure with ok=false does not trigger retry)
|
|
63
|
+
*/
|
|
64
|
+
maxAttempts?: number;
|
|
65
|
+
onEvent?: (event: JobEvent) => void;
|
|
66
|
+
}
|
|
67
|
+
export interface RunJobOptions {
|
|
68
|
+
jobId?: string;
|
|
69
|
+
seedMemory?: ReducedJobMemory | null;
|
|
70
|
+
resume?: boolean;
|
|
71
|
+
}
|
|
72
|
+
export interface JobRunSuccess {
|
|
73
|
+
ok: true;
|
|
74
|
+
jobId: string;
|
|
75
|
+
plan: JobPlan;
|
|
76
|
+
finalSummary: string;
|
|
77
|
+
artifactsIndex: Record<string, any>;
|
|
78
|
+
memory: ReducedJobMemory | null;
|
|
79
|
+
taskStatus: Record<string, TaskStatus>;
|
|
80
|
+
snapshotFile?: string;
|
|
81
|
+
eventsFile?: string;
|
|
82
|
+
}
|
|
83
|
+
export interface JobRunFailure {
|
|
84
|
+
ok: false;
|
|
85
|
+
jobId: string;
|
|
86
|
+
plan?: JobPlan;
|
|
87
|
+
failedTaskId?: string;
|
|
88
|
+
error: string;
|
|
89
|
+
errorSummary: {
|
|
90
|
+
taskId?: string;
|
|
91
|
+
taskTitle?: string;
|
|
92
|
+
nature: string;
|
|
93
|
+
progress?: string;
|
|
94
|
+
nextAction: string;
|
|
95
|
+
};
|
|
96
|
+
memory: ReducedJobMemory | null;
|
|
97
|
+
taskStatus: Record<string, TaskStatus>;
|
|
98
|
+
snapshotFile?: string;
|
|
99
|
+
eventsFile?: string;
|
|
100
|
+
}
|
|
101
|
+
export type JobRunOutcome = JobRunSuccess | JobRunFailure;
|
|
102
|
+
export declare class JobRunner {
|
|
103
|
+
private readonly storeDir?;
|
|
104
|
+
private readonly maxAttempts;
|
|
105
|
+
private readonly planner;
|
|
106
|
+
private readonly executor;
|
|
107
|
+
private readonly reducer;
|
|
108
|
+
private readonly onEvent?;
|
|
109
|
+
constructor(config: JobRunnerConfig);
|
|
110
|
+
/**
|
|
111
|
+
* Run a job end-to-end (Plan → Execute → Reduce) with persistence and retries.
|
|
112
|
+
*/
|
|
113
|
+
run(input: any, options?: RunJobOptions): Promise<JobRunOutcome>;
|
|
114
|
+
private planJob;
|
|
115
|
+
private initTaskStatus;
|
|
116
|
+
private nextReadyTask;
|
|
117
|
+
private executeWithRetry;
|
|
118
|
+
private reduceWithRetry;
|
|
119
|
+
private handleFailure;
|
|
120
|
+
private validateJobPlan;
|
|
121
|
+
private validateTaskResult;
|
|
122
|
+
private validateMemory;
|
|
123
|
+
private persistState;
|
|
124
|
+
private loadState;
|
|
125
|
+
private ensureStoreDir;
|
|
126
|
+
private snapshotPath;
|
|
127
|
+
private eventsPath;
|
|
128
|
+
private logEvent;
|
|
129
|
+
private emit;
|
|
130
|
+
}
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.JobRunner = void 0;
|
|
7
|
+
const crypto_1 = require("crypto");
|
|
8
|
+
const fs_1 = require("fs");
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
class JobRunner {
|
|
11
|
+
constructor(config) {
|
|
12
|
+
this.planner = config.planner;
|
|
13
|
+
this.executor = config.executor;
|
|
14
|
+
this.reducer = config.reducer;
|
|
15
|
+
this.storeDir = config.storeDir;
|
|
16
|
+
this.maxAttempts = config.maxAttempts || 2;
|
|
17
|
+
this.onEvent = config.onEvent;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Run a job end-to-end (Plan → Execute → Reduce) with persistence and retries.
|
|
21
|
+
*/
|
|
22
|
+
async run(input, options) {
|
|
23
|
+
const seedMemory = options?.seedMemory ?? null;
|
|
24
|
+
const jobId = options?.jobId || (0, crypto_1.randomUUID)();
|
|
25
|
+
let state = null;
|
|
26
|
+
if (options?.resume && this.storeDir) {
|
|
27
|
+
state = await this.loadState(jobId);
|
|
28
|
+
}
|
|
29
|
+
if (!state) {
|
|
30
|
+
const plan = await this.planJob(jobId, input, seedMemory);
|
|
31
|
+
state = {
|
|
32
|
+
jobId: plan.jobId,
|
|
33
|
+
plan,
|
|
34
|
+
jobStatus: 'running',
|
|
35
|
+
taskStatus: this.initTaskStatus(plan.tasks),
|
|
36
|
+
lastMemory: seedMemory || null
|
|
37
|
+
};
|
|
38
|
+
await this.persistState(state);
|
|
39
|
+
this.emit({ type: 'job_created', at: new Date().toISOString(), payload: { jobId: state.jobId } });
|
|
40
|
+
}
|
|
41
|
+
if (!state.plan) {
|
|
42
|
+
return {
|
|
43
|
+
ok: false,
|
|
44
|
+
jobId,
|
|
45
|
+
error: 'Missing plan in restored state',
|
|
46
|
+
errorSummary: {
|
|
47
|
+
nature: 'state_corruption',
|
|
48
|
+
nextAction: 'Re-run planner to regenerate plan'
|
|
49
|
+
},
|
|
50
|
+
memory: state.lastMemory,
|
|
51
|
+
taskStatus: state.taskStatus
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
this.emit({ type: 'plan_ready', at: new Date().toISOString(), payload: { jobId: state.jobId, tasks: state.plan.tasks.length } });
|
|
55
|
+
const plan = state.plan;
|
|
56
|
+
state.jobStatus = 'running';
|
|
57
|
+
while (true) {
|
|
58
|
+
const nextTask = this.nextReadyTask(plan.tasks, state.taskStatus);
|
|
59
|
+
if (!nextTask)
|
|
60
|
+
break;
|
|
61
|
+
state.taskStatus[nextTask.id] = 'doing';
|
|
62
|
+
await this.persistState(state);
|
|
63
|
+
const execResult = await this.executeWithRetry(nextTask, state.lastMemory);
|
|
64
|
+
if (!execResult.valid) {
|
|
65
|
+
return await this.handleFailure(state, plan, nextTask, execResult.error || 'Execution failed');
|
|
66
|
+
}
|
|
67
|
+
const result = execResult.result;
|
|
68
|
+
this.emit({ type: 'task_done', at: new Date().toISOString(), payload: { jobId: plan.jobId, taskId: nextTask.id, ok: result.ok } });
|
|
69
|
+
const reduced = await this.reduceWithRetry(state.lastMemory, nextTask, result);
|
|
70
|
+
if (!reduced.valid) {
|
|
71
|
+
return await this.handleFailure(state, plan, nextTask, reduced.error || 'Reducer failed');
|
|
72
|
+
}
|
|
73
|
+
state.lastMemory = reduced.memory;
|
|
74
|
+
state.taskStatus[nextTask.id] = result.ok ? 'done' : 'failed';
|
|
75
|
+
await this.persistState(state);
|
|
76
|
+
this.emit({ type: 'memory_reduced', at: new Date().toISOString(), payload: { jobId: plan.jobId, taskId: nextTask.id } });
|
|
77
|
+
if (!result.ok) {
|
|
78
|
+
return await this.handleFailure(state, plan, nextTask, result.error || 'Task reported logical failure');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
state.jobStatus = 'done';
|
|
82
|
+
await this.persistState(state);
|
|
83
|
+
this.emit({ type: 'job_done', at: new Date().toISOString(), payload: { jobId: plan.jobId } });
|
|
84
|
+
return {
|
|
85
|
+
ok: true,
|
|
86
|
+
jobId: plan.jobId,
|
|
87
|
+
plan,
|
|
88
|
+
finalSummary: state.lastMemory?.memory || '',
|
|
89
|
+
artifactsIndex: state.lastMemory?.index || {},
|
|
90
|
+
memory: state.lastMemory,
|
|
91
|
+
taskStatus: state.taskStatus,
|
|
92
|
+
snapshotFile: this.snapshotPath(plan.jobId),
|
|
93
|
+
eventsFile: this.eventsPath(plan.jobId)
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
async planJob(jobId, input, seedMemory) {
|
|
97
|
+
const plan = await this.planner(input, seedMemory);
|
|
98
|
+
const finalPlan = {
|
|
99
|
+
...plan,
|
|
100
|
+
jobId: plan.jobId || jobId
|
|
101
|
+
};
|
|
102
|
+
this.validateJobPlan(finalPlan, true);
|
|
103
|
+
return finalPlan;
|
|
104
|
+
}
|
|
105
|
+
initTaskStatus(tasks) {
|
|
106
|
+
return tasks.reduce((acc, task) => {
|
|
107
|
+
acc[task.id] = 'todo';
|
|
108
|
+
return acc;
|
|
109
|
+
}, {});
|
|
110
|
+
}
|
|
111
|
+
nextReadyTask(tasks, status) {
|
|
112
|
+
return tasks.find(task => {
|
|
113
|
+
if (status[task.id] !== 'todo')
|
|
114
|
+
return false;
|
|
115
|
+
if (!task.dependsOn || task.dependsOn.length === 0)
|
|
116
|
+
return true;
|
|
117
|
+
return task.dependsOn.every(dep => status[dep] === 'done');
|
|
118
|
+
}) || null;
|
|
119
|
+
}
|
|
120
|
+
async executeWithRetry(task, memory) {
|
|
121
|
+
for (let attempt = 1; attempt <= this.maxAttempts; attempt++) {
|
|
122
|
+
this.emit({ type: 'task_started', at: new Date().toISOString(), payload: { taskId: task.id, attempt } });
|
|
123
|
+
try {
|
|
124
|
+
const result = await this.executor(task, memory);
|
|
125
|
+
const errors = this.validateTaskResult(result);
|
|
126
|
+
if (errors.length === 0) {
|
|
127
|
+
return { valid: true, result, attempts: attempt };
|
|
128
|
+
}
|
|
129
|
+
if (attempt >= this.maxAttempts) {
|
|
130
|
+
return { valid: false, error: `TaskResult validation failed: ${errors.join(', ')}`, attempts: attempt };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
if (attempt >= this.maxAttempts) {
|
|
135
|
+
return { valid: false, error: err?.message || 'Executor error', attempts: attempt };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return { valid: false, error: 'Unknown execution failure', attempts: this.maxAttempts };
|
|
140
|
+
}
|
|
141
|
+
async reduceWithRetry(previous, task, result) {
|
|
142
|
+
for (let attempt = 1; attempt <= this.maxAttempts; attempt++) {
|
|
143
|
+
try {
|
|
144
|
+
const nextMemory = await this.reducer(previous, task, result);
|
|
145
|
+
const errors = this.validateMemory(nextMemory);
|
|
146
|
+
if (errors.length === 0) {
|
|
147
|
+
return { valid: true, memory: nextMemory };
|
|
148
|
+
}
|
|
149
|
+
if (attempt >= this.maxAttempts) {
|
|
150
|
+
return { valid: false, error: `Reducer validation failed: ${errors.join(', ')}` };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
if (attempt >= this.maxAttempts) {
|
|
155
|
+
return { valid: false, error: err?.message || 'Reducer error' };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return { valid: false, error: 'Unknown reducer failure' };
|
|
160
|
+
}
|
|
161
|
+
async handleFailure(state, plan, task, reason) {
|
|
162
|
+
state.jobStatus = 'failed';
|
|
163
|
+
state.taskStatus[task.id] = state.taskStatus[task.id] === 'done' ? 'done' : 'failed';
|
|
164
|
+
state.lastError = reason;
|
|
165
|
+
await this.persistState(state);
|
|
166
|
+
const errorSummary = {
|
|
167
|
+
taskId: task.id,
|
|
168
|
+
taskTitle: task.title,
|
|
169
|
+
nature: reason,
|
|
170
|
+
progress: state.lastMemory?.memory,
|
|
171
|
+
nextAction: 'Inspect executor/reducer outputs or planner schema, then retry'
|
|
172
|
+
};
|
|
173
|
+
this.emit({ type: 'job_failed', at: new Date().toISOString(), payload: { jobId: plan.jobId, taskId: task.id, reason } });
|
|
174
|
+
return {
|
|
175
|
+
ok: false,
|
|
176
|
+
jobId: plan.jobId,
|
|
177
|
+
plan,
|
|
178
|
+
failedTaskId: task.id,
|
|
179
|
+
error: reason,
|
|
180
|
+
errorSummary,
|
|
181
|
+
memory: state.lastMemory,
|
|
182
|
+
taskStatus: state.taskStatus,
|
|
183
|
+
snapshotFile: this.snapshotPath(plan.jobId),
|
|
184
|
+
eventsFile: this.eventsPath(plan.jobId)
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
validateJobPlan(plan, throwOnError = false) {
|
|
188
|
+
const errors = [];
|
|
189
|
+
if (!plan || typeof plan !== 'object') {
|
|
190
|
+
errors.push('plan must be an object');
|
|
191
|
+
}
|
|
192
|
+
if (!plan.jobId || typeof plan.jobId !== 'string' || !plan.jobId.trim()) {
|
|
193
|
+
errors.push('jobId is required');
|
|
194
|
+
}
|
|
195
|
+
if (!plan.goal || typeof plan.goal !== 'string' || !plan.goal.trim()) {
|
|
196
|
+
errors.push('goal is required');
|
|
197
|
+
}
|
|
198
|
+
if (!Array.isArray(plan.tasks) || plan.tasks.length === 0) {
|
|
199
|
+
errors.push('tasks must be a non-empty array');
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
plan.tasks.forEach((task, idx) => {
|
|
203
|
+
if (!task || typeof task !== 'object') {
|
|
204
|
+
errors.push(`task[${idx}] must be an object`);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (!task.id || typeof task.id !== 'string' || !task.id.trim()) {
|
|
208
|
+
errors.push(`task[${idx}].id is required`);
|
|
209
|
+
}
|
|
210
|
+
if (!task.title || typeof task.title !== 'string' || !task.title.trim()) {
|
|
211
|
+
errors.push(`task[${idx}].title is required`);
|
|
212
|
+
}
|
|
213
|
+
if (task.dependsOn && !Array.isArray(task.dependsOn)) {
|
|
214
|
+
errors.push(`task[${idx}].dependsOn must be an array if provided`);
|
|
215
|
+
}
|
|
216
|
+
if (task.acceptance && !Array.isArray(task.acceptance)) {
|
|
217
|
+
errors.push(`task[${idx}].acceptance must be an array if provided`);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
if (throwOnError && errors.length) {
|
|
222
|
+
throw new Error(`Invalid JobPlan: ${errors.join(', ')}`);
|
|
223
|
+
}
|
|
224
|
+
return errors;
|
|
225
|
+
}
|
|
226
|
+
validateTaskResult(result) {
|
|
227
|
+
const errors = [];
|
|
228
|
+
if (!result || typeof result !== 'object') {
|
|
229
|
+
errors.push('TaskResult must be an object');
|
|
230
|
+
return errors;
|
|
231
|
+
}
|
|
232
|
+
if (!result.taskId || typeof result.taskId !== 'string' || !result.taskId.trim()) {
|
|
233
|
+
errors.push('taskId is required');
|
|
234
|
+
}
|
|
235
|
+
if (typeof result.ok !== 'boolean') {
|
|
236
|
+
errors.push('ok must be boolean');
|
|
237
|
+
}
|
|
238
|
+
if (!result.summary || typeof result.summary !== 'string') {
|
|
239
|
+
errors.push('summary is required');
|
|
240
|
+
}
|
|
241
|
+
if (result.artifacts && !Array.isArray(result.artifacts)) {
|
|
242
|
+
errors.push('artifacts must be an array if provided');
|
|
243
|
+
}
|
|
244
|
+
else if (Array.isArray(result.artifacts)) {
|
|
245
|
+
result.artifacts.forEach((artifact, idx) => {
|
|
246
|
+
if (!artifact || typeof artifact !== 'object') {
|
|
247
|
+
errors.push(`artifacts[${idx}] must be an object`);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (!artifact.kind || !artifact.ref) {
|
|
251
|
+
errors.push(`artifacts[${idx}] requires kind and ref`);
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
return errors;
|
|
256
|
+
}
|
|
257
|
+
validateMemory(memory) {
|
|
258
|
+
const errors = [];
|
|
259
|
+
if (!memory || typeof memory !== 'object') {
|
|
260
|
+
errors.push('memory must be an object');
|
|
261
|
+
return errors;
|
|
262
|
+
}
|
|
263
|
+
if (typeof memory.memory !== 'string') {
|
|
264
|
+
errors.push('memory.memory must be a string');
|
|
265
|
+
}
|
|
266
|
+
if (!memory.index || typeof memory.index !== 'object') {
|
|
267
|
+
errors.push('memory.index must be an object');
|
|
268
|
+
}
|
|
269
|
+
if (memory.statusLine && typeof memory.statusLine !== 'string') {
|
|
270
|
+
errors.push('memory.statusLine must be a string if provided');
|
|
271
|
+
}
|
|
272
|
+
return errors;
|
|
273
|
+
}
|
|
274
|
+
async persistState(state) {
|
|
275
|
+
if (!this.storeDir)
|
|
276
|
+
return;
|
|
277
|
+
await this.ensureStoreDir();
|
|
278
|
+
const file = this.snapshotPath(state.jobId);
|
|
279
|
+
if (!file)
|
|
280
|
+
return;
|
|
281
|
+
const snapshot = JSON.stringify(state, null, 2);
|
|
282
|
+
await fs_1.promises.writeFile(file, snapshot, 'utf-8');
|
|
283
|
+
}
|
|
284
|
+
async loadState(jobId) {
|
|
285
|
+
if (!this.storeDir)
|
|
286
|
+
return null;
|
|
287
|
+
const file = this.snapshotPath(jobId);
|
|
288
|
+
if (!file)
|
|
289
|
+
return null;
|
|
290
|
+
try {
|
|
291
|
+
const data = await fs_1.promises.readFile(file, 'utf-8');
|
|
292
|
+
return JSON.parse(data);
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
async ensureStoreDir() {
|
|
299
|
+
if (!this.storeDir)
|
|
300
|
+
return;
|
|
301
|
+
await fs_1.promises.mkdir(this.storeDir, { recursive: true });
|
|
302
|
+
}
|
|
303
|
+
snapshotPath(jobId) {
|
|
304
|
+
if (!this.storeDir)
|
|
305
|
+
return undefined;
|
|
306
|
+
return path_1.default.join(this.storeDir, `${jobId}.json`);
|
|
307
|
+
}
|
|
308
|
+
eventsPath(jobId) {
|
|
309
|
+
if (!this.storeDir)
|
|
310
|
+
return undefined;
|
|
311
|
+
return path_1.default.join(this.storeDir, `${jobId}.events.jsonl`);
|
|
312
|
+
}
|
|
313
|
+
async logEvent(jobId, event) {
|
|
314
|
+
if (!this.storeDir)
|
|
315
|
+
return;
|
|
316
|
+
await this.ensureStoreDir();
|
|
317
|
+
const file = this.eventsPath(jobId);
|
|
318
|
+
if (!file)
|
|
319
|
+
return;
|
|
320
|
+
const line = JSON.stringify(event);
|
|
321
|
+
await fs_1.promises.appendFile(file, `${line}\n`, 'utf-8');
|
|
322
|
+
}
|
|
323
|
+
emit(event) {
|
|
324
|
+
if (!event.at) {
|
|
325
|
+
event.at = new Date().toISOString();
|
|
326
|
+
}
|
|
327
|
+
if (this.onEvent) {
|
|
328
|
+
this.onEvent(event);
|
|
329
|
+
}
|
|
330
|
+
// Persist events only when jobId is present in payload or snapshot is known
|
|
331
|
+
const jobId = (event.payload && event.payload.jobId) || undefined;
|
|
332
|
+
if (jobId && this.storeDir) {
|
|
333
|
+
this.logEvent(jobId, event).catch(() => {
|
|
334
|
+
/* silent */
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
exports.JobRunner = JobRunner;
|
|
@@ -12,13 +12,23 @@ export interface AgentReducerConfig {
|
|
|
12
12
|
/** Default agent name to use if task doesn't specify one */
|
|
13
13
|
defaultAgent: string;
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Options for MapLLM constructor
|
|
17
|
+
*/
|
|
18
|
+
export interface MapLLMOptions {
|
|
19
|
+
/** Whether to execute a final reduce pass after all chunks (default: true) */
|
|
20
|
+
finalReduce?: boolean;
|
|
21
|
+
/** Threshold in bytes to trigger automatic intermediate reduce (optional) */
|
|
22
|
+
reduceThresholdBytes?: number;
|
|
23
|
+
}
|
|
15
24
|
/**
|
|
16
25
|
* MapLLM - Orchestrateur principal pour le reduce hiérarchique
|
|
17
26
|
*/
|
|
18
27
|
export declare class MapLLM {
|
|
19
28
|
private loader;
|
|
20
29
|
private agentConfig?;
|
|
21
|
-
|
|
30
|
+
private readonly options;
|
|
31
|
+
constructor(loader: NativeLoader, options?: MapLLMOptions);
|
|
22
32
|
/**
|
|
23
33
|
* Vérifie si le loader fournit des agents (TaskListLoader)
|
|
24
34
|
*/
|