Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/ai-agents/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = {
testEnvironment: 'node',
testMatch: ['**/tests/**/*.test.js'],
collectCoverageFrom: ['src/**/*.js'],
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov'],
verbose: true
};
59 changes: 59 additions & 0 deletions packages/ai-agents/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{
"name": "@friggframework/ai-agents",
"version": "2.0.0-next.0",
"description": "AI agent integration for Frigg Framework",
"main": "src/index.js",
"author": "",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/friggframework/frigg.git"
},
"bugs": {
"url": "https://github.com/friggframework/frigg/issues"
},
"homepage": "https://github.com/friggframework/frigg#readme",
"publishConfig": {
"access": "public"
},
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"lint:fix": "prettier --write --loglevel error . && eslint . --fix"
},
"keywords": [
"frigg",
"ai",
"agents",
"mcp",
"llm"
],
"dependencies": {
"@friggframework/schemas": "^2.0.0-next.0"
},
"peerDependencies": {
"ai": ">=4.0.0",
"@ai-sdk/openai": ">=1.0.0",
"@anthropic-ai/claude-agent-sdk": ">=0.1.0"
},
"peerDependenciesMeta": {
"ai": {
"optional": true
},
"@ai-sdk/openai": {
"optional": true
},
"@anthropic-ai/claude-agent-sdk": {
"optional": true
}
},
"devDependencies": {
"@friggframework/eslint-config": "^2.0.0-next.0",
"@friggframework/prettier-config": "^2.0.0-next.0",
"jest": "^29.7.0",
"eslint": "^8.22.0",
"prettier": "^2.7.1"
},
"prettier": "@friggframework/prettier-config"
}
55 changes: 55 additions & 0 deletions packages/ai-agents/src/domain/entities/agent-event.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
const AgentEventType = {
CONTENT: 'content',
TOOL_CALL: 'tool_call',
TOOL_RESULT: 'tool_result',
USAGE: 'usage',
DONE: 'done',
ERROR: 'error'
};

class AgentEvent {
constructor({ type, timestamp = new Date(), ...data }) {
this.type = type;
this.timestamp = timestamp;
Object.assign(this, data);
}

static content(text) {
return new AgentEvent({ type: AgentEventType.CONTENT, content: text });
}

static toolCall(name, args) {
return new AgentEvent({ type: AgentEventType.TOOL_CALL, name, args });
}

static toolResult(name, result) {
return new AgentEvent({ type: AgentEventType.TOOL_RESULT, name, result });
}

static usage(usage) {
return new AgentEvent({ type: AgentEventType.USAGE, usage });
}

static done() {
return new AgentEvent({ type: AgentEventType.DONE });
}

static error(error) {
return new AgentEvent({ type: AgentEventType.ERROR, error });
}

toJSON() {
return {
type: this.type,
timestamp: this.timestamp.toISOString(),
...(this.content && { content: this.content }),
...(this.name && { name: this.name }),
...(this.args && { args: this.args }),
...(this.result && { result: this.result }),
...(this.usage && { usage: this.usage }),
...(this.error && { error: this.error.message || String(this.error) })
};
}
}

module.exports = { AgentEvent, AgentEventType };
81 changes: 81 additions & 0 deletions packages/ai-agents/src/domain/entities/agent-proposal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
const ProposalStatus = {
PENDING: 'pending',
APPROVED: 'approved',
REJECTED: 'rejected',
MODIFIED: 'modified'
};

class AgentProposal {
constructor({ id, files, validation, checkpointId = null }) {
this.id = id;
this.files = files;
this.validation = validation;
this.checkpointId = checkpointId;
this.status = ProposalStatus.PENDING;
this.createdAt = new Date();
this.approvedAt = null;
this.rejectedAt = null;
this.rejectionReason = null;
}

getSummary() {
let linesAdded = 0;
let createdFiles = 0;
let modifiedFiles = 0;

for (const file of this.files) {
if (file.action === 'create') {
createdFiles++;
if (file.content) {
linesAdded += file.content.split('\n').length;
}
} else if (file.action === 'modify') {
modifiedFiles++;
}
}

return {
fileCount: this.files.length,
createdFiles,
modifiedFiles,
linesAdded,
confidence: this.validation?.confidence,
recommendation: this.validation?.recommendation
};
}

approve() {
if (this.status === ProposalStatus.REJECTED) {
throw new Error('Cannot approve rejected proposal');
}
this.status = ProposalStatus.APPROVED;
this.approvedAt = new Date();
}

reject(reason) {
this.status = ProposalStatus.REJECTED;
this.rejectedAt = new Date();
this.rejectionReason = reason;
}

canRollback() {
return this.checkpointId !== null;
}

toJSON() {
return {
id: this.id,
status: this.status,
files: this.files.map(f => ({ path: f.path, action: f.action })),
validation: this.validation,
checkpointId: this.checkpointId,
summary: this.getSummary(),
createdAt: this.createdAt.toISOString(),
approvedAt: this.approvedAt?.toISOString(),
rejectedAt: this.rejectedAt?.toISOString(),
rejectionReason: this.rejectionReason
};
}
}

module.exports = { AgentProposal, ProposalStatus };
9 changes: 9 additions & 0 deletions packages/ai-agents/src/domain/entities/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const { AgentEvent, AgentEventType } = require('./agent-event');
const { AgentProposal, ProposalStatus } = require('./agent-proposal');

module.exports = {
AgentEvent,
AgentEventType,
AgentProposal,
ProposalStatus
};
7 changes: 7 additions & 0 deletions packages/ai-agents/src/domain/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const interfaces = require('./interfaces');
const entities = require('./entities');

module.exports = {
...interfaces,
...entities
};
29 changes: 29 additions & 0 deletions packages/ai-agents/src/domain/interfaces/agent-framework.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
class NotImplementedError extends Error {
constructor(method) {
super(`Not implemented: ${method}`);
this.name = 'NotImplementedError';
}
}

class IAgentFramework {
async runAgent(_params) {
throw new NotImplementedError('runAgent');
}

async loadMcpTools(_serverConfig) {
throw new NotImplementedError('loadMcpTools');
}

getCapabilities() {
throw new NotImplementedError('getCapabilities');
}

async validateRunParams(params) {
if (!params.prompt) {
throw new Error('prompt is required');
}
return true;
}
}

module.exports = { IAgentFramework, NotImplementedError };
10 changes: 10 additions & 0 deletions packages/ai-agents/src/domain/interfaces/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const { IAgentFramework, NotImplementedError } = require('./agent-framework');
const { IValidationPipeline, WEIGHTS, THRESHOLDS } = require('./validation-pipeline');

module.exports = {
IAgentFramework,
IValidationPipeline,
NotImplementedError,
WEIGHTS,
THRESHOLDS
};
61 changes: 61 additions & 0 deletions packages/ai-agents/src/domain/interfaces/validation-pipeline.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
const { NotImplementedError } = require('./agent-framework');

const WEIGHTS = {
schema: 0.30,

Check warning on line 4 in packages/ai-agents/src/domain/interfaces/validation-pipeline.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Don't use a zero fraction in the number.

See more on https://sonarcloud.io/project/issues?id=friggframework_frigg&issues=AZ09DyxSLQrmbX5-WK63&open=AZ09DyxSLQrmbX5-WK63&pullRequest=561
patterns: 0.25,
security: 0.15,
tests: 0.20,

Check warning on line 7 in packages/ai-agents/src/domain/interfaces/validation-pipeline.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Don't use a zero fraction in the number.

See more on https://sonarcloud.io/project/issues?id=friggframework_frigg&issues=AZ09DyxSLQrmbX5-WK64&open=AZ09DyxSLQrmbX5-WK64&pullRequest=561
lint: 0.10

Check warning on line 8 in packages/ai-agents/src/domain/interfaces/validation-pipeline.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Don't use a zero fraction in the number.

See more on https://sonarcloud.io/project/issues?id=friggframework_frigg&issues=AZ09DyxSLQrmbX5-WK65&open=AZ09DyxSLQrmbX5-WK65&pullRequest=561
};

const THRESHOLDS = {
autoApprove: 95,
requireReview: 80
};

class IValidationPipeline {
async validate(_files) {
throw new NotImplementedError('validate');
}

calculateConfidence(layerResults) {
let totalScore = 0;

for (const [layer, weight] of Object.entries(WEIGHTS)) {
const result = layerResults[layer];
if (result && typeof result.score === 'number') {
totalScore += result.score * weight;
}
}

return Math.round(totalScore);
}

getRecommendation(confidence, thresholds = THRESHOLDS) {
if (confidence >= thresholds.autoApprove) {
return 'auto_approve';
}
if (confidence >= thresholds.requireReview) {
return 'require_review';
}
return 'manual_approval';
}

generateFeedback(layerResults) {
const feedback = [];

for (const [layer, result] of Object.entries(layerResults)) {
if (!result.passed) {
feedback.push({
layer,
score: result.score,
issues: result.errors || result.violations || result.vulnerabilities || result.failures || []
});
}
}

return feedback;
}
}

module.exports = { IValidationPipeline, WEIGHTS, THRESHOLDS };
41 changes: 41 additions & 0 deletions packages/ai-agents/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const { IAgentFramework, NotImplementedError } = require('./domain/interfaces/agent-framework');
const { IValidationPipeline } = require('./domain/interfaces/validation-pipeline');

const { AgentEvent, AgentEventType } = require('./domain/entities/agent-event');
const { AgentProposal, ProposalStatus } = require('./domain/entities/agent-proposal');

const { VercelAIAdapter, SUPPORTED_PROVIDERS } = require('./infrastructure/adapters/vercel-ai-adapter');
const { ClaudeAgentAdapter } = require('./infrastructure/adapters/claude-agent-adapter');

const { ValidationPipeline, ValidationLayer, LAYER_WEIGHTS } = require('./infrastructure/validation/validation-pipeline');

const { createFriggMcpTools, KNOWN_MODULES } = require('./infrastructure/mcp/frigg-tools');

const { GitCheckpointService } = require('./infrastructure/git/git-checkpoint-service');

const { AgentStreamHandler } = require('./infrastructure/streaming/agent-stream-handler');

module.exports = {
IAgentFramework,
IValidationPipeline,
NotImplementedError,

AgentEvent,
AgentEventType,
AgentProposal,
ProposalStatus,

VercelAIAdapter,
ClaudeAgentAdapter,
SUPPORTED_PROVIDERS,

ValidationPipeline,
ValidationLayer,
LAYER_WEIGHTS,

createFriggMcpTools,
KNOWN_MODULES,

GitCheckpointService,
AgentStreamHandler
};
Loading
Loading