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-typescriptships and the generatedopenapi.d.tsis 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 noorval.config.*in the repo. Recorded here rather than deleted so the decision is not re-litigated from scratch later — the same reasonscripts/count-stage-tasks.cjskeeps 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
openapi-typescriptустановлен как devDependencyopenapi-fetchустановлен как dependencyorvalустановлен как devDependency@tanstack/vue-queryустановлен как dependencyzodустановлен как dependency (runtime validation)mswустановлен как devDependency (mocks для dev без агента)scripts/gen-api.mjs— скрипт запуска gen:api:types + gen:api:orvalpackage.jsonscripts:gen:api,gen:api:types,gen:api:orvalorval.config.ts— config: tags-split mode, vue-query client, zod, mock, mutatorsrc/api/mutator.ts— кастомный fetch с auth per-connection (X-Api-Key из safeStorage/mock)src/api/schema.ts— generated types (openapi-typescript output)src/api/generated/— generated Vue Query hooks per domain (Orval output)src/api/models/— generated Zod schemas + DTO interfaces (Orval output)src/api/README.md— “Generated. Do not edit. Run: npm run gen:api”.gitignore— НЕ игнорироватьsrc/api/(generated files коммитятся)npm run gen:apiработает: fetches openapi.json → schema.ts + generated/ + models/npm run buildпроходит с generated filesnpm run typecheckпроходит с generated types
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)
- task_109 (OpenAPI producer + build-time generation — нужен
openapi.jsonв git) - task_110 (WithTags — нужен для tags-split)
- task_111 (Produces
+ DTOs — нужен для качественных типов) - task_112 (WithName — нужен для operationId)
Scope / Likely files
- New:
src/hercules-studio/scripts/gen-api.mjs - New:
src/hercules-studio/orval.config.ts - New:
src/hercules-studio/renderer/src/api/(generated + mutator) - Modified:
src/hercules-studio/package.json(scripts + deps) - Modified:
src/hercules-studio/.gitignore(ensure api/ not ignored) - Replace:
src/hercules-studio/renderer/src/sdk/types.ts→src/api/schema.ts(generated) - Keep:
src/hercules-studio/renderer/src/sdk/client.ts→ thin wrapper over openapi-fetch (or remove if Orval hooks replace it)
Workflow
Option A: From build-time openapi.json (CI, no running agent needed)
dotnet build src/agent/Hercules.WebApi→ generatesopenapi.jsonin project rootcd src/hercules-studio && npm run gen:api(reads../../agent/Hercules.WebApi/openapi.json)- Commit:
git add renderer/src/api/ && git commit -m "regen API client"
Option B: From running agent (dev iteration)
- Запустить агент:
dotnet run --project src/agent/Hercules.WebApi - Temporarily change orval.config.ts target to
http://localhost:8421/openapi/v1.json cd src/hercules-studio && npm run gen:api- Restore orval.config.ts target to build-time path
- Commit
Using in code
import { useListSkillsQuery } from "api/generated/skills/skills";
Links
- Backlog: ../backlog.md
- Epic Studio: ../EPIC_Hercules_Studio/README.md
- Studio Stage 0: ../EPIC_Hercules_Studio/tasks/stage_00_skeleton.md