@studio-foundation/ralph 0.3.0-beta.1 → 0.3.0-beta.6

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.
@@ -0,0 +1,3 @@
1
+ import type { ExecutionContext, RetryEvent } from './loop.js';
2
+ export declare function buildRetryContext<T>(event: RetryEvent<T>): ExecutionContext;
3
+ //# sourceMappingURL=context-enricher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-enricher.d.ts","sourceRoot":"","sources":["../src/context-enricher.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE9D,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,gBAAgB,CAK3E"}
@@ -0,0 +1,7 @@
1
+ export function buildRetryContext(event) {
2
+ return {
3
+ attempt: event.attempt + 1,
4
+ previousFailures: event.allFailures
5
+ };
6
+ }
7
+ //# sourceMappingURL=context-enricher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-enricher.js","sourceRoot":"","sources":["../src/context-enricher.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,iBAAiB,CAAI,KAAoB;IACvD,OAAO;QACL,OAAO,EAAE,KAAK,CAAC,OAAO,GAAG,CAAC;QAC1B,gBAAgB,EAAE,KAAK,CAAC,WAAW;KACpC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { OutputContract } from '@studio-foundation/contracts';
2
+ export declare function loadContract(path: string): Promise<OutputContract>;
3
+ export declare function parseContract(yamlContent: string): OutputContract;
4
+ //# sourceMappingURL=contracts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAEnE,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAGxE;AAED,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,CAiBjE"}
@@ -0,0 +1,22 @@
1
+ // Load and parse output contracts from YAML
2
+ import { readFile } from 'node:fs/promises';
3
+ import yaml from 'js-yaml';
4
+ export async function loadContract(path) {
5
+ const content = await readFile(path, 'utf-8');
6
+ return parseContract(content);
7
+ }
8
+ export function parseContract(yamlContent) {
9
+ const parsed = yaml.load(yamlContent);
10
+ // Validation basique
11
+ if (!parsed || typeof parsed !== 'object') {
12
+ throw new Error('Invalid contract: expected object');
13
+ }
14
+ if (!parsed.name || typeof parsed.name !== 'string') {
15
+ throw new Error('Invalid contract: missing or invalid name');
16
+ }
17
+ if (parsed.version === undefined || typeof parsed.version !== 'number') {
18
+ throw new Error('Invalid contract: missing or invalid version');
19
+ }
20
+ return parsed;
21
+ }
22
+ //# sourceMappingURL=contracts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contracts.js","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,IAAI,MAAM,SAAS,CAAC;AAG3B,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAY;IAC7C,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC9C,OAAO,aAAa,CAAC,OAAO,CAAC,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,WAAmB;IAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,CAAmB,CAAC;IAExD,qBAAqB;IACrB,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC1C,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;IACvD,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;IAC/D,CAAC;IAED,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,IAAI,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QACvE,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAClE,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,6 @@
1
+ export * from './loop.js';
2
+ export * from './validator.js';
3
+ export * from './contracts.js';
4
+ export * from './retry-strategy.js';
5
+ export * from './context-enricher.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC"}
@@ -1,7 +1,7 @@
1
1
  // Export barrel for @studio/ralph
2
-
3
2
  export * from './loop.js';
4
3
  export * from './validator.js';
5
4
  export * from './contracts.js';
6
5
  export * from './retry-strategy.js';
7
6
  export * from './context-enricher.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kCAAkC;AAElC,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC"}
package/dist/loop.d.ts ADDED
@@ -0,0 +1,40 @@
1
+ import type { ValidationResult } from '@studio-foundation/contracts';
2
+ export interface ExecutionContext {
3
+ attempt: number;
4
+ previousFailures: string[];
5
+ }
6
+ export interface RetryEvent<T> {
7
+ attempt: number;
8
+ result: T;
9
+ validation: ValidationResult;
10
+ allFailures: string[];
11
+ }
12
+ export interface RetryStrategy {
13
+ getDelay(attempt: number): number;
14
+ }
15
+ export interface RalphConfig<T> {
16
+ executor: (context: ExecutionContext) => Promise<T>;
17
+ validator: (result: T) => ValidationResult | Promise<ValidationResult>;
18
+ maxAttempts: number;
19
+ retryStrategy: RetryStrategy;
20
+ onRetry?: (event: RetryEvent<T>) => void | Promise<void>;
21
+ onSuccess?: (result: T, attempts: number) => void | Promise<void>;
22
+ onExhausted?: (lastResult: T, allFailures: string[]) => void | Promise<void>;
23
+ signal?: AbortSignal;
24
+ }
25
+ export type RalphResult<T> = {
26
+ status: 'success';
27
+ result: T;
28
+ attempts: number;
29
+ } | {
30
+ status: 'exhausted';
31
+ lastResult: T;
32
+ failures: string[];
33
+ attempts: number;
34
+ } | {
35
+ status: 'cancelled';
36
+ lastResult?: T;
37
+ attempts: number;
38
+ };
39
+ export declare function ralph<T>(config: RalphConfig<T>): Promise<RalphResult<T>>;
40
+ //# sourceMappingURL=loop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loop.d.ts","sourceRoot":"","sources":["../src/loop.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAErE,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,CAAC,CAAC;IACV,UAAU,EAAE,gBAAgB,CAAC;IAC7B,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;CACnC;AAED,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,QAAQ,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACpD,SAAS,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACvE,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,aAAa,CAAC;IAC7B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzD,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,WAAW,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7E,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,MAAM,WAAW,CAAC,CAAC,IACrB;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,CAAC,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,UAAU,EAAE,CAAC,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC5E;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,UAAU,CAAC,EAAE,CAAC,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9D,wBAAsB,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CA0E9E"}
package/dist/loop.js ADDED
@@ -0,0 +1,75 @@
1
+ export async function ralph(config) {
2
+ const { executor, validator, maxAttempts, retryStrategy, onRetry, onSuccess, onExhausted, signal } = config;
3
+ let attempt = 1;
4
+ const allFailures = [];
5
+ let lastResult;
6
+ while (attempt <= maxAttempts) {
7
+ // Check cancellation before each attempt
8
+ if (signal?.aborted) {
9
+ return { status: 'cancelled', lastResult, attempts: attempt };
10
+ }
11
+ // 1. Execute avec contexte
12
+ const context = {
13
+ attempt,
14
+ previousFailures: [...allFailures]
15
+ };
16
+ let result;
17
+ try {
18
+ result = await executor(context);
19
+ }
20
+ catch (err) {
21
+ // If signal was aborted, the executor likely threw an AbortError
22
+ if (signal?.aborted) {
23
+ return { status: 'cancelled', lastResult, attempts: attempt };
24
+ }
25
+ throw err; // Re-throw non-abort errors
26
+ }
27
+ lastResult = result;
28
+ // 2. Validate
29
+ const validation = await Promise.resolve(validator(result));
30
+ // 3. Si valide → SUCCESS
31
+ if (validation.valid) {
32
+ await onSuccess?.(result, attempt);
33
+ return { status: 'success', result, attempts: attempt };
34
+ }
35
+ // 4. Si invalide → accumuler erreurs
36
+ allFailures.push(...validation.errors);
37
+ // 5. Si dernière tentative → EXHAUSTED
38
+ if (attempt >= maxAttempts) {
39
+ await onExhausted?.(result, allFailures);
40
+ return { status: 'exhausted', lastResult: result, attempts: attempt, failures: allFailures };
41
+ }
42
+ // Check cancellation before retry
43
+ if (signal?.aborted) {
44
+ return { status: 'cancelled', lastResult: result, attempts: attempt };
45
+ }
46
+ // 6. Callback + delay + retry
47
+ const retryEvent = {
48
+ attempt,
49
+ result,
50
+ validation,
51
+ allFailures: [...allFailures]
52
+ };
53
+ await onRetry?.(retryEvent);
54
+ const delay = retryStrategy.getDelay(attempt);
55
+ if (delay > 0) {
56
+ await abortableDelay(delay, signal);
57
+ }
58
+ attempt++;
59
+ }
60
+ // Unreachable mais TypeScript est content
61
+ throw new Error('ralph loop should have returned');
62
+ }
63
+ /** Sleep that resolves immediately if signal is aborted */
64
+ function abortableDelay(ms, signal) {
65
+ if (signal?.aborted)
66
+ return Promise.resolve();
67
+ return new Promise((resolve) => {
68
+ const timer = setTimeout(resolve, ms);
69
+ signal?.addEventListener('abort', () => {
70
+ clearTimeout(timer);
71
+ resolve();
72
+ }, { once: true });
73
+ });
74
+ }
75
+ //# sourceMappingURL=loop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loop.js","sourceRoot":"","sources":["../src/loop.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { RetryStrategy } from './loop.js';
2
+ export declare function noDelay(): RetryStrategy;
3
+ export declare function fixedDelay(ms: number): RetryStrategy;
4
+ export declare function exponentialBackoff(baseMs: number, maxMs: number): RetryStrategy;
5
+ //# sourceMappingURL=retry-strategy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry-strategy.d.ts","sourceRoot":"","sources":["../src/retry-strategy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/C,wBAAgB,OAAO,IAAI,aAAa,CAIvC;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,aAAa,CAIpD;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,aAAa,CAO/E"}
@@ -0,0 +1,19 @@
1
+ export function noDelay() {
2
+ return {
3
+ getDelay: () => 0,
4
+ };
5
+ }
6
+ export function fixedDelay(ms) {
7
+ return {
8
+ getDelay: () => ms,
9
+ };
10
+ }
11
+ export function exponentialBackoff(baseMs, maxMs) {
12
+ return {
13
+ getDelay: (attempt) => {
14
+ const delay = baseMs * Math.pow(2, attempt - 1);
15
+ return Math.min(delay, maxMs);
16
+ },
17
+ };
18
+ }
19
+ //# sourceMappingURL=retry-strategy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry-strategy.js","sourceRoot":"","sources":["../src/retry-strategy.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,OAAO;IACrB,OAAO;QACL,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;KAClB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,EAAU;IACnC,OAAO;QACL,QAAQ,EAAE,GAAG,EAAE,CAAC,EAAE;KACnB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,MAAc,EAAE,KAAa;IAC9D,OAAO;QACL,QAAQ,EAAE,CAAC,OAAe,EAAE,EAAE;YAC5B,MAAM,KAAK,GAAG,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC;YAChD,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAChC,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { ValidationResult, OutputContract, ToolCall, ToolCallRequirements } from '@studio-foundation/contracts';
2
+ export type { ToolCallRequirements } from '@studio-foundation/contracts';
3
+ export type Validator<T> = (result: T) => ValidationResult | Promise<ValidationResult>;
4
+ export interface AgentRunResult {
5
+ output: unknown;
6
+ tool_calls: ToolCall[];
7
+ }
8
+ export declare function validateSchema(output: unknown, contract: OutputContract): ValidationResult;
9
+ export declare function validateToolCalls(toolCalls: ToolCall[], requirements?: ToolCallRequirements): ValidationResult;
10
+ export declare function validateRequiredTools(toolCalls: ToolCall[], requirements?: ToolCallRequirements): ValidationResult;
11
+ export declare function validateCountedTools(toolCalls: ToolCall[], requirements?: ToolCallRequirements): ValidationResult;
12
+ export declare function validateToolGroups(toolCalls: ToolCall[], requirements?: ToolCallRequirements): ValidationResult;
13
+ export declare function compose<T>(...validators: Validator<T>[]): Validator<T>;
14
+ //# sourceMappingURL=validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAErH,YAAY,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAEzE,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,KAAK,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;AAEvF,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,QAAQ,EAAE,CAAC;CACxB;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,cAAc,GAAG,gBAAgB,CA+B1F;AAMD,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,QAAQ,EAAE,EAAE,YAAY,CAAC,EAAE,oBAAoB,GAAG,gBAAgB,CA4B9G;AAOD,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,QAAQ,EAAE,EAAE,YAAY,CAAC,EAAE,oBAAoB,GAAG,gBAAgB,CAkBlH;AAED,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,QAAQ,EAAE,EAAE,YAAY,CAAC,EAAE,oBAAoB,GAAG,gBAAgB,CAmBjH;AAED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,QAAQ,EAAE,EAAE,YAAY,CAAC,EAAE,oBAAoB,GAAG,gBAAgB,CAoB/G;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,GAAG,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC,CAYtE"}
@@ -0,0 +1,111 @@
1
+ export function validateSchema(output, contract) {
2
+ const errors = [];
3
+ const warnings = [];
4
+ // If no schema defined, consider it valid
5
+ if (!contract.schema || !contract.schema.required_fields) {
6
+ return { valid: true, errors, warnings };
7
+ }
8
+ const requiredFields = contract.schema.required_fields;
9
+ // Output must be an object to check fields
10
+ if (output === null || typeof output !== 'object') {
11
+ errors.push(`Expected object output, got ${output === null ? 'null' : typeof output}`);
12
+ return { valid: false, errors, warnings };
13
+ }
14
+ const outputObj = output;
15
+ // Check each required field
16
+ for (const field of requiredFields) {
17
+ if (!(field in outputObj)) {
18
+ errors.push(`Missing required field: ${field}`);
19
+ }
20
+ }
21
+ return {
22
+ valid: errors.length === 0,
23
+ errors,
24
+ warnings
25
+ };
26
+ }
27
+ function isSuccessfulToolCall(tc) {
28
+ return !tc.error;
29
+ }
30
+ export function validateToolCalls(toolCalls, requirements) {
31
+ const errors = [];
32
+ const warnings = [];
33
+ const successfulCount = toolCalls.filter(isSuccessfulToolCall).length;
34
+ const failedCount = toolCalls.length - successfulCount;
35
+ if (requirements?.minimum !== undefined) {
36
+ if (successfulCount < requirements.minimum) {
37
+ const plural = requirements.minimum === 1 ? '' : 's';
38
+ const excluded = failedCount > 0 ? ` (${failedCount} failed excluded)` : '';
39
+ errors.push(`Expected at least ${requirements.minimum} successful tool call${plural}, got ${successfulCount} successful${excluded}`);
40
+ }
41
+ }
42
+ if (requirements?.maximum !== undefined) {
43
+ if (successfulCount > requirements.maximum) {
44
+ const plural = successfulCount === 1 ? '' : 's';
45
+ errors.push(`Tool call limit exceeded: made ${successfulCount} successful call${plural}, maximum is ${requirements.maximum}. ` +
46
+ `This may indicate a loop. Check that the agent is not repeating the same operation.`);
47
+ }
48
+ }
49
+ return { valid: errors.length === 0, errors, warnings };
50
+ }
51
+ /** Normalize tool name: dots → hyphens so both conventions match */
52
+ function normalizeToolName(name) {
53
+ return name.replace(/\./g, '-');
54
+ }
55
+ export function validateRequiredTools(toolCalls, requirements) {
56
+ const errors = [];
57
+ const warnings = [];
58
+ if (requirements?.required_tools && requirements.required_tools.length > 0) {
59
+ for (const requiredTool of requirements.required_tools) {
60
+ const normalizedRequired = normalizeToolName(requiredTool);
61
+ const matchingCalls = toolCalls.filter(tc => normalizeToolName(tc.name) === normalizedRequired);
62
+ if (matchingCalls.length === 0) {
63
+ errors.push(`Required tool '${requiredTool}' was not called`);
64
+ }
65
+ else if (!matchingCalls.some(isSuccessfulToolCall)) {
66
+ errors.push(`Required tool '${requiredTool}' has no successful calls (called ${matchingCalls.length} time${matchingCalls.length === 1 ? '' : 's'}, all failed)`);
67
+ }
68
+ }
69
+ }
70
+ return { valid: errors.length === 0, errors, warnings };
71
+ }
72
+ export function validateCountedTools(toolCalls, requirements) {
73
+ const errors = [];
74
+ const warnings = [];
75
+ if (requirements?.counted_tools && requirements.counted_tools.length > 0 && requirements?.minimum !== undefined) {
76
+ const countedSet = new Set(requirements.counted_tools.map(normalizeToolName));
77
+ const count = toolCalls.filter(tc => countedSet.has(normalizeToolName(tc.name)) && isSuccessfulToolCall(tc)).length;
78
+ if (count < requirements.minimum) {
79
+ const toolNames = requirements.counted_tools.join(', ');
80
+ errors.push(`Expected at least ${requirements.minimum} successful call${requirements.minimum === 1 ? '' : 's'} to counted tools [${toolNames}], got ${count}`);
81
+ }
82
+ }
83
+ return { valid: errors.length === 0, errors, warnings };
84
+ }
85
+ export function validateToolGroups(toolCalls, requirements) {
86
+ const errors = [];
87
+ const warnings = [];
88
+ if (requirements?.required_tool_groups) {
89
+ for (const group of requirements.required_tool_groups) {
90
+ if (group.length === 0)
91
+ continue;
92
+ const normalizedGroup = new Set(group.map(normalizeToolName));
93
+ const satisfied = toolCalls.some(tc => normalizedGroup.has(normalizeToolName(tc.name)) && isSuccessfulToolCall(tc));
94
+ if (!satisfied) {
95
+ errors.push(`Expected at least one successful call from [${group.join(', ')}], got none`);
96
+ }
97
+ }
98
+ }
99
+ return { valid: errors.length === 0, errors, warnings };
100
+ }
101
+ export function compose(...validators) {
102
+ return async (result) => {
103
+ const results = await Promise.all(validators.map(v => Promise.resolve(v(result))));
104
+ return {
105
+ valid: results.every(r => r.valid),
106
+ errors: results.flatMap(r => r.errors),
107
+ warnings: results.flatMap(r => r.warnings)
108
+ };
109
+ };
110
+ }
111
+ //# sourceMappingURL=validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.js","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@studio-foundation/ralph",
3
- "version": "0.3.0-beta.1",
3
+ "version": "0.3.0-beta.6",
4
4
  "description": "RALPH loop engine - Recursive Automated Loop for Persistent Handling",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -11,6 +11,9 @@
11
11
  "types": "./dist/index.d.ts"
12
12
  }
13
13
  },
14
+ "files": [
15
+ "dist"
16
+ ],
14
17
  "keywords": [
15
18
  "studio",
16
19
  "ralph",
@@ -21,7 +24,7 @@
21
24
  "license": "AGPL-3.0-only",
22
25
  "dependencies": {
23
26
  "js-yaml": "^4.1.1",
24
- "@studio-foundation/contracts": "0.3.0-beta.1"
27
+ "@studio-foundation/contracts": "0.3.0-beta.6"
25
28
  },
26
29
  "devDependencies": {
27
30
  "@types/js-yaml": "^4.0.9",
package/ARCHITECTURE.md DELETED
@@ -1,41 +0,0 @@
1
- # @studio-foundation/ralph
2
-
3
- RALPH loop engine — retry intelligent avec validation.
4
- "Recursive Automated Loop for Persistent Handling"
5
-
6
- ## Concept
7
-
8
- `ralph<T>()` prend un `executor` générique et un `validator`. Il exécute, valide, retry si fail, s'arrête sur AbortSignal.
9
- C'est tout. C'est générique. Ça marche pour n'importe quoi, pas juste des LLMs.
10
-
11
- ## Règles
12
-
13
- - `ralph()` est UNE fonction. Pas une classe, pas un framework.
14
- - Le `executor` est `() => Promise<T>` — ralph ne sait pas que c'est un LLM derrière
15
- - La validation est composable via `compose(...validators)`
16
- - Les stratégies de retry sont pluggables (`exponentialBackoff`, `fixedDelay`, `noDelay`)
17
- - JAMAIS de dépendance sur `@studio-foundation/runner` ou `@studio-foundation/engine` — ralph est agnostique
18
- - Dépend UNIQUEMENT de `@studio-foundation/contracts`
19
-
20
- ## Résultat
21
-
22
- ```typescript
23
- type RalphResult<T> =
24
- | { status: 'success'; result: T; attempts: number }
25
- | { status: 'exhausted'; lastResult: T; failures: string[]; attempts: number }
26
- | { status: 'cancelled'; lastResult?: T; attempts: number } // AbortSignal
27
- ```
28
-
29
- ## Fichiers clés
30
-
31
- - `loop.ts` — `ralph()` la fonction principale + `RalphConfig`, `RalphResult`
32
- - `validator.ts` — `validateSchema`, `validateToolCalls`, `validateRequiredTools`, `compose`
33
- - `retry-strategy.ts` — `exponentialBackoff(min, max)`, `fixedDelay(ms)`, `noDelay()`
34
- - `context-enricher.ts` — enrichir contexte entre retries
35
- - `contracts.ts` — types internes ralph
36
-
37
- ## Anti-patterns
38
-
39
- - NE PAS mettre de logique LLM ici
40
- - NE PAS importer `@studio-foundation/runner`
41
- - NE PAS hardcoder des règles de validation — tout vient des contracts YAML
@@ -1,19 +0,0 @@
1
- name: analysis
2
- version: 1
3
-
4
- schema:
5
- required_fields:
6
- - summary
7
- - recommendations
8
- summary:
9
- min_length: 50
10
- recommendations:
11
- min_items: 1
12
-
13
- tool_calls:
14
- minimum: 0 # Analysis doesn't require tool calls
15
-
16
- custom_rules:
17
- - name: actionable-recommendations
18
- description: "Recommendations must be specific and actionable"
19
- check: "recommendations.length > 0"
@@ -1,21 +0,0 @@
1
- name: code-generation
2
- version: 1
3
-
4
- schema:
5
- required_fields:
6
- - summary
7
- - files_changed
8
- files_changed:
9
- min_items: 1
10
- item_schema:
11
- required: [path, action, content]
12
-
13
- tool_calls:
14
- minimum: 1
15
- required_tools:
16
- - repo_manager.write_file
17
-
18
- custom_rules:
19
- - name: no-theatre
20
- description: "Agent must actually call tools, not just describe calling them"
21
- check: "tool_calls_count > 0 OR stage_kind != code_generation"
@@ -1,9 +0,0 @@
1
- // Enrich context between retries
2
- import type { ExecutionContext, RetryEvent } from './loop.js';
3
-
4
- export function buildRetryContext<T>(event: RetryEvent<T>): ExecutionContext {
5
- return {
6
- attempt: event.attempt + 1,
7
- previousFailures: event.allFailures
8
- };
9
- }
package/src/contracts.ts DELETED
@@ -1,28 +0,0 @@
1
- // Load and parse output contracts from YAML
2
- import { readFile } from 'node:fs/promises';
3
- import yaml from 'js-yaml';
4
- import type { OutputContract } from '@studio-foundation/contracts';
5
-
6
- export async function loadContract(path: string): Promise<OutputContract> {
7
- const content = await readFile(path, 'utf-8');
8
- return parseContract(content);
9
- }
10
-
11
- export function parseContract(yamlContent: string): OutputContract {
12
- const parsed = yaml.load(yamlContent) as OutputContract;
13
-
14
- // Validation basique
15
- if (!parsed || typeof parsed !== 'object') {
16
- throw new Error('Invalid contract: expected object');
17
- }
18
-
19
- if (!parsed.name || typeof parsed.name !== 'string') {
20
- throw new Error('Invalid contract: missing or invalid name');
21
- }
22
-
23
- if (parsed.version === undefined || typeof parsed.version !== 'number') {
24
- throw new Error('Invalid contract: missing or invalid version');
25
- }
26
-
27
- return parsed;
28
- }