diff --git a/packages/fixtures/valid/basic-openapi/agentic.config.ts b/packages/fixtures/valid/basic-openapi/agentic.config.ts new file mode 100644 index 00000000..9b87f2a2 --- /dev/null +++ b/packages/fixtures/valid/basic-openapi/agentic.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from '@agentic/platform' + +export default defineConfig({ + name: 'test-basic-openapi', + originUrl: 'https://jsonplaceholder.typicode.com', + originAdapter: { + type: 'openapi', + spec: './jsonplaceholder.json' + } +}) diff --git a/packages/fixtures/valid/basic-openapi/jsonplaceholder.json b/packages/fixtures/valid/basic-openapi/jsonplaceholder.json new file mode 100644 index 00000000..9d13bd8c --- /dev/null +++ b/packages/fixtures/valid/basic-openapi/jsonplaceholder.json @@ -0,0 +1,241 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "JSONPlaceholder", + "version": "1.0.0" + }, + "paths": { + "/posts": { + "get": { + "summary": "Get posts", + "operationId": "getPosts", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Post" + } + } + } + } + } + } + }, + "post": { + "summary": "Create post", + "operationId": "createPost", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["userId", "title", "body"], + "properties": { + "userId": { + "type": "integer" + }, + "title": { + "type": "string" + }, + "body": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Post" + } + } + } + } + } + } + }, + "/posts/{postId}": { + "get": { + "summary": "Get post", + "operationId": "getPost", + "parameters": [ + { + "required": true, + "schema": { + "title": "postId", + "type": "string" + }, + "name": "postId", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Post" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Post": { + "type": "object", + "required": ["id", "userId", "title", "body"], + "properties": { + "id": { + "type": "integer" + }, + "userId": { + "type": "integer" + }, + "title": { + "type": "string" + }, + "body": { + "type": "string" + } + } + }, + "Comment": { + "type": "object", + "required": ["id", "postId", "name", "email", "body"], + "properties": { + "id": { + "type": "integer" + }, + "postId": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "body": { + "type": "string" + } + } + }, + "Todo": { + "type": "object", + "required": ["id", "userId", "title", "completed"], + "properties": { + "id": { + "type": "integer" + }, + "userId": { + "type": "integer" + }, + "title": { + "type": "string" + }, + "completed": { + "type": "boolean" + } + } + }, + "User": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "name": { + "type": "string" + }, + "username": { + "type": "string" + }, + "email": { + "type": "string" + }, + "address": { + "type": "object", + "properties": { + "street": { + "type": "string" + }, + "suite": { + "type": "string" + }, + "city": { + "type": "string" + }, + "zipcode": { + "type": "string" + }, + "geo": { + "type": "object", + "properties": { + "lat": { + "type": "string" + }, + "lng": { + "type": "string" + } + }, + "required": ["lat", "lng"] + } + }, + "required": ["street", "suite", "city", "zipcode", "geo"] + }, + "phone": { + "type": "string" + }, + "website": { + "type": "string" + }, + "company": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "catchPhrase": { + "type": "string" + }, + "bs": { + "type": "string" + } + }, + "required": ["name", "catchPhrase", "bs"] + } + }, + "required": [ + "id", + "name", + "username", + "email", + "address", + "phone", + "website", + "company" + ] + } + } + }, + "servers": [ + { + "url": "https://jsonplaceholder.typicode.com" + } + ] +} diff --git a/packages/openapi-utils/src/validate-openapi-spec.ts b/packages/openapi-utils/src/validate-openapi-spec.ts index 9cf95534..8f2f4cbe 100644 --- a/packages/openapi-utils/src/validate-openapi-spec.ts +++ b/packages/openapi-utils/src/validate-openapi-spec.ts @@ -1,3 +1,5 @@ +import fs from 'node:fs/promises' +import path from 'node:path' import { fileURLToPath } from 'node:url' import { assert, type Logger, parseJson } from '@agentic/platform-core' @@ -38,7 +40,7 @@ export async function validateOpenAPISpec< silent = false, dereference = false }: { - cwd?: URL + cwd?: string redoclyConfig?: RedoclyConfig logger?: Logger silent?: boolean @@ -56,7 +58,7 @@ export async function validateOpenAPISpec< absoluteRef = source.protocol === 'file:' ? fileURLToPath(source) : source.href } else { - absoluteRef = fileURLToPath(new URL(cwd ?? `file://${process.cwd()}/`)) + absoluteRef = cwd ?? process.cwd() } const resolver = new BaseResolver(redoclyConfig.resolve) @@ -168,6 +170,19 @@ async function parseSchema( } } + // Path to local file + // TODO: support raw local files (no ./ prefix) + if (schema.startsWith('/') || schema.startsWith('.')) { + const schemaPath = path.join(absoluteRef, schema) + const schemaContent = await fs.readFile(schemaPath, 'utf8') + + // TODO: support local YAML files + return { + source: new Source(schemaPath, schemaContent, 'application/json'), + parsed: parseJson(schemaContent) + } + } + // YAML const result = makeDocumentFromString(schema, absoluteRef) if ( diff --git a/packages/platform/src/__snapshots__/load-agentic-config.test.ts.snap b/packages/platform/src/__snapshots__/load-agentic-config.test.ts.snap index a5217668..d598cd24 100644 --- a/packages/platform/src/__snapshots__/load-agentic-config.test.ts.snap +++ b/packages/platform/src/__snapshots__/load-agentic-config.test.ts.snap @@ -1,5 +1,35 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html +exports[`loadAgenticConfig > basic-openapi 1`] = ` +{ + "name": "test-basic-openapi", + "originAdapter": { + "location": "external", + "spec": "{"openapi":"3.1.0","info":{"title":"JSONPlaceholder","version":"1.0.0"},"paths":{"/posts":{"get":{"summary":"Get posts","operationId":"getPosts","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Post"}}}}}}},"post":{"summary":"Create post","operationId":"createPost","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["userId","title","body"],"properties":{"userId":{"type":"integer"},"title":{"type":"string"},"body":{"type":"string"}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Post"}}}}}}},"/posts/{postId}":{"get":{"summary":"Get post","operationId":"getPost","parameters":[{"required":true,"schema":{"title":"postId","type":"string"},"name":"postId","in":"path"}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Post"}}}}}}}},"components":{"schemas":{"Post":{"type":"object","required":["id","userId","title","body"],"properties":{"id":{"type":"integer"},"userId":{"type":"integer"},"title":{"type":"string"},"body":{"type":"string"}}}}}}", + "type": "openapi", + }, + "originUrl": "https://jsonplaceholder.typicode.com", + "pricingIntervals": [ + "month", + ], + "pricingPlans": [ + { + "lineItems": [ + { + "amount": 0, + "slug": "base", + "usageType": "licensed", + }, + ], + "name": "Free", + "slug": "free", + }, + ], + "toolConfigs": [], + "version": undefined, +} +`; + exports[`loadAgenticConfig > basic-raw-free-json 1`] = ` { "name": "test-basic-raw-free-json", diff --git a/packages/platform/src/load-agentic-config.test.ts b/packages/platform/src/load-agentic-config.test.ts index 81c86ac0..cc8e8133 100644 --- a/packages/platform/src/load-agentic-config.test.ts +++ b/packages/platform/src/load-agentic-config.test.ts @@ -12,7 +12,8 @@ const fixtures = [ 'pricing-pay-as-you-go', 'pricing-3-plans', 'pricing-monthly-annual', - 'pricing-custom-0' + 'pricing-custom-0', + 'basic-openapi' ] const invalidFixtures = [ diff --git a/packages/platform/src/load-agentic-config.ts b/packages/platform/src/load-agentic-config.ts index 480c08f3..7e247a8f 100644 --- a/packages/platform/src/load-agentic-config.ts +++ b/packages/platform/src/load-agentic-config.ts @@ -18,5 +18,5 @@ export async function loadAgenticConfig({ ] }) - return validateAgenticProjectConfig(config) + return validateAgenticProjectConfig(config, { cwd }) } diff --git a/packages/platform/src/origin-adapters/openapi.ts b/packages/platform/src/origin-adapters/openapi.ts index 38b025a8..a135dda7 100644 --- a/packages/platform/src/origin-adapters/openapi.ts +++ b/packages/platform/src/origin-adapters/openapi.ts @@ -17,7 +17,7 @@ export async function resolveOpenAPIOriginAdapter({ }: { originAdapter: OpenAPIOriginAdapterConfig label: string - cwd?: URL + cwd?: string logger?: Logger }): Promise<{ originAdapter: OpenAPIOriginAdapter diff --git a/packages/platform/src/validate-agentic-project-config.ts b/packages/platform/src/validate-agentic-project-config.ts index b25df7b3..e03a86da 100644 --- a/packages/platform/src/validate-agentic-project-config.ts +++ b/packages/platform/src/validate-agentic-project-config.ts @@ -11,7 +11,7 @@ export async function validateAgenticProjectConfig( { strip = false, ...opts - }: { logger?: Logger; cwd?: URL; strip?: boolean; label?: string } = {} + }: { logger?: Logger; cwd?: string; strip?: boolean; label?: string } = {} ): Promise { const config = parseAgenticProjectConfig(inputConfig, { strip, diff --git a/packages/platform/src/validate-origin-adapter.ts b/packages/platform/src/validate-origin-adapter.ts index 67b8b847..6b8912de 100644 --- a/packages/platform/src/validate-origin-adapter.ts +++ b/packages/platform/src/validate-origin-adapter.ts @@ -22,7 +22,7 @@ export async function validateOriginAdapter({ originAdapter: Readonly label: string version?: string - cwd?: URL + cwd?: string logger?: Logger }): Promise { validateOriginUrl({ originUrl, label }) diff --git a/packages/types/src/origin-adapter.ts b/packages/types/src/origin-adapter.ts index 81096d4f..57cd98cb 100644 --- a/packages/types/src/origin-adapter.ts +++ b/packages/types/src/origin-adapter.ts @@ -21,7 +21,7 @@ export type OriginAdapterLocation = z.infer // > export const commonOriginAdapterSchema = z.object({ - location: originAdapterLocationSchema + location: originAdapterLocationSchema.optional().default('external') // TODO: Add support for `internal` hosted API servers // internalType: originAdapterInternalTypeSchema.optional() diff --git a/readme.md b/readme.md index c0e394c8..17cd234c 100644 --- a/readme.md +++ b/readme.md @@ -52,7 +52,7 @@ - add support for custom headers on responses - signed requests - revisit deployment identifiers so possibly be URL-friendly? -- rename parseFaasIdentifier and move validators package into platform-types +- rename parseFaasIdentifier and move validators package into platform-types? ## License