diff --git a/README.md b/README.md index 21009c6..a5bf678 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,13 @@

AI Agent Contract Template with OpenAI

- -
-
- - -
+ + +
+

+

Host your AI Agent Contract on Phala's decentralized serverless cloud.
Explore the docs ยป diff --git a/package.json b/package.json index b65f2e1..506f2dc 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,9 @@ "node": ">=18" }, "scripts": { - "build": "phat-fn build --experimentalAsync", - "test": "tsx tests/test.ts", + "build": "tsup --config tsup.config.ts", + "test": "vitest", + "dev": "tsx watch src/index.ts", "set-secrets": "tsx scripts/setSecrets.ts", "lint": "tsc --noEmit", "publish-agent": "phat-fn build --experimentalAsync && tsx scripts/publish.ts" @@ -21,9 +22,13 @@ "thirdweb": "^5.32.3", "tsx": "^4.7.1", "typescript": "^5.3.3", + "vitest": "^2.1.5", "wyhash": "^1.0.0" }, "dependencies": { - "openai": "^4.56.0" + "@hono/node-server": "^1.13.7", + "hono": "^4.6.11", + "openai": "^4.56.0", + "tsup": "^8.3.5" } } diff --git a/src/httpSupport.ts b/src/httpSupport.ts deleted file mode 100644 index d4fc048..0000000 --- a/src/httpSupport.ts +++ /dev/null @@ -1,86 +0,0 @@ -// DO NOT MODIFY THIS INTERFACE -export interface SerializedRequest { - method: 'GET' | 'POST' | 'PATCH' | 'PUT'; - path: string; - queries: Record; - headers: Record; - body?: string; - secret?: Record; -} - -// DO NOT MODIFY THIS CLASS -export class Request implements SerializedRequest { - method: 'GET' | 'POST' | 'PATCH' | 'PUT'; - path: string; - queries: Record; - headers: Record; - body?: string; - secret?: Record; - constructor(raw: SerializedRequest) { - this.body = raw.body; - this.queries = raw.queries; - this.headers = raw.headers; - this.method = raw.method; - this.path = raw.path; - this.secret = raw.secret; - } - async json(): Promise { - return JSON.parse(this.body!) - } -} - -type ResponseOption = { - status?: number, - headers?: Record -} -export class Response { - status: number; - body?: string; - headers: Record; - constructor(body: string, options?: ResponseOption) { - this.status = options?.status ?? 200; - this.body = body; - this.headers = { - // To change the response type, change the Content-Type header - 'Content-Type': 'application/json', - 'Access-Control-Allow-Origin': '*', - ...options?.headers - } - } -} - -export type RouteConfig = { - GET?: (req: Request) => Promise, - POST?: (req: Request) => Promise, - PATCH?: (req: Request) => Promise, - PUT?: (req: Request) => Promise, -} - -export async function route(config: RouteConfig, request: string) { - const reqObj: SerializedRequest = JSON.parse(request) - let response: Response; - const method = reqObj.method - const req = new Request(reqObj) - if (method == 'GET' && config.GET) { - response = await config.GET(req); - } else if (method == 'POST' && config.POST) { - response = await config.POST(req); - } else if (method == 'PATCH' && config.PATCH) { - response = await config.PATCH(req); - } else if (method == 'PUT' && config.PUT) { - response = await config.PUT(req); - } else { - response = new Response('Not Found'); - response.status = 404 - } - return JSON.stringify(response) -} - -// Only works for ascii string -export function stringToHex(str: string): string { - let hex = ''; - for (let i = 0; i < str.length; i++) { - hex += str.charCodeAt(i).toString(16).padStart(2, '0'); - } - return '0x' + hex; -} diff --git a/src/index.ts b/src/index.ts index a94531b..6ad6134 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,31 +1,68 @@ -import { Request, Response, route } from './httpSupport' - -import OpenAI from 'openai' - -async function GET(req: Request): Promise { - let result = { message: '' } - const secrets = req.secret || {} - const queries = req.queries - const openaiApiKey = (secrets.openaiApiKey) ? secrets.openaiApiKey as string : '' - const openai = new OpenAI({ apiKey: openaiApiKey }) - // Choose from any model listed here https://platform.openai.com/docs/models - const openAiModel = (queries.openAiModel) ? queries.openAiModel[0] : 'gpt-4o'; +import { serve } from '@hono/node-server' +import { Hono } from 'hono' +import OpenAI from "openai"; + +export const app = new Hono() + +const getAPIKey = () => { + console.log('Getting API Key from vault...') + let vault: Record = {}; + try { + vault = JSON.parse(process.env.secret || '') + } catch (e) { + console.error(e) + } + return vault.openaiApiKey || '' +} + +function buildHtmlResponse(message: string) { + const htmlResponse = ` + + + + + + OpenAI Response + + + +

+

Response

+

${message}

+
+ + + `; + return htmlResponse; + } + +app.get('/', async (c) => { + let result = {message: ''} + const openaiApiKey = getAPIKey() + const queries = c.req.queries() || {} + const openai = new OpenAI({apiKey: openaiApiKey}) + const openAiModel = (queries.openAiModel) ? queries.openAiModel[0] : 'gpt-3.5-turbo'; const query = (queries.chatQuery) ? queries.chatQuery[0] as string : 'Who are you?' const completion = await openai.chat.completions.create({ - messages: [{ role: "system", content: `${query}` }], + messages: [{role: "system", content: `${query}`}], model: `${openAiModel}`, }) - result.message = (completion.choices) ? completion.choices[0].message.content as string : 'Failed to get result' - return new Response(JSON.stringify(result)) -} + return c.html(buildHtmlResponse(result.message)) -async function POST(req: Request): Promise { - return new Response(JSON.stringify({message: 'Not Implemented'})) -} +}) -export default async function main(request: string) { - return await route({ GET, POST }, request) -} +const port = 3000 +console.log(`Server is running on http://localhost:${port}`) + +serve({ + fetch: app.fetch, + port +}) diff --git a/tests/index.test.ts b/tests/index.test.ts new file mode 100644 index 0000000..76c0546 --- /dev/null +++ b/tests/index.test.ts @@ -0,0 +1,32 @@ +import {afterAll, describe, test, vi, expect, beforeAll} from 'vitest' +import { app } from '../src/' +import * as path from "node:path"; + +const chatQuery = 'Who are you two?'; +const model = 'claude-3-5-sonnet-20241022'; + +// Set Testing env secrets +const secretsFile = '../secrets/default.json' +vi.stubEnv('secret', JSON.stringify(require(path.join(__dirname, secretsFile)))) + +describe('Test OpenAI Agent Contract', () => { + test('returns default response when no query parameters are provided', async () => { + const resp = await app.request('/') + expect(resp.status).toBe(200) + const text = await resp.text(); + expect(text).toContain('

Response

') + }, 10000) // Define o timeout para 10 segundos + + test('returns response with provided query parameters', async () => { + const resp = await app.request('/?chatQuery=Hello&openAiModel=gpt-3.5-turbo') + expect(resp.status).toBe(200) + const text = await resp.text(); + expect(text).toContain('

Response

') + + }, 10000) // Define o timeout para 10 segundos +// Define o timeout para 10 segundos +}) + +afterAll(async () => { + console.log(`\nNow you are ready to publish your agent, add secrets, and interact with your agent in the following steps:\n- Execute: 'npm run publish-agent'\n- Set secrets: 'npm run set-secrets'\n- Go to the url produced by setting the secrets (e.g. https://wapo-testnet.phala.network/ipfs/QmPQJD5zv3cYDRM25uGAVjLvXGNyQf9Vonz7rqkQB52Jae?key=b092532592cbd0cf)`) +}) ; \ No newline at end of file