From ef5ae72df301c786ecf599cd35deef87cab3bbd3 Mon Sep 17 00:00:00 2001 From: Anze Mur <17564531+anzemur@users.noreply.github.com> Date: Fri, 23 Oct 2026 19:38:20 +0000 Subject: [PATCH] Add the Vercel Observability events endpoint to the Vercel preset Vercel's published OpenAPI spec omits POST /v1/observability/events, the endpoint behind `vercel logs`, and its only runtime-log operation follows a live stream. The preset now adds it as a spec override, so past runtime logs and CDN request records are searchable. --- .changeset/vercel-observability-events.md | 5 + .../plugins/openapi/src/sdk/presets.test.ts | 51 ++++++- packages/plugins/openapi/src/sdk/presets.ts | 134 ++++++++++++++++++ 3 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 .changeset/vercel-observability-events.md diff --git a/.changeset/vercel-observability-events.md b/.changeset/vercel-observability-events.md new file mode 100644 index 0000000000..c6e1e21460 --- /dev/null +++ b/.changeset/vercel-observability-events.md @@ -0,0 +1,5 @@ +--- +"@executor-js/plugin-openapi": patch +--- + +The Vercel preset adds `logs.queryObservabilityEvents` (`POST /v1/observability/events`), the endpoint behind `vercel logs`. It searches runtime logs or CDN request records over a past time range, with filters and cursor paging. Vercel's published spec omits it, and its only runtime-log operation follows a live stream. diff --git a/packages/plugins/openapi/src/sdk/presets.test.ts b/packages/plugins/openapi/src/sdk/presets.test.ts index 2b063309dc..285258a8b1 100644 --- a/packages/plugins/openapi/src/sdk/presets.test.ts +++ b/packages/plugins/openapi/src/sdk/presets.test.ts @@ -1,7 +1,18 @@ import { describe, expect, it } from "@effect/vitest"; +import { Effect, Option, Schema } from "effect"; +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import { extract } from "./extract"; +import { parse } from "./parse"; import { openApiPlugin } from "./plugin"; -import { FIGMA_SPEC_OVERRIDES, FIGMA_SUPPORTED_OAUTH_SCOPES, openApiPresets } from "./presets"; +import { + FIGMA_SPEC_OVERRIDES, + FIGMA_SUPPORTED_OAUTH_SCOPES, + VERCEL_SPEC_OVERRIDES, + openApiPresets, +} from "./presets"; +import { applySpecOverrides } from "./spec-overrides"; const unsupportedFigmaOAuthScopes = [ "file_variables:read", @@ -10,6 +21,12 @@ const unsupportedFigmaOAuthScopes = [ "library_analytics:read", ] as const; +const decodeJsonText = Schema.decodeUnknownSync(Schema.UnknownFromJsonString); +const encodeJsonText = Schema.encodeUnknownSync(Schema.UnknownFromJsonString); +const vercelSpec = decodeJsonText( + readFileSync(resolve(__dirname, "../../fixtures/vercel.json"), "utf-8"), +); + describe("OpenAPI presets", () => { it("narrows Figma OAuth to the supported scope set", () => { expect(FIGMA_SUPPORTED_OAUTH_SCOPES).toHaveLength(15); @@ -34,4 +51,36 @@ describe("OpenAPI presets", () => { expect(sdkPreset?.specOverrides).toEqual(FIGMA_SPEC_OVERRIDES); expect(catalogPreset?.specOverrides).toEqual(FIGMA_SPEC_OVERRIDES); }); + + it("projects Vercel spec overrides through the plugin catalog", () => { + const catalogPreset = openApiPlugin().integrationPresets?.find( + (preset) => preset.id === "vercel", + ); + + expect(catalogPreset?.specOverrides).toEqual(VERCEL_SPEC_OVERRIDES); + }); + + it.effect("adds the Vercel Observability events operation to the published spec", () => + Effect.gen(function* () { + const patched = yield* applySpecOverrides(vercelSpec, VERCEL_SPEC_OVERRIDES); + const result = yield* extract(yield* parse(encodeJsonText(patched))); + const operation = result.operations.find( + (candidate) => candidate.operationId === "queryObservabilityEvents", + ); + + expect(operation?.method).toBe("post"); + expect(operation?.pathTemplate).toBe("/v1/observability/events"); + expect( + operation?.parameters.map((parameter) => [parameter.name, parameter.location]), + ).toEqual([ + ["teamId", "query"], + ["x-vercel-observability-query-reason", "header"], + ]); + expect(operation && Option.isSome(operation.requestBody)).toBe(true); + expect(operation && Option.isSome(operation.outputSchema)).toBe(true); + expect( + result.operations.some((candidate) => candidate.operationId === "getRuntimeLogs"), + ).toBe(true); + }), + ); }); diff --git a/packages/plugins/openapi/src/sdk/presets.ts b/packages/plugins/openapi/src/sdk/presets.ts index 206c28436d..daf694c134 100644 --- a/packages/plugins/openapi/src/sdk/presets.ts +++ b/packages/plugins/openapi/src/sdk/presets.ts @@ -57,6 +57,139 @@ export const FIGMA_SPEC_OVERRIDES: SpecOverrides = [ }, ]; +/** Vercel's published spec lacks the Observability events endpoint the Vercel + * CLI uses for `vercel logs` (runtime logs and CDN requests over a past time + * range). Its only runtime-log operation, `getRuntimeLogs`, follows a live + * stream, so past logs are otherwise unreachable. The request and response + * shapes mirror the CLI's own client. */ +export const VERCEL_SPEC_OVERRIDES: SpecOverrides = [ + { + op: "add", + path: "/paths/~1v1~1observability~1events", + value: { + post: { + operationId: "queryObservabilityEvents", + summary: "Query observability events", + description: + "Returns runtime log entries (event `vercel.log`) or CDN request records (event `vercel.proxy_response`) for a time range, newest first by default. Page with `pagination.nextCursor`. This is the endpoint behind `vercel logs`.", + tags: ["logs"], + security: [{ bearerToken: [] }], + parameters: [ + { + name: "teamId", + in: "query", + required: true, + description: "The Team identifier that owns the events. Must match `scope.ownerId`.", + schema: { type: "string", example: "team_1a2b3c4d5e6f7g8h9i0j1k2l" }, + }, + { + name: "x-vercel-observability-query-reason", + in: "header", + required: false, + description: "Free-form label for why the query runs, e.g. `agent/logs-runtime`.", + schema: { type: "string" }, + }, + ], + requestBody: { + required: true, + content: { + "application/json": { + schema: { + type: "object", + required: ["event", "scope", "timeRange", "select"], + properties: { + event: { + type: "string", + enum: ["vercel.log", "vercel.proxy_response"], + description: + "`vercel.log` for runtime log entries, `vercel.proxy_response` for CDN request records.", + }, + scope: { + type: "object", + required: ["ownerId"], + properties: { + ownerId: { type: "string", description: "The Team identifier." }, + projectIds: { type: "array", items: { type: "string" } }, + }, + }, + timeRange: { + type: "object", + required: ["start", "end"], + properties: { + start: { type: "string", format: "date-time" }, + end: { type: "string", format: "date-time" }, + }, + }, + select: { + type: "array", + items: { type: "string" }, + description: + "Fields to return, e.g. `level`, `message`, `source`, `environment`, `deploymentId`, `requestId`, `requestPath`.", + }, + filter: { + type: "string", + description: + 'KQL-style filter, e.g. `level:warning AND environment:production AND "some text"`.', + }, + order: { type: "string", enum: ["asc", "desc"], default: "desc" }, + limit: { type: "integer", minimum: 1, maximum: 1000 }, + cursor: { + type: "string", + description: "`pagination.nextCursor` from the previous page.", + }, + }, + }, + }, + }, + }, + responses: { + "200": { + description: "One page of events.", + content: { + "application/json": { + schema: { + type: "object", + required: ["events", "pagination"], + properties: { + events: { + type: "array", + items: { type: "object", additionalProperties: true }, + }, + pagination: { + type: "object", + required: ["hasMore", "nextCursor"], + properties: { + hasMore: { type: "boolean" }, + nextCursor: { type: "string", nullable: true }, + }, + }, + meta: { + type: "object", + additionalProperties: true, + properties: { + excludedIntervals: { + type: "array", + items: { + type: "object", + properties: { + start: { type: "string" }, + end: { type: "string" }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, +]; + const openApiOnlyPresets: readonly OpenApiPreset[] = [ { id: "figma", @@ -92,6 +225,7 @@ const openApiOnlyPresets: readonly OpenApiPreset[] = [ url: "https://openapi.vercel.sh", icon: "https://integrations.sh/logo/vercel.com", featured: true, + specOverrides: VERCEL_SPEC_OVERRIDES, }, { id: "neon",