Skip to the content.

Task 113 — Studio: openapi-typescript + openapi-fetch + Orval setup

Phase: 8 Status: skipped (deliberate — Orval not adopted) Owner: — Slug: studio-openapi-codegen-setup Studio Stage: 0

Status note (2026-10-07, 2.0.0 release prep). This task is not outstanding work and is deliberately not being done. openapi-typescript ships and the generated openapi.d.ts is committed, so the codegen half of this task is delivered; Orval was evaluated and skipped in favour of a hand-written typed client (renderer/src/sdk/client.ts) plus a contract test (renderer/src/sdk/client.contract.test.ts) that fails if the agent document drifts. There is no orval.config.* in the repo. Recorded here rather than deleted so the decision is not re-litigated from scratch later — the same reason scripts/count-stage-tasks.cjs keeps the stage files. See VERIFICATION.md (“What remains”, Stage 0 row) for the authoritative statement.

Goal

Настроить pipeline генерации TypeScript API клиента в Hercules Studio из OpenAPI документа агента. Использовать openapi-typescript (types only) + openapi-fetch (typed fetch wrapper) + Orval (Vue Query hooks + Zod schemas + MSW mocks). Generated files коммитятся в git. Regen — ручной (npm run gen:api), можно запустить в любой момент.

Acceptance criteria

orval.config.ts

import { defineConfig } from "orval";

export default defineConfig({
  hercules: {
    // Source: build-time generated openapi.json (committed to git by task_109)
    // Alternative for dev: http://localhost:8421/openapi/v1.json (running agent)
    input: { target: "../../agent/Hercules.WebApi/openapi.json" },
    output: {
      mode: "tags-split",
      target: "src/api/generated",
      schemas: "src/api/models",
      client: "vue-query",
      mock: true,
      zod: true,
    },
    hooks: {
      mutator: "src/api/mutator.ts",
    },
  },
});

mutator.ts

import { useConnectionsStore } from "../stores/connections";

export const customMutator = async (config) => {
  const store = useConnectionsStore();
  const conn = store.active;
  if (!conn) throw new Error("No active agent connection");
  const key = await window.studioAPI.keys.getContributeKey(conn.id);
  
  const res = await fetch(`${conn.baseUrl}${config.url}`, {
    method: config.method,
    headers: { "X-Api-Key": key ?? "", "Content-Type": "application/json", ...config.headers },
    body: config.body ? JSON.stringify(config.body) : undefined,
  });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return { data: await res.json(), status: res.status };
};

Структура generated files

renderer/src/api/
├── schema.ts              # openapi-typescript (types only, all paths + schemas)
├── mutator.ts             # custom fetch wrapper (hand-written, not generated)
├── generated/             # Orval: Vue Query hooks per domain
│   ├── skills/
│   │   ├── skills.ts      # useListSkillsQuery, useCreateSkillMutation...
│   │   └── skills.msw.ts  # MSW handlers for dev
│   ├── chat/
│   │   └── chat.ts
│   ├── mesh/
│   │   └── mesh.ts
│   ├── config/
│   │   └── config.ts
│   └── ... (30 domains)
├── models/                # Orval: Zod schemas + DTO interfaces
│   ├── skillDto.ts
│   ├── chatResponseDto.ts
│   └── ...
└── README.md

Dependencies (backend)

Scope / Likely files

Workflow

Option A: From build-time openapi.json (CI, no running agent needed)

  1. dotnet build src/agent/Hercules.WebApi → generates openapi.json in project root
  2. cd src/hercules-studio && npm run gen:api (reads ../../agent/Hercules.WebApi/openapi.json)
  3. Commit: git add renderer/src/api/ && git commit -m "regen API client"

Option B: From running agent (dev iteration)

  1. Запустить агент: dotnet run --project src/agent/Hercules.WebApi
  2. Temporarily change orval.config.ts target to http://localhost:8421/openapi/v1.json
  3. cd src/hercules-studio && npm run gen:api
  4. Restore orval.config.ts target to build-time path
  5. Commit

Using in code

import { useListSkillsQuery } from "api/generated/skills/skills";