import { createServer, type IncomingMessage, type ServerResponse } from "node:http"; import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, HttpApiSchema, HttpClient, HttpClientResponse, OpenApi,} from "@effect/platform";import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer";import { describe, expect, it } from "@effect/vitest";import { assertInclude, assertTrue } from "@effect/vitest/utils"; import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import { Schema } from "effect"; import type { ToolDefinition, ToolInput, ToolMap } from "@executor/codemode-core"; import { extractOpenApiManifest } from "./openapi-extraction";import { createOpenApiToolsFromManifest, createOpenApiToolsFromSpec } from "./openapi-tools"; type TestServer = { baseUrl: string; requests: Array<{ method: string; path: string; query: string; body: string; headers: Record<string, string>; }>; close: () => Promise<void>;}; type EffectServerHandler = { handler: (nodeRequest: IncomingMessage, nodeResponse: ServerResponse) => void; dispose: () => Promise<void>;}; const makeEffectServerHandler = ( requests: TestServer["requests"],): Effect.Effect<EffectServerHandler> => Effect.gen(function* () { const handlersLayer = HttpApiBuilder.group(GeneratedApi, "repos", (handlers) => handlers .handle("getRepo", ({ path, urlParams }) => Effect.sync(() => { const include = urlParams.include; requests.push({ method: "GET", path: `/repos/${path.owner}/${path.repo}`, query: include ? `?include=${encodeURIComponent(include)}` : "", body: "", headers: {}, }); return { full_name: `${path.owner}/${path.repo}`, include: include ?? null, }; }), ) .handle("createIssue", ({ path, payload }) => Effect.sync(() => { const bodyText = JSON.stringify(payload); requests.push({ method: "POST", path: `/repos/${path.owner}/${path.repo}/issues`, query: "", body: bodyText, headers: {}, }); return { created: true, owner: path.owner, repo: path.repo, body: payload, }; }), ), ); const apiLayer = HttpApiBuilder.api(GeneratedApi).pipe( Layer.provide(handlersLayer), ); const web = HttpApiBuilder.toWebHandler( Layer.mergeAll(apiLayer, NodeHttpServer.layerContext), ); const handler: EffectServerHandler["handler"] = (nodeRequest, nodeResponse) => { void (async () => { const host = nodeRequest.headers.host ?? "127.0.0.1"; const url = `http://${host}${nodeRequest.url ?? "/"}`; const headers = new Headers(); for (const [key, value] of Object.entries(nodeRequest.headers)) { if (value === undefined) { continue; } if (Array.isArray(value)) { for (const item of value) { headers.append(key, item); } } else { headers.set(key, value); } } const method = nodeRequest.method ?? "GET"; const hasBody = method !== "GET" && method !== "HEAD"; const requestInit: RequestInit & { duplex?: "half" } = { method, headers, }; if (hasBody) { (requestInit as { body?: unknown }).body = nodeRequest; requestInit.duplex = "half"; } const webRequest = new Request(url, requestInit); const webResponse = await web.handler(webRequest); nodeResponse.statusCode = webResponse.status; webResponse.headers.forEach((value, key) => { nodeResponse.setHeader(key, value); }); if (!webResponse.body) { nodeResponse.end(); return; } const reader = webResponse.body.getReader(); while (true) { const { done, value } = await reader.read(); if (done) { break; } nodeResponse.write(Buffer.from(value)); } nodeResponse.end(); })().catch((error: unknown) => { nodeResponse.statusCode = 500; nodeResponse.end( JSON.stringify({ error: error instanceof Error ? error.message : String(error), }), ); }); }; return { handler, dispose: web.dispose, }; }); const makeTestServer = Effect.acquireRelease( Effect.promise<TestServer>( () => new Promise<TestServer>((resolve, reject) => { const requests: TestServer["requests"] = []; void Effect.runPromise(makeEffectServerHandler(requests)) .then(({ handler, dispose }) => { const server = createServer(handler); server.once("error", reject); server.listen(0, "127.0.0.1", () => { const address = server.address(); if (!address || typeof address === "string") { reject(new Error("failed to resolve test server address")); return; } resolve({ baseUrl: `http://127.0.0.1:${address.port}`, requests, close: async () => { await new Promise<void>((closeResolve, closeReject) => { server.close((error: Error | undefined) => { if (error) { closeReject(error); return; } closeResolve(); }); }); await dispose(); }, }); }); }) .catch(reject); }), ), (server: TestServer) => Effect.tryPromise({ try: () => server.close(), catch: (error: unknown) => error instanceof Error ? error : new Error(String(error)), }).pipe(Effect.orDie), ); const ownerParam = HttpApiSchema.param("owner", Schema.String);const repoParam = HttpApiSchema.param("repo", Schema.String); const requestHeadersFromNode = ( request: IncomingMessage,): Record<string, string> => Object.fromEntries( Object.entries(request.headers).flatMap(([key, value]) => value === undefined ? [] : [[key, Array.isArray(value) ? value.join(",") : value]]), ); class GeneratedReposApi extends HttpApiGroup.make("repos") .add( HttpApiEndpoint.get("getRepo")`/repos/${ownerParam}/${repoParam}` .setUrlParams( Schema.Struct({ include: Schema.optional(Schema.String), }), ) .addSuccess(Schema.Unknown), ) .add( HttpApiEndpoint.post("createIssue")`/repos/${ownerParam}/${repoParam}/issues` .setPayload( Schema.Struct({ title: Schema.String, }), ) .addSuccess(Schema.Unknown, { status: 201 }), ) {} class GeneratedApi extends HttpApi.make("generated").add(GeneratedReposApi) {} const generatedOpenApiSpec = OpenApi.fromApi(GeneratedApi); const resolveToolDefinition = (value: ToolInput): ToolDefinition => typeof value === "object" && value !== null && "tool" in value ? value : { tool: value }; const resolveToolExecutor = ( tools: ToolMap, path: string,): ((args: unknown) => Promise<unknown>) => { const candidate = tools[path]; if (!candidate) { throw new Error(`Missing tool: ${path}`); } const resolved = resolveToolDefinition(candidate); if (!resolved.tool.execute) { throw new Error(`Tool has no execute function: ${path}`); } return (args: unknown) => Promise.resolve(resolved.tool.execute?.(args));}; describe("openapi-tools", () => { it.effect("extracts manifest and derives codemode tool paths", () => Effect.gen(function* () { const manifest = yield* extractOpenApiManifest("demo", generatedOpenApiSpec); const tools = createOpenApiToolsFromManifest({ manifest, baseUrl: "https://example.com", namespace: "source.demo", sourceKey: "api.demo", }); expect(Object.keys(tools).sort()).toEqual([ "source.demo.repos.createIssue", "source.demo.repos.getRepo", ]); const resolvedGet = resolveToolDefinition(tools["source.demo.repos.getRepo"]!); expect(resolvedGet.metadata?.sourceKey).toBe("api.demo"); expect(resolvedGet.metadata?.providerKind).toBe("openapi"); expect(resolvedGet.metadata?.providerData).toMatchObject({ group: "repos" }); expect(resolvedGet.metadata?.inputSchema).toBeDefined(); }), ); it.effect("enriches extracted manifests with OpenAPI auth and response variants", () => Effect.gen(function* () { const manifest = yield* extractOpenApiManifest("secure-demo", { openapi: "3.1.0", info: { title: "Secure Demo", version: "1.0.0", }, security: [{ bearerToken: [], }], paths: { "/projects/{id}": { get: { operationId: "projects.getProject", security: [], parameters: [{ name: "id", in: "path", required: true, schema: { type: "string", }, }], responses: { "200": { description: "Project", content: { "application/json": { schema: { $ref: "#/$defs/Project", }, }, }, }, "404": { description: "Missing", content: { "application/json": { schema: { $ref: "#/$defs/Missing", }, }, }, }, default: { description: "Unexpected", content: { "application/json": { schema: { type: "object", properties: { error: { type: "string", }, }, }, }, }, }, }, }, }, "/teams": { get: { operationId: "teams.listTeams", security: [ { bearerToken: [] }, { apiKeyHeader: [] }, ], responses: { "200": { description: "Teams", content: { "application/json": { schema: { type: "object", properties: { teams: { type: "array", items: { type: "string", }, }, }, }, }, }, }, }, }, }, }, components: { securitySchemes: { bearerToken: { type: "http", scheme: "bearer", bearerFormat: "JWT", description: "Bearer access token.", }, apiKeyHeader: { type: "apiKey", in: "header", name: "x-api-key", }, }, }, $defs: { Project: { type: "object", properties: { id: { type: "string", }, }, required: ["id"], }, Missing: { type: "object", properties: { code: { type: "string", }, }, required: ["code"], }, }, }); const projectTool = manifest.tools.find((tool) => tool.toolId === "projects.getProject"); const teamsTool = manifest.tools.find((tool) => tool.toolId === "teams.listTeams"); expect(projectTool?.authRequirement).toEqual({ kind: "none", }); expect(projectTool?.responses?.map((response) => response.statusCode)).toEqual([ "200", "default", "404", ]); expect(projectTool?.responses?.find((response) => response.statusCode === "404")?.schema).toMatchObject({ $ref: "#/$defs/Missing", }); expect(teamsTool?.authRequirement).toMatchObject({ kind: "anyOf", }); expect(teamsTool?.securitySchemes).toMatchObject([ { schemeName: "apiKeyHeader", schemeType: "apiKey", placementIn: "header", placementName: "x-api-key", }, { schemeName: "bearerToken", schemeType: "http", scheme: "bearer", bearerFormat: "JWT", }, ]); expect(Object.keys(manifest.refHintTable ?? {})).toEqual( expect.arrayContaining([ "#/$defs/Project", "#/$defs/Missing", ]), ); }), ); it.effect("preserves OpenAPI servers, parameter serialization, and response headers", () => Effect.gen(function* () { const manifest = yield* extractOpenApiManifest("serialized-demo", { openapi: "3.1.0", info: { title: "Serialized Demo", version: "1.0.0", }, servers: [{ url: "https://api.example.test/{version}", variables: { version: { default: "v1", }, }, }], paths: { "/items/{itemId}": { servers: [{ url: "https://regional.example.test/base", }], get: { operationId: "items.getItem", parameters: [ { name: "itemId", in: "path", required: true, style: "label", explode: true, schema: { type: "array",Lines 1–500 of 1230. Load more
import { createServer, type IncomingMessage, type ServerResponse } from "node:http"; import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, HttpApiSchema, HttpClient, HttpClientResponse, OpenApi, } from "@effect/platform"; import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer"; import { describe, expect, it } from "@effect/vitest"; import { assertInclude, assertTrue } from "@effect/vitest/utils"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import { Schema } from "effect"; import type { ToolDefinition, ToolInput, ToolMap } from "@executor/codemode-core"; import { extractOpenApiManifest } from "./openapi-extraction"; import { createOpenApiToolsFromManifest, createOpenApiToolsFromSpec } from "./openapi-tools"; type TestServer = { baseUrl: string; requests: Array<{ method: string; path: string; query: string; body: string; headers: Record<string, string>; }>; close: () => Promise<void>; }; type EffectServerHandler = { handler: (nodeRequest: IncomingMessage, nodeResponse: ServerResponse) => void; dispose: () => Promise<void>; }; const makeEffectServerHandler = ( requests: TestServer["requests"], ): Effect.Effect<EffectServerHandler> => Effect.gen(function* () { const handlersLayer = HttpApiBuilder.group(GeneratedApi, "repos", (handlers) => handlers .handle("getRepo", ({ path, urlParams }) => Effect.sync(() => { const include = urlParams.include; requests.push({ method: "GET", path: `/repos/${path.owner}/${path.repo}`, query: include ? `?include=${encodeURIComponent(include)}` : "", body: "", headers: {}, }); return { full_name: `${path.owner}/${path.repo}`, include: include ?? null, }; }), ) .handle("createIssue", ({ path, payload }) => Effect.sync(() => { const bodyText = JSON.stringify(payload); requests.push({ method: "POST", path: `/repos/${path.owner}/${path.repo}/issues`, query: "", body: bodyText, headers: {}, }); return { created: true, owner: path.owner, repo: path.repo, body: payload, }; }), ), ); const apiLayer = HttpApiBuilder.api(GeneratedApi).pipe( Layer.provide(handlersLayer), ); const web = HttpApiBuilder.toWebHandler( Layer.mergeAll(apiLayer, NodeHttpServer.layerContext), ); const handler: EffectServerHandler["handler"] = (nodeRequest, nodeResponse) => { void (async () => { const host = nodeRequest.headers.host ?? "127.0.0.1"; const url = `http://${host}${nodeRequest.url ?? "/"}`; const headers = new Headers(); for (const [key, value] of Object.entries(nodeRequest.headers)) { if (value === undefined) { continue; } if (Array.isArray(value)) { for (const item of value) { headers.append(key, item); } } else { headers.set(key, value); } } const method = nodeRequest.method ?? "GET"; const hasBody = method !== "GET" && method !== "HEAD"; const requestInit: RequestInit & { duplex?: "half" } = { method, headers, }; if (hasBody) { (requestInit as { body?: unknown }).body = nodeRequest; requestInit.duplex = "half"; } const webRequest = new Request(url, requestInit); const webResponse = await web.handler(webRequest); nodeResponse.statusCode = webResponse.status; webResponse.headers.forEach((value, key) => { nodeResponse.setHeader(key, value); }); if (!webResponse.body) { nodeResponse.end(); return; } const reader = webResponse.body.getReader(); while (true) { const { done, value } = await reader.read(); if (done) { break; } nodeResponse.write(Buffer.from(value)); } nodeResponse.end(); })().catch((error: unknown) => { nodeResponse.statusCode = 500; nodeResponse.end( JSON.stringify({ error: error instanceof Error ? error.message : String(error), }), ); }); }; return { handler, dispose: web.dispose, }; }); const makeTestServer = Effect.acquireRelease( Effect.promise<TestServer>( () => new Promise<TestServer>((resolve, reject) => { const requests: TestServer["requests"] = []; void Effect.runPromise(makeEffectServerHandler(requests)) .then(({ handler, dispose }) => { const server = createServer(handler); server.once("error", reject); server.listen(0, "127.0.0.1", () => { const address = server.address(); if (!address || typeof address === "string") { reject(new Error("failed to resolve test server address")); return; } resolve({ baseUrl: `http://127.0.0.1:${address.port}`, requests, close: async () => { await new Promise<void>((closeResolve, closeReject) => { server.close((error: Error | undefined) => { if (error) { closeReject(error); return; } closeResolve(); }); }); await dispose(); }, }); }); }) .catch(reject); }), ), (server: TestServer) => Effect.tryPromise({ try: () => server.close(), catch: (error: unknown) => error instanceof Error ? error : new Error(String(error)), }).pipe(Effect.orDie), ); const ownerParam = HttpApiSchema.param("owner", Schema.String); const repoParam = HttpApiSchema.param("repo", Schema.String); const requestHeadersFromNode = ( request: IncomingMessage, ): Record<string, string> => Object.fromEntries( Object.entries(request.headers).flatMap(([key, value]) => value === undefined ? [] : [[key, Array.isArray(value) ? value.join(",") : value]]), ); class GeneratedReposApi extends HttpApiGroup.make("repos") .add( HttpApiEndpoint.get("getRepo")`/repos/${ownerParam}/${repoParam}` .setUrlParams( Schema.Struct({ include: Schema.optional(Schema.String), }), ) .addSuccess(Schema.Unknown), ) .add( HttpApiEndpoint.post("createIssue")`/repos/${ownerParam}/${repoParam}/issues` .setPayload( Schema.Struct({ title: Schema.String, }), ) .addSuccess(Schema.Unknown, { status: 201 }), ) {} class GeneratedApi extends HttpApi.make("generated").add(GeneratedReposApi) {} const generatedOpenApiSpec = OpenApi.fromApi(GeneratedApi); const resolveToolDefinition = (value: ToolInput): ToolDefinition => typeof value === "object" && value !== null && "tool" in value ? value : { tool: value }; const resolveToolExecutor = ( tools: ToolMap, path: string, ): ((args: unknown) => Promise<unknown>) => { const candidate = tools[path]; if (!candidate) { throw new Error(`Missing tool: ${path}`); } const resolved = resolveToolDefinition(candidate); if (!resolved.tool.execute) { throw new Error(`Tool has no execute function: ${path}`); } return (args: unknown) => Promise.resolve(resolved.tool.execute?.(args)); }; describe("openapi-tools", () => { it.effect("extracts manifest and derives codemode tool paths", () => Effect.gen(function* () { const manifest = yield* extractOpenApiManifest("demo", generatedOpenApiSpec); const tools = createOpenApiToolsFromManifest({ manifest, baseUrl: "https://example.com", namespace: "source.demo", sourceKey: "api.demo", }); expect(Object.keys(tools).sort()).toEqual([ "source.demo.repos.createIssue", "source.demo.repos.getRepo", ]); const resolvedGet = resolveToolDefinition(tools["source.demo.repos.getRepo"]!); expect(resolvedGet.metadata?.sourceKey).toBe("api.demo"); expect(resolvedGet.metadata?.providerKind).toBe("openapi"); expect(resolvedGet.metadata?.providerData).toMatchObject({ group: "repos" }); expect(resolvedGet.metadata?.inputSchema).toBeDefined(); }), ); it.effect("enriches extracted manifests with OpenAPI auth and response variants", () => Effect.gen(function* () { const manifest = yield* extractOpenApiManifest("secure-demo", { openapi: "3.1.0", info: { title: "Secure Demo", version: "1.0.0", }, security: [{ bearerToken: [], }], paths: { "/projects/{id}": { get: { operationId: "projects.getProject", security: [], parameters: [{ name: "id", in: "path", required: true, schema: { type: "string", }, }], responses: { "200": { description: "Project", content: { "application/json": { schema: { $ref: "#/$defs/Project", }, }, }, }, "404": { description: "Missing", content: { "application/json": { schema: { $ref: "#/$defs/Missing", }, }, }, }, default: { description: "Unexpected", content: { "application/json": { schema: { type: "object", properties: { error: { type: "string", }, }, }, }, }, }, }, }, }, "/teams": { get: { operationId: "teams.listTeams", security: [ { bearerToken: [] }, { apiKeyHeader: [] }, ], responses: { "200": { description: "Teams", content: { "application/json": { schema: { type: "object", properties: { teams: { type: "array", items: { type: "string", }, }, }, }, }, }, }, }, }, }, }, components: { securitySchemes: { bearerToken: { type: "http", scheme: "bearer", bearerFormat: "JWT", description: "Bearer access token.", }, apiKeyHeader: { type: "apiKey", in: "header", name: "x-api-key", }, }, }, $defs: { Project: { type: "object", properties: { id: { type: "string", }, }, required: ["id"], }, Missing: { type: "object", properties: { code: { type: "string", }, }, required: ["code"], }, }, }); const projectTool = manifest.tools.find((tool) => tool.toolId === "projects.getProject"); const teamsTool = manifest.tools.find((tool) => tool.toolId === "teams.listTeams"); expect(projectTool?.authRequirement).toEqual({ kind: "none", }); expect(projectTool?.responses?.map((response) => response.statusCode)).toEqual([ "200", "default", "404", ]); expect(projectTool?.responses?.find((response) => response.statusCode === "404")?.schema).toMatchObject({ $ref: "#/$defs/Missing", }); expect(teamsTool?.authRequirement).toMatchObject({ kind: "anyOf", }); expect(teamsTool?.securitySchemes).toMatchObject([ { schemeName: "apiKeyHeader", schemeType: "apiKey", placementIn: "header", placementName: "x-api-key", }, { schemeName: "bearerToken", schemeType: "http", scheme: "bearer", bearerFormat: "JWT", }, ]); expect(Object.keys(manifest.refHintTable ?? {})).toEqual( expect.arrayContaining([ "#/$defs/Project", "#/$defs/Missing", ]), ); }), ); it.effect("preserves OpenAPI servers, parameter serialization, and response headers", () => Effect.gen(function* () { const manifest = yield* extractOpenApiManifest("serialized-demo", { openapi: "3.1.0", info: { title: "Serialized Demo", version: "1.0.0", }, servers: [{ url: "https://api.example.test/{version}", variables: { version: { default: "v1", }, }, }], paths: { "/items/{itemId}": { servers: [{ url: "https://regional.example.test/base", }], get: { operationId: "items.getItem", parameters: [ { name: "itemId", in: "path", required: true, style: "label", explode: true, schema: { type: "array",