Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 32 additions & 10 deletions apps/web/lib/api/openapi.test.ts
Original file line number Diff line number Diff line change
@@ -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<string, unknown>
Expand All @@ -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)))
Expand All @@ -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)
}
Expand Down
95 changes: 91 additions & 4 deletions apps/web/lib/api/openapi.ts
Original file line number Diff line number Diff line change
@@ -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<typeof swaggerJsdoc> & {
paths?: Record<string, unknown>
}

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"),
Expand All @@ -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:
Expand Down Expand Up @@ -383,5 +469,6 @@ export function createOpenApiDocument() {
security: [{ bearerAuth: [] }],
},
apis: getAgentApiGlobs(),
})
}) as OpenApiDocument
)
}
Loading