outcome-cli 1.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 +261 -0
- package/package.json +95 -0
- package/src/agents/README.md +139 -0
- package/src/agents/adapters/anthropic.adapter.ts +166 -0
- package/src/agents/adapters/dalle.adapter.ts +145 -0
- package/src/agents/adapters/gemini.adapter.ts +134 -0
- package/src/agents/adapters/imagen.adapter.ts +106 -0
- package/src/agents/adapters/nano-banana.adapter.ts +129 -0
- package/src/agents/adapters/openai.adapter.ts +165 -0
- package/src/agents/adapters/veo.adapter.ts +130 -0
- package/src/agents/agent.schema.property.test.ts +379 -0
- package/src/agents/agent.schema.test.ts +148 -0
- package/src/agents/agent.schema.ts +263 -0
- package/src/agents/index.ts +60 -0
- package/src/agents/registered-agent.schema.ts +356 -0
- package/src/agents/registry.ts +97 -0
- package/src/agents/tournament-configs.property.test.ts +266 -0
- package/src/cli/README.md +145 -0
- package/src/cli/commands/define.ts +79 -0
- package/src/cli/commands/list.ts +46 -0
- package/src/cli/commands/logs.ts +83 -0
- package/src/cli/commands/run.ts +416 -0
- package/src/cli/commands/verify.ts +110 -0
- package/src/cli/index.ts +81 -0
- package/src/config/README.md +128 -0
- package/src/config/env.ts +262 -0
- package/src/config/index.ts +19 -0
- package/src/eval/README.md +318 -0
- package/src/eval/ai-judge.test.ts +435 -0
- package/src/eval/ai-judge.ts +368 -0
- package/src/eval/code-validators.ts +414 -0
- package/src/eval/evaluateOutcome.property.test.ts +1174 -0
- package/src/eval/evaluateOutcome.ts +591 -0
- package/src/eval/immigration-validators.ts +122 -0
- package/src/eval/index.ts +90 -0
- package/src/eval/judge-cache.ts +402 -0
- package/src/eval/tournament-validators.property.test.ts +439 -0
- package/src/eval/validators.property.test.ts +1118 -0
- package/src/eval/validators.ts +1199 -0
- package/src/eval/weighted-scorer.ts +285 -0
- package/src/index.ts +17 -0
- package/src/league/README.md +188 -0
- package/src/league/health-check.ts +353 -0
- package/src/league/index.ts +93 -0
- package/src/league/killAgent.ts +151 -0
- package/src/league/league.test.ts +1151 -0
- package/src/league/runLeague.ts +843 -0
- package/src/league/scoreAgent.ts +175 -0
- package/src/modules/omnibridge/__tests__/.gitkeep +1 -0
- package/src/modules/omnibridge/__tests__/auth-tunnel.property.test.ts +524 -0
- package/src/modules/omnibridge/__tests__/deterministic-logger.property.test.ts +965 -0
- package/src/modules/omnibridge/__tests__/ghost-api.property.test.ts +461 -0
- package/src/modules/omnibridge/__tests__/omnibridge-integration.test.ts +542 -0
- package/src/modules/omnibridge/__tests__/parallel-executor.property.test.ts +671 -0
- package/src/modules/omnibridge/__tests__/semantic-normalizer.property.test.ts +521 -0
- package/src/modules/omnibridge/__tests__/semantic-normalizer.test.ts +254 -0
- package/src/modules/omnibridge/__tests__/session-vault.property.test.ts +367 -0
- package/src/modules/omnibridge/__tests__/shadow-session.property.test.ts +523 -0
- package/src/modules/omnibridge/__tests__/triangulation-engine.property.test.ts +292 -0
- package/src/modules/omnibridge/__tests__/verification-engine.property.test.ts +769 -0
- package/src/modules/omnibridge/api/.gitkeep +1 -0
- package/src/modules/omnibridge/api/ghost-api.ts +1087 -0
- package/src/modules/omnibridge/auth/.gitkeep +1 -0
- package/src/modules/omnibridge/auth/auth-tunnel.ts +843 -0
- package/src/modules/omnibridge/auth/session-vault.ts +577 -0
- package/src/modules/omnibridge/core/.gitkeep +1 -0
- package/src/modules/omnibridge/core/semantic-normalizer.ts +702 -0
- package/src/modules/omnibridge/core/triangulation-engine.ts +530 -0
- package/src/modules/omnibridge/core/types.ts +610 -0
- package/src/modules/omnibridge/execution/.gitkeep +1 -0
- package/src/modules/omnibridge/execution/deterministic-logger.ts +629 -0
- package/src/modules/omnibridge/execution/parallel-executor.ts +542 -0
- package/src/modules/omnibridge/execution/shadow-session.ts +794 -0
- package/src/modules/omnibridge/index.ts +212 -0
- package/src/modules/omnibridge/omnibridge.ts +510 -0
- package/src/modules/omnibridge/verification/.gitkeep +1 -0
- package/src/modules/omnibridge/verification/verification-engine.ts +783 -0
- package/src/outcomes/README.md +75 -0
- package/src/outcomes/acquire-pilot-customer.ts +297 -0
- package/src/outcomes/code-delivery-outcomes.ts +89 -0
- package/src/outcomes/code-outcomes.ts +256 -0
- package/src/outcomes/code_review_battle.test.ts +135 -0
- package/src/outcomes/code_review_battle.ts +135 -0
- package/src/outcomes/cold_email_battle.ts +97 -0
- package/src/outcomes/content_creation_battle.ts +160 -0
- package/src/outcomes/f1_stem_opt_compliance.ts +61 -0
- package/src/outcomes/index.ts +107 -0
- package/src/outcomes/lead_gen_battle.test.ts +113 -0
- package/src/outcomes/lead_gen_battle.ts +99 -0
- package/src/outcomes/outcome.schema.property.test.ts +229 -0
- package/src/outcomes/outcome.schema.ts +187 -0
- package/src/outcomes/qualified_sales_interest.ts +118 -0
- package/src/outcomes/swarm_planner.property.test.ts +370 -0
- package/src/outcomes/swarm_planner.ts +96 -0
- package/src/outcomes/web_extraction.ts +234 -0
- package/src/runtime/README.md +220 -0
- package/src/runtime/agentRunner.test.ts +341 -0
- package/src/runtime/agentRunner.ts +746 -0
- package/src/runtime/claudeAdapter.ts +232 -0
- package/src/runtime/costTracker.ts +123 -0
- package/src/runtime/index.ts +34 -0
- package/src/runtime/modelAdapter.property.test.ts +305 -0
- package/src/runtime/modelAdapter.ts +144 -0
- package/src/runtime/openaiAdapter.ts +235 -0
- package/src/utils/README.md +122 -0
- package/src/utils/command-runner.ts +134 -0
- package/src/utils/cost-guard.ts +379 -0
- package/src/utils/errors.test.ts +290 -0
- package/src/utils/errors.ts +442 -0
- package/src/utils/index.ts +37 -0
- package/src/utils/logger.test.ts +361 -0
- package/src/utils/logger.ts +419 -0
- package/src/utils/output-parsers.ts +216 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini Veo Video Generation Adapter
|
|
3
|
+
*
|
|
4
|
+
* Adapts Google's Veo video generation to work with WAI Championship's agent system.
|
|
5
|
+
*
|
|
6
|
+
* @module agents/adapters/veo
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { GoogleGenerativeAI } from '@google/generative-ai';
|
|
10
|
+
import type { AgentConfig } from '../agent.schema.js';
|
|
11
|
+
import type { AgentArtifact } from '../../eval/evaluateOutcome.js';
|
|
12
|
+
import type { ContentCreationPrompt, ContentCreationResult } from './dalle.adapter.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Executes a Veo agent for video generation
|
|
16
|
+
*
|
|
17
|
+
* @param config - WAI agent configuration
|
|
18
|
+
* @param prompt - Content creation prompt
|
|
19
|
+
* @param outcomeId - ID of the outcome being attempted
|
|
20
|
+
* @param attemptNumber - Current attempt number
|
|
21
|
+
* @returns Result of agent execution with artifact if successful
|
|
22
|
+
*/
|
|
23
|
+
export async function runVeoAgent(
|
|
24
|
+
config: AgentConfig,
|
|
25
|
+
prompt: ContentCreationPrompt,
|
|
26
|
+
outcomeId: string,
|
|
27
|
+
attemptNumber: number
|
|
28
|
+
): Promise<ContentCreationResult> {
|
|
29
|
+
try {
|
|
30
|
+
const genAI = new GoogleGenerativeAI(process.env.GOOGLE_API_KEY || '');
|
|
31
|
+
|
|
32
|
+
// Use the latest Veo model
|
|
33
|
+
const model = genAI.getGenerativeModel({ model: 'veo-3.1-generate-preview' });
|
|
34
|
+
|
|
35
|
+
// Build the video generation prompt
|
|
36
|
+
const videoPrompt = `Generate a video: ${prompt.description}${prompt.style ? ` Style: ${prompt.style}` : ''}`;
|
|
37
|
+
|
|
38
|
+
// Note: Veo uses predictLongRunning method, not generateContent
|
|
39
|
+
// This is a placeholder implementation - actual API integration needed
|
|
40
|
+
const result = await model.generateContent(videoPrompt);
|
|
41
|
+
const response = result.response;
|
|
42
|
+
const content = response.text();
|
|
43
|
+
|
|
44
|
+
const artifact: AgentArtifact = {
|
|
45
|
+
agentId: config.id,
|
|
46
|
+
outcomeId,
|
|
47
|
+
attemptNumber,
|
|
48
|
+
content: {
|
|
49
|
+
type: 'video',
|
|
50
|
+
videoUrl: 'https://via.placeholder.com/1280x720/FF4444/FFFFFF?text=Veo+Video+Generated', // Placeholder
|
|
51
|
+
originalPrompt: videoPrompt,
|
|
52
|
+
revisedPrompt: content,
|
|
53
|
+
dimensions: prompt.dimensions || '1280x720',
|
|
54
|
+
model: 'veo-3.1-generate-preview',
|
|
55
|
+
style: prompt.style,
|
|
56
|
+
target: prompt.target,
|
|
57
|
+
generatedText: content,
|
|
58
|
+
duration: '5s', // Default duration
|
|
59
|
+
},
|
|
60
|
+
timestamp: new Date().toISOString(),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Estimate tokens
|
|
64
|
+
const estimatedTokens = Math.ceil((videoPrompt.length + content.length) / 4);
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
success: true,
|
|
68
|
+
message: 'Veo agent completed successfully',
|
|
69
|
+
tokensUsed: estimatedTokens,
|
|
70
|
+
artifact,
|
|
71
|
+
imageUrl: artifact.content.videoUrl, // Using imageUrl field for video URL
|
|
72
|
+
revisedPrompt: content,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
} catch (error) {
|
|
76
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
77
|
+
return {
|
|
78
|
+
success: false,
|
|
79
|
+
message: `Veo agent execution failed: ${errorMessage}`,
|
|
80
|
+
tokensUsed: 0,
|
|
81
|
+
error: errorMessage,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Creates a mock Veo agent run for testing
|
|
88
|
+
*
|
|
89
|
+
* @param config - Agent configuration
|
|
90
|
+
* @param prompt - Content creation prompt
|
|
91
|
+
* @param outcomeId - Outcome ID
|
|
92
|
+
* @param attemptNumber - Attempt number
|
|
93
|
+
* @returns Mock result matching Veo output format
|
|
94
|
+
*/
|
|
95
|
+
export function mockVeoAgent(
|
|
96
|
+
config: AgentConfig,
|
|
97
|
+
prompt: ContentCreationPrompt,
|
|
98
|
+
outcomeId: string,
|
|
99
|
+
attemptNumber: number
|
|
100
|
+
): ContentCreationResult {
|
|
101
|
+
const mockVideoUrl = 'https://via.placeholder.com/1280x720/FF4444/FFFFFF?text=Veo+Mock+Video';
|
|
102
|
+
|
|
103
|
+
const artifact: AgentArtifact = {
|
|
104
|
+
agentId: config.id,
|
|
105
|
+
outcomeId,
|
|
106
|
+
attemptNumber,
|
|
107
|
+
content: {
|
|
108
|
+
type: 'video',
|
|
109
|
+
videoUrl: mockVideoUrl,
|
|
110
|
+
originalPrompt: prompt.description,
|
|
111
|
+
revisedPrompt: `Veo enhanced: ${prompt.description} with cinematic quality and smooth motion`,
|
|
112
|
+
dimensions: prompt.dimensions || '1280x720',
|
|
113
|
+
model: 'veo-3.1-generate-preview',
|
|
114
|
+
style: prompt.style,
|
|
115
|
+
target: prompt.target,
|
|
116
|
+
generatedText: `Created a dynamic video of ${prompt.description} with professional cinematography`,
|
|
117
|
+
duration: '5s',
|
|
118
|
+
},
|
|
119
|
+
timestamp: new Date().toISOString(),
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
success: true,
|
|
124
|
+
message: 'Mock Veo agent completed',
|
|
125
|
+
tokensUsed: 500,
|
|
126
|
+
artifact,
|
|
127
|
+
imageUrl: mockVideoUrl,
|
|
128
|
+
revisedPrompt: `Veo enhanced: ${prompt.description} with cinematic quality and smooth motion`,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Property-based tests for Agent Configuration Schema Validation
|
|
3
|
+
*
|
|
4
|
+
* **Feature: earnd-bounty-engine, Property 10: Agent Configuration Schema Validation**
|
|
5
|
+
* **Validates: Requirements 6.5, 8.3, 8.4**
|
|
6
|
+
*
|
|
7
|
+
* Property 10: Agent Configuration Schema Validation
|
|
8
|
+
* *For any* agent configuration object, the agent config validator SHALL return true
|
|
9
|
+
* if and only if all required fields are present with correct types and constraints.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { describe, test, expect } from 'vitest';
|
|
13
|
+
import * as fc from 'fast-check';
|
|
14
|
+
import { readFile } from 'node:fs/promises';
|
|
15
|
+
import { parse } from 'yaml';
|
|
16
|
+
import {
|
|
17
|
+
AgentConfig,
|
|
18
|
+
ModelProvider,
|
|
19
|
+
validateAgentConfig,
|
|
20
|
+
isAgentConfig,
|
|
21
|
+
parseAgentConfigYaml,
|
|
22
|
+
} from './agent.schema.js';
|
|
23
|
+
|
|
24
|
+
/** Valid model providers per schema definition */
|
|
25
|
+
const VALID_MODEL_PROVIDERS: ModelProvider[] = ['claude', 'openai', 'ollama'];
|
|
26
|
+
|
|
27
|
+
/** Custom arbitrary for valid AgentConfig objects */
|
|
28
|
+
const validAgentConfigArb = fc.record({
|
|
29
|
+
id: fc.string({ minLength: 1 }).filter((s) => s.trim().length > 0),
|
|
30
|
+
name: fc.string({ minLength: 1 }).filter((s) => s.trim().length > 0),
|
|
31
|
+
prompt: fc.string({ minLength: 1 }).filter((s) => s.trim().length > 0),
|
|
32
|
+
strategyDescription: fc.string({ minLength: 1 }).filter((s) => s.trim().length > 0),
|
|
33
|
+
toolAccess: fc.array(fc.string()),
|
|
34
|
+
costCeiling: fc.integer({ min: 1, max: 1000000 }),
|
|
35
|
+
modelProvider: fc.constantFrom(...VALID_MODEL_PROVIDERS),
|
|
36
|
+
modelId: fc.string({ minLength: 1 }).filter((s) => s.trim().length > 0),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('Agent Configuration Schema Validation - Property Tests', () => {
|
|
40
|
+
// **Feature: earnd-bounty-engine, Property 10: Agent Configuration Schema Validation**
|
|
41
|
+
test('valid agent configurations pass validation', () => {
|
|
42
|
+
fc.assert(
|
|
43
|
+
fc.property(validAgentConfigArb, (config) => {
|
|
44
|
+
const result = validateAgentConfig(config);
|
|
45
|
+
expect(result.valid).toBe(true);
|
|
46
|
+
expect(result.errors).toHaveLength(0);
|
|
47
|
+
}),
|
|
48
|
+
{ numRuns: 100 }
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// **Feature: earnd-bounty-engine, Property 10: Agent Configuration Schema Validation**
|
|
53
|
+
test('configurations with empty id are invalid', () => {
|
|
54
|
+
fc.assert(
|
|
55
|
+
fc.property(
|
|
56
|
+
validAgentConfigArb.map((config) => ({ ...config, id: '' })),
|
|
57
|
+
(config) => {
|
|
58
|
+
const result = validateAgentConfig(config);
|
|
59
|
+
expect(result.valid).toBe(false);
|
|
60
|
+
expect(result.errors).toContain('Agent config "id" must not be empty');
|
|
61
|
+
}
|
|
62
|
+
),
|
|
63
|
+
{ numRuns: 100 }
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// **Feature: earnd-bounty-engine, Property 10: Agent Configuration Schema Validation**
|
|
68
|
+
test('configurations with empty name are invalid', () => {
|
|
69
|
+
fc.assert(
|
|
70
|
+
fc.property(
|
|
71
|
+
validAgentConfigArb.map((config) => ({ ...config, name: '' })),
|
|
72
|
+
(config) => {
|
|
73
|
+
const result = validateAgentConfig(config);
|
|
74
|
+
expect(result.valid).toBe(false);
|
|
75
|
+
expect(result.errors).toContain('Agent config "name" must not be empty');
|
|
76
|
+
}
|
|
77
|
+
),
|
|
78
|
+
{ numRuns: 100 }
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// **Feature: earnd-bounty-engine, Property 10: Agent Configuration Schema Validation**
|
|
83
|
+
test('configurations with empty prompt are invalid', () => {
|
|
84
|
+
fc.assert(
|
|
85
|
+
fc.property(
|
|
86
|
+
validAgentConfigArb.map((config) => ({ ...config, prompt: '' })),
|
|
87
|
+
(config) => {
|
|
88
|
+
const result = validateAgentConfig(config);
|
|
89
|
+
expect(result.valid).toBe(false);
|
|
90
|
+
expect(result.errors).toContain('Agent config "prompt" must not be empty');
|
|
91
|
+
}
|
|
92
|
+
),
|
|
93
|
+
{ numRuns: 100 }
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// **Feature: earnd-bounty-engine, Property 10: Agent Configuration Schema Validation**
|
|
98
|
+
test('configurations with empty strategyDescription are invalid', () => {
|
|
99
|
+
fc.assert(
|
|
100
|
+
fc.property(
|
|
101
|
+
validAgentConfigArb.map((config) => ({ ...config, strategyDescription: '' })),
|
|
102
|
+
(config) => {
|
|
103
|
+
const result = validateAgentConfig(config);
|
|
104
|
+
expect(result.valid).toBe(false);
|
|
105
|
+
expect(result.errors).toContain('Agent config "strategyDescription" must not be empty');
|
|
106
|
+
}
|
|
107
|
+
),
|
|
108
|
+
{ numRuns: 100 }
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// **Feature: earnd-bounty-engine, Property 10: Agent Configuration Schema Validation**
|
|
113
|
+
test('configurations with non-positive costCeiling are invalid', () => {
|
|
114
|
+
fc.assert(
|
|
115
|
+
fc.property(
|
|
116
|
+
fc.integer({ min: -1000, max: 0 }),
|
|
117
|
+
validAgentConfigArb,
|
|
118
|
+
(invalidCeiling, config) => {
|
|
119
|
+
const invalidConfig = { ...config, costCeiling: invalidCeiling };
|
|
120
|
+
const result = validateAgentConfig(invalidConfig);
|
|
121
|
+
expect(result.valid).toBe(false);
|
|
122
|
+
expect(result.errors).toContain('Agent config "costCeiling" must be positive');
|
|
123
|
+
}
|
|
124
|
+
),
|
|
125
|
+
{ numRuns: 100 }
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// **Feature: earnd-bounty-engine, Property 10: Agent Configuration Schema Validation**
|
|
130
|
+
test('configurations with invalid modelProvider are invalid', () => {
|
|
131
|
+
const invalidProviders = ['gemini', 'gpt', 'llama', 'invalid', ''];
|
|
132
|
+
|
|
133
|
+
fc.assert(
|
|
134
|
+
fc.property(
|
|
135
|
+
fc.constantFrom(...invalidProviders),
|
|
136
|
+
validAgentConfigArb,
|
|
137
|
+
(invalidProvider, config) => {
|
|
138
|
+
const invalidConfig = { ...config, modelProvider: invalidProvider };
|
|
139
|
+
const result = validateAgentConfig(invalidConfig);
|
|
140
|
+
expect(result.valid).toBe(false);
|
|
141
|
+
expect(result.errors).toContain('Agent config "modelProvider" must be "claude", "openai", or "ollama"');
|
|
142
|
+
}
|
|
143
|
+
),
|
|
144
|
+
{ numRuns: 100 }
|
|
145
|
+
);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// **Feature: earnd-bounty-engine, Property 10: Agent Configuration Schema Validation**
|
|
149
|
+
test('configurations with empty modelId are invalid', () => {
|
|
150
|
+
fc.assert(
|
|
151
|
+
fc.property(
|
|
152
|
+
validAgentConfigArb.map((config) => ({ ...config, modelId: '' })),
|
|
153
|
+
(config) => {
|
|
154
|
+
const result = validateAgentConfig(config);
|
|
155
|
+
expect(result.valid).toBe(false);
|
|
156
|
+
expect(result.errors).toContain('Agent config "modelId" must not be empty');
|
|
157
|
+
}
|
|
158
|
+
),
|
|
159
|
+
{ numRuns: 100 }
|
|
160
|
+
);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// **Feature: earnd-bounty-engine, Property 10: Agent Configuration Schema Validation**
|
|
164
|
+
test('configurations with non-array toolAccess are invalid', () => {
|
|
165
|
+
const nonArrayValues = [null, undefined, 'string', 123, {}, true];
|
|
166
|
+
|
|
167
|
+
fc.assert(
|
|
168
|
+
fc.property(
|
|
169
|
+
fc.constantFrom(...nonArrayValues),
|
|
170
|
+
validAgentConfigArb,
|
|
171
|
+
(invalidToolAccess, config) => {
|
|
172
|
+
const invalidConfig = { ...config, toolAccess: invalidToolAccess };
|
|
173
|
+
const result = validateAgentConfig(invalidConfig);
|
|
174
|
+
expect(result.valid).toBe(false);
|
|
175
|
+
expect(result.errors).toContain('Agent config must have an array "toolAccess"');
|
|
176
|
+
}
|
|
177
|
+
),
|
|
178
|
+
{ numRuns: 100 }
|
|
179
|
+
);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// **Feature: earnd-bounty-engine, Property 10: Agent Configuration Schema Validation**
|
|
183
|
+
test('configurations with non-string tools in toolAccess are invalid', () => {
|
|
184
|
+
fc.assert(
|
|
185
|
+
fc.property(
|
|
186
|
+
fc.array(fc.oneof(fc.integer(), fc.boolean(), fc.object())),
|
|
187
|
+
validAgentConfigArb,
|
|
188
|
+
(invalidTools, config) => {
|
|
189
|
+
if (invalidTools.length === 0) return; // Skip empty arrays
|
|
190
|
+
|
|
191
|
+
const invalidConfig = { ...config, toolAccess: invalidTools };
|
|
192
|
+
const result = validateAgentConfig(invalidConfig);
|
|
193
|
+
expect(result.valid).toBe(false);
|
|
194
|
+
expect(result.errors.some(error => error.includes('must be a string'))).toBe(true);
|
|
195
|
+
}
|
|
196
|
+
),
|
|
197
|
+
{ numRuns: 100 }
|
|
198
|
+
);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// **Feature: earnd-bounty-engine, Property 10: Agent Configuration Schema Validation**
|
|
202
|
+
test('non-object values are invalid', () => {
|
|
203
|
+
const nonObjectValues = [null, undefined, 'string', 123, [], true, false];
|
|
204
|
+
|
|
205
|
+
fc.assert(
|
|
206
|
+
fc.property(
|
|
207
|
+
fc.constantFrom(...nonObjectValues),
|
|
208
|
+
(nonObject) => {
|
|
209
|
+
const result = validateAgentConfig(nonObject);
|
|
210
|
+
expect(result.valid).toBe(false);
|
|
211
|
+
expect(result.errors).toContain('Agent config must be an object');
|
|
212
|
+
}
|
|
213
|
+
),
|
|
214
|
+
{ numRuns: 100 }
|
|
215
|
+
);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// **Feature: earnd-bounty-engine, Property 10: Agent Configuration Schema Validation**
|
|
219
|
+
test('validation is deterministic - same input produces same output', () => {
|
|
220
|
+
fc.assert(
|
|
221
|
+
fc.property(validAgentConfigArb, (config) => {
|
|
222
|
+
const result1 = validateAgentConfig(config);
|
|
223
|
+
const result2 = validateAgentConfig(config);
|
|
224
|
+
expect(result1.valid).toBe(result2.valid);
|
|
225
|
+
expect(result1.errors).toEqual(result2.errors);
|
|
226
|
+
}),
|
|
227
|
+
{ numRuns: 100 }
|
|
228
|
+
);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// **Feature: earnd-bounty-engine, Property 10: Agent Configuration Schema Validation**
|
|
232
|
+
test('validation result structure is correct', () => {
|
|
233
|
+
fc.assert(
|
|
234
|
+
fc.property(fc.anything(), (input) => {
|
|
235
|
+
const result = validateAgentConfig(input);
|
|
236
|
+
// Result must have valid boolean and errors array
|
|
237
|
+
expect(typeof result.valid).toBe('boolean');
|
|
238
|
+
expect(Array.isArray(result.errors)).toBe(true);
|
|
239
|
+
// If valid, errors should be empty; if invalid, errors should have content
|
|
240
|
+
if (result.valid) {
|
|
241
|
+
expect(result.errors).toHaveLength(0);
|
|
242
|
+
} else {
|
|
243
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
244
|
+
}
|
|
245
|
+
}),
|
|
246
|
+
{ numRuns: 100 }
|
|
247
|
+
);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// **Feature: earnd-bounty-engine, Property 10: Agent Configuration Schema Validation**
|
|
251
|
+
test('isAgentConfig type guard matches validateAgentConfig', () => {
|
|
252
|
+
fc.assert(
|
|
253
|
+
fc.property(fc.anything(), (input) => {
|
|
254
|
+
const validationResult = validateAgentConfig(input);
|
|
255
|
+
const typeGuardResult = isAgentConfig(input);
|
|
256
|
+
expect(typeGuardResult).toBe(validationResult.valid);
|
|
257
|
+
}),
|
|
258
|
+
{ numRuns: 100 }
|
|
259
|
+
);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// **Feature: earnd-bounty-engine, Property 10: Agent Configuration Schema Validation**
|
|
263
|
+
test('configurations with infinite or NaN costCeiling are invalid', () => {
|
|
264
|
+
// Test positive infinity and NaN (which should trigger finite number check)
|
|
265
|
+
fc.assert(
|
|
266
|
+
fc.property(
|
|
267
|
+
fc.constantFrom(Infinity, NaN),
|
|
268
|
+
validAgentConfigArb,
|
|
269
|
+
(invalidNumber, config) => {
|
|
270
|
+
const invalidConfig = { ...config, costCeiling: invalidNumber };
|
|
271
|
+
const result = validateAgentConfig(invalidConfig);
|
|
272
|
+
expect(result.valid).toBe(false);
|
|
273
|
+
expect(result.errors).toContain('Agent config "costCeiling" must be a finite number');
|
|
274
|
+
}
|
|
275
|
+
),
|
|
276
|
+
{ numRuns: 100 }
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
// Test negative infinity (which should trigger positive check first)
|
|
280
|
+
const negInfinityConfig = {
|
|
281
|
+
id: 'test',
|
|
282
|
+
name: 'test',
|
|
283
|
+
prompt: 'test',
|
|
284
|
+
strategyDescription: 'test',
|
|
285
|
+
toolAccess: [],
|
|
286
|
+
costCeiling: -Infinity,
|
|
287
|
+
modelProvider: 'claude' as const,
|
|
288
|
+
modelId: 'test'
|
|
289
|
+
};
|
|
290
|
+
const result = validateAgentConfig(negInfinityConfig);
|
|
291
|
+
expect(result.valid).toBe(false);
|
|
292
|
+
expect(result.errors).toContain('Agent config "costCeiling" must be positive');
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// **Feature: earnd-bounty-engine, Property 10: Agent Configuration Schema Validation**
|
|
296
|
+
test('configurations with whitespace-only strings are invalid', () => {
|
|
297
|
+
const whitespaceStrings = [' ', '\t\t', '\n\n', ' \t\n '];
|
|
298
|
+
|
|
299
|
+
fc.assert(
|
|
300
|
+
fc.property(
|
|
301
|
+
fc.constantFrom(...whitespaceStrings),
|
|
302
|
+
validAgentConfigArb,
|
|
303
|
+
(whitespace, config) => {
|
|
304
|
+
// Test each string field with whitespace
|
|
305
|
+
const fields = ['id', 'name', 'prompt', 'strategyDescription', 'modelId'];
|
|
306
|
+
|
|
307
|
+
fields.forEach(field => {
|
|
308
|
+
const invalidConfig = { ...config, [field]: whitespace };
|
|
309
|
+
const result = validateAgentConfig(invalidConfig);
|
|
310
|
+
expect(result.valid).toBe(false);
|
|
311
|
+
expect(result.errors.some(error => error.includes(`"${field}" must not be empty`))).toBe(true);
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
),
|
|
315
|
+
{ numRuns: 100 }
|
|
316
|
+
);
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
describe('Tournament Agent Configuration Files - Property Tests', () => {
|
|
321
|
+
const tournamentAgentFiles = [
|
|
322
|
+
'src/agents/configs/tournament-coderabbit.yaml',
|
|
323
|
+
'src/agents/configs/tournament-claude-sonnet.yaml',
|
|
324
|
+
'src/agents/configs/tournament-gpt4o.yaml',
|
|
325
|
+
'src/agents/configs/tournament-llama3.yaml',
|
|
326
|
+
];
|
|
327
|
+
|
|
328
|
+
// **Feature: earnd-bounty-engine, Property 10: Agent Configuration Schema Validation**
|
|
329
|
+
test('all tournament agent configurations are valid', async () => {
|
|
330
|
+
for (const filePath of tournamentAgentFiles) {
|
|
331
|
+
const yamlContent = await readFile(filePath, 'utf-8');
|
|
332
|
+
const config = parse(yamlContent);
|
|
333
|
+
|
|
334
|
+
const result = validateAgentConfig(config);
|
|
335
|
+
expect(result.valid).toBe(true);
|
|
336
|
+
expect(result.errors).toHaveLength(0);
|
|
337
|
+
|
|
338
|
+
// Verify it's a proper AgentConfig
|
|
339
|
+
expect(isAgentConfig(config)).toBe(true);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
// **Feature: earnd-bounty-engine, Property 10: Agent Configuration Schema Validation**
|
|
344
|
+
test('tournament agents have required tournament-specific properties', async () => {
|
|
345
|
+
for (const filePath of tournamentAgentFiles) {
|
|
346
|
+
const yamlContent = await readFile(filePath, 'utf-8');
|
|
347
|
+
const config = parse(yamlContent) as AgentConfig;
|
|
348
|
+
|
|
349
|
+
// Tournament agents should have specific naming pattern
|
|
350
|
+
expect(config.id).toMatch(/^tournament-/);
|
|
351
|
+
|
|
352
|
+
// Should have reasonable cost ceilings
|
|
353
|
+
expect(config.costCeiling).toBeGreaterThan(0);
|
|
354
|
+
expect(config.costCeiling).toBeLessThanOrEqual(15000);
|
|
355
|
+
|
|
356
|
+
// Should have valid model providers
|
|
357
|
+
expect(VALID_MODEL_PROVIDERS).toContain(config.modelProvider);
|
|
358
|
+
|
|
359
|
+
// Should have non-empty strategy descriptions
|
|
360
|
+
expect(config.strategyDescription.trim().length).toBeGreaterThan(0);
|
|
361
|
+
|
|
362
|
+
// Should have tool access arrays
|
|
363
|
+
expect(Array.isArray(config.toolAccess)).toBe(true);
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
// **Feature: earnd-bounty-engine, Property 10: Agent Configuration Schema Validation**
|
|
368
|
+
test('parseAgentConfigYaml works correctly for tournament configs', async () => {
|
|
369
|
+
for (const filePath of tournamentAgentFiles) {
|
|
370
|
+
const yamlContent = await readFile(filePath, 'utf-8');
|
|
371
|
+
const { config, validation } = await parseAgentConfigYaml(yamlContent);
|
|
372
|
+
|
|
373
|
+
expect(validation.valid).toBe(true);
|
|
374
|
+
expect(validation.errors).toHaveLength(0);
|
|
375
|
+
expect(config).not.toBeNull();
|
|
376
|
+
expect(isAgentConfig(config)).toBe(true);
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
});
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Schema Tests
|
|
3
|
+
*
|
|
4
|
+
* Unit tests for agent configuration validation.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect } from 'vitest';
|
|
8
|
+
import { readFile } from 'node:fs/promises';
|
|
9
|
+
import { parse } from 'yaml';
|
|
10
|
+
import {
|
|
11
|
+
AgentConfig,
|
|
12
|
+
validateAgentConfig,
|
|
13
|
+
isAgentConfig,
|
|
14
|
+
} from './agent.schema.js';
|
|
15
|
+
|
|
16
|
+
describe('validateAgentConfig', () => {
|
|
17
|
+
it('accepts a valid agent config', () => {
|
|
18
|
+
const config: AgentConfig = {
|
|
19
|
+
id: 'test-agent-001',
|
|
20
|
+
name: 'Test Agent',
|
|
21
|
+
prompt: 'You are a test agent.',
|
|
22
|
+
strategyDescription: 'Test strategy',
|
|
23
|
+
toolAccess: ['tool1', 'tool2'],
|
|
24
|
+
costCeiling: 10000,
|
|
25
|
+
modelProvider: 'claude',
|
|
26
|
+
modelId: 'claude-3-sonnet-20240229',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const result = validateAgentConfig(config);
|
|
30
|
+
expect(result.valid).toBe(true);
|
|
31
|
+
expect(result.errors).toHaveLength(0);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('rejects non-object values', () => {
|
|
35
|
+
expect(validateAgentConfig(null).valid).toBe(false);
|
|
36
|
+
expect(validateAgentConfig(undefined).valid).toBe(false);
|
|
37
|
+
expect(validateAgentConfig('string').valid).toBe(false);
|
|
38
|
+
expect(validateAgentConfig(123).valid).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('rejects empty id', () => {
|
|
42
|
+
const config = {
|
|
43
|
+
id: '',
|
|
44
|
+
name: 'Test',
|
|
45
|
+
prompt: 'Test',
|
|
46
|
+
strategyDescription: 'Test',
|
|
47
|
+
toolAccess: [],
|
|
48
|
+
costCeiling: 100,
|
|
49
|
+
modelProvider: 'claude',
|
|
50
|
+
modelId: 'test',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const result = validateAgentConfig(config);
|
|
54
|
+
expect(result.valid).toBe(false);
|
|
55
|
+
expect(result.errors).toContain('Agent config "id" must not be empty');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('rejects invalid modelProvider', () => {
|
|
59
|
+
const config = {
|
|
60
|
+
id: 'test',
|
|
61
|
+
name: 'Test',
|
|
62
|
+
prompt: 'Test',
|
|
63
|
+
strategyDescription: 'Test',
|
|
64
|
+
toolAccess: [],
|
|
65
|
+
costCeiling: 100,
|
|
66
|
+
modelProvider: 'invalid',
|
|
67
|
+
modelId: 'test',
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const result = validateAgentConfig(config);
|
|
71
|
+
expect(result.valid).toBe(false);
|
|
72
|
+
expect(result.errors).toContain(
|
|
73
|
+
'Agent config "modelProvider" must be "claude", "openai", or "ollama"'
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('rejects negative costCeiling', () => {
|
|
78
|
+
const config = {
|
|
79
|
+
id: 'test',
|
|
80
|
+
name: 'Test',
|
|
81
|
+
prompt: 'Test',
|
|
82
|
+
strategyDescription: 'Test',
|
|
83
|
+
toolAccess: [],
|
|
84
|
+
costCeiling: -100,
|
|
85
|
+
modelProvider: 'claude',
|
|
86
|
+
modelId: 'test',
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const result = validateAgentConfig(config);
|
|
90
|
+
expect(result.valid).toBe(false);
|
|
91
|
+
expect(result.errors).toContain(
|
|
92
|
+
'Agent config "costCeiling" must be positive'
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe('isAgentConfig', () => {
|
|
98
|
+
it('returns true for valid config', () => {
|
|
99
|
+
const config: AgentConfig = {
|
|
100
|
+
id: 'test-agent-001',
|
|
101
|
+
name: 'Test Agent',
|
|
102
|
+
prompt: 'You are a test agent.',
|
|
103
|
+
strategyDescription: 'Test strategy',
|
|
104
|
+
toolAccess: [],
|
|
105
|
+
costCeiling: 10000,
|
|
106
|
+
modelProvider: 'openai',
|
|
107
|
+
modelId: 'gpt-4-turbo-preview',
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
expect(isAgentConfig(config)).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('returns false for invalid config', () => {
|
|
114
|
+
expect(isAgentConfig(null)).toBe(false);
|
|
115
|
+
expect(isAgentConfig({})).toBe(false);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('sales_agent.yaml', () => {
|
|
120
|
+
it('is a valid agent configuration', async () => {
|
|
121
|
+
const yamlContent = await readFile(
|
|
122
|
+
'./src/agents/sales_agent.yaml',
|
|
123
|
+
'utf-8'
|
|
124
|
+
);
|
|
125
|
+
const config = parse(yamlContent);
|
|
126
|
+
|
|
127
|
+
const result = validateAgentConfig(config);
|
|
128
|
+
expect(result.valid).toBe(true);
|
|
129
|
+
expect(result.errors).toHaveLength(0);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('has all required fields', async () => {
|
|
133
|
+
const yamlContent = await readFile(
|
|
134
|
+
'./src/agents/sales_agent.yaml',
|
|
135
|
+
'utf-8'
|
|
136
|
+
);
|
|
137
|
+
const config = parse(yamlContent) as AgentConfig;
|
|
138
|
+
|
|
139
|
+
expect(config.id).toBe('sales-agent-001');
|
|
140
|
+
expect(config.name).toBe('Sales Qualifier Agent');
|
|
141
|
+
expect(config.prompt).toContain('sales qualification');
|
|
142
|
+
expect(config.strategyDescription).toContain('consultative');
|
|
143
|
+
expect(config.toolAccess).toContain('email_lookup');
|
|
144
|
+
expect(config.costCeiling).toBe(10000);
|
|
145
|
+
expect(config.modelProvider).toBe('claude');
|
|
146
|
+
expect(config.modelId).toBe('claude-3-sonnet-20240229');
|
|
147
|
+
});
|
|
148
|
+
});
|