diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index 70262a1..7e87452 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -9,6 +9,7 @@ on: type: choice options: - all + - a2a - types - server - sdk-typescript @@ -184,6 +185,7 @@ jobs: max-parallel: 5 matrix: package: + - a2a - types - server - sdk-typescript @@ -313,6 +315,7 @@ jobs: ### Install ```bash + npm install @relaycast/a2a@${{ needs.build.outputs.new_version }} npm install @relaycast/sdk@${{ needs.build.outputs.new_version }} npm install relaycast@${{ needs.build.outputs.new_version }} npm install @relaycast/react@${{ needs.build.outputs.new_version }} @@ -323,6 +326,7 @@ jobs: ### Packages | Package | Version | |---------|---------| + | @relaycast/a2a | ${{ needs.build.outputs.new_version }} | | @relaycast/types | ${{ needs.build.outputs.new_version }} | | @relaycast/server | ${{ needs.build.outputs.new_version }} | | @relaycast/sdk | ${{ needs.build.outputs.new_version }} | diff --git a/package-lock.json b/package-lock.json index ac080a4..013bcb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3279,6 +3279,10 @@ "integrity": "sha512-h2FO7ut/BbfwpAXWpwdDHTzQgUo9ibDFEs6ZO+3cI3KPWQt5XwczK1OLAuPprcjm8T/jl0SH8jSFo5XdU4RbTg==", "license": "MIT" }, + "node_modules/@relaycast/a2a": { + "resolved": "packages/a2a", + "link": true + }, "node_modules/@relaycast/mcp": { "resolved": "packages/mcp", "link": true @@ -7951,7 +7955,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -7973,7 +7976,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -7995,7 +7997,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8017,7 +8018,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8039,7 +8039,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8061,7 +8060,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8083,7 +8081,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8105,7 +8102,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8127,7 +8123,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8149,7 +8144,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8171,7 +8165,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -13364,6 +13357,13 @@ "url": "https://github.com/sponsors/wooorm" } }, + "packages/a2a": { + "name": "@relaycast/a2a", + "version": "1.1.3", + "dependencies": { + "zod": "^4.3.6" + } + }, "packages/cli": { "name": "relaycast", "version": "1.1.3", @@ -13952,6 +13952,7 @@ "dependencies": { "@aws-sdk/client-s3": "^3.985.0", "@aws-sdk/s3-request-presigner": "^3.985.0", + "@relaycast/a2a": "1.1.3", "@relaycast/mcp": "1.1.3", "@relaycast/types": "1.1.3", "drizzle-orm": "^0.45.1", diff --git a/packages/a2a/package.json b/packages/a2a/package.json new file mode 100644 index 0000000..e367604 --- /dev/null +++ b/packages/a2a/package.json @@ -0,0 +1,23 @@ +{ + "name": "@relaycast/a2a", + "version": "1.1.3", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "rm -rf dist tsconfig.tsbuildinfo && tsc", + "test": "vitest run --passWithNoTests", + "lint": "eslint src/" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/AgentWorkforce/relaycast.git", + "directory": "packages/a2a" + }, + "dependencies": { + "zod": "^4.3.6" + }, + "files": [ + "dist" + ] +} diff --git a/packages/a2a/src/index.ts b/packages/a2a/src/index.ts new file mode 100644 index 0000000..33fa465 --- /dev/null +++ b/packages/a2a/src/index.ts @@ -0,0 +1,127 @@ +import { z } from 'zod'; + +export const A2aSkillSchema = z.object({ + id: z.string().min(1).optional(), + name: z.string().min(1), + description: z.string().optional(), + tags: z.array(z.string()).optional(), +}); + +export const A2aAgentCardSchema = z.object({ + name: z.string().min(1), + description: z.string().optional(), + url: z.string().url(), + version: z.string().min(1), + skills: z.array(A2aSkillSchema).min(1), + provider: z.record(z.string(), z.unknown()).optional(), + capabilities: z.record(z.string(), z.unknown()).optional(), + default_input_modes: z.array(z.string()).optional(), + default_output_modes: z.array(z.string()).optional(), + documentation_url: z.string().url().optional(), +}); + +export const A2aFilePartSchema = z.object({ + kind: z.literal('file'), + file: z.object({ + name: z.string().min(1), + mime_type: z.string().min(1).optional(), + uri: z.string().min(1).optional(), + bytes: z.number().int().nonnegative().optional(), + }), +}); + +export const A2aDataPartSchema = z.object({ + kind: z.literal('data'), + data: z.record(z.string(), z.unknown()), +}); + +export const A2aTextPartSchema = z.object({ + kind: z.literal('text'), + text: z.string(), +}); + +export const A2aPartSchema = z.discriminatedUnion('kind', [ + A2aTextPartSchema, + A2aFilePartSchema, + A2aDataPartSchema, +]); + +export const A2aMessageSchema = z.object({ + message_id: z.string(), + role: z.enum(['user', 'agent', 'system']).default('user'), + context_id: z.string().optional(), + parts: z.array(A2aPartSchema).min(1), +}); + +export const A2aArtifactSchema = z.object({ + name: z.string(), + description: z.string().optional(), + parts: z.array(A2aPartSchema).min(1), +}); + +export const A2aTaskStateSchema = z.enum([ + 'submitted', + 'working', + 'input-required', + 'completed', + 'failed', + 'canceled', + 'unknown', +]); + +export const A2aTaskSchema = z.object({ + id: z.string(), + context_id: z.string().optional(), + status: z.object({ + state: A2aTaskStateSchema, + message: z.string().optional(), + }), + artifacts: z.array(A2aArtifactSchema).optional(), + history: z.array(A2aMessageSchema).optional(), + metadata: z.record(z.string(), z.unknown()).optional(), +}); + +export const JsonRpcRequestSchema = z.object({ + jsonrpc: z.literal('2.0'), + id: z.union([z.string(), z.number()]).optional(), + method: z.string().min(1), + params: z.object({ + message: A2aMessageSchema.optional(), + }).passthrough().optional(), +}); + +export const JsonRpcErrorSchema = z.object({ + code: z.number(), + message: z.string(), + data: z.unknown().optional(), +}); + +export const JsonRpcResponseSchema = z.object({ + jsonrpc: z.literal('2.0'), + id: z.union([z.string(), z.number()]).optional(), + result: z.object({ + task: A2aTaskSchema.optional(), + message: A2aMessageSchema.optional(), + }).passthrough().optional(), + error: JsonRpcErrorSchema.optional(), +}); + +export const A2aResponseSchema = z.object({ + task: A2aTaskSchema.optional(), + message: A2aMessageSchema.optional(), + response: JsonRpcResponseSchema.optional(), +}); + +export type A2aSkill = z.infer; +export type A2aAgentCard = z.infer; +export type A2aFilePart = z.infer; +export type A2aDataPart = z.infer; +export type A2aTextPart = z.infer; +export type A2aPart = z.infer; +export type A2aMessage = z.infer; +export type A2aArtifact = z.infer; +export type A2aTaskState = z.infer; +export type A2aTask = z.infer; +export type A2aJsonRpcRequest = z.infer; +export type A2aJsonRpcResponse = z.infer; +export type A2aResponse = z.infer; diff --git a/packages/a2a/tsconfig.json b/packages/a2a/tsconfig.json new file mode 100644 index 0000000..c3d059a --- /dev/null +++ b/packages/a2a/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "tsBuildInfoFile": "tsconfig.tsbuildinfo" + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/server/package.json b/packages/server/package.json index d435752..5559616 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -18,7 +18,8 @@ "drizzle-orm": "^0.45.1", "hono": "^4.11.9", "posthog-node": "^5.29.2", - "zod": "^4.3.6" + "zod": "^4.3.6", + "@relaycast/a2a": "1.1.3" }, "repository": { "type": "git", diff --git a/packages/server/src/engine/a2a.ts b/packages/server/src/engine/a2a.ts index d11d7dc..64c8a51 100644 --- a/packages/server/src/engine/a2a.ts +++ b/packages/server/src/engine/a2a.ts @@ -1,5 +1,23 @@ import crypto from 'node:crypto'; import { and, eq, sql } from 'drizzle-orm'; +import { + A2aAgentCardSchema, + A2aMessageSchema, + A2aPartSchema, + A2aResponseSchema, + A2aTaskSchema, + A2aTaskStateSchema, + JsonRpcRequestSchema, + JsonRpcResponseSchema, + type A2aAgentCard, + type A2aJsonRpcRequest, + type A2aJsonRpcResponse, + type A2aMessage, + type A2aPart, + type A2aResponse, + type A2aTask, + type A2aTaskState, +} from '@relaycast/a2a'; import { z } from 'zod'; import type { FileAttachment } from '@relaycast/types'; import type { getDb } from '../db/index.js'; @@ -12,26 +30,6 @@ type Db = ReturnType; const RETRY_DELAYS_MS = [250, 750] as const; const DEFAULT_WEBHOOK_BASE_PATH = '/a2a/webhook'; -const A2aSkillSchema = z.object({ - id: z.string().min(1).optional(), - name: z.string().min(1), - description: z.string().optional(), - tags: z.array(z.string()).optional(), -}); - -export const A2aAgentCardSchema = z.object({ - name: z.string().min(1), - description: z.string().optional(), - url: z.string().url(), - version: z.string().min(1), - skills: z.array(A2aSkillSchema).min(1), - provider: z.record(z.string(), z.unknown()).optional(), - capabilities: z.record(z.string(), z.unknown()).optional(), - default_input_modes: z.array(z.string()).optional(), - default_output_modes: z.array(z.string()).optional(), - documentation_url: z.string().url().optional(), -}); - const RelayFileAttachmentSchema = z.object({ file_id: z.string(), filename: z.string(), @@ -49,106 +47,28 @@ const RelayDMSchema = z.object({ attachments: z.array(RelayFileAttachmentSchema).optional(), }); -const A2aFilePartSchema = z.object({ - kind: z.literal('file'), - file: z.object({ - name: z.string().min(1), - mime_type: z.string().min(1).optional(), - uri: z.string().min(1).optional(), - bytes: z.number().int().nonnegative().optional(), - }), -}); - -const A2aDataPartSchema = z.object({ - kind: z.literal('data'), - data: z.record(z.string(), z.unknown()), -}); - -const A2aTextPartSchema = z.object({ - kind: z.literal('text'), - text: z.string(), -}); - -export const A2aPartSchema = z.discriminatedUnion('kind', [ - A2aTextPartSchema, - A2aFilePartSchema, - A2aDataPartSchema, -]); - -export const A2aMessageSchema = z.object({ - message_id: z.string(), - role: z.enum(['user', 'agent', 'system']).default('user'), - context_id: z.string().optional(), - parts: z.array(A2aPartSchema).min(1), -}); - -const A2aArtifactSchema = z.object({ - name: z.string(), - description: z.string().optional(), - parts: z.array(A2aPartSchema).min(1), -}); - -export const A2aTaskStateSchema = z.enum([ - 'submitted', - 'working', - 'input-required', - 'completed', - 'failed', - 'canceled', - 'unknown', -]); - -export const A2aTaskSchema = z.object({ - id: z.string(), - context_id: z.string().optional(), - status: z.object({ - state: A2aTaskStateSchema, - message: z.string().optional(), - }), - artifacts: z.array(A2aArtifactSchema).optional(), - history: z.array(A2aMessageSchema).optional(), - metadata: z.record(z.string(), z.unknown()).optional(), -}); - -export const JsonRpcRequestSchema = z.object({ - jsonrpc: z.literal('2.0'), - id: z.union([z.string(), z.number()]).optional(), - method: z.string().min(1), - params: z.object({ - message: A2aMessageSchema.optional(), - }).passthrough().optional(), -}); - -const JsonRpcErrorSchema = z.object({ - code: z.number(), - message: z.string(), - data: z.unknown().optional(), -}); - -export const JsonRpcResponseSchema = z.object({ - jsonrpc: z.literal('2.0'), - id: z.union([z.string(), z.number()]).optional(), - result: z.object({ - task: A2aTaskSchema.optional(), - message: A2aMessageSchema.optional(), - }).passthrough().optional(), - error: JsonRpcErrorSchema.optional(), -}); - -const _A2aResponseSchema = z.object({ - task: A2aTaskSchema.optional(), - message: A2aMessageSchema.optional(), - response: JsonRpcResponseSchema.optional(), -}); +export { + A2aAgentCardSchema, + A2aMessageSchema, + A2aPartSchema, + A2aResponseSchema, + A2aTaskSchema, + A2aTaskStateSchema, + JsonRpcRequestSchema, + JsonRpcResponseSchema, +}; + +export type { + A2aAgentCard, + A2aJsonRpcRequest, + A2aJsonRpcResponse, + A2aMessage, + A2aPart, + A2aResponse, + A2aTask, + A2aTaskState, +}; -export type A2aAgentCard = z.infer; -export type A2aMessage = z.infer; -export type A2aPart = z.infer; -export type A2aTask = z.infer; -export type A2aTaskState = z.infer; -export type A2aJsonRpcRequest = z.infer; -export type A2aJsonRpcResponse = z.infer; -export type A2aResponse = z.infer; export type RelayDM = z.infer; export interface RegisterA2aAgentInput {