-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds protofy js implementation of the agent protocol network
- Loading branch information
Showing
8 changed files
with
352 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
dist |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' }); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters