import { Context, Effect, Layer, Schema } from 'effect'; import { Decision } from '../../Decision.ts'; import type { HarnessRule } from '../HarnessRule.ts'; import { RuleSet } from './RuleSet.ts'; type DecisionValue = Schema.Schema.Type; const beforeAgentStartRules = ( rules: ReadonlyArray ): ReadonlyArray => rules.flatMap((rule) => rule.phase === 'beforeAgentStart' ? [rule] : []); const toolCallRules = ( rules: ReadonlyArray ): ReadonlyArray => rules.flatMap((rule) => (rule.phase === 'toolCall' ? [rule] : [])); const toolResultRules = ( rules: ReadonlyArray ): ReadonlyArray => rules.flatMap((rule) => (rule.phase === 'toolResult' ? [rule] : [])); const runRules = ( rules: ReadonlyArray<{ readonly id: string; readonly evaluate: ( input: Input ) => Effect.Effect>; }>, input: Input ): Effect.Effect> => Effect.forEach(rules, (rule) => rule.evaluate(input)).pipe( Effect.map((decisionsPerRule) => decisionsPerRule.flatMap((decisions) => decisions) ) ); export namespace RuleEngine { export interface Interface { readonly evaluateBeforeAgentStart: ( input: HarnessRule.BeforeAgentStartInput ) => Effect.Effect>; readonly evaluateToolCall: ( input: HarnessRule.ToolCallInput ) => Effect.Effect>; readonly evaluateToolResult: ( input: HarnessRule.ToolResultInput ) => Effect.Effect>; } export class Service extends Context.Service()( 'pi-harness-kit/kernel/RuleEngine' ) {} export const layer = Layer.effect( Service, Effect.gen(function*() { const ruleSet = yield* RuleSet.Service; return Service.of({ evaluateBeforeAgentStart: (input) => Effect.flatMap( ruleSet.all, (rules) => runRules(beforeAgentStartRules(rules), input) ), evaluateToolCall: (input) => Effect.flatMap( ruleSet.all, (rules) => runRules(toolCallRules(rules), input) ), evaluateToolResult: (input) => Effect.flatMap( ruleSet.all, (rules) => runRules(toolResultRules(rules), input) ) }); }) ); }