From 61bd64c6c6f3e082c1f88c86dfc0bafd4e47bd0c Mon Sep 17 00:00:00 2001 From: Kaito Date: Sat, 16 May 2026 22:41:06 +0700 Subject: [PATCH] fix(api): fallback openapi agent paths --- apps/web/lib/api/openapi.test.ts | 42 ++++++++++---- apps/web/lib/api/openapi.ts | 95 ++++++++++++++++++++++++++++++-- 2 files changed, 123 insertions(+), 14 deletions(-) diff --git a/apps/web/lib/api/openapi.test.ts b/apps/web/lib/api/openapi.test.ts index 87dd207..cccf8e6 100644 --- a/apps/web/lib/api/openapi.test.ts +++ b/apps/web/lib/api/openapi.test.ts @@ -1,7 +1,11 @@ import assert from "node:assert/strict" import { fileURLToPath } from "node:url" import { describe, it } from "node:test" -import { createOpenApiDocument } from "./openapi" +import { + createOpenApiDocument, + getAgentApiGlobs, + getFallbackAgentApiPaths, +} from "./openapi" type OpenApiDocument = { paths?: Record @@ -11,6 +15,16 @@ type OpenApiDocument = { } describe("OpenAPI document", () => { + const expectedAgentApiPaths = [ + "/api/agent", + "/api/agent/agents", + "/api/agent/agents/{agentId}", + "/api/agent/projects", + "/api/agent/projects/{projectId}", + "/api/agent/tasks", + "/api/agent/tasks/{taskId}", + ] + it("generates only Agent API paths from the web app working directory", () => { const originalCwd = process.cwd() process.chdir(fileURLToPath(new URL("../..", import.meta.url))) @@ -19,15 +33,23 @@ describe("OpenAPI document", () => { const document = createOpenApiDocument() as OpenApiDocument const paths = Object.keys(document.paths ?? {}).sort() - assert.deepEqual(paths, [ - "/api/agent", - "/api/agent/agents", - "/api/agent/agents/{agentId}", - "/api/agent/projects", - "/api/agent/projects/{projectId}", - "/api/agent/tasks", - "/api/agent/tasks/{taskId}", - ]) + assert.deepEqual(paths, expectedAgentApiPaths) + } finally { + process.chdir(originalCwd) + } + }) + + it("falls back to explicit Agent API paths when route files are unavailable", () => { + const originalCwd = process.cwd() + process.chdir(fileURLToPath(new URL("..", import.meta.url))) + + try { + assert.equal(getAgentApiGlobs().some((glob) => glob.includes("app/api/agent")), true) + const document = createOpenApiDocument() as OpenApiDocument + const paths = Object.keys(document.paths ?? {}).sort() + + assert.deepEqual(paths, expectedAgentApiPaths) + assert.deepEqual(document.paths, getFallbackAgentApiPaths()) } finally { process.chdir(originalCwd) } diff --git a/apps/web/lib/api/openapi.ts b/apps/web/lib/api/openapi.ts index 403217f..c9fd27b 100644 --- a/apps/web/lib/api/openapi.ts +++ b/apps/web/lib/api/openapi.ts @@ -1,6 +1,91 @@ import path from "node:path" import swaggerJsdoc from "swagger-jsdoc" +const fallbackAgentApiPaths = { + "/api/agent": { + get: { tags: ["Profile"], summary: "Get current agent" }, + }, + "/api/agent/agents": { + get: { tags: ["Agents"], summary: "List company agents" }, + post: { tags: ["Agents"], summary: "Create company agent" }, + }, + "/api/agent/agents/{agentId}": { + get: { + tags: ["Agents"], + summary: "Get company agent", + parameters: [{ $ref: "#/components/parameters/PathAgentId" }], + }, + patch: { + tags: ["Agents"], + summary: "Update company agent", + parameters: [{ $ref: "#/components/parameters/PathAgentId" }], + }, + delete: { + tags: ["Agents"], + summary: "Delete company agent", + parameters: [{ $ref: "#/components/parameters/PathAgentId" }], + }, + }, + "/api/agent/projects": { + get: { tags: ["Projects"], summary: "List company projects" }, + post: { tags: ["Projects"], summary: "Create project" }, + }, + "/api/agent/projects/{projectId}": { + get: { + tags: ["Projects"], + summary: "Get project", + parameters: [{ $ref: "#/components/parameters/ProjectId" }], + }, + patch: { + tags: ["Projects"], + summary: "Update project", + parameters: [{ $ref: "#/components/parameters/ProjectId" }], + }, + delete: { + tags: ["Projects"], + summary: "Delete project", + parameters: [{ $ref: "#/components/parameters/ProjectId" }], + }, + }, + "/api/agent/tasks": { + get: { tags: ["Tasks"], summary: "List current agent tasks" }, + post: { tags: ["Tasks"], summary: "Create task" }, + }, + "/api/agent/tasks/{taskId}": { + get: { + tags: ["Tasks"], + summary: "Get task", + parameters: [{ $ref: "#/components/parameters/TaskId" }], + }, + patch: { + tags: ["Tasks"], + summary: "Update task", + parameters: [{ $ref: "#/components/parameters/TaskId" }], + }, + delete: { + tags: ["Tasks"], + summary: "Delete task", + parameters: [{ $ref: "#/components/parameters/TaskId" }], + }, + }, +} + +type OpenApiDocument = ReturnType & { + paths?: Record +} + +export function getFallbackAgentApiPaths() { + return fallbackAgentApiPaths +} + +function withFallbackAgentApiPaths(document: OpenApiDocument) { + if (Object.keys(document.paths ?? {}).length > 0) { + return document + } + + return { ...document, paths: fallbackAgentApiPaths } +} + export function getAgentApiGlobs() { return [ path.join(process.cwd(), "app/api/agent/**/*.ts"), @@ -9,9 +94,10 @@ export function getAgentApiGlobs() { } export function createOpenApiDocument() { - return swaggerJsdoc({ - definition: { - openapi: "3.1.0", + return withFallbackAgentApiPaths( + swaggerJsdoc({ + definition: { + openapi: "3.1.0", info: { title: "AgentBridge Agent API", description: @@ -383,5 +469,6 @@ export function createOpenApiDocument() { security: [{ bearerAuth: [] }], }, apis: getAgentApiGlobs(), - }) + }) as OpenApiDocument + ) }