Skip to content

Commit

Permalink
Adds protofy js implementation of the agent protocol network
Browse files Browse the repository at this point in the history
  • Loading branch information
jcarlosn committed Nov 17, 2024
1 parent b8e55b1 commit f0974ce
Show file tree
Hide file tree
Showing 8 changed files with 352 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/protofy/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist
11 changes: 11 additions & 0 deletions packages/protofy/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ["<rootDir>/tests"],
moduleFileExtensions: ["ts", "js"],
transform: {
"^.+\\.ts$": "ts-jest"
},
testRegex: ".*\\.test\\.ts$" // Coincide con cualquier archivo que termine en .test.ts
};
25 changes: 25 additions & 0 deletions packages/protofy/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "protofy",
"version": "1.0.0",
"description": "Protofy agent network",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"test": "jest"
},
"files": [
"dist"
],
"dependencies": {
"ajv": "8.17.1"
},
"devDependencies": {
"@types/jest": "~29.5.5",
"jest": "~29.7.0",
"ts-jest": "~29.1.1",
"typescript": "~5.3.3",
"zod": "^3.22.2",
"zod-to-json-schema": "3.23.5"
}
}
135 changes: 135 additions & 0 deletions packages/protofy/src/Agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { SchemaObject, Ajv } from "ajv";
import { z } from "zod";

// Compile a generic JSON Schema validator using Ajv
const ajv = new Ajv();
const validateSchemaObject = ajv.compile({ type: "object" });

const AgentProtocolSchema = z.object({
type: z.string(), // function, http-request, http-response, mqtt-message
serializer: z.string().optional(), // json, xml, none (for local function calls)
encoder: z.string().optional(), // body, query, path, arguments, ...
params: z.record(z.any()).optional(), //protocol-specific configs host, port, topic, path...
})

const AgentInterfaceSchema = z.object({
shape: z.custom<SchemaObject>((value) => {
// Use Ajv to validate the value
return validateSchemaObject(value);
}, "SchemaObject"),
protocol: AgentProtocolSchema.optional()
})

const AgentSchema = z.object({
id: z.string(), // Required string
name: z.string().optional(), // Optional string
description: z.string().optional(), // Optional string
tags: z.array(z.string()).optional(), // Optional array of strings
protocol: AgentProtocolSchema.optional(),
input: AgentInterfaceSchema.optional(),
output: AgentInterfaceSchema.optional()
})

// Infer the TypeScript type
export type AgentData = z.infer<typeof AgentSchema>;
export type AgentProtocolData = z.infer<typeof AgentProtocolSchema>;
export type AgentInterfaceData = z.infer<typeof AgentInterfaceSchema>;

export class Agent {
data: AgentData;
children: Agent[];
input: AgentInterface | undefined;
output: AgentInterface | undefined;
constructor(data: AgentData, agents: Agent[] = []) {
this.data = data;
this.children = agents;
this.input = data.input && new AgentInputInterface(data.input, this);
this.output = data.output && new AgentOutputInterface(data.output, this);
}

getName() {
return this.data.name
}

getId() {
return this.data.id
}

getDescription() {
return this.data.description
}

getTags() {
return this.data.tags
}

getProtocol() {
return this.data.protocol
}

getInputShape() {
return this.input?.getShape()
}

getInputProtocol() {
return this.input?.getProtocol()
}

getOutputShape() {
return this.output?.getShape()
}

getOutputProtocol() {
return this.output?.getProtocol()
}

addChildren(agents: Agent[]) {
this.children.push(...agents);
}

getChildren() {
return this.children;
}

addChild(agent: Agent) {
this.children.push(agent);
}

getChild(id: string) {
return this.children && this.children.find(agent => agent.data.id === id);
}
}

export class AgentInterface {
shape: SchemaObject;
protocol: AgentProtocolData;
agent: Agent;
constructor(data: AgentInterfaceData, agent: Agent) {
this.shape = data.shape;
this.protocol = data.protocol;
this.agent = agent;
}

getShape() {
return this.shape;
}

getProtocol() {
return {
...this.agent.getProtocol(),
...this.protocol
}
}
}

export class AgentInputInterface extends AgentInterface {
constructor(data: AgentInterfaceData, agent: Agent) {
super(data, agent);
}
}

export class AgentOutputInterface extends AgentInterface {
constructor(data: AgentInterfaceData, agent: Agent) {
super(data, agent);
}
}
Empty file added packages/protofy/src/index.ts
Empty file.
122 changes: 122 additions & 0 deletions packages/protofy/tests/agent.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { Agent } from '../src/Agent';
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';

let userSchema: z.ZodObject<any>;
let returnSchema: z.ZodString;
let agent: Agent;

describe('Agents basic behavior', () => {
beforeEach(() => {
userSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1),
age: z.number().min(18),
email: z.string().email(),
});

returnSchema = z.string()

agent = new Agent({
id: 'getDisplayInfo',
name: 'getDisplayInfo',
description: 'Get display info of a user',
tags: ['user', 'display'],
protocol: {
type: 'function'
},
input: {
shape: zodToJsonSchema(userSchema, "user"),
protocol: {
encoder: 'object' //instead of the default 'positional' for the function protocol
}
},
output: {
shape: zodToJsonSchema(returnSchema, "displayInfo")
}
})
});

it('Should be able to remember its agent details after created', () => {
expect(agent.getId()).toBe('getDisplayInfo');
expect(agent.getName()).toBe('getDisplayInfo');
expect(agent.getDescription()).toBe('Get display info of a user');
expect(agent.getTags()).toEqual(['user', 'display']);
expect(agent.getProtocol()).toEqual({ type: 'function' });
expect(agent.getInputShape()).toEqual(zodToJsonSchema(userSchema, "user"))
expect(agent.getOutputShape()).toEqual(zodToJsonSchema(returnSchema, "displayInfo"))
expect(agent.getChildren()).toEqual([]);
});

it('Should be able to add children to the agent', () => {
const childAgent = new Agent({
id: 'childAgent',
name: 'childAgent',
description: 'Child agent',
tags: ['child'],
protocol: {
type: 'function'
},
input: {
shape: zodToJsonSchema(userSchema, "user")
},
output: {
shape: zodToJsonSchema(returnSchema, "displayInfo")
}
})

agent.addChildren([childAgent]);
expect(agent.getChildren()).toEqual([childAgent]);
});

it('Should be able to add a single child to the agent', () => {
const childAgent = new Agent({
id: 'childAgent',
name: 'childAgent',
description: 'Child agent',
tags: ['child'],
protocol: {
type: 'function'
},
input: {
shape: zodToJsonSchema(userSchema, "user")
},
output: {
shape: zodToJsonSchema(returnSchema, "displayInfo")
}
})

agent.addChild(childAgent);
expect(agent.getChildren()).toEqual([childAgent]);
});

it('Should be able to get a child by id', () => {
const childAgent = new Agent({
id: 'childAgent',
name: 'childAgent',
description: 'Child agent',
tags: ['child'],
protocol: {
type: 'function'
},
input: {
shape: zodToJsonSchema(userSchema, "user")
},
output: {
shape: zodToJsonSchema(returnSchema, "displayInfo")
}
})

agent.addChild(childAgent);
expect(agent.getChild('childAgent')).toEqual(childAgent);
});

it('Should return undefined if the child does not exist', () => {
expect(agent.getChild('childAgent')).toBeUndefined();
});

it('Should combine agent protocol definition with input and output protocol definition, to reduce verbosity', () => {
expect(agent.getInputProtocol()).toEqual({ type: 'function', encoder: 'object' });
expect(agent.getOutputProtocol()).toEqual({ type: 'function' });
});
});
16 changes: 16 additions & 0 deletions packages/protofy/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "ES2019",
"lib": ["dom", "esnext"],
"module": "commonjs",
"declaration": true,
"outDir": "./dist",
"rootDir": "./src",
"strict": false,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*", "tests/**/*"],
"exclude": ["node_modules", "dist"]
}
42 changes: 42 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -11104,6 +11104,18 @@ __metadata:
languageName: node
linkType: hard

"ajv@npm:8.17.1":
version: 8.17.1
resolution: "ajv@npm:8.17.1"
dependencies:
fast-deep-equal: "npm:^3.1.3"
fast-uri: "npm:^3.0.1"
json-schema-traverse: "npm:^1.0.0"
require-from-string: "npm:^2.0.2"
checksum: 10/ee3c62162c953e91986c838f004132b6a253d700f1e51253b99791e2dbfdb39161bc950ebdc2f156f8568035bb5ed8be7bd78289cd9ecbf3381fe8f5b82e3f33
languageName: node
linkType: hard

"ajv@npm:8.6.3":
version: 8.6.3
resolution: "ajv@npm:8.6.3"
Expand Down Expand Up @@ -17786,6 +17798,13 @@ __metadata:
languageName: node
linkType: hard

"fast-uri@npm:^3.0.1":
version: 3.0.3
resolution: "fast-uri@npm:3.0.3"
checksum: 10/92487c75848b03edc45517fca0148287d342c30818ce43d556391db774d8e01644fb6964315a3336eec5a90f301b218b21f71fb9b2528ba25757435a20392c95
languageName: node
linkType: hard

"fast-xml-parser@npm:^4.0.12":
version: 4.2.2
resolution: "fast-xml-parser@npm:4.2.2"
Expand Down Expand Up @@ -27828,6 +27847,20 @@ __metadata:
languageName: unknown
linkType: soft

"protofy@workspace:packages/protofy":
version: 0.0.0-use.local
resolution: "protofy@workspace:packages/protofy"
dependencies:
"@types/jest": "npm:~29.5.5"
ajv: "npm:8.17.1"
jest: "npm:~29.7.0"
ts-jest: "npm:~29.1.1"
typescript: "npm:~5.3.3"
zod: "npm:^3.22.2"
zod-to-json-schema: "npm:3.23.5"
languageName: unknown
linkType: soft

"protolib@npm:*, protolib@npm:0.0.1, protolib@workspace:packages/protolib":
version: 0.0.0-use.local
resolution: "protolib@workspace:packages/protolib"
Expand Down Expand Up @@ -35019,6 +35052,15 @@ __metadata:
languageName: node
linkType: hard

"zod-to-json-schema@npm:3.23.5":
version: 3.23.5
resolution: "zod-to-json-schema@npm:3.23.5"
peerDependencies:
zod: ^3.23.3
checksum: 10/53d07a419f0f194e0b96711dc11e7e6fa52a366b0ed5fceb405dc55f13252a1bf433712e4fb496c2a5fdc851018ee1acba7b39c2265c43d6fbb180e12c110c3b
languageName: node
linkType: hard

"zod@npm:^3.22.2":
version: 3.22.2
resolution: "zod@npm:3.22.2"
Expand Down

0 comments on commit f0974ce

Please sign in to comment.