diff --git a/.changeset/early-knives-kneel.md b/.changeset/early-knives-kneel.md new file mode 100644 index 0000000000..2d1f4a57ea --- /dev/null +++ b/.changeset/early-knives-kneel.md @@ -0,0 +1,5 @@ +--- +"@hey-api/openapi-ts": patch +--- + +**client**: use `getBaseUrl()` function to determine default value diff --git a/.changeset/many-memes-wish.md b/.changeset/many-memes-wish.md new file mode 100644 index 0000000000..6e07b639e1 --- /dev/null +++ b/.changeset/many-memes-wish.md @@ -0,0 +1,5 @@ +--- +"@hey-api/openapi-ts": patch +--- + +**plugin(msw)**: initial release diff --git a/.changeset/nasty-planes-cry.md b/.changeset/nasty-planes-cry.md new file mode 100644 index 0000000000..f31edf98c7 --- /dev/null +++ b/.changeset/nasty-planes-cry.md @@ -0,0 +1,5 @@ +--- +"@hey-api/shared": patch +--- + +**utils**: export `getBaseUrl()` function diff --git a/dev/opencode.json b/dev/opencode.json new file mode 100644 index 0000000000..a36f14d816 --- /dev/null +++ b/dev/opencode.json @@ -0,0 +1,9806 @@ +{ + "openapi": "3.1.1", + "info": { + "title": "opencode", + "description": "opencode api", + "version": "1.0.0" + }, + "paths": { + "/global/health": { + "get": { + "operationId": "global.health", + "summary": "Get health", + "description": "Get health information about the OpenCode server.", + "responses": { + "200": { + "description": "Health information", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "healthy": { + "type": "boolean", + "const": true + }, + "version": { + "type": "string" + } + }, + "required": ["healthy", "version"] + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.global.health({\n ...\n})" + } + ] + } + }, + "/global/event": { + "get": { + "operationId": "global.event", + "summary": "Get global events", + "description": "Subscribe to global events from the OpenCode system using server-sent events.", + "responses": { + "200": { + "description": "Event stream", + "content": { + "text/event-stream": { + "schema": { + "$ref": "#/components/schemas/GlobalEvent" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.global.event({\n ...\n})" + } + ] + } + }, + "/global/dispose": { + "post": { + "operationId": "global.dispose", + "summary": "Dispose instance", + "description": "Clean up and dispose all OpenCode instances, releasing all resources.", + "responses": { + "200": { + "description": "Global disposed", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.global.dispose({\n ...\n})" + } + ] + } + }, + "/project": { + "get": { + "operationId": "project.list", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "List all projects", + "description": "Get a list of projects that have been opened with OpenCode.", + "responses": { + "200": { + "description": "List of projects", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Project" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.project.list({\n ...\n})" + } + ] + } + }, + "/project/current": { + "get": { + "operationId": "project.current", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Get current project", + "description": "Retrieve the currently active project that OpenCode is working with.", + "responses": { + "200": { + "description": "Current project information", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Project" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.project.current({\n ...\n})" + } + ] + } + }, + "/project/{projectID}": { + "patch": { + "operationId": "project.update", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "projectID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Update project", + "description": "Update project properties such as name, icon and color.", + "responses": { + "200": { + "description": "Updated project information", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Project" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "icon": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "color": { + "type": "string" + } + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.project.update({\n ...\n})" + } + ] + } + }, + "/pty": { + "get": { + "operationId": "pty.list", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "List PTY sessions", + "description": "Get a list of all active pseudo-terminal (PTY) sessions managed by OpenCode.", + "responses": { + "200": { + "description": "List of sessions", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pty" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.pty.list({\n ...\n})" + } + ] + }, + "post": { + "operationId": "pty.create", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Create PTY session", + "description": "Create a new pseudo-terminal (PTY) session for running shell commands and processes.", + "responses": { + "200": { + "description": "Created session", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pty" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "command": { + "type": "string" + }, + "args": { + "type": "array", + "items": { + "type": "string" + } + }, + "cwd": { + "type": "string" + }, + "title": { + "type": "string" + }, + "env": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.pty.create({\n ...\n})" + } + ] + } + }, + "/pty/{ptyID}": { + "get": { + "operationId": "pty.get", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "ptyID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Get PTY session", + "description": "Retrieve detailed information about a specific pseudo-terminal (PTY) session.", + "responses": { + "200": { + "description": "Session info", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pty" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.pty.get({\n ...\n})" + } + ] + }, + "put": { + "operationId": "pty.update", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "ptyID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Update PTY session", + "description": "Update properties of an existing pseudo-terminal (PTY) session.", + "responses": { + "200": { + "description": "Updated session", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pty" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "size": { + "type": "object", + "properties": { + "rows": { + "type": "number" + }, + "cols": { + "type": "number" + } + }, + "required": ["rows", "cols"] + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.pty.update({\n ...\n})" + } + ] + }, + "delete": { + "operationId": "pty.remove", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "ptyID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Remove PTY session", + "description": "Remove and terminate a specific pseudo-terminal (PTY) session.", + "responses": { + "200": { + "description": "Session removed", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.pty.remove({\n ...\n})" + } + ] + } + }, + "/pty/{ptyID}/connect": { + "get": { + "operationId": "pty.connect", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "ptyID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Connect to PTY session", + "description": "Establish a WebSocket connection to interact with a pseudo-terminal (PTY) session in real-time.", + "responses": { + "200": { + "description": "Connected session", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.pty.connect({\n ...\n})" + } + ] + } + }, + "/config": { + "get": { + "operationId": "config.get", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Get configuration", + "description": "Retrieve the current OpenCode configuration settings and preferences.", + "responses": { + "200": { + "description": "Get config info", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Config" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.config.get({\n ...\n})" + } + ] + }, + "patch": { + "operationId": "config.update", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Update configuration", + "description": "Update OpenCode configuration settings and preferences.", + "responses": { + "200": { + "description": "Successfully updated config", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Config" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Config" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.config.update({\n ...\n})" + } + ] + } + }, + "/experimental/tool/ids": { + "get": { + "operationId": "tool.ids", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "List tool IDs", + "description": "Get a list of all available tool IDs, including both built-in tools and dynamically registered tools.", + "responses": { + "200": { + "description": "Tool IDs", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ToolIDs" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.tool.ids({\n ...\n})" + } + ] + } + }, + "/experimental/tool": { + "get": { + "operationId": "tool.list", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "provider", + "schema": { + "type": "string" + }, + "required": true + }, + { + "in": "query", + "name": "model", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "List tools", + "description": "Get a list of available tools with their JSON schema parameters for a specific provider and model combination.", + "responses": { + "200": { + "description": "Tools", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ToolList" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.tool.list({\n ...\n})" + } + ] + } + }, + "/instance/dispose": { + "post": { + "operationId": "instance.dispose", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Dispose instance", + "description": "Clean up and dispose the current OpenCode instance, releasing all resources.", + "responses": { + "200": { + "description": "Instance disposed", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.instance.dispose({\n ...\n})" + } + ] + } + }, + "/path": { + "get": { + "operationId": "path.get", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Get paths", + "description": "Retrieve the current working directory and related path information for the OpenCode instance.", + "responses": { + "200": { + "description": "Path", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Path" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.path.get({\n ...\n})" + } + ] + } + }, + "/vcs": { + "get": { + "operationId": "vcs.get", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Get VCS info", + "description": "Retrieve version control system (VCS) information for the current project, such as git branch.", + "responses": { + "200": { + "description": "VCS info", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VcsInfo" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.vcs.get({\n ...\n})" + } + ] + } + }, + "/session": { + "get": { + "operationId": "session.list", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "List sessions", + "description": "Get a list of all OpenCode sessions, sorted by most recently updated.", + "responses": { + "200": { + "description": "List of sessions", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Session" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.list({\n ...\n})" + } + ] + }, + "post": { + "operationId": "session.create", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Create session", + "description": "Create a new OpenCode session for interacting with AI assistants and managing conversations.", + "responses": { + "200": { + "description": "Successfully created session", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Session" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "parentID": { + "type": "string", + "pattern": "^ses.*" + }, + "title": { + "type": "string" + }, + "permission": { + "$ref": "#/components/schemas/PermissionRuleset" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.create({\n ...\n})" + } + ] + } + }, + "/session/status": { + "get": { + "operationId": "session.status", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Get session status", + "description": "Retrieve the current status of all sessions, including active, idle, and completed states.", + "responses": { + "200": { + "description": "Get session status", + "content": { + "application/json": { + "schema": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "$ref": "#/components/schemas/SessionStatus" + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.status({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}": { + "get": { + "operationId": "session.get", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string", + "pattern": "^ses.*" + }, + "required": true + } + ], + "summary": "Get session", + "description": "Retrieve detailed information about a specific OpenCode session.", + "tags": ["Session"], + "responses": { + "200": { + "description": "Get session", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Session" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.get({\n ...\n})" + } + ] + }, + "delete": { + "operationId": "session.delete", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string", + "pattern": "^ses.*" + }, + "required": true + } + ], + "summary": "Delete session", + "description": "Delete a session and permanently remove all associated data, including messages and history.", + "responses": { + "200": { + "description": "Successfully deleted session", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.delete({\n ...\n})" + } + ] + }, + "patch": { + "operationId": "session.update", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Update session", + "description": "Update properties of an existing session, such as title or other metadata.", + "responses": { + "200": { + "description": "Successfully updated session", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Session" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "time": { + "type": "object", + "properties": { + "archived": { + "type": "number" + } + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.update({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}/children": { + "get": { + "operationId": "session.children", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string", + "pattern": "^ses.*" + }, + "required": true + } + ], + "summary": "Get session children", + "tags": ["Session"], + "description": "Retrieve all child sessions that were forked from the specified parent session.", + "responses": { + "200": { + "description": "List of children", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Session" + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.children({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}/todo": { + "get": { + "operationId": "session.todo", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Session ID" + } + ], + "summary": "Get session todos", + "description": "Retrieve the todo list associated with a specific session, showing tasks and action items.", + "responses": { + "200": { + "description": "Todo list", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Todo" + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.todo({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}/init": { + "post": { + "operationId": "session.init", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Session ID" + } + ], + "summary": "Initialize session", + "description": "Analyze the current application and create an AGENTS.md file with project-specific agent configurations.", + "responses": { + "200": { + "description": "200", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "modelID": { + "type": "string" + }, + "providerID": { + "type": "string" + }, + "messageID": { + "type": "string", + "pattern": "^msg.*" + } + }, + "required": ["modelID", "providerID", "messageID"] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.init({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}/fork": { + "post": { + "operationId": "session.fork", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string", + "pattern": "^ses.*" + }, + "required": true + } + ], + "summary": "Fork session", + "description": "Create a new session by forking an existing session at a specific message point.", + "responses": { + "200": { + "description": "200", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Session" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "messageID": { + "type": "string", + "pattern": "^msg.*" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.fork({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}/abort": { + "post": { + "operationId": "session.abort", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Abort session", + "description": "Abort an active session and stop any ongoing AI processing or command execution.", + "responses": { + "200": { + "description": "Aborted session", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.abort({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}/share": { + "post": { + "operationId": "session.share", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Share session", + "description": "Create a shareable link for a session, allowing others to view the conversation.", + "responses": { + "200": { + "description": "Successfully shared session", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Session" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.share({\n ...\n})" + } + ] + }, + "delete": { + "operationId": "session.unshare", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string", + "pattern": "^ses.*" + }, + "required": true + } + ], + "summary": "Unshare session", + "description": "Remove the shareable link for a session, making it private again.", + "responses": { + "200": { + "description": "Successfully unshared session", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Session" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.unshare({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}/diff": { + "get": { + "operationId": "session.diff", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Session ID" + }, + { + "in": "query", + "name": "messageID", + "schema": { + "type": "string", + "pattern": "^msg.*" + } + } + ], + "summary": "Get session diff", + "description": "Get all file changes (diffs) made during this session.", + "responses": { + "200": { + "description": "List of diffs", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FileDiff" + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.diff({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}/summarize": { + "post": { + "operationId": "session.summarize", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Session ID" + } + ], + "summary": "Summarize session", + "description": "Generate a concise summary of the session using AI compaction to preserve key information.", + "responses": { + "200": { + "description": "Summarized session", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "providerID": { + "type": "string" + }, + "modelID": { + "type": "string" + }, + "auto": { + "default": false, + "type": "boolean" + } + }, + "required": ["providerID", "modelID"] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.summarize({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}/message": { + "get": { + "operationId": "session.messages", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Session ID" + }, + { + "in": "query", + "name": "limit", + "schema": { + "type": "number" + } + } + ], + "summary": "Get session messages", + "description": "Retrieve all messages in a session, including user prompts and AI responses.", + "responses": { + "200": { + "description": "List of messages", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/Message" + }, + "parts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Part" + } + } + }, + "required": ["info", "parts"] + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.messages({\n ...\n})" + } + ] + }, + "post": { + "operationId": "session.prompt", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Session ID" + } + ], + "summary": "Send message", + "description": "Create and send a new message to a session, streaming the AI response.", + "responses": { + "200": { + "description": "Created message", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/AssistantMessage" + }, + "parts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Part" + } + } + }, + "required": ["info", "parts"] + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "messageID": { + "type": "string", + "pattern": "^msg.*" + }, + "model": { + "type": "object", + "properties": { + "providerID": { + "type": "string" + }, + "modelID": { + "type": "string" + } + }, + "required": ["providerID", "modelID"] + }, + "agent": { + "type": "string" + }, + "noReply": { + "type": "boolean" + }, + "tools": { + "description": "@deprecated tools and permissions have been merged, you can set permissions on the session itself now", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "boolean" + } + }, + "system": { + "type": "string" + }, + "variant": { + "type": "string" + }, + "parts": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/TextPartInput" + }, + { + "$ref": "#/components/schemas/FilePartInput" + }, + { + "$ref": "#/components/schemas/AgentPartInput" + }, + { + "$ref": "#/components/schemas/SubtaskPartInput" + } + ] + } + } + }, + "required": ["parts"] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.prompt({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}/message/{messageID}": { + "get": { + "operationId": "session.message", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Session ID" + }, + { + "in": "path", + "name": "messageID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Message ID" + } + ], + "summary": "Get message", + "description": "Retrieve a specific message from a session by its message ID.", + "responses": { + "200": { + "description": "Message", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/Message" + }, + "parts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Part" + } + } + }, + "required": ["info", "parts"] + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.message({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}/message/{messageID}/part/{partID}": { + "delete": { + "operationId": "part.delete", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Session ID" + }, + { + "in": "path", + "name": "messageID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Message ID" + }, + { + "in": "path", + "name": "partID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Part ID" + } + ], + "description": "Delete a part from a message", + "responses": { + "200": { + "description": "Successfully deleted part", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.part.delete({\n ...\n})" + } + ] + }, + "patch": { + "operationId": "part.update", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Session ID" + }, + { + "in": "path", + "name": "messageID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Message ID" + }, + { + "in": "path", + "name": "partID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Part ID" + } + ], + "description": "Update a part in a message", + "responses": { + "200": { + "description": "Successfully updated part", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Part" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Part" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.part.update({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}/prompt_async": { + "post": { + "operationId": "session.prompt_async", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Session ID" + } + ], + "summary": "Send async message", + "description": "Create and send a new message to a session asynchronously, starting the session if needed and returning immediately.", + "responses": { + "204": { + "description": "Prompt accepted" + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "messageID": { + "type": "string", + "pattern": "^msg.*" + }, + "model": { + "type": "object", + "properties": { + "providerID": { + "type": "string" + }, + "modelID": { + "type": "string" + } + }, + "required": ["providerID", "modelID"] + }, + "agent": { + "type": "string" + }, + "noReply": { + "type": "boolean" + }, + "tools": { + "description": "@deprecated tools and permissions have been merged, you can set permissions on the session itself now", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "boolean" + } + }, + "system": { + "type": "string" + }, + "variant": { + "type": "string" + }, + "parts": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/TextPartInput" + }, + { + "$ref": "#/components/schemas/FilePartInput" + }, + { + "$ref": "#/components/schemas/AgentPartInput" + }, + { + "$ref": "#/components/schemas/SubtaskPartInput" + } + ] + } + } + }, + "required": ["parts"] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.prompt_async({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}/command": { + "post": { + "operationId": "session.command", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Session ID" + } + ], + "summary": "Send command", + "description": "Send a new command to a session for execution by the AI assistant.", + "responses": { + "200": { + "description": "Created message", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/AssistantMessage" + }, + "parts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Part" + } + } + }, + "required": ["info", "parts"] + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "messageID": { + "type": "string", + "pattern": "^msg.*" + }, + "agent": { + "type": "string" + }, + "model": { + "type": "string" + }, + "arguments": { + "type": "string" + }, + "command": { + "type": "string" + }, + "variant": { + "type": "string" + } + }, + "required": ["arguments", "command"] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.command({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}/shell": { + "post": { + "operationId": "session.shell", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Session ID" + } + ], + "summary": "Run shell command", + "description": "Execute a shell command within the session context and return the AI's response.", + "responses": { + "200": { + "description": "Created message", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssistantMessage" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "agent": { + "type": "string" + }, + "model": { + "type": "object", + "properties": { + "providerID": { + "type": "string" + }, + "modelID": { + "type": "string" + } + }, + "required": ["providerID", "modelID"] + }, + "command": { + "type": "string" + } + }, + "required": ["agent", "command"] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.shell({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}/revert": { + "post": { + "operationId": "session.revert", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Revert message", + "description": "Revert a specific message in a session, undoing its effects and restoring the previous state.", + "responses": { + "200": { + "description": "Updated session", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Session" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "messageID": { + "type": "string", + "pattern": "^msg.*" + }, + "partID": { + "type": "string", + "pattern": "^prt.*" + } + }, + "required": ["messageID"] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.revert({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}/unrevert": { + "post": { + "operationId": "session.unrevert", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Restore reverted messages", + "description": "Restore all previously reverted messages in a session.", + "responses": { + "200": { + "description": "Updated session", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Session" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.unrevert({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}/permissions/{permissionID}": { + "post": { + "operationId": "permission.respond", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true + }, + { + "in": "path", + "name": "permissionID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Respond to permission", + "deprecated": true, + "description": "Approve or deny a permission request from the AI assistant.", + "responses": { + "200": { + "description": "Permission processed successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "response": { + "type": "string", + "enum": ["once", "always", "reject"] + } + }, + "required": ["response"] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.permission.respond({\n ...\n})" + } + ] + } + }, + "/permission/{requestID}/reply": { + "post": { + "operationId": "permission.reply", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "requestID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Respond to permission request", + "description": "Approve or deny a permission request from the AI assistant.", + "responses": { + "200": { + "description": "Permission processed successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "reply": { + "type": "string", + "enum": ["once", "always", "reject"] + } + }, + "required": ["reply"] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.permission.reply({\n ...\n})" + } + ] + } + }, + "/permission": { + "get": { + "operationId": "permission.list", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "List pending permissions", + "description": "Get all pending permission requests across all sessions.", + "responses": { + "200": { + "description": "List of pending permissions", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PermissionRequest" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.permission.list({\n ...\n})" + } + ] + } + }, + "/command": { + "get": { + "operationId": "command.list", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "List commands", + "description": "Get a list of all available commands in the OpenCode system.", + "responses": { + "200": { + "description": "List of commands", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Command" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.command.list({\n ...\n})" + } + ] + } + }, + "/config/providers": { + "get": { + "operationId": "config.providers", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "List config providers", + "description": "Get a list of all configured AI providers and their default models.", + "responses": { + "200": { + "description": "List of providers", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "providers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Provider" + } + }, + "default": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + } + }, + "required": ["providers", "default"] + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.config.providers({\n ...\n})" + } + ] + } + }, + "/provider": { + "get": { + "operationId": "provider.list", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "List providers", + "description": "Get a list of all available AI providers, including both available and connected ones.", + "responses": { + "200": { + "description": "List of providers", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "all": { + "type": "array", + "items": { + "type": "object", + "properties": { + "api": { + "type": "string" + }, + "name": { + "type": "string" + }, + "env": { + "type": "array", + "items": { + "type": "string" + } + }, + "id": { + "type": "string" + }, + "npm": { + "type": "string" + }, + "models": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "family": { + "type": "string" + }, + "release_date": { + "type": "string" + }, + "attachment": { + "type": "boolean" + }, + "reasoning": { + "type": "boolean" + }, + "temperature": { + "type": "boolean" + }, + "tool_call": { + "type": "boolean" + }, + "interleaved": { + "anyOf": [ + { + "type": "boolean", + "const": true + }, + { + "type": "object", + "properties": { + "field": { + "type": "string", + "enum": ["reasoning_content", "reasoning_details"] + } + }, + "required": ["field"], + "additionalProperties": false + } + ] + }, + "cost": { + "type": "object", + "properties": { + "input": { + "type": "number" + }, + "output": { + "type": "number" + }, + "cache_read": { + "type": "number" + }, + "cache_write": { + "type": "number" + }, + "context_over_200k": { + "type": "object", + "properties": { + "input": { + "type": "number" + }, + "output": { + "type": "number" + }, + "cache_read": { + "type": "number" + }, + "cache_write": { + "type": "number" + } + }, + "required": ["input", "output"] + } + }, + "required": ["input", "output"] + }, + "limit": { + "type": "object", + "properties": { + "context": { + "type": "number" + }, + "output": { + "type": "number" + } + }, + "required": ["context", "output"] + }, + "modalities": { + "type": "object", + "properties": { + "input": { + "type": "array", + "items": { + "type": "string", + "enum": ["text", "audio", "image", "video", "pdf"] + } + }, + "output": { + "type": "array", + "items": { + "type": "string", + "enum": ["text", "audio", "image", "video", "pdf"] + } + } + }, + "required": ["input", "output"] + }, + "experimental": { + "type": "boolean" + }, + "status": { + "type": "string", + "enum": ["alpha", "beta", "deprecated"] + }, + "options": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "headers": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + }, + "provider": { + "type": "object", + "properties": { + "npm": { + "type": "string" + } + }, + "required": ["npm"] + }, + "variants": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + } + } + }, + "required": [ + "id", + "name", + "release_date", + "attachment", + "reasoning", + "temperature", + "tool_call", + "limit", + "options" + ] + } + } + }, + "required": ["name", "env", "id", "models"] + } + }, + "default": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + }, + "connected": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": ["all", "default", "connected"] + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.provider.list({\n ...\n})" + } + ] + } + }, + "/provider/auth": { + "get": { + "operationId": "provider.auth", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Get provider auth methods", + "description": "Retrieve available authentication methods for all AI providers.", + "responses": { + "200": { + "description": "Provider auth methods", + "content": { + "application/json": { + "schema": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProviderAuthMethod" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.provider.auth({\n ...\n})" + } + ] + } + }, + "/provider/{providerID}/oauth/authorize": { + "post": { + "operationId": "provider.oauth.authorize", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "providerID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Provider ID" + } + ], + "summary": "OAuth authorize", + "description": "Initiate OAuth authorization for a specific AI provider to get an authorization URL.", + "responses": { + "200": { + "description": "Authorization URL and method", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProviderAuthAuthorization" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "method": { + "description": "Auth method index", + "type": "number" + } + }, + "required": ["method"] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.provider.oauth.authorize({\n ...\n})" + } + ] + } + }, + "/provider/{providerID}/oauth/callback": { + "post": { + "operationId": "provider.oauth.callback", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "providerID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Provider ID" + } + ], + "summary": "OAuth callback", + "description": "Handle the OAuth callback from a provider after user authorization.", + "responses": { + "200": { + "description": "OAuth callback processed successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "method": { + "description": "Auth method index", + "type": "number" + }, + "code": { + "description": "OAuth authorization code", + "type": "string" + } + }, + "required": ["method"] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.provider.oauth.callback({\n ...\n})" + } + ] + } + }, + "/find": { + "get": { + "operationId": "find.text", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "pattern", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Find text", + "description": "Search for text patterns across files in the project using ripgrep.", + "responses": { + "200": { + "description": "Matches", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "path": { + "type": "object", + "properties": { + "text": { + "type": "string" + } + }, + "required": ["text"] + }, + "lines": { + "type": "object", + "properties": { + "text": { + "type": "string" + } + }, + "required": ["text"] + }, + "line_number": { + "type": "number" + }, + "absolute_offset": { + "type": "number" + }, + "submatches": { + "type": "array", + "items": { + "type": "object", + "properties": { + "match": { + "type": "object", + "properties": { + "text": { + "type": "string" + } + }, + "required": ["text"] + }, + "start": { + "type": "number" + }, + "end": { + "type": "number" + } + }, + "required": ["match", "start", "end"] + } + } + }, + "required": ["path", "lines", "line_number", "absolute_offset", "submatches"] + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.find.text({\n ...\n})" + } + ] + } + }, + "/find/file": { + "get": { + "operationId": "find.files", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "query", + "schema": { + "type": "string" + }, + "required": true + }, + { + "in": "query", + "name": "dirs", + "schema": { + "type": "string", + "enum": ["true", "false"] + } + }, + { + "in": "query", + "name": "type", + "schema": { + "type": "string", + "enum": ["file", "directory"] + } + }, + { + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 200 + } + } + ], + "summary": "Find files", + "description": "Search for files or directories by name or pattern in the project directory.", + "responses": { + "200": { + "description": "File paths", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.find.files({\n ...\n})" + } + ] + } + }, + "/find/symbol": { + "get": { + "operationId": "find.symbols", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "query", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Find symbols", + "description": "Search for workspace symbols like functions, classes, and variables using LSP.", + "responses": { + "200": { + "description": "Symbols", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Symbol" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.find.symbols({\n ...\n})" + } + ] + } + }, + "/file": { + "get": { + "operationId": "file.list", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "path", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "List files", + "description": "List files and directories in a specified path.", + "responses": { + "200": { + "description": "Files and directories", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FileNode" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.file.list({\n ...\n})" + } + ] + } + }, + "/file/content": { + "get": { + "operationId": "file.read", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "path", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Read file", + "description": "Read the content of a specified file.", + "responses": { + "200": { + "description": "File content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FileContent" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.file.read({\n ...\n})" + } + ] + } + }, + "/file/status": { + "get": { + "operationId": "file.status", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Get file status", + "description": "Get the git status of all files in the project.", + "responses": { + "200": { + "description": "File status", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/File" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.file.status({\n ...\n})" + } + ] + } + }, + "/log": { + "post": { + "operationId": "app.log", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Write log", + "description": "Write a log entry to the server logs with specified level and metadata.", + "responses": { + "200": { + "description": "Log entry written successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "service": { + "description": "Service name for the log entry", + "type": "string" + }, + "level": { + "description": "Log level", + "type": "string", + "enum": ["debug", "info", "error", "warn"] + }, + "message": { + "description": "Log message", + "type": "string" + }, + "extra": { + "description": "Additional metadata for the log entry", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + } + }, + "required": ["service", "level", "message"] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.app.log({\n ...\n})" + } + ] + } + }, + "/agent": { + "get": { + "operationId": "app.agents", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "List agents", + "description": "Get a list of all available AI agents in the OpenCode system.", + "responses": { + "200": { + "description": "List of agents", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Agent" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.app.agents({\n ...\n})" + } + ] + } + }, + "/mcp": { + "get": { + "operationId": "mcp.status", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Get MCP status", + "description": "Get the status of all Model Context Protocol (MCP) servers.", + "responses": { + "200": { + "description": "MCP server status", + "content": { + "application/json": { + "schema": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "$ref": "#/components/schemas/MCPStatus" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.mcp.status({\n ...\n})" + } + ] + }, + "post": { + "operationId": "mcp.add", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Add MCP server", + "description": "Dynamically add a new Model Context Protocol (MCP) server to the system.", + "responses": { + "200": { + "description": "MCP server added successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "$ref": "#/components/schemas/MCPStatus" + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "config": { + "anyOf": [ + { + "$ref": "#/components/schemas/McpLocalConfig" + }, + { + "$ref": "#/components/schemas/McpRemoteConfig" + } + ] + } + }, + "required": ["name", "config"] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.mcp.add({\n ...\n})" + } + ] + } + }, + "/mcp/{name}/auth": { + "post": { + "operationId": "mcp.auth.start", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "name", + "required": true + } + ], + "summary": "Start MCP OAuth", + "description": "Start OAuth authentication flow for a Model Context Protocol (MCP) server.", + "responses": { + "200": { + "description": "OAuth flow started", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "authorizationUrl": { + "description": "URL to open in browser for authorization", + "type": "string" + } + }, + "required": ["authorizationUrl"] + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.mcp.auth.start({\n ...\n})" + } + ] + }, + "delete": { + "operationId": "mcp.auth.remove", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "name", + "required": true + } + ], + "summary": "Remove MCP OAuth", + "description": "Remove OAuth credentials for an MCP server", + "responses": { + "200": { + "description": "OAuth credentials removed", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "const": true + } + }, + "required": ["success"] + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.mcp.auth.remove({\n ...\n})" + } + ] + } + }, + "/mcp/{name}/auth/callback": { + "post": { + "operationId": "mcp.auth.callback", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "name", + "required": true + } + ], + "summary": "Complete MCP OAuth", + "description": "Complete OAuth authentication for a Model Context Protocol (MCP) server using the authorization code.", + "responses": { + "200": { + "description": "OAuth authentication completed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MCPStatus" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "description": "Authorization code from OAuth callback", + "type": "string" + } + }, + "required": ["code"] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.mcp.auth.callback({\n ...\n})" + } + ] + } + }, + "/mcp/{name}/auth/authenticate": { + "post": { + "operationId": "mcp.auth.authenticate", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "name", + "required": true + } + ], + "summary": "Authenticate MCP OAuth", + "description": "Start OAuth flow and wait for callback (opens browser)", + "responses": { + "200": { + "description": "OAuth authentication completed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MCPStatus" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.mcp.auth.authenticate({\n ...\n})" + } + ] + } + }, + "/mcp/{name}/connect": { + "post": { + "operationId": "mcp.connect", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "name", + "schema": { + "type": "string" + }, + "required": true + } + ], + "description": "Connect an MCP server", + "responses": { + "200": { + "description": "MCP server connected successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.mcp.connect({\n ...\n})" + } + ] + } + }, + "/mcp/{name}/disconnect": { + "post": { + "operationId": "mcp.disconnect", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "name", + "schema": { + "type": "string" + }, + "required": true + } + ], + "description": "Disconnect an MCP server", + "responses": { + "200": { + "description": "MCP server disconnected successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.mcp.disconnect({\n ...\n})" + } + ] + } + }, + "/lsp": { + "get": { + "operationId": "lsp.status", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Get LSP status", + "description": "Get LSP server status", + "responses": { + "200": { + "description": "LSP server status", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LSPStatus" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.lsp.status({\n ...\n})" + } + ] + } + }, + "/formatter": { + "get": { + "operationId": "formatter.status", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Get formatter status", + "description": "Get formatter status", + "responses": { + "200": { + "description": "Formatter status", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FormatterStatus" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.formatter.status({\n ...\n})" + } + ] + } + }, + "/tui/append-prompt": { + "post": { + "operationId": "tui.appendPrompt", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Append TUI prompt", + "description": "Append prompt to the TUI", + "responses": { + "200": { + "description": "Prompt processed successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "text": { + "type": "string" + } + }, + "required": ["text"] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.tui.appendPrompt({\n ...\n})" + } + ] + } + }, + "/tui/open-help": { + "post": { + "operationId": "tui.openHelp", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Open help dialog", + "description": "Open the help dialog in the TUI to display user assistance information.", + "responses": { + "200": { + "description": "Help dialog opened successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.tui.openHelp({\n ...\n})" + } + ] + } + }, + "/tui/open-sessions": { + "post": { + "operationId": "tui.openSessions", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Open sessions dialog", + "description": "Open the session dialog", + "responses": { + "200": { + "description": "Session dialog opened successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.tui.openSessions({\n ...\n})" + } + ] + } + }, + "/tui/open-themes": { + "post": { + "operationId": "tui.openThemes", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Open themes dialog", + "description": "Open the theme dialog", + "responses": { + "200": { + "description": "Theme dialog opened successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.tui.openThemes({\n ...\n})" + } + ] + } + }, + "/tui/open-models": { + "post": { + "operationId": "tui.openModels", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Open models dialog", + "description": "Open the model dialog", + "responses": { + "200": { + "description": "Model dialog opened successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.tui.openModels({\n ...\n})" + } + ] + } + }, + "/tui/submit-prompt": { + "post": { + "operationId": "tui.submitPrompt", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Submit TUI prompt", + "description": "Submit the prompt", + "responses": { + "200": { + "description": "Prompt submitted successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.tui.submitPrompt({\n ...\n})" + } + ] + } + }, + "/tui/clear-prompt": { + "post": { + "operationId": "tui.clearPrompt", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Clear TUI prompt", + "description": "Clear the prompt", + "responses": { + "200": { + "description": "Prompt cleared successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.tui.clearPrompt({\n ...\n})" + } + ] + } + }, + "/tui/execute-command": { + "post": { + "operationId": "tui.executeCommand", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Execute TUI command", + "description": "Execute a TUI command (e.g. agent_cycle)", + "responses": { + "200": { + "description": "Command executed successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "command": { + "type": "string" + } + }, + "required": ["command"] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.tui.executeCommand({\n ...\n})" + } + ] + } + }, + "/tui/show-toast": { + "post": { + "operationId": "tui.showToast", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Show TUI toast", + "description": "Show a toast notification in the TUI", + "responses": { + "200": { + "description": "Toast notification shown successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "message": { + "type": "string" + }, + "variant": { + "type": "string", + "enum": ["info", "success", "warning", "error"] + }, + "duration": { + "description": "Duration in milliseconds", + "default": 5000, + "type": "number" + } + }, + "required": ["message", "variant"] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.tui.showToast({\n ...\n})" + } + ] + } + }, + "/tui/publish": { + "post": { + "operationId": "tui.publish", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Publish TUI event", + "description": "Publish a TUI event", + "responses": { + "200": { + "description": "Event published successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/Event.tui.prompt.append" + }, + { + "$ref": "#/components/schemas/Event.tui.command.execute" + }, + { + "$ref": "#/components/schemas/Event.tui.toast.show" + } + ] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.tui.publish({\n ...\n})" + } + ] + } + }, + "/tui/control/next": { + "get": { + "operationId": "tui.control.next", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Get next TUI request", + "description": "Retrieve the next TUI (Terminal User Interface) request from the queue for processing.", + "responses": { + "200": { + "description": "Next TUI request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "body": {} + }, + "required": ["path", "body"] + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.tui.control.next({\n ...\n})" + } + ] + } + }, + "/tui/control/response": { + "post": { + "operationId": "tui.control.response", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Submit TUI response", + "description": "Submit a response to the TUI request queue to complete a pending request.", + "responses": { + "200": { + "description": "Response submitted successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": {} + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.tui.control.response({\n ...\n})" + } + ] + } + }, + "/auth/{providerID}": { + "put": { + "operationId": "auth.set", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "providerID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Set auth credentials", + "description": "Set authentication credentials", + "responses": { + "200": { + "description": "Successfully set authentication credentials", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Auth" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.auth.set({\n ...\n})" + } + ] + } + }, + "/event": { + "get": { + "operationId": "event.subscribe", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Subscribe to events", + "description": "Get events", + "responses": { + "200": { + "description": "Event stream", + "content": { + "text/event-stream": { + "schema": { + "$ref": "#/components/schemas/Event" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.event.subscribe({\n ...\n})" + } + ] + } + } + }, + "components": { + "schemas": { + "Event.installation.updated": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "installation.updated" + }, + "properties": { + "type": "object", + "properties": { + "version": { + "type": "string" + } + }, + "required": ["version"] + } + }, + "required": ["type", "properties"] + }, + "Event.installation.update-available": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "installation.update-available" + }, + "properties": { + "type": "object", + "properties": { + "version": { + "type": "string" + } + }, + "required": ["version"] + } + }, + "required": ["type", "properties"] + }, + "Project": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "worktree": { + "type": "string" + }, + "vcs": { + "type": "string", + "const": "git" + }, + "name": { + "type": "string" + }, + "icon": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "color": { + "type": "string" + } + } + }, + "time": { + "type": "object", + "properties": { + "created": { + "type": "number" + }, + "updated": { + "type": "number" + }, + "initialized": { + "type": "number" + } + }, + "required": ["created", "updated"] + } + }, + "required": ["id", "worktree", "time"] + }, + "Event.project.updated": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "project.updated" + }, + "properties": { + "$ref": "#/components/schemas/Project" + } + }, + "required": ["type", "properties"] + }, + "Event.server.instance.disposed": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "server.instance.disposed" + }, + "properties": { + "type": "object", + "properties": { + "directory": { + "type": "string" + } + }, + "required": ["directory"] + } + }, + "required": ["type", "properties"] + }, + "Event.lsp.client.diagnostics": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "lsp.client.diagnostics" + }, + "properties": { + "type": "object", + "properties": { + "serverID": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": ["serverID", "path"] + } + }, + "required": ["type", "properties"] + }, + "Event.lsp.updated": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "lsp.updated" + }, + "properties": { + "type": "object", + "properties": {} + } + }, + "required": ["type", "properties"] + }, + "FileDiff": { + "type": "object", + "properties": { + "file": { + "type": "string" + }, + "before": { + "type": "string" + }, + "after": { + "type": "string" + }, + "additions": { + "type": "number" + }, + "deletions": { + "type": "number" + } + }, + "required": ["file", "before", "after", "additions", "deletions"] + }, + "UserMessage": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "sessionID": { + "type": "string" + }, + "role": { + "type": "string", + "const": "user" + }, + "time": { + "type": "object", + "properties": { + "created": { + "type": "number" + } + }, + "required": ["created"] + }, + "summary": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "body": { + "type": "string" + }, + "diffs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FileDiff" + } + } + }, + "required": ["diffs"] + }, + "agent": { + "type": "string" + }, + "model": { + "type": "object", + "properties": { + "providerID": { + "type": "string" + }, + "modelID": { + "type": "string" + } + }, + "required": ["providerID", "modelID"] + }, + "system": { + "type": "string" + }, + "tools": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "boolean" + } + }, + "variant": { + "type": "string" + } + }, + "required": ["id", "sessionID", "role", "time", "agent", "model"] + }, + "ProviderAuthError": { + "type": "object", + "properties": { + "name": { + "type": "string", + "const": "ProviderAuthError" + }, + "data": { + "type": "object", + "properties": { + "providerID": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": ["providerID", "message"] + } + }, + "required": ["name", "data"] + }, + "UnknownError": { + "type": "object", + "properties": { + "name": { + "type": "string", + "const": "UnknownError" + }, + "data": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": ["message"] + } + }, + "required": ["name", "data"] + }, + "MessageOutputLengthError": { + "type": "object", + "properties": { + "name": { + "type": "string", + "const": "MessageOutputLengthError" + }, + "data": { + "type": "object", + "properties": {} + } + }, + "required": ["name", "data"] + }, + "MessageAbortedError": { + "type": "object", + "properties": { + "name": { + "type": "string", + "const": "MessageAbortedError" + }, + "data": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": ["message"] + } + }, + "required": ["name", "data"] + }, + "APIError": { + "type": "object", + "properties": { + "name": { + "type": "string", + "const": "APIError" + }, + "data": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "statusCode": { + "type": "number" + }, + "isRetryable": { + "type": "boolean" + }, + "responseHeaders": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + }, + "responseBody": { + "type": "string" + }, + "metadata": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + } + }, + "required": ["message", "isRetryable"] + } + }, + "required": ["name", "data"] + }, + "AssistantMessage": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "sessionID": { + "type": "string" + }, + "role": { + "type": "string", + "const": "assistant" + }, + "time": { + "type": "object", + "properties": { + "created": { + "type": "number" + }, + "completed": { + "type": "number" + } + }, + "required": ["created"] + }, + "error": { + "anyOf": [ + { + "$ref": "#/components/schemas/ProviderAuthError" + }, + { + "$ref": "#/components/schemas/UnknownError" + }, + { + "$ref": "#/components/schemas/MessageOutputLengthError" + }, + { + "$ref": "#/components/schemas/MessageAbortedError" + }, + { + "$ref": "#/components/schemas/APIError" + } + ] + }, + "parentID": { + "type": "string" + }, + "modelID": { + "type": "string" + }, + "providerID": { + "type": "string" + }, + "mode": { + "type": "string" + }, + "agent": { + "type": "string" + }, + "path": { + "type": "object", + "properties": { + "cwd": { + "type": "string" + }, + "root": { + "type": "string" + } + }, + "required": ["cwd", "root"] + }, + "summary": { + "type": "boolean" + }, + "cost": { + "type": "number" + }, + "tokens": { + "type": "object", + "properties": { + "input": { + "type": "number" + }, + "output": { + "type": "number" + }, + "reasoning": { + "type": "number" + }, + "cache": { + "type": "object", + "properties": { + "read": { + "type": "number" + }, + "write": { + "type": "number" + } + }, + "required": ["read", "write"] + } + }, + "required": ["input", "output", "reasoning", "cache"] + }, + "finish": { + "type": "string" + } + }, + "required": [ + "id", + "sessionID", + "role", + "time", + "parentID", + "modelID", + "providerID", + "mode", + "agent", + "path", + "cost", + "tokens" + ] + }, + "Message": { + "anyOf": [ + { + "$ref": "#/components/schemas/UserMessage" + }, + { + "$ref": "#/components/schemas/AssistantMessage" + } + ] + }, + "Event.message.updated": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "message.updated" + }, + "properties": { + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/Message" + } + }, + "required": ["info"] + } + }, + "required": ["type", "properties"] + }, + "Event.message.removed": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "message.removed" + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string" + }, + "messageID": { + "type": "string" + } + }, + "required": ["sessionID", "messageID"] + } + }, + "required": ["type", "properties"] + }, + "TextPart": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "sessionID": { + "type": "string" + }, + "messageID": { + "type": "string" + }, + "type": { + "type": "string", + "const": "text" + }, + "text": { + "type": "string" + }, + "synthetic": { + "type": "boolean" + }, + "ignored": { + "type": "boolean" + }, + "time": { + "type": "object", + "properties": { + "start": { + "type": "number" + }, + "end": { + "type": "number" + } + }, + "required": ["start"] + }, + "metadata": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + } + }, + "required": ["id", "sessionID", "messageID", "type", "text"] + }, + "ReasoningPart": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "sessionID": { + "type": "string" + }, + "messageID": { + "type": "string" + }, + "type": { + "type": "string", + "const": "reasoning" + }, + "text": { + "type": "string" + }, + "metadata": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "time": { + "type": "object", + "properties": { + "start": { + "type": "number" + }, + "end": { + "type": "number" + } + }, + "required": ["start"] + } + }, + "required": ["id", "sessionID", "messageID", "type", "text", "time"] + }, + "FilePartSourceText": { + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "start": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + "end": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + } + }, + "required": ["value", "start", "end"] + }, + "FileSource": { + "type": "object", + "properties": { + "text": { + "$ref": "#/components/schemas/FilePartSourceText" + }, + "type": { + "type": "string", + "const": "file" + }, + "path": { + "type": "string" + } + }, + "required": ["text", "type", "path"] + }, + "Range": { + "type": "object", + "properties": { + "start": { + "type": "object", + "properties": { + "line": { + "type": "number" + }, + "character": { + "type": "number" + } + }, + "required": ["line", "character"] + }, + "end": { + "type": "object", + "properties": { + "line": { + "type": "number" + }, + "character": { + "type": "number" + } + }, + "required": ["line", "character"] + } + }, + "required": ["start", "end"] + }, + "SymbolSource": { + "type": "object", + "properties": { + "text": { + "$ref": "#/components/schemas/FilePartSourceText" + }, + "type": { + "type": "string", + "const": "symbol" + }, + "path": { + "type": "string" + }, + "range": { + "$ref": "#/components/schemas/Range" + }, + "name": { + "type": "string" + }, + "kind": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + } + }, + "required": ["text", "type", "path", "range", "name", "kind"] + }, + "FilePartSource": { + "anyOf": [ + { + "$ref": "#/components/schemas/FileSource" + }, + { + "$ref": "#/components/schemas/SymbolSource" + } + ] + }, + "FilePart": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "sessionID": { + "type": "string" + }, + "messageID": { + "type": "string" + }, + "type": { + "type": "string", + "const": "file" + }, + "mime": { + "type": "string" + }, + "filename": { + "type": "string" + }, + "url": { + "type": "string" + }, + "source": { + "$ref": "#/components/schemas/FilePartSource" + } + }, + "required": ["id", "sessionID", "messageID", "type", "mime", "url"] + }, + "ToolStatePending": { + "type": "object", + "properties": { + "status": { + "type": "string", + "const": "pending" + }, + "input": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "raw": { + "type": "string" + } + }, + "required": ["status", "input", "raw"] + }, + "ToolStateRunning": { + "type": "object", + "properties": { + "status": { + "type": "string", + "const": "running" + }, + "input": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "title": { + "type": "string" + }, + "metadata": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "time": { + "type": "object", + "properties": { + "start": { + "type": "number" + } + }, + "required": ["start"] + } + }, + "required": ["status", "input", "time"] + }, + "ToolStateCompleted": { + "type": "object", + "properties": { + "status": { + "type": "string", + "const": "completed" + }, + "input": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "output": { + "type": "string" + }, + "title": { + "type": "string" + }, + "metadata": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "time": { + "type": "object", + "properties": { + "start": { + "type": "number" + }, + "end": { + "type": "number" + }, + "compacted": { + "type": "number" + } + }, + "required": ["start", "end"] + }, + "attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FilePart" + } + } + }, + "required": ["status", "input", "output", "title", "metadata", "time"] + }, + "ToolStateError": { + "type": "object", + "properties": { + "status": { + "type": "string", + "const": "error" + }, + "input": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "error": { + "type": "string" + }, + "metadata": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "time": { + "type": "object", + "properties": { + "start": { + "type": "number" + }, + "end": { + "type": "number" + } + }, + "required": ["start", "end"] + } + }, + "required": ["status", "input", "error", "time"] + }, + "ToolState": { + "anyOf": [ + { + "$ref": "#/components/schemas/ToolStatePending" + }, + { + "$ref": "#/components/schemas/ToolStateRunning" + }, + { + "$ref": "#/components/schemas/ToolStateCompleted" + }, + { + "$ref": "#/components/schemas/ToolStateError" + } + ] + }, + "ToolPart": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "sessionID": { + "type": "string" + }, + "messageID": { + "type": "string" + }, + "type": { + "type": "string", + "const": "tool" + }, + "callID": { + "type": "string" + }, + "tool": { + "type": "string" + }, + "state": { + "$ref": "#/components/schemas/ToolState" + }, + "metadata": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + } + }, + "required": ["id", "sessionID", "messageID", "type", "callID", "tool", "state"] + }, + "StepStartPart": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "sessionID": { + "type": "string" + }, + "messageID": { + "type": "string" + }, + "type": { + "type": "string", + "const": "step-start" + }, + "snapshot": { + "type": "string" + } + }, + "required": ["id", "sessionID", "messageID", "type"] + }, + "StepFinishPart": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "sessionID": { + "type": "string" + }, + "messageID": { + "type": "string" + }, + "type": { + "type": "string", + "const": "step-finish" + }, + "reason": { + "type": "string" + }, + "snapshot": { + "type": "string" + }, + "cost": { + "type": "number" + }, + "tokens": { + "type": "object", + "properties": { + "input": { + "type": "number" + }, + "output": { + "type": "number" + }, + "reasoning": { + "type": "number" + }, + "cache": { + "type": "object", + "properties": { + "read": { + "type": "number" + }, + "write": { + "type": "number" + } + }, + "required": ["read", "write"] + } + }, + "required": ["input", "output", "reasoning", "cache"] + } + }, + "required": ["id", "sessionID", "messageID", "type", "reason", "cost", "tokens"] + }, + "SnapshotPart": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "sessionID": { + "type": "string" + }, + "messageID": { + "type": "string" + }, + "type": { + "type": "string", + "const": "snapshot" + }, + "snapshot": { + "type": "string" + } + }, + "required": ["id", "sessionID", "messageID", "type", "snapshot"] + }, + "PatchPart": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "sessionID": { + "type": "string" + }, + "messageID": { + "type": "string" + }, + "type": { + "type": "string", + "const": "patch" + }, + "hash": { + "type": "string" + }, + "files": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": ["id", "sessionID", "messageID", "type", "hash", "files"] + }, + "AgentPart": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "sessionID": { + "type": "string" + }, + "messageID": { + "type": "string" + }, + "type": { + "type": "string", + "const": "agent" + }, + "name": { + "type": "string" + }, + "source": { + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "start": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + "end": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + } + }, + "required": ["value", "start", "end"] + } + }, + "required": ["id", "sessionID", "messageID", "type", "name"] + }, + "RetryPart": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "sessionID": { + "type": "string" + }, + "messageID": { + "type": "string" + }, + "type": { + "type": "string", + "const": "retry" + }, + "attempt": { + "type": "number" + }, + "error": { + "$ref": "#/components/schemas/APIError" + }, + "time": { + "type": "object", + "properties": { + "created": { + "type": "number" + } + }, + "required": ["created"] + } + }, + "required": ["id", "sessionID", "messageID", "type", "attempt", "error", "time"] + }, + "CompactionPart": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "sessionID": { + "type": "string" + }, + "messageID": { + "type": "string" + }, + "type": { + "type": "string", + "const": "compaction" + }, + "auto": { + "type": "boolean" + } + }, + "required": ["id", "sessionID", "messageID", "type", "auto"] + }, + "Part": { + "anyOf": [ + { + "$ref": "#/components/schemas/TextPart" + }, + { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "sessionID": { + "type": "string" + }, + "messageID": { + "type": "string" + }, + "type": { + "type": "string", + "const": "subtask" + }, + "prompt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "agent": { + "type": "string" + }, + "command": { + "type": "string" + } + }, + "required": ["id", "sessionID", "messageID", "type", "prompt", "description", "agent"] + }, + { + "$ref": "#/components/schemas/ReasoningPart" + }, + { + "$ref": "#/components/schemas/FilePart" + }, + { + "$ref": "#/components/schemas/ToolPart" + }, + { + "$ref": "#/components/schemas/StepStartPart" + }, + { + "$ref": "#/components/schemas/StepFinishPart" + }, + { + "$ref": "#/components/schemas/SnapshotPart" + }, + { + "$ref": "#/components/schemas/PatchPart" + }, + { + "$ref": "#/components/schemas/AgentPart" + }, + { + "$ref": "#/components/schemas/RetryPart" + }, + { + "$ref": "#/components/schemas/CompactionPart" + } + ] + }, + "Event.message.part.updated": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "message.part.updated" + }, + "properties": { + "type": "object", + "properties": { + "part": { + "$ref": "#/components/schemas/Part" + }, + "delta": { + "type": "string" + } + }, + "required": ["part"] + } + }, + "required": ["type", "properties"] + }, + "Event.message.part.removed": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "message.part.removed" + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string" + }, + "messageID": { + "type": "string" + }, + "partID": { + "type": "string" + } + }, + "required": ["sessionID", "messageID", "partID"] + } + }, + "required": ["type", "properties"] + }, + "PermissionRequest": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^per.*" + }, + "sessionID": { + "type": "string", + "pattern": "^ses.*" + }, + "permission": { + "type": "string" + }, + "patterns": { + "type": "array", + "items": { + "type": "string" + } + }, + "metadata": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "always": { + "type": "array", + "items": { + "type": "string" + } + }, + "tool": { + "type": "object", + "properties": { + "messageID": { + "type": "string" + }, + "callID": { + "type": "string" + } + }, + "required": ["messageID", "callID"] + } + }, + "required": ["id", "sessionID", "permission", "patterns", "metadata", "always"] + }, + "Event.permission.asked": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "permission.asked" + }, + "properties": { + "$ref": "#/components/schemas/PermissionRequest" + } + }, + "required": ["type", "properties"] + }, + "Event.permission.replied": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "permission.replied" + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string" + }, + "requestID": { + "type": "string" + }, + "reply": { + "type": "string", + "enum": ["once", "always", "reject"] + } + }, + "required": ["sessionID", "requestID", "reply"] + } + }, + "required": ["type", "properties"] + }, + "SessionStatus": { + "anyOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "idle" + } + }, + "required": ["type"] + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "retry" + }, + "attempt": { + "type": "number" + }, + "message": { + "type": "string" + }, + "next": { + "type": "number" + } + }, + "required": ["type", "attempt", "message", "next"] + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "busy" + } + }, + "required": ["type"] + } + ] + }, + "Event.session.status": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "session.status" + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/SessionStatus" + } + }, + "required": ["sessionID", "status"] + } + }, + "required": ["type", "properties"] + }, + "Event.session.idle": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "session.idle" + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string" + } + }, + "required": ["sessionID"] + } + }, + "required": ["type", "properties"] + }, + "Event.session.compacted": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "session.compacted" + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string" + } + }, + "required": ["sessionID"] + } + }, + "required": ["type", "properties"] + }, + "Event.file.edited": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "file.edited" + }, + "properties": { + "type": "object", + "properties": { + "file": { + "type": "string" + } + }, + "required": ["file"] + } + }, + "required": ["type", "properties"] + }, + "Todo": { + "type": "object", + "properties": { + "content": { + "description": "Brief description of the task", + "type": "string" + }, + "status": { + "description": "Current status of the task: pending, in_progress, completed, cancelled", + "type": "string" + }, + "priority": { + "description": "Priority level of the task: high, medium, low", + "type": "string" + }, + "id": { + "description": "Unique identifier for the todo item", + "type": "string" + } + }, + "required": ["content", "status", "priority", "id"] + }, + "Event.todo.updated": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "todo.updated" + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string" + }, + "todos": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Todo" + } + } + }, + "required": ["sessionID", "todos"] + } + }, + "required": ["type", "properties"] + }, + "Event.tui.prompt.append": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "tui.prompt.append" + }, + "properties": { + "type": "object", + "properties": { + "text": { + "type": "string" + } + }, + "required": ["text"] + } + }, + "required": ["type", "properties"] + }, + "Event.tui.command.execute": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "tui.command.execute" + }, + "properties": { + "type": "object", + "properties": { + "command": { + "anyOf": [ + { + "type": "string", + "enum": [ + "session.list", + "session.new", + "session.share", + "session.interrupt", + "session.compact", + "session.page.up", + "session.page.down", + "session.half.page.up", + "session.half.page.down", + "session.first", + "session.last", + "prompt.clear", + "prompt.submit", + "agent.cycle" + ] + }, + { + "type": "string" + } + ] + } + }, + "required": ["command"] + } + }, + "required": ["type", "properties"] + }, + "Event.tui.toast.show": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "tui.toast.show" + }, + "properties": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "message": { + "type": "string" + }, + "variant": { + "type": "string", + "enum": ["info", "success", "warning", "error"] + }, + "duration": { + "description": "Duration in milliseconds", + "default": 5000, + "type": "number" + } + }, + "required": ["message", "variant"] + } + }, + "required": ["type", "properties"] + }, + "Event.mcp.tools.changed": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "mcp.tools.changed" + }, + "properties": { + "type": "object", + "properties": { + "server": { + "type": "string" + } + }, + "required": ["server"] + } + }, + "required": ["type", "properties"] + }, + "Event.command.executed": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "command.executed" + }, + "properties": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "sessionID": { + "type": "string", + "pattern": "^ses.*" + }, + "arguments": { + "type": "string" + }, + "messageID": { + "type": "string", + "pattern": "^msg.*" + } + }, + "required": ["name", "sessionID", "arguments", "messageID"] + } + }, + "required": ["type", "properties"] + }, + "PermissionAction": { + "type": "string", + "enum": ["allow", "deny", "ask"] + }, + "PermissionRule": { + "type": "object", + "properties": { + "permission": { + "type": "string" + }, + "pattern": { + "type": "string" + }, + "action": { + "$ref": "#/components/schemas/PermissionAction" + } + }, + "required": ["permission", "pattern", "action"] + }, + "PermissionRuleset": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PermissionRule" + } + }, + "Session": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^ses.*" + }, + "projectID": { + "type": "string" + }, + "directory": { + "type": "string" + }, + "parentID": { + "type": "string", + "pattern": "^ses.*" + }, + "summary": { + "type": "object", + "properties": { + "additions": { + "type": "number" + }, + "deletions": { + "type": "number" + }, + "files": { + "type": "number" + }, + "diffs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FileDiff" + } + } + }, + "required": ["additions", "deletions", "files"] + }, + "share": { + "type": "object", + "properties": { + "url": { + "type": "string" + } + }, + "required": ["url"] + }, + "title": { + "type": "string" + }, + "version": { + "type": "string" + }, + "time": { + "type": "object", + "properties": { + "created": { + "type": "number" + }, + "updated": { + "type": "number" + }, + "compacting": { + "type": "number" + }, + "archived": { + "type": "number" + } + }, + "required": ["created", "updated"] + }, + "permission": { + "$ref": "#/components/schemas/PermissionRuleset" + }, + "revert": { + "type": "object", + "properties": { + "messageID": { + "type": "string" + }, + "partID": { + "type": "string" + }, + "snapshot": { + "type": "string" + }, + "diff": { + "type": "string" + } + }, + "required": ["messageID"] + } + }, + "required": ["id", "projectID", "directory", "title", "version", "time"] + }, + "Event.session.created": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "session.created" + }, + "properties": { + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/Session" + } + }, + "required": ["info"] + } + }, + "required": ["type", "properties"] + }, + "Event.session.updated": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "session.updated" + }, + "properties": { + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/Session" + } + }, + "required": ["info"] + } + }, + "required": ["type", "properties"] + }, + "Event.session.deleted": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "session.deleted" + }, + "properties": { + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/Session" + } + }, + "required": ["info"] + } + }, + "required": ["type", "properties"] + }, + "Event.session.diff": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "session.diff" + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string" + }, + "diff": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FileDiff" + } + } + }, + "required": ["sessionID", "diff"] + } + }, + "required": ["type", "properties"] + }, + "Event.session.error": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "session.error" + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string" + }, + "error": { + "anyOf": [ + { + "$ref": "#/components/schemas/ProviderAuthError" + }, + { + "$ref": "#/components/schemas/UnknownError" + }, + { + "$ref": "#/components/schemas/MessageOutputLengthError" + }, + { + "$ref": "#/components/schemas/MessageAbortedError" + }, + { + "$ref": "#/components/schemas/APIError" + } + ] + } + } + } + }, + "required": ["type", "properties"] + }, + "Event.file.watcher.updated": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "file.watcher.updated" + }, + "properties": { + "type": "object", + "properties": { + "file": { + "type": "string" + }, + "event": { + "anyOf": [ + { + "type": "string", + "const": "add" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "unlink" + } + ] + } + }, + "required": ["file", "event"] + } + }, + "required": ["type", "properties"] + }, + "Event.vcs.branch.updated": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "vcs.branch.updated" + }, + "properties": { + "type": "object", + "properties": { + "branch": { + "type": "string" + } + } + } + }, + "required": ["type", "properties"] + }, + "Pty": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^pty.*" + }, + "title": { + "type": "string" + }, + "command": { + "type": "string" + }, + "args": { + "type": "array", + "items": { + "type": "string" + } + }, + "cwd": { + "type": "string" + }, + "status": { + "type": "string", + "enum": ["running", "exited"] + }, + "pid": { + "type": "number" + } + }, + "required": ["id", "title", "command", "args", "cwd", "status", "pid"] + }, + "Event.pty.created": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "pty.created" + }, + "properties": { + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/Pty" + } + }, + "required": ["info"] + } + }, + "required": ["type", "properties"] + }, + "Event.pty.updated": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "pty.updated" + }, + "properties": { + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/Pty" + } + }, + "required": ["info"] + } + }, + "required": ["type", "properties"] + }, + "Event.pty.exited": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "pty.exited" + }, + "properties": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^pty.*" + }, + "exitCode": { + "type": "number" + } + }, + "required": ["id", "exitCode"] + } + }, + "required": ["type", "properties"] + }, + "Event.pty.deleted": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "pty.deleted" + }, + "properties": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^pty.*" + } + }, + "required": ["id"] + } + }, + "required": ["type", "properties"] + }, + "Event.server.connected": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "server.connected" + }, + "properties": { + "type": "object", + "properties": {} + } + }, + "required": ["type", "properties"] + }, + "Event.global.disposed": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "global.disposed" + }, + "properties": { + "type": "object", + "properties": {} + } + }, + "required": ["type", "properties"] + }, + "Event": { + "anyOf": [ + { + "$ref": "#/components/schemas/Event.installation.updated" + }, + { + "$ref": "#/components/schemas/Event.installation.update-available" + }, + { + "$ref": "#/components/schemas/Event.project.updated" + }, + { + "$ref": "#/components/schemas/Event.server.instance.disposed" + }, + { + "$ref": "#/components/schemas/Event.lsp.client.diagnostics" + }, + { + "$ref": "#/components/schemas/Event.lsp.updated" + }, + { + "$ref": "#/components/schemas/Event.message.updated" + }, + { + "$ref": "#/components/schemas/Event.message.removed" + }, + { + "$ref": "#/components/schemas/Event.message.part.updated" + }, + { + "$ref": "#/components/schemas/Event.message.part.removed" + }, + { + "$ref": "#/components/schemas/Event.permission.asked" + }, + { + "$ref": "#/components/schemas/Event.permission.replied" + }, + { + "$ref": "#/components/schemas/Event.session.status" + }, + { + "$ref": "#/components/schemas/Event.session.idle" + }, + { + "$ref": "#/components/schemas/Event.session.compacted" + }, + { + "$ref": "#/components/schemas/Event.file.edited" + }, + { + "$ref": "#/components/schemas/Event.todo.updated" + }, + { + "$ref": "#/components/schemas/Event.tui.prompt.append" + }, + { + "$ref": "#/components/schemas/Event.tui.command.execute" + }, + { + "$ref": "#/components/schemas/Event.tui.toast.show" + }, + { + "$ref": "#/components/schemas/Event.mcp.tools.changed" + }, + { + "$ref": "#/components/schemas/Event.command.executed" + }, + { + "$ref": "#/components/schemas/Event.session.created" + }, + { + "$ref": "#/components/schemas/Event.session.updated" + }, + { + "$ref": "#/components/schemas/Event.session.deleted" + }, + { + "$ref": "#/components/schemas/Event.session.diff" + }, + { + "$ref": "#/components/schemas/Event.session.error" + }, + { + "$ref": "#/components/schemas/Event.file.watcher.updated" + }, + { + "$ref": "#/components/schemas/Event.vcs.branch.updated" + }, + { + "$ref": "#/components/schemas/Event.pty.created" + }, + { + "$ref": "#/components/schemas/Event.pty.updated" + }, + { + "$ref": "#/components/schemas/Event.pty.exited" + }, + { + "$ref": "#/components/schemas/Event.pty.deleted" + }, + { + "$ref": "#/components/schemas/Event.server.connected" + }, + { + "$ref": "#/components/schemas/Event.global.disposed" + } + ] + }, + "GlobalEvent": { + "type": "object", + "properties": { + "directory": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/Event" + } + }, + "required": ["directory", "payload"] + }, + "BadRequestError": { + "type": "object", + "properties": { + "data": {}, + "errors": { + "type": "array", + "items": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + } + }, + "success": { + "type": "boolean", + "const": false + } + }, + "required": ["data", "errors", "success"] + }, + "NotFoundError": { + "type": "object", + "properties": { + "name": { + "type": "string", + "const": "NotFoundError" + }, + "data": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": ["message"] + } + }, + "required": ["name", "data"] + }, + "KeybindsConfig": { + "description": "Custom keybind configurations", + "type": "object", + "properties": { + "leader": { + "description": "Leader key for keybind combinations", + "default": "ctrl+x", + "type": "string" + }, + "app_exit": { + "description": "Exit the application", + "default": "ctrl+c,ctrl+d,q", + "type": "string" + }, + "editor_open": { + "description": "Open external editor", + "default": "e", + "type": "string" + }, + "theme_list": { + "description": "List available themes", + "default": "t", + "type": "string" + }, + "sidebar_toggle": { + "description": "Toggle sidebar", + "default": "b", + "type": "string" + }, + "scrollbar_toggle": { + "description": "Toggle session scrollbar", + "default": "none", + "type": "string" + }, + "username_toggle": { + "description": "Toggle username visibility", + "default": "none", + "type": "string" + }, + "status_view": { + "description": "View status", + "default": "s", + "type": "string" + }, + "session_export": { + "description": "Export session to editor", + "default": "x", + "type": "string" + }, + "session_new": { + "description": "Create a new session", + "default": "n", + "type": "string" + }, + "session_list": { + "description": "List all sessions", + "default": "l", + "type": "string" + }, + "session_timeline": { + "description": "Show session timeline", + "default": "g", + "type": "string" + }, + "session_fork": { + "description": "Fork session from message", + "default": "none", + "type": "string" + }, + "session_rename": { + "description": "Rename session", + "default": "none", + "type": "string" + }, + "session_share": { + "description": "Share current session", + "default": "none", + "type": "string" + }, + "session_unshare": { + "description": "Unshare current session", + "default": "none", + "type": "string" + }, + "session_interrupt": { + "description": "Interrupt current session", + "default": "escape", + "type": "string" + }, + "session_compact": { + "description": "Compact the session", + "default": "c", + "type": "string" + }, + "messages_page_up": { + "description": "Scroll messages up by one page", + "default": "pageup", + "type": "string" + }, + "messages_page_down": { + "description": "Scroll messages down by one page", + "default": "pagedown", + "type": "string" + }, + "messages_half_page_up": { + "description": "Scroll messages up by half page", + "default": "ctrl+alt+u", + "type": "string" + }, + "messages_half_page_down": { + "description": "Scroll messages down by half page", + "default": "ctrl+alt+d", + "type": "string" + }, + "messages_first": { + "description": "Navigate to first message", + "default": "ctrl+g,home", + "type": "string" + }, + "messages_last": { + "description": "Navigate to last message", + "default": "ctrl+alt+g,end", + "type": "string" + }, + "messages_next": { + "description": "Navigate to next message", + "default": "none", + "type": "string" + }, + "messages_previous": { + "description": "Navigate to previous message", + "default": "none", + "type": "string" + }, + "messages_last_user": { + "description": "Navigate to last user message", + "default": "none", + "type": "string" + }, + "messages_copy": { + "description": "Copy message", + "default": "y", + "type": "string" + }, + "messages_undo": { + "description": "Undo message", + "default": "u", + "type": "string" + }, + "messages_redo": { + "description": "Redo message", + "default": "r", + "type": "string" + }, + "messages_toggle_conceal": { + "description": "Toggle code block concealment in messages", + "default": "h", + "type": "string" + }, + "tool_details": { + "description": "Toggle tool details visibility", + "default": "none", + "type": "string" + }, + "model_list": { + "description": "List available models", + "default": "m", + "type": "string" + }, + "model_cycle_recent": { + "description": "Next recently used model", + "default": "f2", + "type": "string" + }, + "model_cycle_recent_reverse": { + "description": "Previous recently used model", + "default": "shift+f2", + "type": "string" + }, + "model_cycle_favorite": { + "description": "Next favorite model", + "default": "none", + "type": "string" + }, + "model_cycle_favorite_reverse": { + "description": "Previous favorite model", + "default": "none", + "type": "string" + }, + "command_list": { + "description": "List available commands", + "default": "ctrl+p", + "type": "string" + }, + "agent_list": { + "description": "List agents", + "default": "a", + "type": "string" + }, + "agent_cycle": { + "description": "Next agent", + "default": "tab", + "type": "string" + }, + "agent_cycle_reverse": { + "description": "Previous agent", + "default": "shift+tab", + "type": "string" + }, + "variant_cycle": { + "description": "Cycle model variants", + "default": "ctrl+t", + "type": "string" + }, + "input_clear": { + "description": "Clear input field", + "default": "ctrl+c", + "type": "string" + }, + "input_paste": { + "description": "Paste from clipboard", + "default": "ctrl+v", + "type": "string" + }, + "input_submit": { + "description": "Submit input", + "default": "return", + "type": "string" + }, + "input_newline": { + "description": "Insert newline in input", + "default": "shift+return,ctrl+return,alt+return,ctrl+j", + "type": "string" + }, + "input_move_left": { + "description": "Move cursor left in input", + "default": "left,ctrl+b", + "type": "string" + }, + "input_move_right": { + "description": "Move cursor right in input", + "default": "right,ctrl+f", + "type": "string" + }, + "input_move_up": { + "description": "Move cursor up in input", + "default": "up", + "type": "string" + }, + "input_move_down": { + "description": "Move cursor down in input", + "default": "down", + "type": "string" + }, + "input_select_left": { + "description": "Select left in input", + "default": "shift+left", + "type": "string" + }, + "input_select_right": { + "description": "Select right in input", + "default": "shift+right", + "type": "string" + }, + "input_select_up": { + "description": "Select up in input", + "default": "shift+up", + "type": "string" + }, + "input_select_down": { + "description": "Select down in input", + "default": "shift+down", + "type": "string" + }, + "input_line_home": { + "description": "Move to start of line in input", + "default": "ctrl+a", + "type": "string" + }, + "input_line_end": { + "description": "Move to end of line in input", + "default": "ctrl+e", + "type": "string" + }, + "input_select_line_home": { + "description": "Select to start of line in input", + "default": "ctrl+shift+a", + "type": "string" + }, + "input_select_line_end": { + "description": "Select to end of line in input", + "default": "ctrl+shift+e", + "type": "string" + }, + "input_visual_line_home": { + "description": "Move to start of visual line in input", + "default": "alt+a", + "type": "string" + }, + "input_visual_line_end": { + "description": "Move to end of visual line in input", + "default": "alt+e", + "type": "string" + }, + "input_select_visual_line_home": { + "description": "Select to start of visual line in input", + "default": "alt+shift+a", + "type": "string" + }, + "input_select_visual_line_end": { + "description": "Select to end of visual line in input", + "default": "alt+shift+e", + "type": "string" + }, + "input_buffer_home": { + "description": "Move to start of buffer in input", + "default": "home", + "type": "string" + }, + "input_buffer_end": { + "description": "Move to end of buffer in input", + "default": "end", + "type": "string" + }, + "input_select_buffer_home": { + "description": "Select to start of buffer in input", + "default": "shift+home", + "type": "string" + }, + "input_select_buffer_end": { + "description": "Select to end of buffer in input", + "default": "shift+end", + "type": "string" + }, + "input_delete_line": { + "description": "Delete line in input", + "default": "ctrl+shift+d", + "type": "string" + }, + "input_delete_to_line_end": { + "description": "Delete to end of line in input", + "default": "ctrl+k", + "type": "string" + }, + "input_delete_to_line_start": { + "description": "Delete to start of line in input", + "default": "ctrl+u", + "type": "string" + }, + "input_backspace": { + "description": "Backspace in input", + "default": "backspace,shift+backspace", + "type": "string" + }, + "input_delete": { + "description": "Delete character in input", + "default": "ctrl+d,delete,shift+delete", + "type": "string" + }, + "input_undo": { + "description": "Undo in input", + "default": "ctrl+-,super+z", + "type": "string" + }, + "input_redo": { + "description": "Redo in input", + "default": "ctrl+.,super+shift+z", + "type": "string" + }, + "input_word_forward": { + "description": "Move word forward in input", + "default": "alt+f,alt+right,ctrl+right", + "type": "string" + }, + "input_word_backward": { + "description": "Move word backward in input", + "default": "alt+b,alt+left,ctrl+left", + "type": "string" + }, + "input_select_word_forward": { + "description": "Select word forward in input", + "default": "alt+shift+f,alt+shift+right", + "type": "string" + }, + "input_select_word_backward": { + "description": "Select word backward in input", + "default": "alt+shift+b,alt+shift+left", + "type": "string" + }, + "input_delete_word_forward": { + "description": "Delete word forward in input", + "default": "alt+d,alt+delete,ctrl+delete", + "type": "string" + }, + "input_delete_word_backward": { + "description": "Delete word backward in input", + "default": "ctrl+w,ctrl+backspace,alt+backspace", + "type": "string" + }, + "history_previous": { + "description": "Previous history item", + "default": "up", + "type": "string" + }, + "history_next": { + "description": "Next history item", + "default": "down", + "type": "string" + }, + "session_child_cycle": { + "description": "Next child session", + "default": "right", + "type": "string" + }, + "session_child_cycle_reverse": { + "description": "Previous child session", + "default": "left", + "type": "string" + }, + "session_parent": { + "description": "Go to parent session", + "default": "up", + "type": "string" + }, + "terminal_suspend": { + "description": "Suspend terminal", + "default": "ctrl+z", + "type": "string" + }, + "terminal_title_toggle": { + "description": "Toggle terminal title", + "default": "none", + "type": "string" + }, + "tips_toggle": { + "description": "Toggle tips on home screen", + "default": "h", + "type": "string" + } + }, + "additionalProperties": false + }, + "LogLevel": { + "description": "Log level", + "type": "string", + "enum": ["DEBUG", "INFO", "WARN", "ERROR"] + }, + "ServerConfig": { + "description": "Server configuration for opencode serve and web commands", + "type": "object", + "properties": { + "port": { + "description": "Port to listen on", + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + }, + "hostname": { + "description": "Hostname to listen on", + "type": "string" + }, + "mdns": { + "description": "Enable mDNS service discovery", + "type": "boolean" + }, + "cors": { + "description": "Additional domains to allow for CORS", + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "PermissionActionConfig": { + "type": "string", + "enum": ["ask", "allow", "deny"] + }, + "PermissionObjectConfig": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "$ref": "#/components/schemas/PermissionActionConfig" + } + }, + "PermissionRuleConfig": { + "anyOf": [ + { + "$ref": "#/components/schemas/PermissionActionConfig" + }, + { + "$ref": "#/components/schemas/PermissionObjectConfig" + } + ] + }, + "PermissionConfig": { + "anyOf": [ + { + "type": "object", + "properties": { + "read": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "edit": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "glob": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "grep": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "list": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "bash": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "task": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "external_directory": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "todowrite": { + "$ref": "#/components/schemas/PermissionActionConfig" + }, + "todoread": { + "$ref": "#/components/schemas/PermissionActionConfig" + }, + "webfetch": { + "$ref": "#/components/schemas/PermissionActionConfig" + }, + "websearch": { + "$ref": "#/components/schemas/PermissionActionConfig" + }, + "codesearch": { + "$ref": "#/components/schemas/PermissionActionConfig" + }, + "lsp": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "doom_loop": { + "$ref": "#/components/schemas/PermissionActionConfig" + } + }, + "additionalProperties": { + "$ref": "#/components/schemas/PermissionRuleConfig" + } + }, + { + "$ref": "#/components/schemas/PermissionActionConfig" + } + ] + }, + "AgentConfig": { + "type": "object", + "properties": { + "model": { + "type": "string" + }, + "temperature": { + "type": "number" + }, + "top_p": { + "type": "number" + }, + "prompt": { + "type": "string" + }, + "tools": { + "description": "@deprecated Use 'permission' field instead", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "boolean" + } + }, + "disable": { + "type": "boolean" + }, + "description": { + "description": "Description of when to use the agent", + "type": "string" + }, + "mode": { + "type": "string", + "enum": ["subagent", "primary", "all"] + }, + "options": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "color": { + "description": "Hex color code for the agent (e.g., #FF5733)", + "type": "string", + "pattern": "^#[0-9a-fA-F]{6}$" + }, + "steps": { + "description": "Maximum number of agentic iterations before forcing text-only response", + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + }, + "maxSteps": { + "description": "@deprecated Use 'steps' field instead.", + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + }, + "permission": { + "$ref": "#/components/schemas/PermissionConfig" + } + }, + "additionalProperties": {} + }, + "ProviderConfig": { + "type": "object", + "properties": { + "api": { + "type": "string" + }, + "name": { + "type": "string" + }, + "env": { + "type": "array", + "items": { + "type": "string" + } + }, + "id": { + "type": "string" + }, + "npm": { + "type": "string" + }, + "models": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "family": { + "type": "string" + }, + "release_date": { + "type": "string" + }, + "attachment": { + "type": "boolean" + }, + "reasoning": { + "type": "boolean" + }, + "temperature": { + "type": "boolean" + }, + "tool_call": { + "type": "boolean" + }, + "interleaved": { + "anyOf": [ + { + "type": "boolean", + "const": true + }, + { + "type": "object", + "properties": { + "field": { + "type": "string", + "enum": ["reasoning_content", "reasoning_details"] + } + }, + "required": ["field"], + "additionalProperties": false + } + ] + }, + "cost": { + "type": "object", + "properties": { + "input": { + "type": "number" + }, + "output": { + "type": "number" + }, + "cache_read": { + "type": "number" + }, + "cache_write": { + "type": "number" + }, + "context_over_200k": { + "type": "object", + "properties": { + "input": { + "type": "number" + }, + "output": { + "type": "number" + }, + "cache_read": { + "type": "number" + }, + "cache_write": { + "type": "number" + } + }, + "required": ["input", "output"] + } + }, + "required": ["input", "output"] + }, + "limit": { + "type": "object", + "properties": { + "context": { + "type": "number" + }, + "output": { + "type": "number" + } + }, + "required": ["context", "output"] + }, + "modalities": { + "type": "object", + "properties": { + "input": { + "type": "array", + "items": { + "type": "string", + "enum": ["text", "audio", "image", "video", "pdf"] + } + }, + "output": { + "type": "array", + "items": { + "type": "string", + "enum": ["text", "audio", "image", "video", "pdf"] + } + } + }, + "required": ["input", "output"] + }, + "experimental": { + "type": "boolean" + }, + "status": { + "type": "string", + "enum": ["alpha", "beta", "deprecated"] + }, + "options": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "headers": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + }, + "provider": { + "type": "object", + "properties": { + "npm": { + "type": "string" + } + }, + "required": ["npm"] + }, + "variants": { + "description": "Variant-specific configuration", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "object", + "properties": { + "disabled": { + "description": "Disable this variant for the model", + "type": "boolean" + } + }, + "additionalProperties": {} + } + } + } + } + }, + "whitelist": { + "type": "array", + "items": { + "type": "string" + } + }, + "blacklist": { + "type": "array", + "items": { + "type": "string" + } + }, + "options": { + "type": "object", + "properties": { + "apiKey": { + "type": "string" + }, + "baseURL": { + "type": "string" + }, + "enterpriseUrl": { + "description": "GitHub Enterprise URL for copilot authentication", + "type": "string" + }, + "setCacheKey": { + "description": "Enable promptCacheKey for this provider (default false)", + "type": "boolean" + }, + "timeout": { + "description": "Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.", + "anyOf": [ + { + "description": "Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.", + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + }, + { + "description": "Disable timeout for this provider entirely.", + "type": "boolean", + "const": false + } + ] + } + }, + "additionalProperties": {} + } + }, + "additionalProperties": false + }, + "McpLocalConfig": { + "type": "object", + "properties": { + "type": { + "description": "Type of MCP server connection", + "type": "string", + "const": "local" + }, + "command": { + "description": "Command and arguments to run the MCP server", + "type": "array", + "items": { + "type": "string" + } + }, + "environment": { + "description": "Environment variables to set when running the MCP server", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + }, + "enabled": { + "description": "Enable or disable the MCP server on startup", + "type": "boolean" + }, + "timeout": { + "description": "Timeout in ms for fetching tools from the MCP server. Defaults to 5000 (5 seconds) if not specified.", + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + } + }, + "required": ["type", "command"], + "additionalProperties": false + }, + "McpOAuthConfig": { + "type": "object", + "properties": { + "clientId": { + "description": "OAuth client ID. If not provided, dynamic client registration (RFC 7591) will be attempted.", + "type": "string" + }, + "clientSecret": { + "description": "OAuth client secret (if required by the authorization server)", + "type": "string" + }, + "scope": { + "description": "OAuth scopes to request during authorization", + "type": "string" + } + }, + "additionalProperties": false + }, + "McpRemoteConfig": { + "type": "object", + "properties": { + "type": { + "description": "Type of MCP server connection", + "type": "string", + "const": "remote" + }, + "url": { + "description": "URL of the remote MCP server", + "type": "string" + }, + "enabled": { + "description": "Enable or disable the MCP server on startup", + "type": "boolean" + }, + "headers": { + "description": "Headers to send with the request", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + }, + "oauth": { + "description": "OAuth authentication configuration for the MCP server. Set to false to disable OAuth auto-detection.", + "anyOf": [ + { + "$ref": "#/components/schemas/McpOAuthConfig" + }, + { + "type": "boolean", + "const": false + } + ] + }, + "timeout": { + "description": "Timeout in ms for fetching tools from the MCP server. Defaults to 5000 (5 seconds) if not specified.", + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + } + }, + "required": ["type", "url"], + "additionalProperties": false + }, + "LayoutConfig": { + "description": "@deprecated Always uses stretch layout.", + "type": "string", + "enum": ["auto", "stretch"] + }, + "Config": { + "type": "object", + "properties": { + "$schema": { + "description": "JSON schema reference for configuration validation", + "type": "string" + }, + "theme": { + "description": "Theme name to use for the interface", + "type": "string" + }, + "keybinds": { + "$ref": "#/components/schemas/KeybindsConfig" + }, + "logLevel": { + "$ref": "#/components/schemas/LogLevel" + }, + "tui": { + "description": "TUI specific settings", + "type": "object", + "properties": { + "scroll_speed": { + "description": "TUI scroll speed", + "type": "number", + "minimum": 0.001 + }, + "scroll_acceleration": { + "description": "Scroll acceleration settings", + "type": "object", + "properties": { + "enabled": { + "description": "Enable scroll acceleration", + "type": "boolean" + } + }, + "required": ["enabled"] + }, + "diff_style": { + "description": "Control diff rendering style: 'auto' adapts to terminal width, 'stacked' always shows single column", + "type": "string", + "enum": ["auto", "stacked"] + } + } + }, + "server": { + "$ref": "#/components/schemas/ServerConfig" + }, + "command": { + "description": "Command configuration, see https://opencode.ai/docs/commands", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "object", + "properties": { + "template": { + "type": "string" + }, + "description": { + "type": "string" + }, + "agent": { + "type": "string" + }, + "model": { + "type": "string" + }, + "subtask": { + "type": "boolean" + } + }, + "required": ["template"] + } + }, + "watcher": { + "type": "object", + "properties": { + "ignore": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "plugin": { + "type": "array", + "items": { + "type": "string" + } + }, + "snapshot": { + "type": "boolean" + }, + "share": { + "description": "Control sharing behavior:'manual' allows manual sharing via commands, 'auto' enables automatic sharing, 'disabled' disables all sharing", + "type": "string", + "enum": ["manual", "auto", "disabled"] + }, + "autoshare": { + "description": "@deprecated Use 'share' field instead. Share newly created sessions automatically", + "type": "boolean" + }, + "autoupdate": { + "description": "Automatically update to the latest version. Set to true to auto-update, false to disable, or 'notify' to show update notifications", + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "const": "notify" + } + ] + }, + "disabled_providers": { + "description": "Disable providers that are loaded automatically", + "type": "array", + "items": { + "type": "string" + } + }, + "enabled_providers": { + "description": "When set, ONLY these providers will be enabled. All other providers will be ignored", + "type": "array", + "items": { + "type": "string" + } + }, + "model": { + "description": "Model to use in the format of provider/model, eg anthropic/claude-2", + "type": "string" + }, + "small_model": { + "description": "Small model to use for tasks like title generation in the format of provider/model", + "type": "string" + }, + "default_agent": { + "description": "Default agent to use when none is specified. Must be a primary agent. Falls back to 'build' if not set or if the specified agent is invalid.", + "type": "string" + }, + "username": { + "description": "Custom username to display in conversations instead of system username", + "type": "string" + }, + "mode": { + "description": "@deprecated Use `agent` field instead.", + "type": "object", + "properties": { + "build": { + "$ref": "#/components/schemas/AgentConfig" + }, + "plan": { + "$ref": "#/components/schemas/AgentConfig" + } + }, + "additionalProperties": { + "$ref": "#/components/schemas/AgentConfig" + } + }, + "agent": { + "description": "Agent configuration, see https://opencode.ai/docs/agent", + "type": "object", + "properties": { + "plan": { + "$ref": "#/components/schemas/AgentConfig" + }, + "build": { + "$ref": "#/components/schemas/AgentConfig" + }, + "general": { + "$ref": "#/components/schemas/AgentConfig" + }, + "explore": { + "$ref": "#/components/schemas/AgentConfig" + }, + "title": { + "$ref": "#/components/schemas/AgentConfig" + }, + "summary": { + "$ref": "#/components/schemas/AgentConfig" + }, + "compaction": { + "$ref": "#/components/schemas/AgentConfig" + } + }, + "additionalProperties": { + "$ref": "#/components/schemas/AgentConfig" + } + }, + "provider": { + "description": "Custom provider configurations and model overrides", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "$ref": "#/components/schemas/ProviderConfig" + } + }, + "mcp": { + "description": "MCP (Model Context Protocol) server configurations", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "anyOf": [ + { + "$ref": "#/components/schemas/McpLocalConfig" + }, + { + "$ref": "#/components/schemas/McpRemoteConfig" + } + ] + } + }, + "formatter": { + "anyOf": [ + { + "type": "boolean", + "const": false + }, + { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "object", + "properties": { + "disabled": { + "type": "boolean" + }, + "command": { + "type": "array", + "items": { + "type": "string" + } + }, + "environment": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + }, + "extensions": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + ] + }, + "lsp": { + "anyOf": [ + { + "type": "boolean", + "const": false + }, + { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "anyOf": [ + { + "type": "object", + "properties": { + "disabled": { + "type": "boolean", + "const": true + } + }, + "required": ["disabled"] + }, + { + "type": "object", + "properties": { + "command": { + "type": "array", + "items": { + "type": "string" + } + }, + "extensions": { + "type": "array", + "items": { + "type": "string" + } + }, + "disabled": { + "type": "boolean" + }, + "env": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + }, + "initialization": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + } + }, + "required": ["command"] + } + ] + } + } + ] + }, + "instructions": { + "description": "Additional instruction files or patterns to include", + "type": "array", + "items": { + "type": "string" + } + }, + "layout": { + "$ref": "#/components/schemas/LayoutConfig" + }, + "permission": { + "$ref": "#/components/schemas/PermissionConfig" + }, + "tools": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "boolean" + } + }, + "enterprise": { + "type": "object", + "properties": { + "url": { + "description": "Enterprise URL", + "type": "string" + } + } + }, + "compaction": { + "type": "object", + "properties": { + "auto": { + "description": "Enable automatic compaction when context is full (default: true)", + "type": "boolean" + }, + "prune": { + "description": "Enable pruning of old tool outputs (default: true)", + "type": "boolean" + } + } + }, + "experimental": { + "type": "object", + "properties": { + "hook": { + "type": "object", + "properties": { + "file_edited": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "array", + "items": { + "type": "object", + "properties": { + "command": { + "type": "array", + "items": { + "type": "string" + } + }, + "environment": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + } + }, + "required": ["command"] + } + } + }, + "session_completed": { + "type": "array", + "items": { + "type": "object", + "properties": { + "command": { + "type": "array", + "items": { + "type": "string" + } + }, + "environment": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + } + }, + "required": ["command"] + } + } + } + }, + "chatMaxRetries": { + "description": "Number of retries for chat completions on failure", + "type": "number" + }, + "disable_paste_summary": { + "type": "boolean" + }, + "batch_tool": { + "description": "Enable the batch tool", + "type": "boolean" + }, + "openTelemetry": { + "description": "Enable OpenTelemetry spans for AI SDK calls (using the 'experimental_telemetry' flag)", + "type": "boolean" + }, + "primary_tools": { + "description": "Tools that should only be available to primary agents.", + "type": "array", + "items": { + "type": "string" + } + }, + "continue_loop_on_deny": { + "description": "Continue the agent loop when a tool call is denied", + "type": "boolean" + }, + "mcp_timeout": { + "description": "Timeout in milliseconds for model context protocol (MCP) requests", + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + } + } + } + }, + "additionalProperties": false + }, + "ToolIDs": { + "type": "array", + "items": { + "type": "string" + } + }, + "ToolListItem": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "description": { + "type": "string" + }, + "parameters": {} + }, + "required": ["id", "description", "parameters"] + }, + "ToolList": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ToolListItem" + } + }, + "Path": { + "type": "object", + "properties": { + "home": { + "type": "string" + }, + "state": { + "type": "string" + }, + "config": { + "type": "string" + }, + "worktree": { + "type": "string" + }, + "directory": { + "type": "string" + } + }, + "required": ["home", "state", "config", "worktree", "directory"] + }, + "VcsInfo": { + "type": "object", + "properties": { + "branch": { + "type": "string" + } + }, + "required": ["branch"] + }, + "TextPartInput": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "const": "text" + }, + "text": { + "type": "string" + }, + "synthetic": { + "type": "boolean" + }, + "ignored": { + "type": "boolean" + }, + "time": { + "type": "object", + "properties": { + "start": { + "type": "number" + }, + "end": { + "type": "number" + } + }, + "required": ["start"] + }, + "metadata": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + } + }, + "required": ["type", "text"] + }, + "FilePartInput": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "const": "file" + }, + "mime": { + "type": "string" + }, + "filename": { + "type": "string" + }, + "url": { + "type": "string" + }, + "source": { + "$ref": "#/components/schemas/FilePartSource" + } + }, + "required": ["type", "mime", "url"] + }, + "AgentPartInput": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "const": "agent" + }, + "name": { + "type": "string" + }, + "source": { + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "start": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + "end": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + } + }, + "required": ["value", "start", "end"] + } + }, + "required": ["type", "name"] + }, + "SubtaskPartInput": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "const": "subtask" + }, + "prompt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "agent": { + "type": "string" + }, + "command": { + "type": "string" + } + }, + "required": ["type", "prompt", "description", "agent"] + }, + "Command": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "agent": { + "type": "string" + }, + "model": { + "type": "string" + }, + "mcp": { + "type": "boolean" + }, + "template": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string" + } + ] + }, + "subtask": { + "type": "boolean" + }, + "hints": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": ["name", "template", "hints"] + }, + "Model": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "providerID": { + "type": "string" + }, + "api": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "url": { + "type": "string" + }, + "npm": { + "type": "string" + } + }, + "required": ["id", "url", "npm"] + }, + "name": { + "type": "string" + }, + "family": { + "type": "string" + }, + "capabilities": { + "type": "object", + "properties": { + "temperature": { + "type": "boolean" + }, + "reasoning": { + "type": "boolean" + }, + "attachment": { + "type": "boolean" + }, + "toolcall": { + "type": "boolean" + }, + "input": { + "type": "object", + "properties": { + "text": { + "type": "boolean" + }, + "audio": { + "type": "boolean" + }, + "image": { + "type": "boolean" + }, + "video": { + "type": "boolean" + }, + "pdf": { + "type": "boolean" + } + }, + "required": ["text", "audio", "image", "video", "pdf"] + }, + "output": { + "type": "object", + "properties": { + "text": { + "type": "boolean" + }, + "audio": { + "type": "boolean" + }, + "image": { + "type": "boolean" + }, + "video": { + "type": "boolean" + }, + "pdf": { + "type": "boolean" + } + }, + "required": ["text", "audio", "image", "video", "pdf"] + }, + "interleaved": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "object", + "properties": { + "field": { + "type": "string", + "enum": ["reasoning_content", "reasoning_details"] + } + }, + "required": ["field"] + } + ] + } + }, + "required": [ + "temperature", + "reasoning", + "attachment", + "toolcall", + "input", + "output", + "interleaved" + ] + }, + "cost": { + "type": "object", + "properties": { + "input": { + "type": "number" + }, + "output": { + "type": "number" + }, + "cache": { + "type": "object", + "properties": { + "read": { + "type": "number" + }, + "write": { + "type": "number" + } + }, + "required": ["read", "write"] + }, + "experimentalOver200K": { + "type": "object", + "properties": { + "input": { + "type": "number" + }, + "output": { + "type": "number" + }, + "cache": { + "type": "object", + "properties": { + "read": { + "type": "number" + }, + "write": { + "type": "number" + } + }, + "required": ["read", "write"] + } + }, + "required": ["input", "output", "cache"] + } + }, + "required": ["input", "output", "cache"] + }, + "limit": { + "type": "object", + "properties": { + "context": { + "type": "number" + }, + "output": { + "type": "number" + } + }, + "required": ["context", "output"] + }, + "status": { + "type": "string", + "enum": ["alpha", "beta", "deprecated", "active"] + }, + "options": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "headers": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + }, + "release_date": { + "type": "string" + }, + "variants": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + } + } + }, + "required": [ + "id", + "providerID", + "api", + "name", + "capabilities", + "cost", + "limit", + "status", + "options", + "headers", + "release_date" + ] + }, + "Provider": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "source": { + "type": "string", + "enum": ["env", "config", "custom", "api"] + }, + "env": { + "type": "array", + "items": { + "type": "string" + } + }, + "key": { + "type": "string" + }, + "options": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "models": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "$ref": "#/components/schemas/Model" + } + } + }, + "required": ["id", "name", "source", "env", "options", "models"] + }, + "ProviderAuthMethod": { + "type": "object", + "properties": { + "type": { + "anyOf": [ + { + "type": "string", + "const": "oauth" + }, + { + "type": "string", + "const": "api" + } + ] + }, + "label": { + "type": "string" + } + }, + "required": ["type", "label"] + }, + "ProviderAuthAuthorization": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "method": { + "anyOf": [ + { + "type": "string", + "const": "auto" + }, + { + "type": "string", + "const": "code" + } + ] + }, + "instructions": { + "type": "string" + } + }, + "required": ["url", "method", "instructions"] + }, + "Symbol": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "kind": { + "type": "number" + }, + "location": { + "type": "object", + "properties": { + "uri": { + "type": "string" + }, + "range": { + "$ref": "#/components/schemas/Range" + } + }, + "required": ["uri", "range"] + } + }, + "required": ["name", "kind", "location"] + }, + "FileNode": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "absolute": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["file", "directory"] + }, + "ignored": { + "type": "boolean" + } + }, + "required": ["name", "path", "absolute", "type", "ignored"] + }, + "FileContent": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "text" + }, + "content": { + "type": "string" + }, + "diff": { + "type": "string" + }, + "patch": { + "type": "object", + "properties": { + "oldFileName": { + "type": "string" + }, + "newFileName": { + "type": "string" + }, + "oldHeader": { + "type": "string" + }, + "newHeader": { + "type": "string" + }, + "hunks": { + "type": "array", + "items": { + "type": "object", + "properties": { + "oldStart": { + "type": "number" + }, + "oldLines": { + "type": "number" + }, + "newStart": { + "type": "number" + }, + "newLines": { + "type": "number" + }, + "lines": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": ["oldStart", "oldLines", "newStart", "newLines", "lines"] + } + }, + "index": { + "type": "string" + } + }, + "required": ["oldFileName", "newFileName", "hunks"] + }, + "encoding": { + "type": "string", + "const": "base64" + }, + "mimeType": { + "type": "string" + } + }, + "required": ["type", "content"] + }, + "File": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "added": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + "removed": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + "status": { + "type": "string", + "enum": ["added", "deleted", "modified"] + } + }, + "required": ["path", "added", "removed", "status"] + }, + "Agent": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "mode": { + "type": "string", + "enum": ["subagent", "primary", "all"] + }, + "native": { + "type": "boolean" + }, + "hidden": { + "type": "boolean" + }, + "topP": { + "type": "number" + }, + "temperature": { + "type": "number" + }, + "color": { + "type": "string" + }, + "permission": { + "$ref": "#/components/schemas/PermissionRuleset" + }, + "model": { + "type": "object", + "properties": { + "modelID": { + "type": "string" + }, + "providerID": { + "type": "string" + } + }, + "required": ["modelID", "providerID"] + }, + "prompt": { + "type": "string" + }, + "options": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "steps": { + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + } + }, + "required": ["name", "mode", "permission", "options"] + }, + "MCPStatusConnected": { + "type": "object", + "properties": { + "status": { + "type": "string", + "const": "connected" + } + }, + "required": ["status"] + }, + "MCPStatusDisabled": { + "type": "object", + "properties": { + "status": { + "type": "string", + "const": "disabled" + } + }, + "required": ["status"] + }, + "MCPStatusFailed": { + "type": "object", + "properties": { + "status": { + "type": "string", + "const": "failed" + }, + "error": { + "type": "string" + } + }, + "required": ["status", "error"] + }, + "MCPStatusNeedsAuth": { + "type": "object", + "properties": { + "status": { + "type": "string", + "const": "needs_auth" + } + }, + "required": ["status"] + }, + "MCPStatusNeedsClientRegistration": { + "type": "object", + "properties": { + "status": { + "type": "string", + "const": "needs_client_registration" + }, + "error": { + "type": "string" + } + }, + "required": ["status", "error"] + }, + "MCPStatus": { + "anyOf": [ + { + "$ref": "#/components/schemas/MCPStatusConnected" + }, + { + "$ref": "#/components/schemas/MCPStatusDisabled" + }, + { + "$ref": "#/components/schemas/MCPStatusFailed" + }, + { + "$ref": "#/components/schemas/MCPStatusNeedsAuth" + }, + { + "$ref": "#/components/schemas/MCPStatusNeedsClientRegistration" + } + ] + }, + "LSPStatus": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "root": { + "type": "string" + }, + "status": { + "anyOf": [ + { + "type": "string", + "const": "connected" + }, + { + "type": "string", + "const": "error" + } + ] + } + }, + "required": ["id", "name", "root", "status"] + }, + "FormatterStatus": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "extensions": { + "type": "array", + "items": { + "type": "string" + } + }, + "enabled": { + "type": "boolean" + } + }, + "required": ["name", "extensions", "enabled"] + }, + "OAuth": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "oauth" + }, + "refresh": { + "type": "string" + }, + "access": { + "type": "string" + }, + "expires": { + "type": "number" + }, + "enterpriseUrl": { + "type": "string" + } + }, + "required": ["type", "refresh", "access", "expires"] + }, + "ApiAuth": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "api" + }, + "key": { + "type": "string" + } + }, + "required": ["type", "key"] + }, + "WellKnownAuth": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "wellknown" + }, + "key": { + "type": "string" + }, + "token": { + "type": "string" + } + }, + "required": ["type", "key", "token"] + }, + "Auth": { + "anyOf": [ + { + "$ref": "#/components/schemas/OAuth" + }, + { + "$ref": "#/components/schemas/ApiAuth" + }, + { + "$ref": "#/components/schemas/WellKnownAuth" + } + ] + } + } + } +} diff --git a/dev/playground.ts b/dev/playground.ts index 893e9bcd14..00625afd95 100644 --- a/dev/playground.ts +++ b/dev/playground.ts @@ -1,8 +1,12 @@ import type { DefinePlugin, IR } from '@hey-api/openapi-ts'; +import { fromOpenApi } from '@msw/source/open-api'; +import { setupServer } from 'msw/node'; -// import { createOpencode } from '@opencode-ai/sdk'; import { client } from './gen/typescript/client.gen'; +// import { createOpencode } from '@opencode-ai/sdk'; +import { createMswHandlers } from './gen/typescript/msw.gen'; import { OpenCode } from './gen/typescript/sdk.gen'; +import spec from './opencode.json'; type MyPluginConfig = { readonly name: 'myplugin' }; type MyPlugin = DefinePlugin; @@ -17,6 +21,78 @@ export const handler: MyPlugin['Handler'] = ({ plugin }) => { }); }; +const sourceHandlers = await fromOpenApi({ + ...(spec as any), + basePath: 'https://api.example.com', +}); +const handlers = createMswHandlers({ + baseUrl: 'https://api.example.com', +}); + +const server = setupServer( + // ...sourceHandlers, + ...handlers.all({ + one: { + // projectUpdate(info) { + // console.log('Received request for projectUpdate with info:', info); + // }, + projectUpdate: [ + undefined, + // (info) => { + // console.log('Received request for projectUpdate with info:', info); + // }, + // { + // result: { + // id: '123', + // name: 'Updated Project Name', + // time: { + // created: 1678900000000, + // updated: 1678900000000, + // }, + // worktree: 'main', + // icon: { + // url: 'https://example.com/icon.png', + // color: 'blue', + // }, + // vcs: 'git', + // }, + // status: 200, + // }, + { + // baseUrl: 'https://api.example.com', + }, + ], + // projectUpdate: { + // result: { + // id: '123', + // name: 'Updated Project Name', + // time: { + // created: 1678900000000, + // updated: 1678900000000, + // }, + // worktree: 'main', + // icon: { + // url: 'https://example.com/icon.png', + // color: 'blue', + // }, + // vcs: 'git', + // }, + // }, + }, + }), + // handlers.one.projectUpdate(), + handlers.one.globalEvent({ + result: { + directory: 'main', + payload: { + properties: {}, + type: 'global.disposed', + }, + }, + }), +); +server.listen(); + async function run() { // const { client, server } = await createOpencode(); // console.log(client, server); @@ -24,16 +100,27 @@ async function run() { baseUrl: 'https://api.example.com', }); const sdk = new OpenCode({ client }); - sdk.tui.publish({ - body: { - properties: { - message: 'Hello from Hey API OpenAPI TypeScript Playground!', - variant: 'success', - }, - type: 'tui.toast.show', - }, + // const published = await sdk.tui.publish({ + // body: { + // properties: { + // message: 'Hello from Hey API OpenAPI TypeScript Playground!', + // variant: 'success', + // }, + // type: 'tui.toast.show', + // }, + // directory: 'main', + // }); + // console.log('Published:', published.data, published.error); + const project = await sdk.project.update({ + projectID: '123', directory: 'main', + icon: { + color: 'blue', + url: 'https://example.com/icon.png', + }, + name: 'Updated Project Name', }); + console.log('Updated Project:', project.data, project.error); } run(); diff --git a/dev/typescript/presets.ts b/dev/typescript/presets.ts index 4a38c5cc1e..50315abcb8 100644 --- a/dev/typescript/presets.ts +++ b/dev/typescript/presets.ts @@ -4,6 +4,7 @@ export type PluginConfig = NonNullable[number export const presets = { angular: () => [ + /** Angular HttpClient with typed services */ { httpRequests: 'flat', name: '@angular/common', @@ -11,7 +12,9 @@ export const presets = { ], full: () => [ /** Full kitchen sink for comprehensive testing */ - '@hey-api/typescript', + { + name: '@hey-api/typescript', + }, { name: '@hey-api/sdk', paramsStructure: 'flat', @@ -30,6 +33,20 @@ export const presets = { }, }, ], + msw: () => [ + /** SDK + MSW handlers */ + { + name: '@hey-api/sdk', + operations: { + containerName: 'OpenCode', + strategy: 'single', + }, + paramsStructure: 'flat', + }, + { + name: 'msw', + }, + ], rpc: () => [ /** RPC-style SDK with Zod validation */ 'orpc', @@ -37,7 +54,6 @@ export const presets = { ], sdk: () => [ /** SDK with types */ - '@hey-api/typescript', { name: '@hey-api/sdk', operations: { @@ -49,8 +65,6 @@ export const presets = { ], tanstack: () => [ /** SDK + TanStack Query */ - '@hey-api/typescript', - '@hey-api/sdk', { name: '@tanstack/react-query', queryKeys: { @@ -64,7 +78,6 @@ export const presets = { ], validated: () => [ /** SDK + Zod validation */ - '@hey-api/typescript', { name: '@hey-api/sdk', validator: 'zod', diff --git a/docs/.vitepress/config/en.ts b/docs/.vitepress/config/en.ts index c0dbe96200..0419ce78ec 100644 --- a/docs/.vitepress/config/en.ts +++ b/docs/.vitepress/config/en.ts @@ -188,6 +188,10 @@ export default defineConfig({ { collapsed: true, items: [ + { + link: '/openapi-ts/plugins/msw', + text: 'MSW', + }, { link: '/openapi-ts/plugins/chance', text: 'Chance soon', @@ -200,10 +204,6 @@ export default defineConfig({ link: '/openapi-ts/plugins/falso', text: 'Falso soon', }, - { - link: '/openapi-ts/plugins/msw', - text: 'MSW soon', - }, { link: '/openapi-ts/plugins/nock', text: 'Nock soon', diff --git a/docs/openapi-ts/mocks.md b/docs/openapi-ts/mocks.md index 7b702e511c..4fbc67694d 100644 --- a/docs/openapi-ts/mocks.md +++ b/docs/openapi-ts/mocks.md @@ -11,10 +11,10 @@ Realistic mock data is an important component of every robust development proces Hey API natively supports the following mocking frameworks. +- [MSW](/openapi-ts/plugins/msw) - [Chance](/openapi-ts/plugins/chance) Soon - [Faker](/openapi-ts/plugins/faker) Soon - [Falso](/openapi-ts/plugins/falso) Soon -- [MSW](/openapi-ts/plugins/msw) Soon - [Nock](/openapi-ts/plugins/nock) Soon - [Supertest](/openapi-ts/plugins/supertest) Soon diff --git a/docs/openapi-ts/plugins/msw.md b/docs/openapi-ts/plugins/msw.md index 56decd8c05..b2900cc7ed 100644 --- a/docs/openapi-ts/plugins/msw.md +++ b/docs/openapi-ts/plugins/msw.md @@ -4,15 +4,243 @@ description: MSW plugin for Hey API. Compatible with all our features. --- -# MSW soon + +

MSW v2

+ +
- +::: warning +MSW plugin is currently in beta. The interface might change before it becomes stable. We encourage you to leave feedback on [GitHub](https://github.com/hey-api/openapi-ts/issues). +::: ### About [MSW](https://mswjs.io) is an API mocking library that allows you to write client-agnostic mocks and reuse them across any frameworks, tools, and environments. +The MSW plugin for Hey API generates type-safe mock handler factories from your OpenAPI spec, fully compatible with SDKs, transformers, and all core features. + +### Collaborators + + + +## Features + +- MSW v2 support +- seamless integration with `@hey-api/openapi-ts` ecosystem +- type-safe mock handlers +- minimal learning curve thanks to extending the underlying technology + +## Installation + +In your [configuration](/openapi-ts/get-started), add `msw` to your plugins and you'll be ready to generate MSW artifacts. :tada: + +```js +export default { + input: 'hey-api/backend', // sign up at app.heyapi.dev + output: 'src/client', + plugins: [ + // ...other plugins + 'msw', // [!code ++] + ], +}; +``` + +## Output + +The MSW plugin will generate the following artifacts, depending on the input specification. + +### Handler Exports + +Each operation is exported as a handler creator named `Mock`. These use a wildcard (`*`) base URL so they match requests regardless of origin. A `getAllMocks` function is also exported. + +```ts +import { getPetByIdMock, getAllMocks } from './client/msw.gen'; +``` + +## Usage + +### Static Response + +The simplest way to mock an endpoint is to provide a static response object with a `result` property. The `status` property is optional — when omitted, it defaults to the operation's dominant success status code (e.g. `200`). + +```ts +import { setupServer } from 'msw/node'; +import { getPetByIdMock } from './client/msw.gen'; + +const mockServer = setupServer( + // result only — status defaults to the dominant success code (200) + getPetByIdMock({ result: { id: 1, name: 'Fido' } }), + + // explicit status code + getPetByIdMock({ status: 200, result: { id: 1, name: 'Fido' } }), + + // type error if result type is incorrect + // @ts-expect-error + getPetByIdMock({ result: 'wrong type' }), +); +``` + +### Custom Resolver + +For more control, pass an MSW `HttpResponseResolver` function. The resolver receives typed path parameters and request body when available. + +```ts +import { delay, HttpResponse } from 'msw'; +import { setupServer } from 'msw/node'; +import { getPetByIdMock, updatePetMock } from './client/msw.gen'; + +const mockServer = setupServer( + // custom resolver with typed params and body + updatePetMock(async ({ request, params }) => { + const body = await request.json(); + return HttpResponse.json({ id: Number(params.petId), ...body }, { status: 200 }); + }), + + // async resolver with delay + getPetByIdMock(async () => { + await delay(100); + return HttpResponse.json({ id: 1, name: 'Fido' }); + }), +); +``` + +::: tip +Path parameters are typed as `string` because MSW normalizes all path parameters to strings. Use `Number()` or similar conversions if you need numeric values. +::: + +### Operations Without Responses + +For operations that don't define a response type, the handler creator can be invoked without arguments or with a custom resolver function. + +```ts +import { deletePetMock } from './client/msw.gen'; + +const mockServer = setupServer( + deletePetMock(), + + deletePetMock(() => new HttpResponse(null, { status: 204 })), +); +``` + +### Response Examples + +When your OpenAPI spec includes response examples, the generated handlers will use them as default values. This means you can call the handler without arguments and it will return the example response automatically. + +```ts +import { getFooMock } from './client/msw.gen'; + +const mockServer = setupServer( + // uses the example response from the OpenAPI spec as default + getFooMock(), + + // you can still override with a custom response + getFooMock({ result: { name: 'Custom' } }), +); +``` + +By default, `valueSources` is set to `['example']`, which embeds OpenAPI examples in the generated output. To disable this, set `valueSources` to an empty array. + +::: code-group + +```js [config] +export default { + // ...other options + plugins: [ + // ...other plugins + { + name: 'msw', + valueSources: [], // [!code ++] + }, + ], +}; +``` + +::: + +### All Handlers (`getAllMocks`) + +The `getAllMocks` function generates handlers for all operations at once. This is useful for quickly setting up a mock server without manually listing each operation. + +```ts +import { setupServer } from 'msw/node'; +import { getAllMocks } from './client/msw.gen'; + +const server = setupServer(...getAllMocks()); +``` + +#### `onMissingMock` + +Some operations require a response argument (because they have no default example value). The `onMissingMock` option controls what happens for these operations: + +- `'skip'` (default) — skips handlers that require an argument, only including operations that have default values or no response type +- `'error'` — includes all handlers, but operations without a provided response return a `501` error with the message `'[heyapi-msw] The mock of this request is not implemented.'` + +```ts +// strict mode: all endpoints are mocked, missing ones return 501 +const server = setupServer(...getAllMocks({ onMissingMock: 'error' })); +``` + +#### `overrides` + +Use `overrides` to provide static responses for specific operations. The keys are handler names (operation ID suffixed with `Mock`) and the values are the same as what you'd pass to the individual handler creator. + +```ts +import { setupServer } from 'msw/node'; +import { getAllMocks } from './client/msw.gen'; + +const server = setupServer( + ...getAllMocks({ + onMissingMock: 'skip', + overrides: { + getPetByIdMock: { + result: { id: 1, name: 'Fido', photoUrls: [] }, + }, + }, + }), +); +``` + +When an override is provided for a required handler, it will always be included regardless of the `onMissingMock` setting. + +### Custom Base URL + +The individually exported handlers use a wildcard (`*`) base URL that matches requests regardless of origin. If you need handlers bound to a specific base URL, use the `createMswHandlerFactory` function: + +```ts +import { setupServer } from 'msw/node'; +import { createMswHandlerFactory } from './client/msw.gen'; + +const createMock = createMswHandlerFactory({ + baseUrl: 'http://localhost:8000', +}); + +const server = setupServer(createMock.getPetByIdMock({ result: { id: 1, name: 'Fido' } })); +``` + +When called without arguments, the factory infers the base URL from the OpenAPI spec's `servers` field. + +### Handler Options + +[Handler options](https://mswjs.io/docs/api/http#handler-options) can be provided. The object will be passed on to MSW helpers. + +```ts +const mockServer = setupServer(getPetByIdMock({ result: { id: 1, name: 'Fido' } }, { once: true })); +``` + +## Known Limitations + +- Query parameters are not typed in the resolver. MSW doesn't provide typed query params natively — use `new URL(request.url).searchParams` instead. +- The response type generic is omitted from `HttpResponseResolver` to avoid MSW's `DefaultBodyType` constraint issues with union and void response types. + +## API + +You can view the complete list of options in the [UserConfig](https://github.com/hey-api/openapi-ts/blob/main/packages/openapi-ts/src/plugins/msw/types.ts) interface. + + diff --git a/examples/openapi-ts-fetch/openapi-ts.config.ts b/examples/openapi-ts-fetch/openapi-ts.config.ts index acb2c6549c..535ba57340 100644 --- a/examples/openapi-ts-fetch/openapi-ts.config.ts +++ b/examples/openapi-ts-fetch/openapi-ts.config.ts @@ -18,5 +18,6 @@ export default defineConfig({ enums: 'javascript', name: '@hey-api/typescript', }, + 'msw', ], }); diff --git a/examples/openapi-ts-fetch/package.json b/examples/openapi-ts-fetch/package.json index 01c2339396..337c70b099 100644 --- a/examples/openapi-ts-fetch/package.json +++ b/examples/openapi-ts-fetch/package.json @@ -9,6 +9,7 @@ "lint": "eslint . --report-unused-disable-directives --max-warnings 0", "openapi-ts": "openapi-ts", "preview": "vite preview", + "test": "vitest run", "typecheck": "tsgo --noEmit" }, "dependencies": { diff --git a/examples/openapi-ts-fetch/src/client/msw.gen.ts b/examples/openapi-ts-fetch/src/client/msw.gen.ts new file mode 100644 index 0000000000..eddb85d63d --- /dev/null +++ b/examples/openapi-ts-fetch/src/client/msw.gen.ts @@ -0,0 +1,907 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { + http, + type HttpHandler, + HttpResponse, + type HttpResponseResolver, + type RequestHandlerOptions as RequestHandlerOptions2, +} from 'msw'; + +import type { + AddPetData, + AddPetResponses, + CreateUserData, + CreateUserResponses, + CreateUsersWithListInputData, + CreateUsersWithListInputResponses, + DeleteOrderResponses, + DeletePetResponses, + DeleteUserResponses, + FindPetsByStatusResponses, + FindPetsByTagsResponses, + GetInventoryResponses, + GetOrderByIdResponses, + GetPetByIdResponses, + GetUserByNameResponses, + LoginUserResponses, + LogoutUserResponses, + PlaceOrderData, + PlaceOrderResponses, + UpdatePetData, + UpdatePetResponses, + UpdatePetWithFormResponses, + UpdateUserData, + UpdateUserResponses, + UploadFileData, + UploadFileResponses, +} from './types.gen'; + +export type RequestHandlerOptions = RequestHandlerOptions2 & { + baseUrl?: string; + responseFallback?: 'error' | 'passthrough'; +}; + +/** + * Handler for the `POST /pet` operation. + */ +export function handleAddPet( + resolver?: + | { + result: AddPetResponses[200]; + status?: 200; + } + | HttpResponseResolver, + options?: RequestHandlerOptions, +): HttpHandler { + return http.post( + `${options?.baseUrl ?? '*'}/pet`, + (info) => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return HttpResponse.json(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented', + }); + }, + options, + ); +} + +/** + * Handler for the `PUT /pet` operation. + */ +export function handleUpdatePet( + resolver?: + | { + result: UpdatePetResponses[200]; + status?: 200; + } + | HttpResponseResolver, + options?: RequestHandlerOptions, +): HttpHandler { + return http.put( + `${options?.baseUrl ?? '*'}/pet`, + (info) => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return HttpResponse.json(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented', + }); + }, + options, + ); +} + +/** + * Handler for the `GET /pet/findByStatus` operation. + */ +export function handleFindPetsByStatus( + resolver?: + | { + result: FindPetsByStatusResponses[200]; + status?: 200; + } + | HttpResponseResolver, + options?: RequestHandlerOptions, +): HttpHandler { + return http.get( + `${options?.baseUrl ?? '*'}/pet/findByStatus`, + (info) => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return HttpResponse.json(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented', + }); + }, + options, + ); +} + +/** + * Handler for the `GET /pet/findByTags` operation. + */ +export function handleFindPetsByTags( + resolver?: + | { + result: FindPetsByTagsResponses[200]; + status?: 200; + } + | HttpResponseResolver, + options?: RequestHandlerOptions, +): HttpHandler { + return http.get( + `${options?.baseUrl ?? '*'}/pet/findByTags`, + (info) => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return HttpResponse.json(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented', + }); + }, + options, + ); +} + +/** + * Handler for the `DELETE /pet/{petId}` operation. + */ +export function handleDeletePet( + resolver?: + | { + result: DeletePetResponses[200]; + status?: 200; + } + | HttpResponseResolver< + { + petId: string; + }, + never + >, + options?: RequestHandlerOptions, +): HttpHandler { + return http.delete< + { + petId: string; + }, + never + >( + `${options?.baseUrl ?? '*'}/pet/:petId`, + (info) => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return new HttpResponse(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented', + }); + }, + options, + ); +} + +/** + * Handler for the `GET /pet/{petId}` operation. + */ +export function handleGetPetById( + resolver?: + | { + result: GetPetByIdResponses[200]; + status?: 200; + } + | HttpResponseResolver< + { + petId: string; + }, + never + >, + options?: RequestHandlerOptions, +): HttpHandler { + return http.get< + { + petId: string; + }, + never + >( + `${options?.baseUrl ?? '*'}/pet/:petId`, + (info) => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return HttpResponse.json(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented', + }); + }, + options, + ); +} + +/** + * Handler for the `POST /pet/{petId}` operation. + */ +export function handleUpdatePetWithForm( + resolver?: + | { + result: UpdatePetWithFormResponses[200]; + status?: 200; + } + | HttpResponseResolver< + { + petId: string; + }, + never + >, + options?: RequestHandlerOptions, +): HttpHandler { + return http.post< + { + petId: string; + }, + never + >( + `${options?.baseUrl ?? '*'}/pet/:petId`, + (info) => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return HttpResponse.json(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented', + }); + }, + options, + ); +} + +/** + * Handler for the `POST /pet/{petId}/uploadImage` operation. + */ +export function handleUploadFile( + resolver?: + | { + result: UploadFileResponses[200]; + status?: 200; + } + | HttpResponseResolver< + { + petId: string; + }, + UploadFileData['body'] + >, + options?: RequestHandlerOptions, +): HttpHandler { + return http.post< + { + petId: string; + }, + UploadFileData['body'] + >( + `${options?.baseUrl ?? '*'}/pet/:petId/uploadImage`, + (info) => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return HttpResponse.json(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented', + }); + }, + options, + ); +} + +/** + * Handler for the `GET /store/inventory` operation. + */ +export function handleGetInventory( + resolver?: + | { + result: GetInventoryResponses[200]; + status?: 200; + } + | HttpResponseResolver, + options?: RequestHandlerOptions, +): HttpHandler { + return http.get( + `${options?.baseUrl ?? '*'}/store/inventory`, + (info) => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return HttpResponse.json(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented', + }); + }, + options, + ); +} + +/** + * Handler for the `POST /store/order` operation. + */ +export function handlePlaceOrder( + resolver?: + | { + result: PlaceOrderResponses[200]; + status?: 200; + } + | HttpResponseResolver, + options?: RequestHandlerOptions, +): HttpHandler { + return http.post( + `${options?.baseUrl ?? '*'}/store/order`, + (info) => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return HttpResponse.json(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented', + }); + }, + options, + ); +} + +/** + * Handler for the `DELETE /store/order/{orderId}` operation. + */ +export function handleDeleteOrder( + resolver?: + | { + result: DeleteOrderResponses[200]; + status?: 200; + } + | HttpResponseResolver< + { + orderId: string; + }, + never + >, + options?: RequestHandlerOptions, +): HttpHandler { + return http.delete< + { + orderId: string; + }, + never + >( + `${options?.baseUrl ?? '*'}/store/order/:orderId`, + (info) => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return new HttpResponse(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented', + }); + }, + options, + ); +} + +/** + * Handler for the `GET /store/order/{orderId}` operation. + */ +export function handleGetOrderById( + resolver?: + | { + result: GetOrderByIdResponses[200]; + status?: 200; + } + | HttpResponseResolver< + { + orderId: string; + }, + never + >, + options?: RequestHandlerOptions, +): HttpHandler { + return http.get< + { + orderId: string; + }, + never + >( + `${options?.baseUrl ?? '*'}/store/order/:orderId`, + (info) => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return HttpResponse.json(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented', + }); + }, + options, + ); +} + +/** + * Handler for the `POST /user` operation. + */ +export function handleCreateUser( + resolver?: + | { + result: CreateUserResponses[200]; + status?: 200; + } + | HttpResponseResolver, + options?: RequestHandlerOptions, +): HttpHandler { + return http.post( + `${options?.baseUrl ?? '*'}/user`, + (info) => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return HttpResponse.json(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented', + }); + }, + options, + ); +} + +/** + * Handler for the `POST /user/createWithList` operation. + */ +export function handleCreateUsersWithListInput( + resolver?: + | { + result: CreateUsersWithListInputResponses[200]; + status?: 200; + } + | HttpResponseResolver, + options?: RequestHandlerOptions, +): HttpHandler { + return http.post( + `${options?.baseUrl ?? '*'}/user/createWithList`, + (info) => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return HttpResponse.json(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented', + }); + }, + options, + ); +} + +/** + * Handler for the `GET /user/login` operation. + */ +export function handleLoginUser( + resolver?: + | { + result: LoginUserResponses[200]; + status?: 200; + } + | HttpResponseResolver, + options?: RequestHandlerOptions, +): HttpHandler { + return http.get( + `${options?.baseUrl ?? '*'}/user/login`, + (info) => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return HttpResponse.json(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented', + }); + }, + options, + ); +} + +/** + * Handler for the `GET /user/logout` operation. + */ +export function handleLogoutUser( + resolver?: + | { + result: LogoutUserResponses[200]; + status?: 200; + } + | HttpResponseResolver, + options?: RequestHandlerOptions, +): HttpHandler { + return http.get( + `${options?.baseUrl ?? '*'}/user/logout`, + (info) => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return new HttpResponse(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented', + }); + }, + options, + ); +} + +/** + * Handler for the `DELETE /user/{username}` operation. + */ +export function handleDeleteUser( + resolver?: + | { + result: DeleteUserResponses[200]; + status?: 200; + } + | HttpResponseResolver< + { + username: string; + }, + never + >, + options?: RequestHandlerOptions, +): HttpHandler { + return http.delete< + { + username: string; + }, + never + >( + `${options?.baseUrl ?? '*'}/user/:username`, + (info) => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return new HttpResponse(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented', + }); + }, + options, + ); +} + +/** + * Handler for the `GET /user/{username}` operation. + */ +export function handleGetUserByName( + resolver?: + | { + result: GetUserByNameResponses[200]; + status?: 200; + } + | HttpResponseResolver< + { + username: string; + }, + never + >, + options?: RequestHandlerOptions, +): HttpHandler { + return http.get< + { + username: string; + }, + never + >( + `${options?.baseUrl ?? '*'}/user/:username`, + (info) => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return HttpResponse.json(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented', + }); + }, + options, + ); +} + +/** + * Handler for the `PUT /user/{username}` operation. + */ +export function handleUpdateUser( + resolver?: + | { + result: UpdateUserResponses[200]; + status?: 200; + } + | HttpResponseResolver< + { + username: string; + }, + UpdateUserData['body'] + >, + options?: RequestHandlerOptions, +): HttpHandler { + return http.put< + { + username: string; + }, + UpdateUserData['body'] + >( + `${options?.baseUrl ?? '*'}/user/:username`, + (info) => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return new HttpResponse(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented', + }); + }, + options, + ); +} + +export type MswHandlerFactories = { + /** + * Handler for the `POST /pet` operation. + */ + addPet: typeof handleAddPet; + /** + * Handler for the `POST /user` operation. + */ + createUser: typeof handleCreateUser; + /** + * Handler for the `POST /user/createWithList` operation. + */ + createUsersWithListInput: typeof handleCreateUsersWithListInput; + /** + * Handler for the `DELETE /store/order/{orderId}` operation. + */ + deleteOrder: typeof handleDeleteOrder; + /** + * Handler for the `DELETE /pet/{petId}` operation. + */ + deletePet: typeof handleDeletePet; + /** + * Handler for the `DELETE /user/{username}` operation. + */ + deleteUser: typeof handleDeleteUser; + /** + * Handler for the `GET /pet/findByStatus` operation. + */ + findPetsByStatus: typeof handleFindPetsByStatus; + /** + * Handler for the `GET /pet/findByTags` operation. + */ + findPetsByTags: typeof handleFindPetsByTags; + /** + * Handler for the `GET /store/inventory` operation. + */ + getInventory: typeof handleGetInventory; + /** + * Handler for the `GET /store/order/{orderId}` operation. + */ + getOrderById: typeof handleGetOrderById; + /** + * Handler for the `GET /pet/{petId}` operation. + */ + getPetById: typeof handleGetPetById; + /** + * Handler for the `GET /user/{username}` operation. + */ + getUserByName: typeof handleGetUserByName; + /** + * Handler for the `GET /user/login` operation. + */ + loginUser: typeof handleLoginUser; + /** + * Handler for the `GET /user/logout` operation. + */ + logoutUser: typeof handleLogoutUser; + /** + * Handler for the `POST /store/order` operation. + */ + placeOrder: typeof handlePlaceOrder; + /** + * Handler for the `PUT /pet` operation. + */ + updatePet: typeof handleUpdatePet; + /** + * Handler for the `POST /pet/{petId}` operation. + */ + updatePetWithForm: typeof handleUpdatePetWithForm; + /** + * Handler for the `PUT /user/{username}` operation. + */ + updateUser: typeof handleUpdateUser; + /** + * Handler for the `POST /pet/{petId}/uploadImage` operation. + */ + uploadFile: typeof handleUploadFile; +}; + +export type CreateMswHandlersResult = { + all: (options?: { + one?: { + [K in keyof MswHandlerFactories]?: + | Parameters[0] + | Parameters; + }; + }) => ReadonlyArray; + one: MswHandlerFactories; +}; + +export function createMswHandlers(config: RequestHandlerOptions = {}): CreateMswHandlersResult { + type Handler = (resolver?: R, options?: RequestHandlerOptions) => HttpHandler; + function wrap(handler: Handler): Handler { + return (resolver, options) => handler(resolver, { ...config, ...options }); + } + const one: CreateMswHandlersResult['one'] = { + addPet: wrap(handleAddPet), + createUser: wrap(handleCreateUser), + createUsersWithListInput: wrap(handleCreateUsersWithListInput), + deleteOrder: wrap(handleDeleteOrder), + deletePet: wrap(handleDeletePet), + deleteUser: wrap(handleDeleteUser), + findPetsByStatus: wrap(handleFindPetsByStatus), + findPetsByTags: wrap(handleFindPetsByTags), + getInventory: wrap(handleGetInventory), + getOrderById: wrap(handleGetOrderById), + getPetById: wrap(handleGetPetById), + getUserByName: wrap(handleGetUserByName), + loginUser: wrap(handleLoginUser), + logoutUser: wrap(handleLogoutUser), + placeOrder: wrap(handlePlaceOrder), + updatePet: wrap(handleUpdatePet), + updatePetWithForm: wrap(handleUpdatePetWithForm), + updateUser: wrap(handleUpdateUser), + uploadFile: wrap(handleUploadFile), + }; + const all: CreateMswHandlersResult['all'] = (options = {}) => { + type OverrideValue = R | [resolver?: R, options?: RequestHandlerOptions]; + function invoke(fn: Handler, override?: OverrideValue): HttpHandler { + return Array.isArray(override) ? fn(...override) : fn(override); + } + const overrides = options.one ?? {}; + return [ + invoke(one.deleteOrder, overrides.deleteOrder), + invoke(one.getOrderById, overrides.getOrderById), + invoke(one.uploadFile, overrides.uploadFile), + invoke(one.findPetsByStatus, overrides.findPetsByStatus), + invoke(one.findPetsByTags, overrides.findPetsByTags), + invoke(one.getInventory, overrides.getInventory), + invoke(one.placeOrder, overrides.placeOrder), + invoke(one.createUsersWithListInput, overrides.createUsersWithListInput), + invoke(one.loginUser, overrides.loginUser), + invoke(one.logoutUser, overrides.logoutUser), + invoke(one.deletePet, overrides.deletePet), + invoke(one.getPetById, overrides.getPetById), + invoke(one.updatePetWithForm, overrides.updatePetWithForm), + invoke(one.deleteUser, overrides.deleteUser), + invoke(one.getUserByName, overrides.getUserByName), + invoke(one.updateUser, overrides.updateUser), + invoke(one.addPet, overrides.addPet), + invoke(one.updatePet, overrides.updatePet), + invoke(one.createUser, overrides.createUser), + ]; + }; + return { all, one }; +} diff --git a/examples/openapi-ts-fetch/test/msw-types.test-d.ts b/examples/openapi-ts-fetch/test/msw-types.test-d.ts new file mode 100644 index 0000000000..1a8388d535 --- /dev/null +++ b/examples/openapi-ts-fetch/test/msw-types.test-d.ts @@ -0,0 +1,165 @@ +import { type HttpHandler, HttpResponse } from 'msw'; +import { describe, expectTypeOf, it } from 'vitest'; + +import { createMswHandlers } from '../src/client/msw.gen'; +import type { Order, Pet } from '../src/client/types.gen'; + +const handlers = createMswHandlers(); + +describe('MSW plugin type-level tests', () => { + describe('static response values', () => { + it('accepts correct response type (Pet) with status', () => { + const pet: Pet = { name: 'Fido', photoUrls: [] }; + handlers.one.getPetById({ result: pet, status: 200 }); + handlers.one.addPet({ result: pet, status: 200 }); + handlers.one.updatePet({ result: pet, status: 200 }); + handlers.one.findPetsByStatus({ result: [pet], status: 200 }); + }); + + it('accepts correct response type (Order) with status', () => { + const order: Order = { id: 1, petId: 1, quantity: 1 }; + handlers.one.placeOrder({ result: order, status: 200 }); + handlers.one.getOrderById({ result: order, status: 200 }); + }); + + it('accepts correct response type (string) with status', () => { + handlers.one.loginUser({ result: 'session-token', status: 200 }); + }); + + it('accepts correct response type (record) with status', () => { + handlers.one.getInventory({ result: { available: 10, pending: 5 }, status: 200 }); + }); + + it('rejects wrong response type for getPetById', () => { + // @ts-expect-error - string is not a valid Pet response + handlers.one.getPetById({ result: 'wrong type', status: 200 }); + }); + + it('rejects wrong response type for addPet', () => { + // @ts-expect-error - number is not a valid Pet response + handlers.one.addPet({ result: 42, status: 200 }); + }); + + it('rejects wrong response type for findPetsByStatus', () => { + // @ts-expect-error - a single Pet is not Array + handlers.one.findPetsByStatus({ result: { name: 'Fido', photoUrls: [] }, status: 200 }); + }); + + it('rejects wrong response type for placeOrder', () => { + // @ts-expect-error - Pet is not a valid Order response + handlers.one.placeOrder({ result: { name: 'Fido', photoUrls: [] }, status: 200 }); + }); + + it('rejects wrong response type for loginUser', () => { + // @ts-expect-error - number is not a valid string response + handlers.one.loginUser({ result: 123, status: 200 }); + }); + + it('rejects wrong status code', () => { + const pet: Pet = { name: 'Fido', photoUrls: [] }; + // @ts-expect-error - 999 is not a valid status code + handlers.one.getPetById({ result: pet, status: 999 }); + }); + + it('accepts result without status (uses dominant response default)', () => { + const pet: Pet = { name: 'Fido', photoUrls: [] }; + handlers.one.getPetById({ result: pet }); + handlers.one.addPet({ result: pet }); + handlers.one.updatePet({ result: pet }); + handlers.one.findPetsByStatus({ result: [pet] }); + }); + + it('accepts result without status for Order operations', () => { + const order: Order = { id: 1, petId: 1, quantity: 1 }; + handlers.one.placeOrder({ result: order }); + handlers.one.getOrderById({ result: order }); + }); + + it('accepts result without status for string response', () => { + handlers.one.loginUser({ result: 'session-token' }); + }); + + it('accepts result without status for record response', () => { + handlers.one.getInventory({ result: { available: 10, pending: 5 } }); + }); + + it('rejects wrong response type even without status', () => { + // @ts-expect-error - string is not a valid Pet response + handlers.one.getPetById({ result: 'wrong type' }); + }); + }); + + describe('void operations accept no arguments', () => { + it('logoutUser accepts no arguments', () => { + handlers.one.logoutUser(); + }); + + it('deletePet accepts no arguments', () => { + handlers.one.deletePet(); + }); + + it('deleteOrder accepts no arguments', () => { + handlers.one.deleteOrder(); + }); + + it('deleteUser accepts no arguments', () => { + handlers.one.deleteUser(); + }); + }); + + describe('resolver function typing', () => { + it('accepts HttpResponseResolver', () => { + handlers.one.getInventory(() => HttpResponse.json({ available: 1 })); + }); + + it('accepts async HttpResponseResolver', () => { + handlers.one.getInventory(async () => HttpResponse.json({ available: 1 })); + }); + + it('resolver for path-param operation receives typed params', () => { + handlers.one.getPetById(({ params }) => { + // params.petId should be string (StringifyPathParams) + expectTypeOf(params.petId).toEqualTypeOf(); + return HttpResponse.json({ name: 'Test', photoUrls: [] }); + }); + }); + + it('resolver for body operation receives typed body via request', () => { + handlers.one.addPet(async ({ request }) => { + const body = await request.json(); + // body should be typed as Pet (AddPetData['body']) + expectTypeOf(body).toEqualTypeOf(); + return HttpResponse.json({ name: body.name, photoUrls: body.photoUrls }); + }); + }); + + it('resolver for void operation is typed correctly', () => { + handlers.one.logoutUser(() => HttpResponse.json(null)); + }); + + it('resolver for void operation with path params', () => { + handlers.one.deletePet(({ params }) => { + expectTypeOf(params.petId).toEqualTypeOf(); + return new HttpResponse(null); + }); + }); + }); + + describe('return type', () => { + it('all handler creators return HttpHandler', () => { + const handler = handlers.one.getPetById({ + result: { name: 'Test', photoUrls: [] }, + status: 200, + }); + expectTypeOf(handler).toExtend(); + }); + }); + + describe('factory configuration', () => { + it('accepts optional config', () => { + createMswHandlers(); + createMswHandlers({}); + createMswHandlers({ baseUrl: 'http://localhost:3000' }); + }); + }); +}); diff --git a/examples/openapi-ts-fetch/test/msw.test.ts b/examples/openapi-ts-fetch/test/msw.test.ts new file mode 100644 index 0000000000..2235b5d75a --- /dev/null +++ b/examples/openapi-ts-fetch/test/msw.test.ts @@ -0,0 +1,301 @@ +import { HttpResponse } from 'msw'; +import { setupServer } from 'msw/node'; +import { afterAll, afterEach, beforeAll, describe, expect, it } from 'vitest'; + +import { client } from '../src/client/client.gen'; +import { createMswHandlers } from '../src/client/msw.gen'; +import { + addPet, + findPetsByStatus, + getInventory, + getOrderById, + getPetById, + getUserByName, + uploadFile, +} from '../src/client/sdk.gen'; +import type { Pet } from '../src/client/types.gen'; + +const BASE_URL = 'http://localhost:3000/api/v3'; + +const handlers = createMswHandlers({ baseUrl: BASE_URL }); + +const server = setupServer(); + +beforeAll(() => { + client.setConfig({ baseUrl: BASE_URL }); + server.listen({ onUnhandledRequest: 'error' }); +}); + +afterEach(() => { + server.resetHandlers(); +}); + +afterAll(() => { + server.close(); +}); + +describe('MSW plugin runtime tests', () => { + describe('static response value', () => { + it('returns static response for GET without path params', async () => { + const mockInventory = { available: 10, pending: 5 }; + server.use(handlers.one.getInventory({ result: mockInventory, status: 200 })); + + const result = await getInventory({ client }); + + expect(result.data).toEqual(mockInventory); + }); + + it('returns static response for GET with path params', async () => { + const mockPet: Pet = { + id: 1, + name: 'Fido', + photoUrls: ['https://example.com/fido.jpg'], + status: 'available', + }; + server.use(handlers.one.getPetById({ result: mockPet, status: 200 })); + + const result = await getPetById({ + client, + path: { petId: 1 }, + }); + + expect(result.data).toEqual(mockPet); + }); + + it('returns static response for GET with query params', async () => { + const mockPets: Pet[] = [ + { id: 1, name: 'Fido', photoUrls: [], status: 'available' }, + { id: 2, name: 'Rex', photoUrls: [], status: 'available' }, + ]; + server.use(handlers.one.findPetsByStatus({ result: mockPets, status: 200 })); + + const result = await findPetsByStatus({ + client, + query: { status: 'available' }, + }); + + expect(result.data).toEqual(mockPets); + }); + + it('returns static response for POST with body', async () => { + const mockPet: Pet = { + id: 10, + name: 'NewPet', + photoUrls: ['https://example.com/new.jpg'], + status: 'pending', + }; + server.use(handlers.one.addPet({ result: mockPet, status: 200 })); + + const result = await addPet({ + body: { + name: 'NewPet', + photoUrls: ['https://example.com/new.jpg'], + }, + client, + }); + + expect(result.data).toEqual(mockPet); + }); + }); + + describe('static response without status (dominant response default)', () => { + it('returns correct data when status is omitted', async () => { + const mockPet: Pet = { + id: 1, + name: 'Fido', + photoUrls: ['https://example.com/fido.jpg'], + status: 'available', + }; + server.use(handlers.one.getPetById({ result: mockPet })); + + const result = await getPetById({ + client, + path: { petId: 1 }, + }); + + expect(result.data).toEqual(mockPet); + }); + + it('returns correct data for POST when status is omitted', async () => { + const mockPet: Pet = { + id: 10, + name: 'NewPet', + photoUrls: ['https://example.com/new.jpg'], + status: 'pending', + }; + server.use(handlers.one.addPet({ result: mockPet })); + + const result = await addPet({ + body: { + name: 'NewPet', + photoUrls: ['https://example.com/new.jpg'], + }, + client, + }); + + expect(result.data).toEqual(mockPet); + }); + }); + + describe('custom resolver function', () => { + it('supports custom resolver for GET', async () => { + server.use( + handlers.one.getPetById(({ params }) => + HttpResponse.json({ + id: Number(params.petId), + name: `Pet-${params.petId}`, + photoUrls: [], + status: 'available', + }), + ), + ); + + const result = await getPetById({ + client, + path: { petId: 42 }, + }); + + expect(result.data).toEqual({ + id: 42, + name: 'Pet-42', + photoUrls: [], + status: 'available', + }); + }); + + it('supports custom resolver with request body', async () => { + server.use( + handlers.one.addPet(async ({ request }) => { + const body = await request.json(); + return HttpResponse.json({ + id: 99, + ...body, + }); + }), + ); + + const result = await addPet({ + body: { + name: 'EchoedPet', + photoUrls: ['https://example.com/echo.jpg'], + }, + client, + }); + + expect(result.data).toMatchObject({ + id: 99, + name: 'EchoedPet', + }); + }); + + it('supports custom status codes', async () => { + server.use( + handlers.one.getPetById(() => HttpResponse.json({ message: 'not found' }, { status: 404 })), + ); + + const result = await getPetById({ + client, + path: { petId: 999 }, + }); + + expect(result.response.status).toBe(404); + expect(result.error).toEqual({ message: 'not found' }); + }); + }); + + describe('toMswPath conversion', () => { + it('handles numeric path param (e.g. /pet/{petId})', async () => { + const mockPet: Pet = { + id: 5, + name: 'PathTest', + photoUrls: [], + }; + server.use(handlers.one.getPetById({ result: mockPet, status: 200 })); + + const result = await getPetById({ + client, + path: { petId: 5 }, + }); + + expect(result.response.ok).toBe(true); + expect(result.data).toEqual(mockPet); + }); + + it('handles string path param (e.g. /user/{username})', async () => { + const mockUser = { + email: 'john@example.com', + firstName: 'John', + id: 1, + lastName: 'Doe', + username: 'john_doe', + }; + server.use(handlers.one.getUserByName({ result: mockUser, status: 200 })); + + const result = await getUserByName({ + client, + path: { username: 'john_doe' }, + }); + + expect(result.response.ok).toBe(true); + expect(result.data).toEqual(mockUser); + }); + + it('handles path param mid-path (e.g. /pet/{petId}/uploadImage)', async () => { + const mockResponse = { code: 200, message: 'uploaded', type: 'ok' }; + server.use(handlers.one.uploadFile({ result: mockResponse, status: 200 })); + + const result = await uploadFile({ + body: new Blob(['fake-image']), + client, + path: { petId: 7 }, + }); + + expect(result.response.ok).toBe(true); + expect(result.data).toEqual(mockResponse); + }); + + it('resolver receives correct path param values', async () => { + server.use( + handlers.one.getOrderById(({ params }) => { + // MSW normalizes path params to strings + expect(typeof params.orderId).toBe('string'); + return HttpResponse.json({ + complete: false, + id: Number(params.orderId), + petId: 1, + quantity: 1, + status: 'placed', + }); + }), + ); + + const result = await getOrderById({ + client, + path: { orderId: 123 }, + }); + + expect(result.data).toMatchObject({ id: 123 }); + }); + }); + + describe('handler override', () => { + it('later handlers override earlier ones', async () => { + server.use(handlers.one.getInventory({ result: { available: 1 }, status: 200 })); + server.use(handlers.one.getInventory({ result: { available: 999 }, status: 200 })); + + const result = await getInventory({ client }); + + expect(result.data).toEqual({ available: 999 }); + }); + }); + + describe('void operations', () => { + it('handles operations with no response body', async () => { + server.use(handlers.one.logoutUser()); + + const result = await (await import('../src/client/sdk.gen')).logoutUser({ client }); + + expect(result.response.ok).toBe(true); + }); + }); +}); diff --git a/examples/openapi-ts-fetch/vitest.config.ts b/examples/openapi-ts-fetch/vitest.config.ts new file mode 100644 index 0000000000..052a593edc --- /dev/null +++ b/examples/openapi-ts-fetch/vitest.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'node', + typecheck: { + enabled: true, + }, + }, +}); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/schema-unknown/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/schema-unknown/client.gen.ts index cab3c70195..52dac25543 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/schema-unknown/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/schema-unknown/client.gen.ts @@ -13,4 +13,4 @@ import type { ClientOptions as ClientOptions2 } from './types.gen'; */ export type CreateClientConfig = (override?: Config) => Config & T>; -export const client = createClient(createConfig()); +export const client = createClient(createConfig({ baseUrl: 'api.postmarkapp.com/' })); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/servers-host/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/servers-host/client.gen.ts index cab3c70195..684843b24b 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/servers-host/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/servers-host/client.gen.ts @@ -13,4 +13,4 @@ import type { ClientOptions as ClientOptions2 } from './types.gen'; */ export type CreateClientConfig = (override?: Config) => Config & T>; -export const client = createClient(createConfig()); +export const client = createClient(createConfig({ baseUrl: 'foo.com' })); diff --git a/packages/openapi-ts-tests/msw/v2/.gitignore b/packages/openapi-ts-tests/msw/v2/.gitignore new file mode 100644 index 0000000000..d276aefe26 --- /dev/null +++ b/packages/openapi-ts-tests/msw/v2/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +.idea +logs +node_modules +coverage diff --git a/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/default/index.ts b/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/default/index.ts new file mode 100644 index 0000000000..2ef59c1f0c --- /dev/null +++ b/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/default/index.ts @@ -0,0 +1,3 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type { _3eNum1Период, _400, AdditionalPropertiesIntegerIssue, AdditionalPropertiesUnknownIssue, AdditionalPropertiesUnknownIssue2, AdditionalPropertiesUnknownIssue3, AdditionalPropertiesUnknownIssueWritable, AnyOfAnyAndNull, AnyOfArrays, ApiVVersionODataControllerCountData, ApiVVersionODataControllerCountResponse, ApiVVersionODataControllerCountResponses, ArrayWithAnyOfProperties, ArrayWithArray, ArrayWithBooleans, ArrayWithNumbers, ArrayWithProperties, ArrayWithReferences, ArrayWithStrings, CallToTestOrderOfParamsData, CallWithDefaultOptionalParametersData, CallWithDefaultParametersData, CallWithDescriptionsData, CallWithDuplicateResponsesData, CallWithDuplicateResponsesError, CallWithDuplicateResponsesErrors, CallWithDuplicateResponsesResponse, CallWithDuplicateResponsesResponses, CallWithNoContentResponseData, CallWithNoContentResponseResponse, CallWithNoContentResponseResponses, CallWithParametersData, CallWithResponseAndNoContentResponseData, CallWithResponseAndNoContentResponseResponse, CallWithResponseAndNoContentResponseResponses, CallWithResponseData, CallWithResponseResponse, CallWithResponseResponses, CallWithResponsesData, CallWithResponsesError, CallWithResponsesErrors, CallWithResponsesResponse, CallWithResponsesResponses, CallWithResultFromHeaderData, CallWithResultFromHeaderErrors, CallWithResultFromHeaderResponses, CallWithWeirdParameterNamesData, CamelCaseCommentWithBreaks, CharactersInDescription, ClientOptions, CollectionFormatData, CommentWithBackticks, CommentWithBackticksAndQuotes, CommentWithBreaks, CommentWithExpressionPlaceholders, CommentWithQuotes, CommentWithReservedCharacters, CommentWithSlashes, ComplexParamsData, ComplexParamsResponse, ComplexParamsResponses, ComplexTypesData, ComplexTypesErrors, ComplexTypesResponse, ComplexTypesResponses, CompositionBaseModel, CompositionExtendedModel, CompositionWithAllOfAndNullable, CompositionWithAnyOf, CompositionWithAnyOfAndNullable, CompositionWithAnyOfAnonymous, CompositionWithNestedAnyAndTypeNull, CompositionWithNestedAnyOfAndNull, CompositionWithOneOf, CompositionWithOneOfAndComplexArrayDictionary, CompositionWithOneOfAndNullable, CompositionWithOneOfAndProperties, CompositionWithOneOfAndSimpleArrayDictionary, CompositionWithOneOfAndSimpleDictionary, CompositionWithOneOfAnonymous, CompositionWithOneOfDiscriminator, ConstValue, Default, DeleteCallWithoutParametersAndResponseData, DeleteFooData, DeleteFooData2, DeleteFooData3, DeprecatedCallData, DeprecatedModel, DictionaryWithArray, DictionaryWithDictionary, DictionaryWithProperties, DictionaryWithPropertiesAndAdditionalProperties, DictionaryWithReference, DictionaryWithString, DummyAData, DummyAResponse, DummyAResponses, DummyBData, DummyBResponse, DummyBResponses, DuplicateName2Data, DuplicateName3Data, DuplicateName4Data, DuplicateNameData, EnumFromDescription, EnumWithExtensions, EnumWithNumbers, EnumWithReplacedCharacters, EnumWithStrings, EnumWithXEnumNames, ExportData, ExternalRefA, ExternalRefB, ExternalSharedModel, File, FileResponseData, FileResponseResponse, FileResponseResponses, FileWritable, FooWowData, FooWowResponses, FreeFormObjectWithAdditionalPropertiesEqEmptyObject, FreeFormObjectWithAdditionalPropertiesEqTrue, FreeFormObjectWithoutAdditionalProperties, GenericSchemaDuplicateIssue1SystemBoolean, GenericSchemaDuplicateIssue1SystemBooleanWritable, GenericSchemaDuplicateIssue1SystemString, GenericSchemaDuplicateIssue1SystemStringWritable, GetApiVbyApiVersionSimpleOperationData, GetApiVbyApiVersionSimpleOperationError, GetApiVbyApiVersionSimpleOperationErrors, GetApiVbyApiVersionSimpleOperationResponse, GetApiVbyApiVersionSimpleOperationResponses, GetCallWithOptionalParamData, GetCallWithoutParametersAndResponseData, HeadCallWithoutParametersAndResponseData, Import, ImportData, ImportResponse, ImportResponses, IoK8sApimachineryPkgApisMetaV1DeleteOptions, IoK8sApimachineryPkgApisMetaV1Preconditions, ModelCircle, ModelFromZendesk, ModelSquare, ModelThatExtends, ModelThatExtendsExtends, ModelWithAdditionalPropertiesEqTrue, ModelWithAdditionalPropertiesRef, ModelWithAnyOfConstantSizeArray, ModelWithAnyOfConstantSizeArrayAndIntersect, ModelWithAnyOfConstantSizeArrayNullable, ModelWithAnyOfConstantSizeArrayWithNSizeAndOptions, ModelWithAnyOfConstantSizeArrayWithNSizeAndOptionsWritable, ModelWithArray, ModelWithArrayReadOnlyAndWriteOnly, ModelWithArrayReadOnlyAndWriteOnlyWritable, ModelWithBackticksInDescription, ModelWithBoolean, ModelWithCircularReference, ModelWithConst, ModelWithConstantSizeArray, ModelWithDictionary, ModelWithDuplicateImports, ModelWithDuplicateProperties, ModelWithEnum, ModelWithEnumFromDescription, ModelWithEnumWithHyphen, ModelWithInteger, ModelWithNestedArrayEnums, ModelWithNestedArrayEnumsData, ModelWithNestedArrayEnumsDataBar, ModelWithNestedArrayEnumsDataFoo, ModelWithNestedCompositionEnums, ModelWithNestedEnums, ModelWithNestedProperties, ModelWithNullableObject, ModelWithNullableString, ModelWithNumericEnumUnion, ModelWithOneOfAndProperties, ModelWithOneOfEnum, ModelWithOrderedProperties, ModelWithPattern, ModelWithPatternWritable, ModelWithPrefixItemsConstantSizeArray, ModelWithProperties, ModelWithPropertiesWritable, ModelWithReadOnlyAndWriteOnly, ModelWithReadOnlyAndWriteOnlyWritable, ModelWithReference, ModelWithReferenceWritable, ModelWithString, ModelWithStringError, MultipartRequestData, MultipartResponseData, MultipartResponseResponse, MultipartResponseResponses, NestedAnyOfArraysNullable, NonAsciiæøåÆøÅöôêÊ字符串Data, NonAsciiæøåÆøÅöôêÊ字符串Response, NonAsciiæøåÆøÅöôêÊ字符串Responses, NonAsciiStringæøåÆøÅöôêÊ字符串, NullableObject, OneOfAllOfIssue, OneOfAllOfIssueWritable, OptionsCallWithoutParametersAndResponseData, Pageable, ParameterSimpleParameterUnused, PatchApiVbyApiVersionNoTagData, PatchApiVbyApiVersionNoTagResponses, PatchCallWithoutParametersAndResponseData, PostApiVbyApiVersionFormDataData, PostApiVbyApiVersionRequestBodyData, PostCallWithOptionalParamData, PostCallWithOptionalParamResponse, PostCallWithOptionalParamResponses, PostCallWithoutParametersAndResponseData, PostServiceWithEmptyTagResponse, PostServiceWithEmptyTagResponse2, PutCallWithoutParametersAndResponseData, PutWithFormUrlEncodedData, SchemaWithFormRestrictedKeys, SimpleBoolean, SimpleFile, SimpleFormData, SimpleInteger, SimpleParameter, SimpleReference, SimpleRequestBody, SimpleString, SimpleStringWithPattern, TestErrorCodeData, TestErrorCodeErrors, TestErrorCodeResponses, TypesData, TypesResponse, TypesResponses, UploadFileData, UploadFileResponse, UploadFileResponses, XFooBar } from './types.gen'; diff --git a/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/default/msw.gen.ts b/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/default/msw.gen.ts new file mode 100644 index 0000000000..d2b04275c0 --- /dev/null +++ b/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/default/msw.gen.ts @@ -0,0 +1,1363 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { http, type HttpHandler, HttpResponse, type HttpResponseResolver, type RequestHandlerOptions as RequestHandlerOptions2 } from 'msw'; + +import type { ApiVVersionODataControllerCountResponses, CallWithDuplicateResponsesResponses, CallWithNoContentResponseResponses, CallWithParametersData, CallWithResponseAndNoContentResponseResponses, CallWithResponsesResponses, CallWithResultFromHeaderResponses, CallWithWeirdParameterNamesData, ComplexParamsData, ComplexParamsResponses, ComplexTypesResponses, DummyAResponses, DummyBResponses, FileResponseResponses, GetApiVbyApiVersionSimpleOperationResponses, GetCallWithOptionalParamData, ImportData, ImportResponses, MultipartRequestData, MultipartResponseResponses, NonAsciiæøåÆøÅöôêÊ字符串Responses, PostApiVbyApiVersionFormDataData, PostApiVbyApiVersionRequestBodyData, PostCallWithOptionalParamData, PostCallWithOptionalParamResponses, PutWithFormUrlEncodedData, TestErrorCodeResponses, TypesResponses, UploadFileData, UploadFileResponses } from './types.gen'; + +export type RequestHandlerOptions = RequestHandlerOptions2 & { + baseUrl?: string; + responseFallback?: 'error' | 'passthrough'; +}; + +/** + * Handler for the `GET /api/v{api-version}/no+tag` operation. + */ +export function handleExport(resolver?: HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.get(`${options?.baseUrl ?? '*'}/api/v:apiVersion/no+tag`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `PATCH /api/v{api-version}/no+tag` operation. + */ +export function handlePatchApiVbyApiVersionNoTag(resolver?: HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.patch(`${options?.baseUrl ?? '*'}/api/v:apiVersion/no+tag`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `POST /api/v{api-version}/no+tag` operation. + */ +export function handleImport(resolver?: { + result: ImportResponses[200]; + status?: 200; +} | HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.post(`${options?.baseUrl ?? '*'}/api/v:apiVersion/no+tag`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return HttpResponse.json(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `PUT /api/v{api-version}/no+tag` operation. + */ +export function handleFooWow(resolver?: HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.put(`${options?.baseUrl ?? '*'}/api/v:apiVersion/no+tag`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `GET /api/v{api-version}/simple/$count` operation. + */ +export function handleApiVVersionODataControllerCount(resolver?: { + result: ApiVVersionODataControllerCountResponses[200]; + status?: 200; +} | HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.get(`${options?.baseUrl ?? '*'}/api/v:apiVersion/simple/$count`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return HttpResponse.json(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `GET /api/v{api-version}/simple:operation` operation. + */ +export function handleGetApiVbyApiVersionSimpleOperation(resolver?: { + result: GetApiVbyApiVersionSimpleOperationResponses[200]; + status?: 200; +} | HttpResponseResolver<{ + foo_param: string; +}, never>, options?: RequestHandlerOptions): HttpHandler { + return http.get<{ + foo_param: string; + }, never>(`${options?.baseUrl ?? '*'}/api/v:apiVersion/simple\\:operation`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return HttpResponse.json(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `DELETE /api/v{api-version}/simple` operation. + */ +export function handleDeleteCallWithoutParametersAndResponse(resolver?: HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.delete(`${options?.baseUrl ?? '*'}/api/v:apiVersion/simple`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `GET /api/v{api-version}/simple` operation. + */ +export function handleGetCallWithoutParametersAndResponse(resolver?: HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.get(`${options?.baseUrl ?? '*'}/api/v:apiVersion/simple`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `HEAD /api/v{api-version}/simple` operation. + */ +export function handleHeadCallWithoutParametersAndResponse(resolver?: HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.head(`${options?.baseUrl ?? '*'}/api/v:apiVersion/simple`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `OPTIONS /api/v{api-version}/simple` operation. + */ +export function handleOptionsCallWithoutParametersAndResponse(resolver?: HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.options(`${options?.baseUrl ?? '*'}/api/v:apiVersion/simple`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `PATCH /api/v{api-version}/simple` operation. + */ +export function handlePatchCallWithoutParametersAndResponse(resolver?: HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.patch(`${options?.baseUrl ?? '*'}/api/v:apiVersion/simple`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `POST /api/v{api-version}/simple` operation. + */ +export function handlePostCallWithoutParametersAndResponse(resolver?: HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.post(`${options?.baseUrl ?? '*'}/api/v:apiVersion/simple`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `PUT /api/v{api-version}/simple` operation. + */ +export function handlePutCallWithoutParametersAndResponse(resolver?: HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.put(`${options?.baseUrl ?? '*'}/api/v:apiVersion/simple`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `DELETE /api/v{api-version}/foo/{foo_param}/bar/{BarParam}` operation. + */ +export function handleDeleteFoo(resolver?: HttpResponseResolver<{ + foo_param: string; + BarParam: string; +}, never>, options?: RequestHandlerOptions): HttpHandler { + return http.delete<{ + foo_param: string; + BarParam: string; + }, never>(`${options?.baseUrl ?? '*'}/api/v:apiVersion/foo/:foo_param/bar/:BarParam`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `POST /api/v{api-version}/descriptions` operation. + */ +export function handleCallWithDescriptions(resolver?: HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.post(`${options?.baseUrl ?? '*'}/api/v:apiVersion/descriptions`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `POST /api/v{api-version}/parameters/deprecated` operation. + */ +export function handleDeprecatedCall(resolver?: HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.post(`${options?.baseUrl ?? '*'}/api/v:apiVersion/parameters/deprecated`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `POST /api/v{api-version}/parameters/{parameterPath}` operation. + */ +export function handleCallWithParameters(resolver?: HttpResponseResolver<{ + parameterPath: string; + apiVersion: string; +}, CallWithParametersData['body']>, options?: RequestHandlerOptions): HttpHandler { + return http.post<{ + parameterPath: string; + apiVersion: string; + }, CallWithParametersData['body']>(`${options?.baseUrl ?? '*'}/api/v:apiVersion/parameters/:parameterPath`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `POST /api/v{api-version}/parameters/{parameter.path.1}/{parameter-path-2}/{PARAMETER-PATH-3}` operation. + */ +export function handleCallWithWeirdParameterNames(resolver?: HttpResponseResolver<{ + parameterPath1: string; + parameterPath2: string; + PARAMETERPATH3: string; + apiVersion: string; +}, CallWithWeirdParameterNamesData['body']>, options?: RequestHandlerOptions): HttpHandler { + return http.post<{ + parameterPath1: string; + parameterPath2: string; + PARAMETERPATH3: string; + apiVersion: string; + }, CallWithWeirdParameterNamesData['body']>(`${options?.baseUrl ?? '*'}/api/v:apiVersion/parameters/:parameterPath1/:parameterPath2/:PARAMETERPATH3`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `GET /api/v{api-version}/parameters` operation. + */ +export function handleGetCallWithOptionalParam(resolver?: HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.get(`${options?.baseUrl ?? '*'}/api/v:apiVersion/parameters`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +type ToResponseUnion = { + [K in Extract]: { + status: K; + result: T[K]; + }; +}[Extract]; + +/** + * Handler for the `POST /api/v{api-version}/parameters` operation. + */ +export function handlePostCallWithOptionalParam(resolver?: { + result: PostCallWithOptionalParamResponses[200]; + status?: 200; +} | ToResponseUnion | HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.post(`${options?.baseUrl ?? '*'}/api/v:apiVersion/parameters`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return HttpResponse.json(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `POST /api/v{api-version}/requestBody` operation. + */ +export function handlePostApiVbyApiVersionRequestBody(resolver?: HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.post(`${options?.baseUrl ?? '*'}/api/v:apiVersion/requestBody`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `POST /api/v{api-version}/formData` operation. + */ +export function handlePostApiVbyApiVersionFormData(resolver?: HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.post(`${options?.baseUrl ?? '*'}/api/v:apiVersion/formData`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `GET /api/v{api-version}/defaults` operation. + */ +export function handleCallWithDefaultParameters(resolver?: HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.get(`${options?.baseUrl ?? '*'}/api/v:apiVersion/defaults`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `POST /api/v{api-version}/defaults` operation. + */ +export function handleCallWithDefaultOptionalParameters(resolver?: HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.post(`${options?.baseUrl ?? '*'}/api/v:apiVersion/defaults`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `PUT /api/v{api-version}/defaults` operation. + */ +export function handleCallToTestOrderOfParams(resolver?: HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.put(`${options?.baseUrl ?? '*'}/api/v:apiVersion/defaults`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `DELETE /api/v{api-version}/duplicate` operation. + */ +export function handleDuplicateName(resolver?: HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.delete(`${options?.baseUrl ?? '*'}/api/v:apiVersion/duplicate`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `GET /api/v{api-version}/duplicate` operation. + */ +export function handleDuplicateName2(resolver?: HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.get(`${options?.baseUrl ?? '*'}/api/v:apiVersion/duplicate`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `POST /api/v{api-version}/duplicate` operation. + */ +export function handleDuplicateName3(resolver?: HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.post(`${options?.baseUrl ?? '*'}/api/v:apiVersion/duplicate`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `PUT /api/v{api-version}/duplicate` operation. + */ +export function handleDuplicateName4(resolver?: HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.put(`${options?.baseUrl ?? '*'}/api/v:apiVersion/duplicate`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `GET /api/v{api-version}/no-content` operation. + */ +export function handleCallWithNoContentResponse(resolver?: { + result: CallWithNoContentResponseResponses[204]; + status?: 204; +} | HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.get(`${options?.baseUrl ?? '*'}/api/v:apiVersion/no-content`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return new HttpResponse(resolver.result ?? null, { status: resolver.status ?? 204 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `GET /api/v{api-version}/multiple-tags/response-and-no-content` operation. + */ +export function handleCallWithResponseAndNoContentResponse(resolver?: { + result: CallWithResponseAndNoContentResponseResponses[200]; + status?: 200; +} | ToResponseUnion | HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.get(`${options?.baseUrl ?? '*'}/api/v:apiVersion/multiple-tags/response-and-no-content`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return HttpResponse.json(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `GET /api/v{api-version}/multiple-tags/a` operation. + */ +export function handleDummyA(resolver?: { + result: DummyAResponses[200]; + status?: 200; +} | HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.get(`${options?.baseUrl ?? '*'}/api/v:apiVersion/multiple-tags/a`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return HttpResponse.json(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `GET /api/v{api-version}/multiple-tags/b` operation. + */ +export function handleDummyB(resolver?: { + result: DummyBResponses[204]; + status?: 204; +} | HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.get(`${options?.baseUrl ?? '*'}/api/v:apiVersion/multiple-tags/b`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return new HttpResponse(resolver.result ?? null, { status: resolver.status ?? 204 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `GET /api/v{api-version}/response` operation. + */ +export function handleCallWithResponse(resolver?: HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.get(`${options?.baseUrl ?? '*'}/api/v:apiVersion/response`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `POST /api/v{api-version}/response` operation. + */ +export function handleCallWithDuplicateResponses(resolver?: { + result: CallWithDuplicateResponsesResponses[200]; + status?: 200; +} | ToResponseUnion | HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.post(`${options?.baseUrl ?? '*'}/api/v:apiVersion/response`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return HttpResponse.json(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `PUT /api/v{api-version}/response` operation. + */ +export function handleCallWithResponses(resolver?: { + result: CallWithResponsesResponses[200]; + status?: 200; +} | ToResponseUnion | HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.put(`${options?.baseUrl ?? '*'}/api/v:apiVersion/response`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return HttpResponse.json(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `GET /api/v{api-version}/collectionFormat` operation. + */ +export function handleCollectionFormat(resolver?: HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.get(`${options?.baseUrl ?? '*'}/api/v:apiVersion/collectionFormat`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `GET /api/v{api-version}/types` operation. + */ +export function handleTypes(resolver?: { + result: TypesResponses[200]; + status?: 200; +} | ToResponseUnion | HttpResponseResolver<{ + id: string; +}, never>, options?: RequestHandlerOptions): HttpHandler { + return http.get<{ + id: string; + }, never>(`${options?.baseUrl ?? '*'}/api/v:apiVersion/types`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return HttpResponse.json(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `POST /api/v{api-version}/upload` operation. + */ +export function handleUploadFile(resolver?: { + result: UploadFileResponses[200]; + status?: 200; +} | HttpResponseResolver<{ + apiVersion: string; +}, UploadFileData['body']>, options?: RequestHandlerOptions): HttpHandler { + return http.post<{ + apiVersion: string; + }, UploadFileData['body']>(`${options?.baseUrl ?? '*'}/api/v:apiVersion/upload`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return HttpResponse.json(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `GET /api/v{api-version}/file/{id}` operation. + */ +export function handleFileResponse(resolver?: { + result: FileResponseResponses[200]; + status?: 200; +} | HttpResponseResolver<{ + id: string; + apiVersion: string; +}, never>, options?: RequestHandlerOptions): HttpHandler { + return http.get<{ + id: string; + apiVersion: string; + }, never>(`${options?.baseUrl ?? '*'}/api/v:apiVersion/file/:id`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return new HttpResponse(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `GET /api/v{api-version}/complex` operation. + */ +export function handleComplexTypes(resolver?: { + result: ComplexTypesResponses[200]; + status?: 200; +} | HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.get(`${options?.baseUrl ?? '*'}/api/v:apiVersion/complex`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return HttpResponse.json(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `GET /api/v{api-version}/multipart` operation. + */ +export function handleMultipartResponse(resolver?: { + result: MultipartResponseResponses[200]; + status?: 200; +} | HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.get(`${options?.baseUrl ?? '*'}/api/v:apiVersion/multipart`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return HttpResponse.json(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `POST /api/v{api-version}/multipart` operation. + */ +export function handleMultipartRequest(resolver?: HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.post(`${options?.baseUrl ?? '*'}/api/v:apiVersion/multipart`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `PUT /api/v{api-version}/complex/{id}` operation. + */ +export function handleComplexParams(resolver?: { + result: ComplexParamsResponses[200]; + status?: 200; +} | HttpResponseResolver<{ + id: string; + apiVersion: string; +}, ComplexParamsData['body']>, options?: RequestHandlerOptions): HttpHandler { + return http.put<{ + id: string; + apiVersion: string; + }, ComplexParamsData['body']>(`${options?.baseUrl ?? '*'}/api/v:apiVersion/complex/:id`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return HttpResponse.json(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `POST /api/v{api-version}/header` operation. + */ +export function handleCallWithResultFromHeader(resolver?: { + result: CallWithResultFromHeaderResponses[200]; + status?: 200; +} | HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.post(`${options?.baseUrl ?? '*'}/api/v:apiVersion/header`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return new HttpResponse(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `POST /api/v{api-version}/error` operation. + */ +export function handleTestErrorCode(resolver?: { + result: TestErrorCodeResponses[200]; + status?: 200; +} | HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.post(`${options?.baseUrl ?? '*'}/api/v:apiVersion/error`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return new HttpResponse(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `POST /api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串` operation. + */ +export function handleNonAsciiæøåÆøÅöôêÊ字符串(resolver?: { + result: NonAsciiæøåÆøÅöôêÊ字符串Responses[200]; + status?: 200; +} | HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.post(`${options?.baseUrl ?? '*'}/api/v:apiVersion/non-ascii-æøåÆØÅöôêÊ字符串`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return HttpResponse.json(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +/** + * Handler for the `PUT /api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串` operation. + */ +export function handlePutWithFormUrlEncoded(resolver?: HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.put(`${options?.baseUrl ?? '*'}/api/v:apiVersion/non-ascii-æøåÆØÅöôêÊ字符串`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +export type MswHandlerFactories = { + /** + * Handler for the `GET /api/v{api-version}/no+tag` operation. + */ + export: typeof handleExport; + /** + * Handler for the `PATCH /api/v{api-version}/no+tag` operation. + */ + patchApiVbyApiVersionNoTag: typeof handlePatchApiVbyApiVersionNoTag; + /** + * Handler for the `POST /api/v{api-version}/no+tag` operation. + */ + import: typeof handleImport; + /** + * Handler for the `PUT /api/v{api-version}/no+tag` operation. + */ + fooWow: typeof handleFooWow; + /** + * Handler for the `GET /api/v{api-version}/simple/$count` operation. + */ + apiVVersionODataControllerCount: typeof handleApiVVersionODataControllerCount; + /** + * Handler for the `GET /api/v{api-version}/simple:operation` operation. + */ + getApiVbyApiVersionSimpleOperation: typeof handleGetApiVbyApiVersionSimpleOperation; + /** + * Handler for the `DELETE /api/v{api-version}/simple` operation. + */ + deleteCallWithoutParametersAndResponse: typeof handleDeleteCallWithoutParametersAndResponse; + /** + * Handler for the `GET /api/v{api-version}/simple` operation. + */ + getCallWithoutParametersAndResponse: typeof handleGetCallWithoutParametersAndResponse; + /** + * Handler for the `HEAD /api/v{api-version}/simple` operation. + */ + headCallWithoutParametersAndResponse: typeof handleHeadCallWithoutParametersAndResponse; + /** + * Handler for the `OPTIONS /api/v{api-version}/simple` operation. + */ + optionsCallWithoutParametersAndResponse: typeof handleOptionsCallWithoutParametersAndResponse; + /** + * Handler for the `PATCH /api/v{api-version}/simple` operation. + */ + patchCallWithoutParametersAndResponse: typeof handlePatchCallWithoutParametersAndResponse; + /** + * Handler for the `POST /api/v{api-version}/simple` operation. + */ + postCallWithoutParametersAndResponse: typeof handlePostCallWithoutParametersAndResponse; + /** + * Handler for the `PUT /api/v{api-version}/simple` operation. + */ + putCallWithoutParametersAndResponse: typeof handlePutCallWithoutParametersAndResponse; + /** + * Handler for the `DELETE /api/v{api-version}/foo/{foo_param}/bar/{BarParam}` operation. + */ + deleteFoo: typeof handleDeleteFoo; + /** + * Handler for the `POST /api/v{api-version}/descriptions` operation. + */ + callWithDescriptions: typeof handleCallWithDescriptions; + /** + * Handler for the `POST /api/v{api-version}/parameters/deprecated` operation. + */ + deprecatedCall: typeof handleDeprecatedCall; + /** + * Handler for the `POST /api/v{api-version}/parameters/{parameterPath}` operation. + */ + callWithParameters: typeof handleCallWithParameters; + /** + * Handler for the `POST /api/v{api-version}/parameters/{parameter.path.1}/{parameter-path-2}/{PARAMETER-PATH-3}` operation. + */ + callWithWeirdParameterNames: typeof handleCallWithWeirdParameterNames; + /** + * Handler for the `GET /api/v{api-version}/parameters` operation. + */ + getCallWithOptionalParam: typeof handleGetCallWithOptionalParam; + /** + * Handler for the `POST /api/v{api-version}/parameters` operation. + */ + postCallWithOptionalParam: typeof handlePostCallWithOptionalParam; + /** + * Handler for the `POST /api/v{api-version}/requestBody` operation. + */ + postApiVbyApiVersionRequestBody: typeof handlePostApiVbyApiVersionRequestBody; + /** + * Handler for the `POST /api/v{api-version}/formData` operation. + */ + postApiVbyApiVersionFormData: typeof handlePostApiVbyApiVersionFormData; + /** + * Handler for the `GET /api/v{api-version}/defaults` operation. + */ + callWithDefaultParameters: typeof handleCallWithDefaultParameters; + /** + * Handler for the `POST /api/v{api-version}/defaults` operation. + */ + callWithDefaultOptionalParameters: typeof handleCallWithDefaultOptionalParameters; + /** + * Handler for the `PUT /api/v{api-version}/defaults` operation. + */ + callToTestOrderOfParams: typeof handleCallToTestOrderOfParams; + /** + * Handler for the `DELETE /api/v{api-version}/duplicate` operation. + */ + duplicateName: typeof handleDuplicateName; + /** + * Handler for the `GET /api/v{api-version}/duplicate` operation. + */ + duplicateName2: typeof handleDuplicateName2; + /** + * Handler for the `POST /api/v{api-version}/duplicate` operation. + */ + duplicateName3: typeof handleDuplicateName3; + /** + * Handler for the `PUT /api/v{api-version}/duplicate` operation. + */ + duplicateName4: typeof handleDuplicateName4; + /** + * Handler for the `GET /api/v{api-version}/no-content` operation. + */ + callWithNoContentResponse: typeof handleCallWithNoContentResponse; + /** + * Handler for the `GET /api/v{api-version}/multiple-tags/response-and-no-content` operation. + */ + callWithResponseAndNoContentResponse: typeof handleCallWithResponseAndNoContentResponse; + /** + * Handler for the `GET /api/v{api-version}/multiple-tags/a` operation. + */ + dummyA: typeof handleDummyA; + /** + * Handler for the `GET /api/v{api-version}/multiple-tags/b` operation. + */ + dummyB: typeof handleDummyB; + /** + * Handler for the `GET /api/v{api-version}/response` operation. + */ + callWithResponse: typeof handleCallWithResponse; + /** + * Handler for the `POST /api/v{api-version}/response` operation. + */ + callWithDuplicateResponses: typeof handleCallWithDuplicateResponses; + /** + * Handler for the `PUT /api/v{api-version}/response` operation. + */ + callWithResponses: typeof handleCallWithResponses; + /** + * Handler for the `GET /api/v{api-version}/collectionFormat` operation. + */ + collectionFormat: typeof handleCollectionFormat; + /** + * Handler for the `GET /api/v{api-version}/types` operation. + */ + types: typeof handleTypes; + /** + * Handler for the `POST /api/v{api-version}/upload` operation. + */ + uploadFile: typeof handleUploadFile; + /** + * Handler for the `GET /api/v{api-version}/file/{id}` operation. + */ + fileResponse: typeof handleFileResponse; + /** + * Handler for the `GET /api/v{api-version}/complex` operation. + */ + complexTypes: typeof handleComplexTypes; + /** + * Handler for the `GET /api/v{api-version}/multipart` operation. + */ + multipartResponse: typeof handleMultipartResponse; + /** + * Handler for the `POST /api/v{api-version}/multipart` operation. + */ + multipartRequest: typeof handleMultipartRequest; + /** + * Handler for the `PUT /api/v{api-version}/complex/{id}` operation. + */ + complexParams: typeof handleComplexParams; + /** + * Handler for the `POST /api/v{api-version}/header` operation. + */ + callWithResultFromHeader: typeof handleCallWithResultFromHeader; + /** + * Handler for the `POST /api/v{api-version}/error` operation. + */ + testErrorCode: typeof handleTestErrorCode; + /** + * Handler for the `POST /api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串` operation. + */ + nonAsciiæøåÆøÅöôêÊ字符串: typeof handleNonAsciiæøåÆøÅöôêÊ字符串; + /** + * Handler for the `PUT /api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串` operation. + */ + putWithFormUrlEncoded: typeof handlePutWithFormUrlEncoded; +}; + +export type CreateMswHandlersResult = { + all: (options?: { + one?: { + [K in keyof MswHandlerFactories]?: Parameters[0] | Parameters; + }; + }) => ReadonlyArray; + one: MswHandlerFactories; +}; + +export function createMswHandlers(config: RequestHandlerOptions = {}): CreateMswHandlersResult { + type Handler = (resolver?: R, options?: RequestHandlerOptions) => HttpHandler; + function wrap(handler: Handler): Handler { + return (resolver, options) => handler(resolver, { ...config, ...options }); + } + const one: CreateMswHandlersResult['one'] = { + export: wrap(handleExport), + patchApiVbyApiVersionNoTag: wrap(handlePatchApiVbyApiVersionNoTag), + import: wrap(handleImport), + fooWow: wrap(handleFooWow), + apiVVersionODataControllerCount: wrap(handleApiVVersionODataControllerCount), + getApiVbyApiVersionSimpleOperation: wrap(handleGetApiVbyApiVersionSimpleOperation), + deleteCallWithoutParametersAndResponse: wrap(handleDeleteCallWithoutParametersAndResponse), + getCallWithoutParametersAndResponse: wrap(handleGetCallWithoutParametersAndResponse), + headCallWithoutParametersAndResponse: wrap(handleHeadCallWithoutParametersAndResponse), + optionsCallWithoutParametersAndResponse: wrap(handleOptionsCallWithoutParametersAndResponse), + patchCallWithoutParametersAndResponse: wrap(handlePatchCallWithoutParametersAndResponse), + postCallWithoutParametersAndResponse: wrap(handlePostCallWithoutParametersAndResponse), + putCallWithoutParametersAndResponse: wrap(handlePutCallWithoutParametersAndResponse), + deleteFoo: wrap(handleDeleteFoo), + callWithDescriptions: wrap(handleCallWithDescriptions), + deprecatedCall: wrap(handleDeprecatedCall), + callWithParameters: wrap(handleCallWithParameters), + callWithWeirdParameterNames: wrap(handleCallWithWeirdParameterNames), + getCallWithOptionalParam: wrap(handleGetCallWithOptionalParam), + postCallWithOptionalParam: wrap(handlePostCallWithOptionalParam), + postApiVbyApiVersionRequestBody: wrap(handlePostApiVbyApiVersionRequestBody), + postApiVbyApiVersionFormData: wrap(handlePostApiVbyApiVersionFormData), + callWithDefaultParameters: wrap(handleCallWithDefaultParameters), + callWithDefaultOptionalParameters: wrap(handleCallWithDefaultOptionalParameters), + callToTestOrderOfParams: wrap(handleCallToTestOrderOfParams), + duplicateName: wrap(handleDuplicateName), + duplicateName2: wrap(handleDuplicateName2), + duplicateName3: wrap(handleDuplicateName3), + duplicateName4: wrap(handleDuplicateName4), + callWithNoContentResponse: wrap(handleCallWithNoContentResponse), + callWithResponseAndNoContentResponse: wrap(handleCallWithResponseAndNoContentResponse), + dummyA: wrap(handleDummyA), + dummyB: wrap(handleDummyB), + callWithResponse: wrap(handleCallWithResponse), + callWithDuplicateResponses: wrap(handleCallWithDuplicateResponses), + callWithResponses: wrap(handleCallWithResponses), + collectionFormat: wrap(handleCollectionFormat), + types: wrap(handleTypes), + uploadFile: wrap(handleUploadFile), + fileResponse: wrap(handleFileResponse), + complexTypes: wrap(handleComplexTypes), + multipartResponse: wrap(handleMultipartResponse), + multipartRequest: wrap(handleMultipartRequest), + complexParams: wrap(handleComplexParams), + callWithResultFromHeader: wrap(handleCallWithResultFromHeader), + testErrorCode: wrap(handleTestErrorCode), + nonAsciiæøåÆøÅöôêÊ字符串: wrap(handleNonAsciiæøåÆøÅöôêÊ字符串), + putWithFormUrlEncoded: wrap(handlePutWithFormUrlEncoded) + }; + const all: CreateMswHandlersResult['all'] = (options = {}) => { + type OverrideValue = R | [ + resolver?: R, + options?: RequestHandlerOptions + ]; + function invoke(fn: Handler, override?: OverrideValue): HttpHandler { + return Array.isArray(override) ? fn(...override) : fn(override); + } + const overrides = options.one ?? {}; + return [ + invoke(one.deleteFoo, overrides.deleteFoo), + invoke(one.callWithWeirdParameterNames, overrides.callWithWeirdParameterNames), + invoke(one.apiVVersionODataControllerCount, overrides.apiVVersionODataControllerCount), + invoke(one.deprecatedCall, overrides.deprecatedCall), + invoke(one.callWithResponseAndNoContentResponse, overrides.callWithResponseAndNoContentResponse), + invoke(one.dummyA, overrides.dummyA), + invoke(one.dummyB, overrides.dummyB), + invoke(one.callWithParameters, overrides.callWithParameters), + invoke(one.fileResponse, overrides.fileResponse), + invoke(one.complexParams, overrides.complexParams), + invoke(one.export, overrides.export), + invoke(one.patchApiVbyApiVersionNoTag, overrides.patchApiVbyApiVersionNoTag), + invoke(one.import, overrides.import), + invoke(one.fooWow, overrides.fooWow), + invoke(one.getApiVbyApiVersionSimpleOperation, overrides.getApiVbyApiVersionSimpleOperation), + invoke(one.deleteCallWithoutParametersAndResponse, overrides.deleteCallWithoutParametersAndResponse), + invoke(one.getCallWithoutParametersAndResponse, overrides.getCallWithoutParametersAndResponse), + invoke(one.headCallWithoutParametersAndResponse, overrides.headCallWithoutParametersAndResponse), + invoke(one.optionsCallWithoutParametersAndResponse, overrides.optionsCallWithoutParametersAndResponse), + invoke(one.patchCallWithoutParametersAndResponse, overrides.patchCallWithoutParametersAndResponse), + invoke(one.postCallWithoutParametersAndResponse, overrides.postCallWithoutParametersAndResponse), + invoke(one.putCallWithoutParametersAndResponse, overrides.putCallWithoutParametersAndResponse), + invoke(one.callWithDescriptions, overrides.callWithDescriptions), + invoke(one.getCallWithOptionalParam, overrides.getCallWithOptionalParam), + invoke(one.postCallWithOptionalParam, overrides.postCallWithOptionalParam), + invoke(one.postApiVbyApiVersionRequestBody, overrides.postApiVbyApiVersionRequestBody), + invoke(one.postApiVbyApiVersionFormData, overrides.postApiVbyApiVersionFormData), + invoke(one.callWithDefaultParameters, overrides.callWithDefaultParameters), + invoke(one.callWithDefaultOptionalParameters, overrides.callWithDefaultOptionalParameters), + invoke(one.callToTestOrderOfParams, overrides.callToTestOrderOfParams), + invoke(one.duplicateName, overrides.duplicateName), + invoke(one.duplicateName2, overrides.duplicateName2), + invoke(one.duplicateName3, overrides.duplicateName3), + invoke(one.duplicateName4, overrides.duplicateName4), + invoke(one.callWithNoContentResponse, overrides.callWithNoContentResponse), + invoke(one.callWithResponse, overrides.callWithResponse), + invoke(one.callWithDuplicateResponses, overrides.callWithDuplicateResponses), + invoke(one.callWithResponses, overrides.callWithResponses), + invoke(one.collectionFormat, overrides.collectionFormat), + invoke(one.types, overrides.types), + invoke(one.uploadFile, overrides.uploadFile), + invoke(one.complexTypes, overrides.complexTypes), + invoke(one.multipartResponse, overrides.multipartResponse), + invoke(one.multipartRequest, overrides.multipartRequest), + invoke(one.callWithResultFromHeader, overrides.callWithResultFromHeader), + invoke(one.testErrorCode, overrides.testErrorCode), + invoke(one.nonAsciiæøåÆøÅöôêÊ字符串, overrides.nonAsciiæøåÆøÅöôêÊ字符串), + invoke(one.putWithFormUrlEncoded, overrides.putWithFormUrlEncoded) + ]; + }; + return { all, one }; +} diff --git a/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/default/types.gen.ts b/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/default/types.gen.ts new file mode 100644 index 0000000000..d819316644 --- /dev/null +++ b/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/default/types.gen.ts @@ -0,0 +1,2100 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type ClientOptions = { + baseUrl: 'http://localhost:3000/base' | (string & {}); +}; + +/** + * Model with number-only name + */ +export type _400 = string; + +/** + * External ref to shared model (A) + */ +export type ExternalRefA = ExternalSharedModel; + +/** + * External ref to shared model (B) + */ +export type ExternalRefB = ExternalSharedModel; + +/** + * Testing multiline comments in string: First line + * Second line + * + * Fourth line + */ +export type CamelCaseCommentWithBreaks = number; + +/** + * Testing multiline comments in string: First line + * Second line + * + * Fourth line + */ +export type CommentWithBreaks = number; + +/** + * Testing backticks in string: `backticks` and ```multiple backticks``` should work + */ +export type CommentWithBackticks = number; + +/** + * Testing backticks and quotes in string: `backticks`, 'quotes', "double quotes" and ```multiple backticks``` should work + */ +export type CommentWithBackticksAndQuotes = number; + +/** + * Testing slashes in string: \backwards\\\ and /forwards/// should work + */ +export type CommentWithSlashes = number; + +/** + * Testing expression placeholders in string: ${expression} should work + */ +export type CommentWithExpressionPlaceholders = number; + +/** + * Testing quotes in string: 'single quote''' and "double quotes""" should work + */ +export type CommentWithQuotes = number; + +/** + * Testing reserved characters in string: * inline * and ** inline ** should work + */ +export type CommentWithReservedCharacters = number; + +/** + * This is a simple number + */ +export type SimpleInteger = number; + +/** + * This is a simple boolean + */ +export type SimpleBoolean = boolean; + +/** + * This is a simple string + */ +export type SimpleString = string; + +/** + * A string with non-ascii (unicode) characters valid in typescript identifiers (æøåÆØÅöÔèÈ字符串) + */ +export type NonAsciiStringæøåÆøÅöôêÊ字符串 = string; + +/** + * This is a simple file + */ +export type SimpleFile = Blob | File; + +/** + * This is a simple reference + */ +export type SimpleReference = ModelWithString; + +/** + * This is a simple string + */ +export type SimpleStringWithPattern = string | null; + +/** + * This is a simple enum with strings + */ +export type EnumWithStrings = 'Success' | 'Warning' | 'Error' | '\'Single Quote\'' | '"Double Quotes"' | 'Non-ascii: øæåôöØÆÅÔÖ字符串'; + +export type EnumWithReplacedCharacters = '\'Single Quote\'' | '"Double Quotes"' | 'øæåôöØÆÅÔÖ字符串' | 3.1 | ''; + +/** + * This is a simple enum with numbers + */ +export type EnumWithNumbers = 1 | 2 | 3 | 1.1 | 1.2 | 1.3 | 100 | 200 | 300 | -100 | -200 | -300 | -1.1 | -1.2 | -1.3; + +/** + * Success=1,Warning=2,Error=3 + */ +export type EnumFromDescription = number; + +/** + * This is a simple enum with numbers + */ +export type EnumWithExtensions = 200 | 400 | 500; + +export type EnumWithXEnumNames = 0 | 1 | 2; + +/** + * This is a simple array with numbers + */ +export type ArrayWithNumbers = Array; + +/** + * This is a simple array with booleans + */ +export type ArrayWithBooleans = Array; + +/** + * This is a simple array with strings + */ +export type ArrayWithStrings = Array; + +/** + * This is a simple array with references + */ +export type ArrayWithReferences = Array; + +/** + * This is a simple array containing an array + */ +export type ArrayWithArray = Array>; + +/** + * This is a simple array with properties + */ +export type ArrayWithProperties = Array<{ + '16x16'?: CamelCaseCommentWithBreaks; + bar?: string; +}>; + +/** + * This is a simple array with any of properties + */ +export type ArrayWithAnyOfProperties = Array<{ + foo?: string; +} | { + bar?: string; +}>; + +export type AnyOfAnyAndNull = { + data?: unknown | null; +}; + +/** + * This is a simple array with any of properties + */ +export type AnyOfArrays = { + results?: Array<{ + foo?: string; + } | { + bar?: string; + }>; +}; + +/** + * This is a string dictionary + */ +export type DictionaryWithString = { + [key: string]: string; +}; + +export type DictionaryWithPropertiesAndAdditionalProperties = { + foo?: number; + bar?: boolean; + [key: string]: string | number | boolean | undefined; +}; + +/** + * This is a string reference + */ +export type DictionaryWithReference = { + [key: string]: ModelWithString; +}; + +/** + * This is a complex dictionary + */ +export type DictionaryWithArray = { + [key: string]: Array; +}; + +/** + * This is a string dictionary + */ +export type DictionaryWithDictionary = { + [key: string]: { + [key: string]: string; + }; +}; + +/** + * This is a complex dictionary + */ +export type DictionaryWithProperties = { + [key: string]: { + foo?: string; + bar?: string; + }; +}; + +/** + * This is a model with one number property + */ +export type ModelWithInteger = { + /** + * This is a simple number property + */ + prop?: number; +}; + +/** + * This is a model with one boolean property + */ +export type ModelWithBoolean = { + /** + * This is a simple boolean property + */ + prop?: boolean; +}; + +/** + * This is a model with one string property + */ +export type ModelWithString = { + /** + * This is a simple string property + */ + prop?: string; +}; + +/** + * This is a model with one string property + */ +export type ModelWithStringError = { + /** + * This is a simple string property + */ + prop?: string; +}; + +/** + * `Comment` or `VoiceComment`. The JSON object for adding voice comments to tickets is different. See [Adding voice comments to tickets](/documentation/ticketing/managing-tickets/adding-voice-comments-to-tickets) + */ +export type ModelFromZendesk = string; + +/** + * This is a model with one string property + */ +export type ModelWithNullableString = { + /** + * This is a simple string property + */ + nullableProp1?: string | null; + /** + * This is a simple string property + */ + nullableRequiredProp1: string | null; + /** + * This is a simple string property + */ + nullableProp2?: string | null; + /** + * This is a simple string property + */ + nullableRequiredProp2: string | null; + /** + * This is a simple enum with strings + */ + 'foo_bar-enum'?: 'Success' | 'Warning' | 'Error' | 'ØÆÅ字符串'; +}; + +/** + * This is a model with one enum + */ +export type ModelWithEnum = { + /** + * This is a simple enum with strings + */ + 'foo_bar-enum'?: 'Success' | 'Warning' | 'Error' | 'ØÆÅ字符串'; + /** + * These are the HTTP error code enums + */ + statusCode?: '100' | '200 FOO' | '300 FOO_BAR' | '400 foo-bar' | '500 foo.bar' | '600 foo&bar'; + /** + * Simple boolean enum + */ + bool?: true; +}; + +/** + * This is a model with one enum with escaped name + */ +export type ModelWithEnumWithHyphen = { + /** + * Foo-Bar-Baz-Qux + */ + 'foo-bar-baz-qux'?: '3.0'; +}; + +/** + * This is a model with one enum + */ +export type ModelWithEnumFromDescription = { + /** + * Success=1,Warning=2,Error=3 + */ + test?: number; +}; + +/** + * This is a model with nested enums + */ +export type ModelWithNestedEnums = { + dictionaryWithEnum?: { + [key: string]: 'Success' | 'Warning' | 'Error'; + }; + dictionaryWithEnumFromDescription?: { + [key: string]: number; + }; + arrayWithEnum?: Array<'Success' | 'Warning' | 'Error'>; + arrayWithDescription?: Array; + /** + * This is a simple enum with strings + */ + 'foo_bar-enum'?: 'Success' | 'Warning' | 'Error' | 'ØÆÅ字符串'; +}; + +/** + * This is a model with one property containing a reference + */ +export type ModelWithReference = { + prop?: ModelWithProperties; +}; + +/** + * This is a model with one property containing an array + */ +export type ModelWithArrayReadOnlyAndWriteOnly = { + prop?: Array; + propWithFile?: Array; + propWithNumber?: Array; +}; + +/** + * This is a model with one property containing an array + */ +export type ModelWithArray = { + prop?: Array; + propWithFile?: Array; + propWithNumber?: Array; +}; + +/** + * This is a model with one property containing a dictionary + */ +export type ModelWithDictionary = { + prop?: { + [key: string]: string; + }; +}; + +/** + * This is a deprecated model with a deprecated property + * + * @deprecated + */ +export type DeprecatedModel = { + /** + * This is a deprecated property + * + * @deprecated + */ + prop?: string; +}; + +/** + * This is a model with one property containing a circular reference + */ +export type ModelWithCircularReference = { + prop?: ModelWithCircularReference; +}; + +/** + * This is a model with one property with a 'one of' relationship + */ +export type CompositionWithOneOf = { + propA?: ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary; +}; + +/** + * This is a model with one property with a 'one of' relationship where the options are not $ref + */ +export type CompositionWithOneOfAnonymous = { + propA?: { + propA?: string; + } | string | number; +}; + +/** + * Circle + */ +export type ModelCircle = { + kind: string; + radius?: number; +}; + +/** + * Square + */ +export type ModelSquare = { + kind: string; + sideLength?: number; +}; + +/** + * This is a model with one property with a 'one of' relationship where the options are not $ref + */ +export type CompositionWithOneOfDiscriminator = ({ + kind: 'circle'; +} & ModelCircle) | ({ + kind: 'square'; +} & ModelSquare); + +/** + * This is a model with one property with a 'any of' relationship + */ +export type CompositionWithAnyOf = { + propA?: ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary; +}; + +/** + * This is a model with one property with a 'any of' relationship where the options are not $ref + */ +export type CompositionWithAnyOfAnonymous = { + propA?: { + propA?: string; + } | string | number; +}; + +/** + * This is a model with nested 'any of' property with a type null + */ +export type CompositionWithNestedAnyAndTypeNull = { + propA?: Array | Array; +}; + +export type _3eNum1Период = 'Bird' | 'Dog'; + +export type ConstValue = 'ConstValue'; + +/** + * This is a model with one property with a 'any of' relationship where the options are not $ref + */ +export type CompositionWithNestedAnyOfAndNull = { + /** + * Scopes + */ + propA?: Array<_3eNum1Период | ConstValue> | null; +}; + +/** + * This is a model with one property with a 'one of' relationship + */ +export type CompositionWithOneOfAndNullable = { + propA?: { + boolean?: boolean; + } | ModelWithEnum | ModelWithArray | ModelWithDictionary | null; +}; + +/** + * This is a model that contains a simple dictionary within composition + */ +export type CompositionWithOneOfAndSimpleDictionary = { + propA?: boolean | { + [key: string]: number; + }; +}; + +/** + * This is a model that contains a dictionary of simple arrays within composition + */ +export type CompositionWithOneOfAndSimpleArrayDictionary = { + propA?: boolean | { + [key: string]: Array; + }; +}; + +/** + * This is a model that contains a dictionary of complex arrays (composited) within composition + */ +export type CompositionWithOneOfAndComplexArrayDictionary = { + propA?: boolean | { + [key: string]: Array; + }; +}; + +/** + * This is a model with one property with a 'all of' relationship + */ +export type CompositionWithAllOfAndNullable = { + propA?: ({ + boolean?: boolean; + } & ModelWithEnum & ModelWithArray & ModelWithDictionary) | null; +}; + +/** + * This is a model with one property with a 'any of' relationship + */ +export type CompositionWithAnyOfAndNullable = { + propA?: { + boolean?: boolean; + } | ModelWithEnum | ModelWithArray | ModelWithDictionary | null; +}; + +/** + * This is a base model with two simple optional properties + */ +export type CompositionBaseModel = { + firstName?: string; + lastname?: string; +}; + +/** + * This is a model that extends the base model + */ +export type CompositionExtendedModel = CompositionBaseModel & { + age: number; + firstName: string; + lastname: string; +}; + +/** + * This is a model with one nested property + */ +export type ModelWithProperties = { + required: string; + readonly requiredAndReadOnly: string; + requiredAndNullable: string | null; + string?: string; + number?: number; + boolean?: boolean; + reference?: ModelWithString; + 'property with space'?: string; + default?: string; + try?: string; + readonly '@namespace.string'?: string; + readonly '@namespace.integer'?: number; +}; + +/** + * This is a model with one nested property + */ +export type ModelWithNestedProperties = { + readonly first: { + readonly second: { + readonly third: string | null; + } | null; + } | null; +}; + +/** + * This is a model with duplicated properties + */ +export type ModelWithDuplicateProperties = { + prop?: ModelWithString; +}; + +/** + * This is a model with ordered properties + */ +export type ModelWithOrderedProperties = { + zebra?: string; + apple?: string; + hawaii?: string; +}; + +/** + * This is a model with duplicated imports + */ +export type ModelWithDuplicateImports = { + propA?: ModelWithString; + propB?: ModelWithString; + propC?: ModelWithString; +}; + +/** + * This is a model that extends another model + */ +export type ModelThatExtends = ModelWithString & { + propExtendsA?: string; + propExtendsB?: ModelWithString; +}; + +/** + * This is a model that extends another model + */ +export type ModelThatExtendsExtends = ModelWithString & ModelThatExtends & { + propExtendsC?: string; + propExtendsD?: ModelWithString; +}; + +/** + * This is a model that contains a some patterns + */ +export type ModelWithPattern = { + key: string; + name: string; + readonly enabled?: boolean; + readonly modified?: string; + id?: string; + text?: string; + patternWithSingleQuotes?: string; + patternWithNewline?: string; + patternWithBacktick?: string; + patternWithUnicode?: string; +}; + +export type File = { + /** + * Id + */ + readonly id?: string; + /** + * Updated at + */ + readonly updated_at?: string; + /** + * Created at + */ + readonly created_at?: string; + /** + * Mime + */ + mime: string; + /** + * File + */ + readonly file?: string; +}; + +export type Default = { + name?: string; +}; + +export type Pageable = { + page?: number; + size?: number; + sort?: Array; +}; + +/** + * This is a free-form object without additionalProperties. + */ +export type FreeFormObjectWithoutAdditionalProperties = { + [key: string]: unknown; +}; + +/** + * This is a free-form object with additionalProperties: true. + */ +export type FreeFormObjectWithAdditionalPropertiesEqTrue = { + [key: string]: unknown; +}; + +/** + * This is a free-form object with additionalProperties: {}. + */ +export type FreeFormObjectWithAdditionalPropertiesEqEmptyObject = { + [key: string]: unknown; +}; + +export type ModelWithConst = { + String?: 'String'; + number?: 0; + null?: null; + withType?: 'Some string'; +}; + +/** + * This is a model with one property and additionalProperties: true + */ +export type ModelWithAdditionalPropertiesEqTrue = { + /** + * This is a simple string property + */ + prop?: string; + [key: string]: unknown; +}; + +export type NestedAnyOfArraysNullable = { + nullableArray?: Array | null; +}; + +export type CompositionWithOneOfAndProperties = ({ + foo: SimpleParameter; +} | { + bar: NonAsciiStringæøåÆøÅöôêÊ字符串; +}) & { + baz: number | null; + qux: number; +}; + +/** + * An object that can be null + */ +export type NullableObject = { + foo?: string; +} | null; + +/** + * Some % character + */ +export type CharactersInDescription = string; + +export type ModelWithNullableObject = { + data?: NullableObject; +}; + +/** + * An object with additional properties that can be null (anyOf ref + null) + */ +export type ModelWithAdditionalPropertiesRef = { + [key: string]: NullableObject | null; +}; + +export type ModelWithOneOfEnum = { + foo: 'Bar'; +} | { + foo: 'Baz'; +} | { + foo: 'Qux'; +} | { + content: string; + foo: 'Quux'; +} | { + content: [ + string, + string + ]; + foo: 'Corge'; +}; + +export type ModelWithNestedArrayEnumsDataFoo = 'foo' | 'bar'; + +export type ModelWithNestedArrayEnumsDataBar = 'baz' | 'qux'; + +export type ModelWithNestedArrayEnumsData = { + foo?: Array; + bar?: Array; +}; + +export type ModelWithNestedArrayEnums = { + array_strings?: Array; + data?: ModelWithNestedArrayEnumsData; +}; + +export type ModelWithNestedCompositionEnums = { + foo?: ModelWithNestedArrayEnumsDataFoo; +}; + +export type ModelWithReadOnlyAndWriteOnly = { + foo: string; + readonly bar: string; +}; + +export type ModelWithConstantSizeArray = [ + number, + number +]; + +export type ModelWithAnyOfConstantSizeArray = [ + number | string, + number | string, + number | string +]; + +export type ModelWithPrefixItemsConstantSizeArray = [ + ModelWithInteger, + number | string, + string +]; + +export type ModelWithAnyOfConstantSizeArrayNullable = [ + number | null | string, + number | null | string, + number | null | string +]; + +export type ModelWithAnyOfConstantSizeArrayWithNSizeAndOptions = [ + number | Import, + number | Import +]; + +export type ModelWithAnyOfConstantSizeArrayAndIntersect = [ + number & string, + number & string +]; + +export type ModelWithNumericEnumUnion = { + /** + * Период + */ + value?: -10 | -1 | 0 | 1 | 3 | 6 | 12; +}; + +/** + * Some description with `back ticks` + */ +export type ModelWithBackticksInDescription = { + /** + * The template `that` should be used for parsing and importing the contents of the CSV file. + * + *

There is one placeholder currently supported:

  • ${x} - refers to the n-th column in the CSV file, e.g. ${1}, ${2}, ...)

Example of a correct JSON template:

+ *
+     * [
+     * {
+     * "resourceType": "Asset",
+     * "identifier": {
+     * "name": "${1}",
+     * "domain": {
+     * "name": "${2}",
+     * "community": {
+     * "name": "Some Community"
+     * }
+     * }
+     * },
+     * "attributes" : {
+     * "00000000-0000-0000-0000-000000003115" : [ {
+     * "value" : "${3}"
+     * } ],
+     * "00000000-0000-0000-0000-000000000222" : [ {
+     * "value" : "${4}"
+     * } ]
+     * }
+     * }
+     * ]
+     * 
+ */ + template?: string; +}; + +export type ModelWithOneOfAndProperties = (SimpleParameter | NonAsciiStringæøåÆøÅöôêÊ字符串) & { + baz: number | null; + qux: number; +}; + +/** + * Model used to test deduplication strategy (unused) + */ +export type ParameterSimpleParameterUnused = string; + +/** + * Model used to test deduplication strategy + */ +export type PostServiceWithEmptyTagResponse = string; + +/** + * Model used to test deduplication strategy + */ +export type PostServiceWithEmptyTagResponse2 = string; + +/** + * Model used to test deduplication strategy + */ +export type DeleteFooData = string; + +/** + * Model used to test deduplication strategy + */ +export type DeleteFooData2 = string; + +/** + * Model with restricted keyword name + */ +export type Import = string; + +export type SchemaWithFormRestrictedKeys = { + description?: string; + 'x-enum-descriptions'?: string; + 'x-enum-varnames'?: string; + 'x-enumNames'?: string; + title?: string; + object?: { + description?: string; + 'x-enum-descriptions'?: string; + 'x-enum-varnames'?: string; + 'x-enumNames'?: string; + title?: string; + }; + array?: Array<{ + description?: string; + 'x-enum-descriptions'?: string; + 'x-enum-varnames'?: string; + 'x-enumNames'?: string; + title?: string; + }>; +}; + +/** + * This schema was giving PascalCase transformations a hard time + */ +export type IoK8sApimachineryPkgApisMetaV1DeleteOptions = { + /** + * Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned. + */ + preconditions?: IoK8sApimachineryPkgApisMetaV1Preconditions; +}; + +/** + * This schema was giving PascalCase transformations a hard time + */ +export type IoK8sApimachineryPkgApisMetaV1Preconditions = { + /** + * Specifies the target ResourceVersion + */ + resourceVersion?: string; + /** + * Specifies the target UID. + */ + uid?: string; +}; + +export type AdditionalPropertiesUnknownIssue = { + [key: string]: string | number; +}; + +export type AdditionalPropertiesUnknownIssue2 = { + [key: string]: string | number; +}; + +export type AdditionalPropertiesUnknownIssue3 = string & { + entries: { + [key: string]: AdditionalPropertiesUnknownIssue; + }; +}; + +export type AdditionalPropertiesIntegerIssue = { + value: number; + [key: string]: number; +}; + +export type OneOfAllOfIssue = ((ConstValue | GenericSchemaDuplicateIssue1SystemBoolean) & _3eNum1Период) | GenericSchemaDuplicateIssue1SystemString; + +export type GenericSchemaDuplicateIssue1SystemBoolean = { + item?: boolean; + error?: string | null; + readonly hasError?: boolean; + data?: { + [key: string]: never; + }; +}; + +export type GenericSchemaDuplicateIssue1SystemString = { + item?: string | null; + error?: string | null; + readonly hasError?: boolean; +}; + +export type ExternalSharedModel = { + id: string; + name?: string; +}; + +/** + * This is a model with one property containing a reference + */ +export type ModelWithReferenceWritable = { + prop?: ModelWithPropertiesWritable; +}; + +/** + * This is a model with one property containing an array + */ +export type ModelWithArrayReadOnlyAndWriteOnlyWritable = { + prop?: Array; + propWithFile?: Array; + propWithNumber?: Array; +}; + +/** + * This is a model with one nested property + */ +export type ModelWithPropertiesWritable = { + required: string; + requiredAndNullable: string | null; + string?: string; + number?: number; + boolean?: boolean; + reference?: ModelWithString; + 'property with space'?: string; + default?: string; + try?: string; +}; + +/** + * This is a model that contains a some patterns + */ +export type ModelWithPatternWritable = { + key: string; + name: string; + id?: string; + text?: string; + patternWithSingleQuotes?: string; + patternWithNewline?: string; + patternWithBacktick?: string; + patternWithUnicode?: string; +}; + +export type FileWritable = { + /** + * Mime + */ + mime: string; +}; + +export type ModelWithReadOnlyAndWriteOnlyWritable = { + foo: string; + baz: string; +}; + +export type ModelWithAnyOfConstantSizeArrayWithNSizeAndOptionsWritable = [ + number | Import, + number | Import +]; + +export type AdditionalPropertiesUnknownIssueWritable = { + [key: string]: string | number; +}; + +export type OneOfAllOfIssueWritable = ((ConstValue | GenericSchemaDuplicateIssue1SystemBoolean) & _3eNum1Период) | GenericSchemaDuplicateIssue1SystemString; + +export type GenericSchemaDuplicateIssue1SystemBooleanWritable = { + item?: boolean; + error?: string | null; + data?: { + [key: string]: never; + }; +}; + +export type GenericSchemaDuplicateIssue1SystemStringWritable = { + item?: string | null; + error?: string | null; +}; + +/** + * This is a reusable parameter + */ +export type SimpleParameter = string; + +/** + * Parameter with illegal characters + */ +export type XFooBar = ModelWithString; + +/** + * A reusable request body + */ +export type SimpleRequestBody = ModelWithString; + +/** + * A reusable request body + */ +export type SimpleFormData = ModelWithString; + +export type ExportData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type PatchApiVbyApiVersionNoTagData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type PatchApiVbyApiVersionNoTagResponses = { + /** + * OK + */ + default: unknown; +}; + +export type ImportData = { + body: ModelWithReadOnlyAndWriteOnlyWritable | ModelWithArrayReadOnlyAndWriteOnlyWritable; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type ImportResponses = { + /** + * Success + */ + 200: ModelFromZendesk; + /** + * Default success response + */ + default: ModelWithReadOnlyAndWriteOnly; +}; + +export type ImportResponse = ImportResponses[keyof ImportResponses]; + +export type FooWowData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type FooWowResponses = { + /** + * OK + */ + default: unknown; +}; + +export type ApiVVersionODataControllerCountData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple/$count'; +}; + +export type ApiVVersionODataControllerCountResponses = { + /** + * Success + */ + 200: ModelFromZendesk; +}; + +export type ApiVVersionODataControllerCountResponse = ApiVVersionODataControllerCountResponses[keyof ApiVVersionODataControllerCountResponses]; + +export type GetApiVbyApiVersionSimpleOperationData = { + body?: never; + path: { + /** + * foo in method + */ + foo_param: string; + }; + query?: never; + url: '/api/v{api-version}/simple:operation'; +}; + +export type GetApiVbyApiVersionSimpleOperationErrors = { + /** + * Default error response + */ + default: ModelWithBoolean; +}; + +export type GetApiVbyApiVersionSimpleOperationError = GetApiVbyApiVersionSimpleOperationErrors[keyof GetApiVbyApiVersionSimpleOperationErrors]; + +export type GetApiVbyApiVersionSimpleOperationResponses = { + /** + * Response is a simple number + */ + 200: number; +}; + +export type GetApiVbyApiVersionSimpleOperationResponse = GetApiVbyApiVersionSimpleOperationResponses[keyof GetApiVbyApiVersionSimpleOperationResponses]; + +export type DeleteCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type GetCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type HeadCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type OptionsCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type PatchCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type PostCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type PutCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type DeleteFooData3 = { + body?: never; + headers: { + /** + * Parameter with illegal characters + */ + 'x-Foo-Bar': ModelWithString; + }; + path: { + /** + * foo in method + */ + foo_param: string; + /** + * bar in method + */ + BarParam: string; + }; + query?: never; + url: '/api/v{api-version}/foo/{foo_param}/bar/{BarParam}'; +}; + +export type CallWithDescriptionsData = { + body?: never; + path?: never; + query?: { + /** + * Testing multiline comments in string: First line + * Second line + * + * Fourth line + */ + parameterWithBreaks?: string; + /** + * Testing backticks in string: `backticks` and ```multiple backticks``` should work + */ + parameterWithBackticks?: string; + /** + * Testing slashes in string: \backwards\\\ and /forwards/// should work + */ + parameterWithSlashes?: string; + /** + * Testing expression placeholders in string: ${expression} should work + */ + parameterWithExpressionPlaceholders?: string; + /** + * Testing quotes in string: 'single quote''' and "double quotes""" should work + */ + parameterWithQuotes?: string; + /** + * Testing reserved characters in string: * inline * and ** inline ** should work + */ + parameterWithReservedCharacters?: string; + }; + url: '/api/v{api-version}/descriptions'; +}; + +export type DeprecatedCallData = { + body?: never; + headers: { + /** + * This parameter is deprecated + * + * @deprecated + */ + parameter: DeprecatedModel | null; + }; + path?: never; + query?: never; + url: '/api/v{api-version}/parameters/deprecated'; +}; + +export type CallWithParametersData = { + /** + * This is the parameter that goes into the body + */ + body: { + [key: string]: unknown; + } | null; + headers: { + /** + * This is the parameter that goes into the header + */ + parameterHeader: string | null; + }; + path: { + /** + * This is the parameter that goes into the path + */ + parameterPath: string | null; + /** + * api-version should be required in standalone clients + */ + 'api-version': string | null; + }; + query: { + foo_ref_enum?: ModelWithNestedArrayEnumsDataFoo; + foo_all_of_enum: ModelWithNestedArrayEnumsDataFoo; + /** + * This is the parameter that goes into the query params + */ + cursor: string | null; + }; + url: '/api/v{api-version}/parameters/{parameterPath}'; +}; + +export type CallWithWeirdParameterNamesData = { + /** + * This is the parameter that goes into the body + */ + body: ModelWithString | null; + headers: { + /** + * This is the parameter that goes into the request header + */ + 'parameter.header': string | null; + }; + path: { + /** + * This is the parameter that goes into the path + */ + 'parameter.path.1'?: string; + /** + * This is the parameter that goes into the path + */ + 'parameter-path-2'?: string; + /** + * This is the parameter that goes into the path + */ + 'PARAMETER-PATH-3'?: string; + /** + * api-version should be required in standalone clients + */ + 'api-version': string | null; + }; + query: { + /** + * This is the parameter with a reserved keyword + */ + default?: string; + /** + * This is the parameter that goes into the request query params + */ + 'parameter-query': string | null; + }; + url: '/api/v{api-version}/parameters/{parameter.path.1}/{parameter-path-2}/{PARAMETER-PATH-3}'; +}; + +export type GetCallWithOptionalParamData = { + /** + * This is a required parameter + */ + body: ModelWithOneOfEnum; + path?: never; + query?: { + /** + * This is an optional parameter + */ + page?: number; + }; + url: '/api/v{api-version}/parameters'; +}; + +export type PostCallWithOptionalParamData = { + /** + * This is an optional parameter + */ + body?: { + offset?: number | null; + }; + path?: never; + query: { + /** + * This is a required parameter + */ + parameter: Pageable; + }; + url: '/api/v{api-version}/parameters'; +}; + +export type PostCallWithOptionalParamResponses = { + /** + * Response is a simple number + */ + 200: number; + /** + * Success + */ + 204: void; +}; + +export type PostCallWithOptionalParamResponse = PostCallWithOptionalParamResponses[keyof PostCallWithOptionalParamResponses]; + +export type PostApiVbyApiVersionRequestBodyData = { + /** + * A reusable request body + */ + body?: SimpleRequestBody; + path?: never; + query?: { + /** + * This is a reusable parameter + */ + parameter?: string; + }; + url: '/api/v{api-version}/requestBody'; +}; + +export type PostApiVbyApiVersionFormDataData = { + /** + * A reusable request body + */ + body?: SimpleFormData; + path?: never; + query?: { + /** + * This is a reusable parameter + */ + parameter?: string; + }; + url: '/api/v{api-version}/formData'; +}; + +export type CallWithDefaultParametersData = { + body?: never; + path?: never; + query?: { + /** + * This is a simple string with default value + */ + parameterString?: string | null; + /** + * This is a simple number with default value + */ + parameterNumber?: number | null; + /** + * This is a simple boolean with default value + */ + parameterBoolean?: boolean | null; + /** + * This is a simple enum with default value + */ + parameterEnum?: 'Success' | 'Warning' | 'Error'; + /** + * This is a simple model with default value + */ + parameterModel?: ModelWithString | null; + }; + url: '/api/v{api-version}/defaults'; +}; + +export type CallWithDefaultOptionalParametersData = { + body?: never; + path?: never; + query?: { + /** + * This is a simple string that is optional with default value + */ + parameterString?: string; + /** + * This is a simple number that is optional with default value + */ + parameterNumber?: number; + /** + * This is a simple boolean that is optional with default value + */ + parameterBoolean?: boolean; + /** + * This is a simple enum that is optional with default value + */ + parameterEnum?: 'Success' | 'Warning' | 'Error'; + /** + * This is a simple model that is optional with default value + */ + parameterModel?: ModelWithString; + }; + url: '/api/v{api-version}/defaults'; +}; + +export type CallToTestOrderOfParamsData = { + body?: never; + path?: never; + query: { + /** + * This is a optional string with default + */ + parameterOptionalStringWithDefault?: string; + /** + * This is a optional string with empty default + */ + parameterOptionalStringWithEmptyDefault?: string; + /** + * This is a optional string with no default + */ + parameterOptionalStringWithNoDefault?: string; + /** + * This is a string with default + */ + parameterStringWithDefault: string; + /** + * This is a string with empty default + */ + parameterStringWithEmptyDefault: string; + /** + * This is a string with no default + */ + parameterStringWithNoDefault: string; + /** + * This is a string that can be null with no default + */ + parameterStringNullableWithNoDefault?: string | null; + /** + * This is a string that can be null with default + */ + parameterStringNullableWithDefault?: string | null; + }; + url: '/api/v{api-version}/defaults'; +}; + +export type DuplicateNameData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type DuplicateName2Data = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type DuplicateName3Data = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type DuplicateName4Data = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type CallWithNoContentResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no-content'; +}; + +export type CallWithNoContentResponseResponses = { + /** + * Success + */ + 204: void; +}; + +export type CallWithNoContentResponseResponse = CallWithNoContentResponseResponses[keyof CallWithNoContentResponseResponses]; + +export type CallWithResponseAndNoContentResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multiple-tags/response-and-no-content'; +}; + +export type CallWithResponseAndNoContentResponseResponses = { + /** + * Response is a simple number + */ + 200: number; + /** + * Success + */ + 204: void; +}; + +export type CallWithResponseAndNoContentResponseResponse = CallWithResponseAndNoContentResponseResponses[keyof CallWithResponseAndNoContentResponseResponses]; + +export type DummyAData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multiple-tags/a'; +}; + +export type DummyAResponses = { + 200: _400; +}; + +export type DummyAResponse = DummyAResponses[keyof DummyAResponses]; + +export type DummyBData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multiple-tags/b'; +}; + +export type DummyBResponses = { + /** + * Success + */ + 204: void; +}; + +export type DummyBResponse = DummyBResponses[keyof DummyBResponses]; + +export type CallWithResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/response'; +}; + +export type CallWithResponseResponses = { + default: Import; +}; + +export type CallWithResponseResponse = CallWithResponseResponses[keyof CallWithResponseResponses]; + +export type CallWithDuplicateResponsesData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/response'; +}; + +export type CallWithDuplicateResponsesErrors = { + /** + * Message for 500 error + */ + 500: ModelWithStringError; + /** + * Message for 501 error + */ + 501: ModelWithStringError; + /** + * Message for 502 error + */ + 502: ModelWithStringError; + /** + * Message for 4XX errors + */ + '4XX': DictionaryWithArray; + /** + * Default error response + */ + default: ModelWithBoolean; +}; + +export type CallWithDuplicateResponsesError = CallWithDuplicateResponsesErrors[keyof CallWithDuplicateResponsesErrors]; + +export type CallWithDuplicateResponsesResponses = { + /** + * Message for 200 response + */ + 200: ModelWithBoolean & ModelWithInteger; + /** + * Message for 201 response + */ + 201: ModelWithString; + /** + * Message for 202 response + */ + 202: ModelWithString; +}; + +export type CallWithDuplicateResponsesResponse = CallWithDuplicateResponsesResponses[keyof CallWithDuplicateResponsesResponses]; + +export type CallWithResponsesData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/response'; +}; + +export type CallWithResponsesErrors = { + /** + * Message for 500 error + */ + 500: ModelWithStringError; + /** + * Message for 501 error + */ + 501: ModelWithStringError; + /** + * Message for 502 error + */ + 502: ModelWithStringError; + /** + * Message for default response + */ + default: ModelWithStringError; +}; + +export type CallWithResponsesError = CallWithResponsesErrors[keyof CallWithResponsesErrors]; + +export type CallWithResponsesResponses = { + /** + * Message for 200 response + */ + 200: { + readonly '@namespace.string'?: string; + readonly '@namespace.integer'?: number; + readonly value?: Array; + }; + /** + * Message for 201 response + */ + 201: ModelThatExtends; + /** + * Message for 202 response + */ + 202: ModelThatExtendsExtends; +}; + +export type CallWithResponsesResponse = CallWithResponsesResponses[keyof CallWithResponsesResponses]; + +export type CollectionFormatData = { + body?: never; + path?: never; + query: { + /** + * This is an array parameter that is sent as csv format (comma-separated values) + */ + parameterArrayCSV: Array | null; + /** + * This is an array parameter that is sent as ssv format (space-separated values) + */ + parameterArraySSV: Array | null; + /** + * This is an array parameter that is sent as tsv format (tab-separated values) + */ + parameterArrayTSV: Array | null; + /** + * This is an array parameter that is sent as pipes format (pipe-separated values) + */ + parameterArrayPipes: Array | null; + /** + * This is an array parameter that is sent as multi format (multiple parameter instances) + */ + parameterArrayMulti: Array | null; + }; + url: '/api/v{api-version}/collectionFormat'; +}; + +export type TypesData = { + body?: never; + path?: { + /** + * This is a number parameter + */ + id?: number; + }; + query: { + /** + * This is a number parameter + */ + parameterNumber: number; + /** + * This is a string parameter + */ + parameterString: string | null; + /** + * This is a boolean parameter + */ + parameterBoolean: boolean | null; + /** + * This is an object parameter + */ + parameterObject: { + [key: string]: unknown; + } | null; + /** + * This is an array parameter + */ + parameterArray: Array | null; + /** + * This is a dictionary parameter + */ + parameterDictionary: { + [key: string]: unknown; + } | null; + /** + * This is an enum parameter + */ + parameterEnum: 'Success' | 'Warning' | 'Error' | null; + }; + url: '/api/v{api-version}/types'; +}; + +export type TypesResponses = { + /** + * Response is a simple number + */ + 200: number; + /** + * Response is a simple string + */ + 201: string; + /** + * Response is a simple boolean + */ + 202: boolean; + /** + * Response is a simple object + */ + 203: { + [key: string]: unknown; + }; +}; + +export type TypesResponse = TypesResponses[keyof TypesResponses]; + +export type UploadFileData = { + body: Blob | File; + path: { + /** + * api-version should be required in standalone clients + */ + 'api-version': string | null; + }; + query?: never; + url: '/api/v{api-version}/upload'; +}; + +export type UploadFileResponses = { + 200: boolean; +}; + +export type UploadFileResponse = UploadFileResponses[keyof UploadFileResponses]; + +export type FileResponseData = { + body?: never; + path: { + id: string; + /** + * api-version should be required in standalone clients + */ + 'api-version': string; + }; + query?: never; + url: '/api/v{api-version}/file/{id}'; +}; + +export type FileResponseResponses = { + /** + * Success + */ + 200: Blob | File; +}; + +export type FileResponseResponse = FileResponseResponses[keyof FileResponseResponses]; + +export type ComplexTypesData = { + body?: never; + path?: never; + query: { + /** + * Parameter containing object + */ + parameterObject: { + first?: { + second?: { + third?: string; + }; + }; + }; + /** + * Parameter containing reference + */ + parameterReference: ModelWithString; + }; + url: '/api/v{api-version}/complex'; +}; + +export type ComplexTypesErrors = { + /** + * 400 `server` error + */ + 400: unknown; + /** + * 500 server error + */ + 500: unknown; +}; + +export type ComplexTypesResponses = { + /** + * Successful response + */ + 200: Array; +}; + +export type ComplexTypesResponse = ComplexTypesResponses[keyof ComplexTypesResponses]; + +export type MultipartResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multipart'; +}; + +export type MultipartResponseResponses = { + /** + * OK + */ + 200: { + file?: Blob | File; + metadata?: { + foo?: string; + bar?: string; + }; + }; +}; + +export type MultipartResponseResponse = MultipartResponseResponses[keyof MultipartResponseResponses]; + +export type MultipartRequestData = { + body?: { + content?: Blob | File; + data?: ModelWithString | null; + }; + path?: never; + query?: never; + url: '/api/v{api-version}/multipart'; +}; + +export type ComplexParamsData = { + body?: { + readonly key: string | null; + name: string | null; + enabled?: boolean; + type: 'Monkey' | 'Horse' | 'Bird'; + listOfModels?: Array | null; + listOfStrings?: Array | null; + parameters: ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary; + readonly user?: { + readonly id?: number; + readonly name?: string | null; + }; + }; + path: { + id: number; + /** + * api-version should be required in standalone clients + */ + 'api-version': string; + }; + query?: never; + url: '/api/v{api-version}/complex/{id}'; +}; + +export type ComplexParamsResponses = { + /** + * Success + */ + 200: ModelWithString; +}; + +export type ComplexParamsResponse = ComplexParamsResponses[keyof ComplexParamsResponses]; + +export type CallWithResultFromHeaderData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/header'; +}; + +export type CallWithResultFromHeaderErrors = { + /** + * 400 server error + */ + 400: unknown; + /** + * 500 server error + */ + 500: unknown; +}; + +export type CallWithResultFromHeaderResponses = { + /** + * Successful response + */ + 200: unknown; +}; + +export type TestErrorCodeData = { + body?: never; + path?: never; + query: { + /** + * Status code to return + */ + status: number; + }; + url: '/api/v{api-version}/error'; +}; + +export type TestErrorCodeErrors = { + /** + * Custom message: Internal Server Error + */ + 500: unknown; + /** + * Custom message: Not Implemented + */ + 501: unknown; + /** + * Custom message: Bad Gateway + */ + 502: unknown; + /** + * Custom message: Service Unavailable + */ + 503: unknown; +}; + +export type TestErrorCodeResponses = { + /** + * Custom message: Successful response + */ + 200: unknown; +}; + +export type NonAsciiæøåÆøÅöôêÊ字符串Data = { + body?: never; + path?: never; + query: { + /** + * Dummy input param + */ + nonAsciiParamæøåÆØÅöôêÊ: number; + }; + url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串'; +}; + +export type NonAsciiæøåÆøÅöôêÊ字符串Responses = { + /** + * Successful response + */ + 200: Array; +}; + +export type NonAsciiæøåÆøÅöôêÊ字符串Response = NonAsciiæøåÆøÅöôêÊ字符串Responses[keyof NonAsciiæøåÆøÅöôêÊ字符串Responses]; + +export type PutWithFormUrlEncodedData = { + body: ArrayWithStrings; + path?: never; + query?: never; + url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串'; +}; diff --git a/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/response-example-disabled/index.ts b/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/response-example-disabled/index.ts new file mode 100644 index 0000000000..b545604fba --- /dev/null +++ b/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/response-example-disabled/index.ts @@ -0,0 +1,3 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type { ClientOptions, GetFooData, GetFooResponse, GetFooResponses, Person, PostFooData, PostFooResponse, PostFooResponses } from './types.gen'; diff --git a/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/response-example-disabled/msw.gen.ts b/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/response-example-disabled/msw.gen.ts new file mode 100644 index 0000000000..1f216e5be8 --- /dev/null +++ b/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/response-example-disabled/msw.gen.ts @@ -0,0 +1,108 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { http, type HttpHandler, HttpResponse, type HttpResponseResolver, type RequestHandlerOptions as RequestHandlerOptions2 } from 'msw'; + +import type { GetFooResponses, PostFooData, PostFooResponses } from './types.gen'; + +export type RequestHandlerOptions = RequestHandlerOptions2 & { + baseUrl?: string; + responseFallback?: 'error' | 'passthrough'; +}; + +/** + * Handler for the `GET /foo` operation. + */ +export function handleGetFoo(resolver?: { + result: GetFooResponses[200]; + status?: 200; +} | HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.get(`${options?.baseUrl ?? '*'}/foo`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return HttpResponse.json(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +type ToResponseUnion = { + [K in Extract]: { + status: K; + result: T[K]; + }; +}[Extract]; + +/** + * Handler for the `POST /foo` operation. + */ +export function handlePostFoo(resolver?: { + result: PostFooResponses[200]; + status?: 200; +} | ToResponseUnion | HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.post(`${options?.baseUrl ?? '*'}/foo`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return HttpResponse.json(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +export type MswHandlerFactories = { + /** + * Handler for the `GET /foo` operation. + */ + getFoo: typeof handleGetFoo; + /** + * Handler for the `POST /foo` operation. + */ + postFoo: typeof handlePostFoo; +}; + +export type CreateMswHandlersResult = { + all: (options?: { + one?: { + [K in keyof MswHandlerFactories]?: Parameters[0] | Parameters; + }; + }) => ReadonlyArray; + one: MswHandlerFactories; +}; + +export function createMswHandlers(config: RequestHandlerOptions = {}): CreateMswHandlersResult { + type Handler = (resolver?: R, options?: RequestHandlerOptions) => HttpHandler; + function wrap(handler: Handler): Handler { + return (resolver, options) => handler(resolver, { ...config, ...options }); + } + const one: CreateMswHandlersResult['one'] = { + getFoo: wrap(handleGetFoo), + postFoo: wrap(handlePostFoo) + }; + const all: CreateMswHandlersResult['all'] = (options = {}) => { + type OverrideValue = R | [ + resolver?: R, + options?: RequestHandlerOptions + ]; + function invoke(fn: Handler, override?: OverrideValue): HttpHandler { + return Array.isArray(override) ? fn(...override) : fn(override); + } + const overrides = options.one ?? {}; + return [invoke(one.getFoo, overrides.getFoo), invoke(one.postFoo, overrides.postFoo)]; + }; + return { all, one }; +} diff --git a/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/response-example-disabled/types.gen.ts b/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/response-example-disabled/types.gen.ts new file mode 100644 index 0000000000..70782e584a --- /dev/null +++ b/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/response-example-disabled/types.gen.ts @@ -0,0 +1,50 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type ClientOptions = { + baseUrl: `${string}://${string}` | (string & {}); +}; + +export type Person = { + firstName?: string; + lastName?: unknown; + age?: number; +}; + +export type GetFooData = { + body?: never; + path?: never; + query?: never; + url: '/foo'; +}; + +export type GetFooResponses = { + /** + * OK + */ + 200: Person; +}; + +export type GetFooResponse = GetFooResponses[keyof GetFooResponses]; + +export type PostFooData = { + body: string; + path?: never; + query?: never; + url: '/foo'; +}; + +export type PostFooResponses = { + /** + * OK + */ + 200: { + fullName?: string; + age?: number; + }; + /** + * SUCCESSFUL + */ + 204: void; +}; + +export type PostFooResponse = PostFooResponses[keyof PostFooResponses]; diff --git a/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/response-example/index.ts b/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/response-example/index.ts new file mode 100644 index 0000000000..b545604fba --- /dev/null +++ b/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/response-example/index.ts @@ -0,0 +1,3 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type { ClientOptions, GetFooData, GetFooResponse, GetFooResponses, Person, PostFooData, PostFooResponse, PostFooResponses } from './types.gen'; diff --git a/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/response-example/msw.gen.ts b/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/response-example/msw.gen.ts new file mode 100644 index 0000000000..46e6bcde0d --- /dev/null +++ b/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/response-example/msw.gen.ts @@ -0,0 +1,112 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { http, type HttpHandler, HttpResponse, type HttpResponseResolver, type RequestHandlerOptions as RequestHandlerOptions2 } from 'msw'; + +import type { GetFooResponses, PostFooData, PostFooResponses } from './types.gen'; + +export type RequestHandlerOptions = RequestHandlerOptions2 & { + baseUrl?: string; + responseFallback?: 'error' | 'passthrough'; +}; + +/** + * Handler for the `GET /foo` operation. + */ +export function handleGetFoo(resolver?: { + result: GetFooResponses[200]; + status?: 200; +} | HttpResponseResolver = { result: { + firstName: 'Marry', + lastName: 'Jane', + age: 30 + }, status: 200 }, options?: RequestHandlerOptions): HttpHandler { + return http.get(`${options?.baseUrl ?? '*'}/foo`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return HttpResponse.json(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +type ToResponseUnion = { + [K in Extract]: { + status: K; + result: T[K]; + }; +}[Extract]; + +/** + * Handler for the `POST /foo` operation. + */ +export function handlePostFoo(resolver?: { + result: PostFooResponses[200]; + status?: 200; +} | ToResponseUnion | HttpResponseResolver = { result: { fullName: 'John Doe', age: 34 }, status: 200 }, options?: RequestHandlerOptions): HttpHandler { + return http.post(`${options?.baseUrl ?? '*'}/foo`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return HttpResponse.json(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +export type MswHandlerFactories = { + /** + * Handler for the `GET /foo` operation. + */ + getFoo: typeof handleGetFoo; + /** + * Handler for the `POST /foo` operation. + */ + postFoo: typeof handlePostFoo; +}; + +export type CreateMswHandlersResult = { + all: (options?: { + one?: { + [K in keyof MswHandlerFactories]?: Parameters[0] | Parameters; + }; + }) => ReadonlyArray; + one: MswHandlerFactories; +}; + +export function createMswHandlers(config: RequestHandlerOptions = {}): CreateMswHandlersResult { + type Handler = (resolver?: R, options?: RequestHandlerOptions) => HttpHandler; + function wrap(handler: Handler): Handler { + return (resolver, options) => handler(resolver, { ...config, ...options }); + } + const one: CreateMswHandlersResult['one'] = { + getFoo: wrap(handleGetFoo), + postFoo: wrap(handlePostFoo) + }; + const all: CreateMswHandlersResult['all'] = (options = {}) => { + type OverrideValue = R | [ + resolver?: R, + options?: RequestHandlerOptions + ]; + function invoke(fn: Handler, override?: OverrideValue): HttpHandler { + return Array.isArray(override) ? fn(...override) : fn(override); + } + const overrides = options.one ?? {}; + return [invoke(one.getFoo, overrides.getFoo), invoke(one.postFoo, overrides.postFoo)]; + }; + return { all, one }; +} diff --git a/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/response-example/types.gen.ts b/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/response-example/types.gen.ts new file mode 100644 index 0000000000..70782e584a --- /dev/null +++ b/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/response-example/types.gen.ts @@ -0,0 +1,50 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type ClientOptions = { + baseUrl: `${string}://${string}` | (string & {}); +}; + +export type Person = { + firstName?: string; + lastName?: unknown; + age?: number; +}; + +export type GetFooData = { + body?: never; + path?: never; + query?: never; + url: '/foo'; +}; + +export type GetFooResponses = { + /** + * OK + */ + 200: Person; +}; + +export type GetFooResponse = GetFooResponses[keyof GetFooResponses]; + +export type PostFooData = { + body: string; + path?: never; + query?: never; + url: '/foo'; +}; + +export type PostFooResponses = { + /** + * OK + */ + 200: { + fullName?: string; + age?: number; + }; + /** + * SUCCESSFUL + */ + 204: void; +}; + +export type PostFooResponse = PostFooResponses[keyof PostFooResponses]; diff --git a/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/response-types/index.ts b/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/response-types/index.ts new file mode 100644 index 0000000000..f389f85547 --- /dev/null +++ b/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/response-types/index.ts @@ -0,0 +1,3 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type { ClientOptions, GetFooData, GetFooResponse, GetFooResponses } from './types.gen'; diff --git a/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/response-types/msw.gen.ts b/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/response-types/msw.gen.ts new file mode 100644 index 0000000000..aac377d6e5 --- /dev/null +++ b/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/response-types/msw.gen.ts @@ -0,0 +1,72 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { http, type HttpHandler, HttpResponse, type HttpResponseResolver, type RequestHandlerOptions as RequestHandlerOptions2 } from 'msw'; + +import type { GetFooResponses } from './types.gen'; + +export type RequestHandlerOptions = RequestHandlerOptions2 & { + baseUrl?: string; + responseFallback?: 'error' | 'passthrough'; +}; + +/** + * Handler for the `GET /foo` operation. + */ +export function handleGetFoo(resolver?: { + result: GetFooResponses[200]; + status?: 200; +} | HttpResponseResolver = { result: { name: 'Alice' }, status: 200 }, options?: RequestHandlerOptions): HttpHandler { + return http.get(`${options?.baseUrl ?? '*'}/foo`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return HttpResponse.json(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +export type MswHandlerFactories = { + /** + * Handler for the `GET /foo` operation. + */ + getFoo: typeof handleGetFoo; +}; + +export type CreateMswHandlersResult = { + all: (options?: { + one?: { + [K in keyof MswHandlerFactories]?: Parameters[0] | Parameters; + }; + }) => ReadonlyArray; + one: MswHandlerFactories; +}; + +export function createMswHandlers(config: RequestHandlerOptions = {}): CreateMswHandlersResult { + type Handler = (resolver?: R, options?: RequestHandlerOptions) => HttpHandler; + function wrap(handler: Handler): Handler { + return (resolver, options) => handler(resolver, { ...config, ...options }); + } + const one: CreateMswHandlersResult['one'] = { + getFoo: wrap(handleGetFoo) + }; + const all: CreateMswHandlersResult['all'] = (options = {}) => { + type OverrideValue = R | [ + resolver?: R, + options?: RequestHandlerOptions + ]; + function invoke(fn: Handler, override?: OverrideValue): HttpHandler { + return Array.isArray(override) ? fn(...override) : fn(override); + } + const overrides = options.one ?? {}; + return [invoke(one.getFoo, overrides.getFoo)]; + }; + return { all, one }; +} diff --git a/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/response-types/types.gen.ts b/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/response-types/types.gen.ts new file mode 100644 index 0000000000..a0b37c70a4 --- /dev/null +++ b/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/response-types/types.gen.ts @@ -0,0 +1,23 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type ClientOptions = { + baseUrl: `${string}://${string}` | (string & {}); +}; + +export type GetFooData = { + body?: never; + path?: never; + query?: never; + url: '/foo'; +}; + +export type GetFooResponses = { + /** + * OK + */ + 200: { + name?: string; + }; +}; + +export type GetFooResponse = GetFooResponses[keyof GetFooResponses]; diff --git a/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/servers/index.ts b/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/servers/index.ts new file mode 100644 index 0000000000..f389f85547 --- /dev/null +++ b/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/servers/index.ts @@ -0,0 +1,3 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type { ClientOptions, GetFooData, GetFooResponse, GetFooResponses } from './types.gen'; diff --git a/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/servers/msw.gen.ts b/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/servers/msw.gen.ts new file mode 100644 index 0000000000..04a6a410bf --- /dev/null +++ b/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/servers/msw.gen.ts @@ -0,0 +1,72 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { http, type HttpHandler, HttpResponse, type HttpResponseResolver, type RequestHandlerOptions as RequestHandlerOptions2 } from 'msw'; + +import type { GetFooResponses } from './types.gen'; + +export type RequestHandlerOptions = RequestHandlerOptions2 & { + baseUrl?: string; + responseFallback?: 'error' | 'passthrough'; +}; + +/** + * Handler for the `GET /foo` operation. + */ +export function handleGetFoo(resolver?: { + result: GetFooResponses[200]; + status?: 200; +} | HttpResponseResolver, options?: RequestHandlerOptions): HttpHandler { + return http.get(`${options?.baseUrl ?? '*'}/foo`, info => { + if (typeof resolver === 'function') { + return resolver(info); + } + if (resolver) { + return HttpResponse.json(resolver.result ?? null, { status: resolver.status ?? 200 }); + } + if (options?.responseFallback === 'passthrough') { + return; + } + return new Response('Not Implemented', { + status: 501, + statusText: 'Not Implemented' + }); + }, options); +} + +export type MswHandlerFactories = { + /** + * Handler for the `GET /foo` operation. + */ + getFoo: typeof handleGetFoo; +}; + +export type CreateMswHandlersResult = { + all: (options?: { + one?: { + [K in keyof MswHandlerFactories]?: Parameters[0] | Parameters; + }; + }) => ReadonlyArray; + one: MswHandlerFactories; +}; + +export function createMswHandlers(config: RequestHandlerOptions = {}): CreateMswHandlersResult { + type Handler = (resolver?: R, options?: RequestHandlerOptions) => HttpHandler; + function wrap(handler: Handler): Handler { + return (resolver, options) => handler(resolver, { ...config, ...options }); + } + const one: CreateMswHandlersResult['one'] = { + getFoo: wrap(handleGetFoo) + }; + const all: CreateMswHandlersResult['all'] = (options = {}) => { + type OverrideValue = R | [ + resolver?: R, + options?: RequestHandlerOptions + ]; + function invoke(fn: Handler, override?: OverrideValue): HttpHandler { + return Array.isArray(override) ? fn(...override) : fn(override); + } + const overrides = options.one ?? {}; + return [invoke(one.getFoo, overrides.getFoo)]; + }; + return { all, one }; +} diff --git a/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/servers/types.gen.ts b/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/servers/types.gen.ts new file mode 100644 index 0000000000..d7a0ac4610 --- /dev/null +++ b/packages/openapi-ts-tests/msw/v2/__snapshots__/3.1.x/servers/types.gen.ts @@ -0,0 +1,21 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type ClientOptions = { + baseUrl: 'https://foo.com/v1' | `${string}://${string}/v1` | (string & {}); +}; + +export type GetFooData = { + body?: never; + path?: never; + query?: never; + url: '/foo'; +}; + +export type GetFooResponses = { + /** + * OK + */ + 200: string; +}; + +export type GetFooResponse = GetFooResponses[keyof GetFooResponses]; diff --git a/packages/openapi-ts-tests/msw/v2/package.json b/packages/openapi-ts-tests/msw/v2/package.json new file mode 100644 index 0000000000..625739c84f --- /dev/null +++ b/packages/openapi-ts-tests/msw/v2/package.json @@ -0,0 +1,17 @@ +{ + "name": "@test/openapi-ts-msw-v2", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "typecheck": "tsgo --noEmit" + }, + "devDependencies": { + "@hey-api/openapi-ts": "workspace:*", + "msw": "2.12.14", + "typescript": "5.9.3" + }, + "engines": { + "node": ">=20.19.0" + } +} diff --git a/packages/openapi-ts-tests/msw/v2/test/3.1.x.test.ts b/packages/openapi-ts-tests/msw/v2/test/3.1.x.test.ts new file mode 100644 index 0000000000..9b4d100ec1 --- /dev/null +++ b/packages/openapi-ts-tests/msw/v2/test/3.1.x.test.ts @@ -0,0 +1,75 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +import { createClient } from '@hey-api/openapi-ts'; + +import { getFilePaths } from '../../../utils'; +import { snapshotsDir, tmpDir } from './constants'; +import { createConfigFactory } from './utils'; + +const version = '3.1.x'; + +const outputDir = path.join(tmpDir, version); + +describe(`OpenAPI ${version}`, () => { + const createConfig = createConfigFactory({ openApiVersion: version, outputDir }); + + const scenarios = [ + { + config: createConfig({ + output: 'default', + }), + description: 'generates mock handlers', + }, + { + config: createConfig({ + input: 'response-example.yaml', + output: 'response-example', + }), + description: 'generates mock handlers with example', + }, + { + config: createConfig({ + input: 'response-example.yaml', + output: 'response-example-disabled', + plugins: [ + { + name: 'msw', + valueSources: [], + }, + ], + }), + description: 'generates mock handlers with example but disabled', + }, + { + config: createConfig({ + input: 'response-types.yaml', + output: 'response-types', + }), + description: 'generates mock handlers with multiple response types', + }, + { + config: createConfig({ + input: 'servers.yaml', + output: 'servers', + }), + description: 'generates mock handlers with servers field', + }, + ]; + + it.each(scenarios)('$description', async ({ config }) => { + await createClient(config); + + const outputString = config.output as string; + const filePaths = getFilePaths(outputString); + + await Promise.all( + filePaths.map(async (filePath) => { + const fileContent = fs.readFileSync(filePath, 'utf-8'); + await expect(fileContent).toMatchFileSnapshot( + path.join(snapshotsDir, version, filePath.slice(outputDir.length + 1)), + ); + }), + ); + }); +}); diff --git a/packages/openapi-ts-tests/msw/v2/test/constants.ts b/packages/openapi-ts-tests/msw/v2/test/constants.ts new file mode 100644 index 0000000000..e72988d8bb --- /dev/null +++ b/packages/openapi-ts-tests/msw/v2/test/constants.ts @@ -0,0 +1,4 @@ +import path from 'node:path'; + +export const snapshotsDir = path.join(__dirname, '..', '__snapshots__'); +export const tmpDir = path.join(__dirname, '..', '.tmp'); diff --git a/packages/openapi-ts-tests/msw/v2/test/globalTeardown.ts b/packages/openapi-ts-tests/msw/v2/test/globalTeardown.ts new file mode 100644 index 0000000000..7c8712f70f --- /dev/null +++ b/packages/openapi-ts-tests/msw/v2/test/globalTeardown.ts @@ -0,0 +1,7 @@ +import fs from 'node:fs'; + +import { tmpDir } from './constants'; + +export function teardown() { + fs.rmSync(tmpDir, { force: true, recursive: true }); +} diff --git a/packages/openapi-ts-tests/msw/v2/test/utils.ts b/packages/openapi-ts-tests/msw/v2/test/utils.ts new file mode 100644 index 0000000000..5623d1c084 --- /dev/null +++ b/packages/openapi-ts-tests/msw/v2/test/utils.ts @@ -0,0 +1,33 @@ +import path from 'node:path'; + +import type { UserConfig } from '@hey-api/openapi-ts'; + +import { getSpecsPath } from '../../../utils'; + +export function createConfigFactory({ + openApiVersion, + outputDir, +}: { + openApiVersion: string; + outputDir: string; +}) { + return ( + userConfig: Omit & Partial>, + ): UserConfig => { + const input = userConfig.input instanceof Array ? userConfig.input[0]! : userConfig.input; + const inputPath = path.join( + getSpecsPath(), + openApiVersion, + typeof input === 'string' ? input : 'full.yaml', + ); + const output = userConfig.output instanceof Array ? userConfig.output[0]! : userConfig.output; + const outputPath = typeof output === 'string' ? output : (output?.path ?? ''); + return { + plugins: ['msw'], + ...userConfig, + input: inputPath, + logs: { level: 'silent', path: './logs' }, + output: path.join(outputDir, outputPath), + }; + }; +} diff --git a/packages/openapi-ts-tests/msw/v2/tsconfig.json b/packages/openapi-ts-tests/msw/v2/tsconfig.json new file mode 100644 index 0000000000..e7572df83d --- /dev/null +++ b/packages/openapi-ts-tests/msw/v2/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["test/**/*", "__snapshots__/**/*"], + "references": [{ "path": "../../../openapi-ts" }] +} diff --git a/packages/openapi-ts-tests/msw/v2/turbo.json b/packages/openapi-ts-tests/msw/v2/turbo.json new file mode 100644 index 0000000000..f25a4152d3 --- /dev/null +++ b/packages/openapi-ts-tests/msw/v2/turbo.json @@ -0,0 +1,10 @@ +{ + "$schema": "../../../../node_modules/turbo/schema.json", + "extends": ["//"], + "tasks": { + "build": { + "dependsOn": [], + "outputs": ["dist/**"] + } + } +} diff --git a/packages/openapi-ts-tests/msw/v2/vitest.setup.ts b/packages/openapi-ts-tests/msw/v2/vitest.setup.ts new file mode 100644 index 0000000000..c6178ba405 --- /dev/null +++ b/packages/openapi-ts-tests/msw/v2/vitest.setup.ts @@ -0,0 +1,7 @@ +import { fileURLToPath } from 'node:url'; + +import { beforeAll } from 'vitest'; + +beforeAll(() => { + process.chdir(fileURLToPath(new URL('.', import.meta.url))); +}); diff --git a/packages/openapi-ts/README.md b/packages/openapi-ts/README.md index 27d4c3a7bf..e65948147e 100644 --- a/packages/openapi-ts/README.md +++ b/packages/openapi-ts/README.md @@ -343,6 +343,7 @@ These plugins help reduce boilerplate associated with third-party dependencies. - [`@tanstack/svelte-query`](https://heyapi.dev/openapi-ts/plugins/tanstack-query) - [`@tanstack/vue-query`](https://heyapi.dev/openapi-ts/plugins/tanstack-query) - [`fastify`](https://heyapi.dev/openapi-ts/plugins/fastify) +- [`msw`](https://heyapi.dev/openapi-ts/plugins/msw) - [`orpc`](https://heyapi.dev/openapi-ts/plugins/orpc) - [`nestjs`](https://heyapi.dev/openapi-ts/plugins/nest) - [`valibot`](https://heyapi.dev/openapi-ts/plugins/valibot) @@ -363,7 +364,6 @@ The following plugins are roadmap proposals and are not started yet. You can hel - [Hono](https://heyapi.dev/openapi-ts/plugins/hono) - [Joi](https://heyapi.dev/openapi-ts/plugins/joi) - [Koa](https://heyapi.dev/openapi-ts/plugins/koa) -- [MSW](https://heyapi.dev/openapi-ts/plugins/msw) - [Nock](https://heyapi.dev/openapi-ts/plugins/nock) - [Superstruct](https://heyapi.dev/openapi-ts/plugins/superstruct) - [Supertest](https://heyapi.dev/openapi-ts/plugins/supertest) diff --git a/packages/openapi-ts/src/index.ts b/packages/openapi-ts/src/index.ts index d473eaa6d0..8ab250aea0 100644 --- a/packages/openapi-ts/src/index.ts +++ b/packages/openapi-ts/src/index.ts @@ -47,6 +47,7 @@ declare module '@hey-api/codegen-core' { | 'arktype' | 'fastify' | 'json-schema' + | 'msw' | 'sdk' | 'typescript' | 'valibot' @@ -80,6 +81,7 @@ declare module '@hey-api/shared' { '@tanstack/vue-query': Plugins.TanStackVueQuery.Types['Types']; arktype: Plugins.Arktype.Types['Types']; fastify: Plugins.Fastify.Types['Types']; + msw: Plugins.Msw.Types['Types']; nestjs: Plugins.NestJs.Types['Types']; orpc: Plugins.Orpc.Types['Types']; swr: Plugins.Swr.Types['Types']; @@ -142,6 +144,7 @@ import type { TanStackSvelteQueryPlugin } from './plugins/@tanstack/svelte-query import type { TanStackVueQueryPlugin } from './plugins/@tanstack/vue-query'; import type { ArktypePlugin } from './plugins/arktype'; import type { FastifyPlugin } from './plugins/fastify'; +import type { MswPlugin } from './plugins/msw'; import type { NestJsPlugin } from './plugins/nestjs'; import type { OrpcPlugin } from './plugins/orpc'; import type { SwrPlugin } from './plugins/swr'; @@ -264,6 +267,10 @@ export namespace Plugins { export type Types = HeyApiTypeScriptPlugin; } + export namespace Msw { + export type Types = MswPlugin; + } + export namespace NestJs { export type Types = NestJsPlugin; } diff --git a/packages/openapi-ts/src/plugins/@hey-api/client-core/client.ts b/packages/openapi-ts/src/plugins/@hey-api/client-core/client.ts index c2578ab3a3..725f57c10f 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/client-core/client.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/client-core/client.ts @@ -1,4 +1,4 @@ -import { parseUrl } from '@hey-api/shared'; +import { getBaseUrl } from '@hey-api/shared'; import { getTypedConfig } from '../../../config/utils'; import { clientFolderAbsolutePath } from '../../../generate/client'; @@ -6,26 +6,6 @@ import { $ } from '../../../ts-dsl'; import type { PluginHandler } from './types'; import { getClientBaseUrlKey } from './utils'; -const resolveBaseUrlString = ({ plugin }: Parameters[0]): string | undefined => { - const { baseUrl } = plugin.config; - - if (baseUrl === false) { - return; - } - - if (typeof baseUrl === 'string') { - return baseUrl; - } - - const { servers } = plugin.context.ir; - - if (!servers) { - return; - } - - return servers[typeof baseUrl === 'number' ? baseUrl : 0]?.url; -}; - export const createClient: PluginHandler = ({ plugin }) => { const clientModule = clientFolderAbsolutePath(getTypedConfig(plugin)); const symbolCreateClient = plugin.symbol('createClient', { @@ -47,26 +27,13 @@ export const createClient: PluginHandler = ({ plugin }) => { }) : undefined; - const defaultVals = $.object(); + const baseUrl = getBaseUrl(plugin.config.baseUrl ?? true, plugin.context.ir); - const resolvedBaseUrl = resolveBaseUrlString({ - plugin: plugin as any, - }); - if (resolvedBaseUrl) { - const url = parseUrl(resolvedBaseUrl); - if (url.protocol && url.host && !resolvedBaseUrl.includes('{')) { - defaultVals.prop(getClientBaseUrlKey(getTypedConfig(plugin)), $.literal(resolvedBaseUrl)); - } else if (resolvedBaseUrl !== '/' && resolvedBaseUrl.startsWith('/')) { - const baseUrl = resolvedBaseUrl.endsWith('/') - ? resolvedBaseUrl.slice(0, -1) - : resolvedBaseUrl; - defaultVals.prop(getClientBaseUrlKey(getTypedConfig(plugin)), $.literal(baseUrl)); - } - } - - if ('throwOnError' in plugin.config && plugin.config.throwOnError) { - defaultVals.prop('throwOnError', $.literal(true)); - } + const defaultVals = $.object() + .$if(baseUrl, (o, v) => o.prop(getClientBaseUrlKey(getTypedConfig(plugin)), $.literal(v))) + .$if('throwOnError' in plugin.config && plugin.config.throwOnError, (o) => + o.prop('throwOnError', $.literal(true)), + ); const createConfigParameters = [ $(symbolCreateConfig) diff --git a/packages/openapi-ts/src/plugins/config.ts b/packages/openapi-ts/src/plugins/config.ts index 4d0abc5795..5e53828ed1 100644 --- a/packages/openapi-ts/src/plugins/config.ts +++ b/packages/openapi-ts/src/plugins/config.ts @@ -22,6 +22,7 @@ import { defaultConfig as tanStackSvelteQuery } from '../plugins/@tanstack/svelt import { defaultConfig as tanStackVueQuery } from '../plugins/@tanstack/vue-query'; import { defaultConfig as arktype } from '../plugins/arktype'; import { defaultConfig as fastify } from '../plugins/fastify'; +import { defaultConfig as msw } from '../plugins/msw'; import { defaultConfig as nestjs } from '../plugins/nestjs'; import { defaultConfig as orpc } from '../plugins/orpc'; import { defaultConfig as swr } from '../plugins/swr'; @@ -53,6 +54,7 @@ export const defaultPluginConfigs: { '@tanstack/vue-query': tanStackVueQuery, arktype, fastify, + msw, nestjs, orpc, swr, diff --git a/packages/openapi-ts/src/plugins/msw/config.ts b/packages/openapi-ts/src/plugins/msw/config.ts new file mode 100644 index 0000000000..4357b09d13 --- /dev/null +++ b/packages/openapi-ts/src/plugins/msw/config.ts @@ -0,0 +1,23 @@ +import { definePluginConfig } from '@hey-api/shared'; + +import { handler } from './plugin'; +import type { MswPlugin } from './types'; + +export const defaultConfig: MswPlugin['Config'] = { + config: { + baseUrl: '*', + comments: true, + includeInEntry: false, + responseFallback: 'error', + valueSources: ['example'], + }, + dependencies: ['@hey-api/typescript'], + handler, + name: 'msw', + tags: ['mocker'], +}; + +/** + * Type helper for `msw` plugin, returns {@link Plugin.Config} object + */ +export const defineConfig = definePluginConfig(defaultConfig); diff --git a/packages/openapi-ts/src/plugins/msw/index.ts b/packages/openapi-ts/src/plugins/msw/index.ts new file mode 100644 index 0000000000..bccf6b6079 --- /dev/null +++ b/packages/openapi-ts/src/plugins/msw/index.ts @@ -0,0 +1,2 @@ +export { defaultConfig, defineConfig } from './config'; +export type { MswPlugin } from './types'; diff --git a/packages/openapi-ts/src/plugins/msw/plugin.ts b/packages/openapi-ts/src/plugins/msw/plugin.ts new file mode 100644 index 0000000000..5efd5d1258 --- /dev/null +++ b/packages/openapi-ts/src/plugins/msw/plugin.ts @@ -0,0 +1,4 @@ +import type { MswPlugin } from './types'; +import { handlerV2 } from './v2/plugin'; + +export const handler: MswPlugin['Handler'] = (args) => handlerV2(args); diff --git a/packages/openapi-ts/src/plugins/msw/shared/computeDominantResponse.ts b/packages/openapi-ts/src/plugins/msw/shared/computeDominantResponse.ts new file mode 100644 index 0000000000..5bd01d8692 --- /dev/null +++ b/packages/openapi-ts/src/plugins/msw/shared/computeDominantResponse.ts @@ -0,0 +1,148 @@ +import type { IR } from '@hey-api/shared'; +import { statusCodeToGroup } from '@hey-api/shared'; + +import type { MswPlugin } from '../types'; + +export type ResponseKind = 'binary' | 'json' | 'text' | 'void'; + +export interface DominantResponse { + allCandidates: Array; + example: unknown; + kind: ResponseKind; + statusCode: number | undefined; +} + +const isValidExample = (example: unknown): boolean => { + if (example === undefined) { + return false; + } + if (example === null) { + return true; + } + const type = typeof example; + if (type === 'string' || type === 'number' || type === 'boolean') { + return true; + } + if (Array.isArray(example)) { + return example.every(isValidExample); + } + if (type === 'object') { + return Object.values(example as Record).every(isValidExample); + } + return false; +}; + +const KIND_PRIORITY: Record = { + binary: 1, + json: 3, + text: 2, + void: 0, +}; + +interface ResponseCandidate { + example: unknown; + kind: ResponseKind; + statusCode: number; +} + +const computeResponse = ({ + plugin, + response, + statusCode, +}: { + plugin: MswPlugin['Instance']; + response: IR.ResponseObject; + statusCode: string; +}): ResponseCandidate => { + const numericStatus = Number(statusCode); + + if (response.schema.type === 'void') { + return { example: undefined, kind: 'void', statusCode: numericStatus }; + } + + // In 2.0, empty responses get type 'unknown' (with inherited mediaType from + // `produces`). A bare 'unknown' without a $ref indicates no real content. + if (response.schema.type === 'unknown' && !response.schema.$ref) { + return { example: undefined, kind: 'void', statusCode: numericStatus }; + } + + let schema = response.schema; + if (schema.$ref) { + schema = plugin.context.resolveIrRef(schema.$ref); + } + + const example = isValidExample(schema.example) ? schema.example : undefined; + + if (schema.format === 'binary') { + return { example, kind: 'binary', statusCode: numericStatus }; + } + + const kind = mediaTypeToKind(response.mediaType); + return { example, kind, statusCode: numericStatus }; +}; + +const mediaTypeToKind = (mediaType: string | undefined): ResponseKind => { + if (!mediaType) { + return 'json'; + } + + const cleanMediaType = mediaType.split(';')[0]?.trim() ?? ''; + + if ( + cleanMediaType.startsWith('application/octet-stream') || + cleanMediaType.startsWith('audio/') || + cleanMediaType.startsWith('image/') || + cleanMediaType.startsWith('video/') + ) { + return 'binary'; + } + + if (cleanMediaType.startsWith('application/json') || cleanMediaType.endsWith('+json')) { + return 'json'; + } + + if (cleanMediaType.startsWith('text/')) { + return 'text'; + } + + // unknown media type, default to json + return 'json'; +}; + +export const computeDominantResponse = ({ + operation, + plugin, +}: { + operation: IR.OperationObject; + plugin: MswPlugin['Instance']; +}): DominantResponse => { + const candidates: Array = []; + + for (const statusCode in operation.responses) { + if (statusCodeToGroup({ statusCode }) !== '2XX') { + continue; + } + candidates.push( + computeResponse({ + plugin, + response: operation.responses[statusCode]!, + statusCode, + }), + ); + } + + if (candidates.length === 0) { + return { allCandidates: [], example: undefined, kind: 'void', statusCode: undefined }; + } + + const dominant = candidates.reduce((best, cur) => + KIND_PRIORITY[cur.kind] > KIND_PRIORITY[best.kind] ? cur : best, + ); + + return { + allCandidates: candidates, + example: dominant.example, + kind: dominant.kind, + statusCode: dominant.statusCode, + }; +}; diff --git a/packages/openapi-ts/src/plugins/msw/shared/handler.ts b/packages/openapi-ts/src/plugins/msw/shared/handler.ts new file mode 100644 index 0000000000..a0bc5b6b79 --- /dev/null +++ b/packages/openapi-ts/src/plugins/msw/shared/handler.ts @@ -0,0 +1,262 @@ +import type { Symbol } from '@hey-api/codegen-core'; +import type { IR } from '@hey-api/shared'; +import { applyNaming } from '@hey-api/shared'; + +import { $ } from '../../../ts-dsl'; +import type { MswPlugin } from '../types'; +import { computeDominantResponse, type DominantResponse } from './computeDominantResponse'; +import { getOperationComment } from './operation'; +import { sanitizeParamName, sanitizePath } from './path'; +import { createHandlerResponse } from './response'; + +const emitToResponseUnion = (plugin: MswPlugin['Instance']) => { + const symbol = plugin.symbol('ToResponseUnion', { + meta: { + category: 'type', + resource: 'to-response-union', + }, + }); + const extractKeyofTNumber = $.type('Extract') + .generic($.type('keyof T')) + .generic($.type('number')); + const toResponseUnionType = $.type + .alias(symbol) + .generic('T') + .type( + $.type.idx( + $.type + .mapped('K') + .key(extractKeyofTNumber) + .type( + $.type + .object() + .prop('status', (p) => p.type('K')) + .prop('result', (p) => p.type($.type('T').idx($.type('K')))), + ), + $.type('Extract').generic($.type('keyof T')).generic($.type('number')), + ), + ); + plugin.node(toResponseUnionType); +}; + +function createHandlerFunc({ + baseUrl, + bodyType, + dominantResponse, + method, + operation, + paramsType, + plugin, + responseOrResolverType, +}: { + baseUrl: string | undefined; + bodyType: ReturnType; + dominantResponse: DominantResponse; + method: string; + operation: IR.OperationObject; + paramsType: ReturnType; + plugin: MswPlugin['Instance']; + responseOrResolverType: ReturnType; +}): Symbol { + const symbolHttp = plugin.external('msw.http'); + const symbolResolver = plugin.symbol('resolver'); + const symbolOptions = plugin.symbol('options'); + + const symbol = plugin.symbol( + applyNaming(`handle-${operation.id}`, { + casing: 'camelCase', // TODO: expose as a config option + }), + ); + + const notImplementedResponse = $.new( + 'Response', + $.literal('Not Implemented'), + $.object() + .pretty() + .prop('status', $.literal(501)) + .prop('statusText', $.literal('Not Implemented')), + ); + + const handlerFunc = $.func(symbol) + .export() + .$if(plugin.config.comments && getOperationComment(operation), (f, v) => f.doc(v)) + .param(symbolResolver, (p) => + p + .optional() + .type(responseOrResolverType) + .$if(dominantResponse.example != null && dominantResponse.statusCode != null, (pp) => + pp.assign( + $.fromValue({ + result: dominantResponse.example, + status: dominantResponse.statusCode, + }), + ), + ), + ) + .param(symbolOptions, (p) => + p.optional().type( + plugin.referenceSymbol({ + category: 'type', + resource: 'request-handler-options', + tool: 'msw', + }), + ), + ) + .returns(plugin.external('msw.HttpHandler')) + .do( + $(symbolHttp) + .attr(method) + .call( + $.template( + $(symbolOptions) + .attr('baseUrl') + .optional() + .coalesce($.literal(baseUrl ?? '')), + ).add(sanitizePath(operation.path)), + $.func() + .param('info') + .do( + $.if($.typeofExpr(symbolResolver).eq($.literal('function'))).do( + $(symbolResolver).call('info').return(), + ), + ) + .$if(dominantResponse.statusCode != null, (f) => + f.do( + $.if(symbolResolver).do( + createHandlerResponse({ + dominantResponse, + plugin, + symbolResolver, + }).return(), + ), + ), + ) + .$if( + plugin.config.responseFallback === 'error', + (f) => + f.do( + $.if( + $(symbolOptions) + .attr('responseFallback') + .optional() + .eq($.literal('passthrough')), + ).do($.return()), + notImplementedResponse.return(), + ), + (f) => + f.do( + $.if( + $(symbolOptions).attr('responseFallback').optional().eq($.literal('error')), + ).do(notImplementedResponse.return()), + ), + ), + symbolOptions, + ) + .generics(paramsType, bodyType) + .return(), + ); + plugin.node(handlerFunc); + return symbol; +} + +export function getHandler({ + baseUrl, + examples, + operation, + plugin, +}: { + baseUrl: string | undefined; + examples: boolean; + operation: IR.OperationObject; + plugin: MswPlugin['Instance']; +}): Symbol { + const dominantResponse = computeDominantResponse({ operation, plugin }); + + const symbolResponsesType = plugin.querySymbol({ + category: 'type', + resource: 'operation', + resourceId: operation.id, + role: 'responses', + }); + let responsesOverrideType: ReturnType | undefined; + if (symbolResponsesType && dominantResponse.allCandidates.length > 1) { + if (!plugin.getSymbol({ category: 'type', resource: 'to-response-union' })) { + emitToResponseUnion(plugin); + } + responsesOverrideType = $.type( + plugin.referenceSymbol({ + category: 'type', + resource: 'to-response-union', + }), + ).generic(symbolResponsesType); + } + + const symbolDataType = plugin.querySymbol({ + category: 'type', + resource: 'operation', + resourceId: operation.id, + role: 'data', + }); + + let bodyType: ReturnType; + if (operation.body && symbolDataType) { + bodyType = $.type(symbolDataType).idx($.type.literal('body')); + } else { + bodyType = $.type('never'); + } + + let paramsType: ReturnType; + if (operation.parameters?.path && Object.keys(operation.parameters.path).length) { + // Generate inline object type with sanitized param names derived from the + // path string (not the IR keys, which are lowercased and may diverge from + // the original spec names that the TypeScript plugin preserves). + const objType = $.type.object(); + for (const parameter of Object.values(operation.parameters.path ?? {})) { + // OpenAPI 3.x path params are always single-segment, so MSW (path-to-regexp v6) + // will always provide a single string. Multi-segment params ({path+}) are proposed + // for OpenAPI 4.0 — revisit this if/when that lands. + objType.prop(sanitizeParamName(parameter.name), (p) => p.type('string')); + } + paramsType = objType; + } else { + paramsType = $.type('never'); + } + + const resolverType = $.type(plugin.external('msw.HttpResponseResolver')).generics( + paramsType, + bodyType, + // omit response type to avoid DefaultBodyType constraint issues + ); + + // When examples are disabled, strip the example from the dominant response + if (!examples) { + dominantResponse.example = undefined; + } + + let responseOrResolverType: ReturnType; + if (dominantResponse.statusCode != null && symbolResponsesType) { + const dominantResponseType = $.type + .object() + .prop('result', (p) => + p.type($.type(symbolResponsesType).idx($.type.literal(dominantResponse.statusCode!))), + ) + .prop('status', (p) => p.optional().type($.type.literal(dominantResponse.statusCode!))); + responseOrResolverType = $.type.or( + dominantResponseType, + responsesOverrideType ? $.type.or(responsesOverrideType, resolverType) : resolverType, + ); + } else { + responseOrResolverType = resolverType; + } + + return createHandlerFunc({ + baseUrl, + bodyType, + dominantResponse, + method: operation.method, + operation, + paramsType, + plugin, + responseOrResolverType, + }); +} diff --git a/packages/openapi-ts/src/plugins/msw/shared/operation.ts b/packages/openapi-ts/src/plugins/msw/shared/operation.ts new file mode 100644 index 0000000000..7a161d8115 --- /dev/null +++ b/packages/openapi-ts/src/plugins/msw/shared/operation.ts @@ -0,0 +1,5 @@ +import type { IR } from '@hey-api/shared'; + +export function getOperationComment(operation: IR.OperationObject): string { + return `Handler for the \`${operation.method.toUpperCase()} ${operation.path}\` operation.`; +} diff --git a/packages/openapi-ts/src/plugins/msw/shared/path.ts b/packages/openapi-ts/src/plugins/msw/shared/path.ts new file mode 100644 index 0000000000..20b1ec207e --- /dev/null +++ b/packages/openapi-ts/src/plugins/msw/shared/path.ts @@ -0,0 +1,24 @@ +/** + * path-to-regexp v6 (used by MSW) only allows word characters in param names. + * So transform what's necessary: replace non-word chars with camelCase transitions, + * preserving the original casing to stay consistent with the TypeScript plugin's types. + */ +export function sanitizeParamName(name: string): string { + return name.replace(/\W+(.)?/g, (_, char?: string) => (char ? char.toUpperCase() : '')); +} + +/** + * Transforms an OpenAPI path template (e.g. `/users/{userId}/posts/{postId}`) + * into an MSW path template (e.g. `/users/:userId/posts/:postId`), sanitizing + * param names as needed to conform to path-to-regexp v6 requirements. + * + * Note: MSW's path-to-regexp v6 does not allow regex patterns in the path + * (e.g. `{id:\d+}`), so this function assumes simple param names without + * patterns. + */ +export function sanitizePath(path: string): string { + return path + .replace(/\{([^}]+)\}/g, (_, name: string) => `\0${sanitizeParamName(name)}`) + .replace(/:/g, String.raw`\:`) + .replace(/\0/g, ':'); +} diff --git a/packages/openapi-ts/src/plugins/msw/shared/response.ts b/packages/openapi-ts/src/plugins/msw/shared/response.ts new file mode 100644 index 0000000000..1e334c56fd --- /dev/null +++ b/packages/openapi-ts/src/plugins/msw/shared/response.ts @@ -0,0 +1,48 @@ +import type { Symbol } from '@hey-api/codegen-core'; + +import { $ } from '../../../ts-dsl'; +import type { MswPlugin } from '../types'; +import type { DominantResponse } from './computeDominantResponse'; + +/** + * Builds the response override expression for the `res` parameter. + */ +export function createHandlerResponse({ + dominantResponse: { kind: responseKind, statusCode: responseStatusCode }, + plugin, + symbolResolver, +}: { + dominantResponse: DominantResponse; + plugin: MswPlugin['Instance']; + symbolResolver: Symbol; +}): ReturnType { + const symbolHttpResponse = plugin.external('msw.HttpResponse'); + + const statusOption = $.object().prop( + 'status', + responseStatusCode + ? $(symbolResolver).attr('status').coalesce($.literal(responseStatusCode)) + : $(symbolResolver).attr('status'), + ); + const resultExpr = $(symbolResolver).attr('result'); + + switch (responseKind) { + case 'binary': + return $.new(symbolHttpResponse, resultExpr.coalesce($.literal(null)), statusOption); + case 'json': + return $(symbolHttpResponse) + .attr('json') + .call(resultExpr.coalesce($.literal(null)), statusOption); + case 'text': + return $(symbolHttpResponse) + .attr('text') + .call( + $('JSON') + .attr('stringify') + .call(resultExpr.coalesce($.literal(''))), + statusOption, + ); + case 'void': + return $.new(symbolHttpResponse, resultExpr.coalesce($.literal(null)), statusOption); + } +} diff --git a/packages/openapi-ts/src/plugins/msw/shared/sort.ts b/packages/openapi-ts/src/plugins/msw/shared/sort.ts new file mode 100644 index 0000000000..5d408d9c69 --- /dev/null +++ b/packages/openapi-ts/src/plugins/msw/shared/sort.ts @@ -0,0 +1,49 @@ +export interface HandlerInfo { + name: string; + path: string; +} + +/** + * Returns 1 for a static segment, 0 for a parameterized segment. + * + * Works only with OpenAPI `{param}` format. + */ +function segmentScore(segment: string): number { + return /\{[^}]+\}/.test(segment) ? 0 : 1; +} + +/** + * Sorts handlers by route specificity. + * + * Without this, routes like `/foo/:bar` could appear before `/foo/baz`. + * + * Inspired by: + * @see https://reactrouter.com/6.28.0/start/concepts#ranking-routes + * @see https://github.com/lukeed/route-sort + */ +export function sortHandlers(handlers: ReadonlyArray): ReadonlyArray { + return handlers + .map((info, index) => ({ index, info })) + .sort((a, b) => { + const segmentsA = a.info.path.split('/').filter(Boolean); + const segmentsB = b.info.path.split('/').filter(Boolean); + + // deeper routes are more specific + if (segmentsA.length !== segmentsB.length) { + return segmentsB.length - segmentsA.length; + } + + // static segments beat dynamic ones + for (let i = 0; i < segmentsA.length; i++) { + const scoreA = segmentScore(segmentsA[i]!); + const scoreB = segmentScore(segmentsB[i]!); + if (scoreA !== scoreB) { + return scoreB - scoreA; + } + } + + // preserve declaration order + return a.index - b.index; + }) + .map((entry) => entry.info); +} diff --git a/packages/openapi-ts/src/plugins/msw/shared/types.ts b/packages/openapi-ts/src/plugins/msw/shared/types.ts new file mode 100644 index 0000000000..dd90179873 --- /dev/null +++ b/packages/openapi-ts/src/plugins/msw/shared/types.ts @@ -0,0 +1,30 @@ +import type { Symbol } from '@hey-api/codegen-core'; + +import { $ } from '../../../ts-dsl'; +import type { MswPlugin } from '../types'; + +export function createRequestHandlerOptions(plugin: MswPlugin['Instance']): Symbol { + const symbol = plugin.symbol('RequestHandlerOptions', { + meta: { + category: 'type', + resource: 'request-handler-options', + tool: 'msw', + }, + }); + const node = $.type + .alias(symbol) + .export() + .type( + $.type.and( + $.type(plugin.external('msw.RequestHandlerOptions')), + $.type + .object() + .prop('baseUrl', (p) => p.type('string').optional()) + .prop('responseFallback', (p) => + p.type($.type.or($.type.literal('error'), $.type.literal('passthrough'))).optional(), + ), + ), + ); + plugin.node(node); + return symbol; +} diff --git a/packages/openapi-ts/src/plugins/msw/types.ts b/packages/openapi-ts/src/plugins/msw/types.ts new file mode 100644 index 0000000000..28dd8c7a1f --- /dev/null +++ b/packages/openapi-ts/src/plugins/msw/types.ts @@ -0,0 +1,53 @@ +import type { DefinePlugin, Plugin } from '@hey-api/shared'; + +export type UserConfig = Plugin.Name<'msw'> & + Plugin.Hooks & + Plugin.UserComments & + Plugin.UserExports & { + /** + * Set a default base URL when creating the handlers? You can set `baseUrl` + * to a string which will be used as the base URL. If your input defines + * server(s), you can set `baseUrl` to a number to pick a specific server + * to use as the base URL. You can disable setting the base URL by setting + * `baseUrl` to `false`. By default, `baseUrl` is `'*'`, which matches all + * URLs. + * + * If the matched URL contains template literals, it will be ignored. + * + * @default '*' + */ + baseUrl?: string | number | boolean; + /** + * Behavior when a response cannot be generated (no response defined + * in the specification, or configured value sources cannot produce a value). + * + * - `'error'` - throw an error (fail fast) + * - `'passthrough'` - let the request pass through to the network + * + * @default 'error' + */ + responseFallback?: 'error' | 'passthrough'; + /** + * Sources for default parameter values in handler factories. Order determines + * priority (earlier entries take precedence). + * + * - `'example'` - use OpenAPI example values + * + * @default ['example'] + */ + valueSources?: ReadonlyArray<'example'>; + }; + +export type Config = Plugin.Name<'msw'> & + Plugin.Hooks & + Plugin.Comments & + Plugin.Exports & { + /** Set a default base URL when creating the handlers. */ + baseUrl: string | number | boolean; + /** Behavior when a response cannot be generated. */ + responseFallback: 'error' | 'passthrough'; + /** Sources for default parameter values in handler factories. */ + valueSources: ReadonlyArray<'example'>; + }; + +export type MswPlugin = DefinePlugin; diff --git a/packages/openapi-ts/src/plugins/msw/v2/plugin.ts b/packages/openapi-ts/src/plugins/msw/v2/plugin.ts new file mode 100644 index 0000000000..ee646a342d --- /dev/null +++ b/packages/openapi-ts/src/plugins/msw/v2/plugin.ts @@ -0,0 +1,197 @@ +import { getBaseUrl } from '@hey-api/shared'; + +import { $ } from '../../../ts-dsl'; +import { getHandler } from '../shared/handler'; +import { getOperationComment } from '../shared/operation'; +import { type HandlerInfo, sortHandlers } from '../shared/sort'; +import { createRequestHandlerOptions } from '../shared/types'; +import type { MswPlugin } from '../types'; + +export const handlerV2: MswPlugin['Handler'] = ({ plugin }) => { + plugin.symbol('http', { + external: 'msw', + }); + const symbolHttpHandler = plugin.symbol('HttpHandler', { + external: 'msw', + kind: 'type', + }); + plugin.symbol('HttpResponse', { + external: 'msw', + }); + plugin.symbol('HttpResponseResolver', { + external: 'msw', + kind: 'type', + }); + plugin.symbol('RequestHandlerOptions', { + external: 'msw', + kind: 'type', + }); + + const baseUrl = getBaseUrl(plugin.config.baseUrl, plugin.context.ir); + + const symbolRequestHandlerOptions = createRequestHandlerOptions(plugin); + + const symbolAll = plugin.symbol('all'); + const symbolFactory = plugin.symbol('createMswHandlers'); + const symbolHandler = plugin.symbol('Handler'); + const symbolInvoke = plugin.symbol('invoke'); + const symbolOne = plugin.symbol('one'); + const symbolOverrideValue = plugin.symbol('OverrideValue'); + const symbolWrap = plugin.symbol('wrap'); + + const oneObject = $.object().pretty(); + const oneType = $.type.object(); + const handlerInfo: Array = []; + + plugin.forEach( + 'operation', + ({ operation }) => { + const symbolHandler = getHandler({ + baseUrl, + examples: plugin.config.valueSources?.includes('example') ?? true, + operation, + plugin, + }); + + const name = operation.id; + oneType.prop(name, (p) => + p + .type($(symbolHandler).typeofType()) + .$if(plugin.config.comments && getOperationComment(operation), (f, v) => f.doc(v)), + ); + oneObject.prop(name, $(symbolWrap).call(symbolHandler)); + handlerInfo.push({ name, path: operation.path }); + }, + { + order: 'declarations', + }, + ); + + const symbolHandlerFactoriesType = plugin.symbol('MswHandlerFactories'); + const handlerFactoriesType = $.type.alias(symbolHandlerFactoriesType).export().type(oneType); + plugin.node(handlerFactoriesType); + + const factoryResultAll = 'all'; + const factoryResultOne = 'one'; + + const symbolFactoryReturnType = plugin.symbol('CreateMswHandlersResult'); + const factoryReturnType = $.type + .alias(symbolFactoryReturnType) + .export() + .type( + $.type + .object() + .prop(factoryResultAll, (p) => + p.type( + $.type + .func() + .param('options', (p) => + p.optional().type( + $.type.object().prop('one', (p) => + p.optional().type( + $.type + .mapped('K') + .key($.type.operator().keyof($.type(symbolHandlerFactoriesType))) + .optional() + .type( + $.type.or( + $.type('Parameters') + .generic($.type(symbolHandlerFactoriesType).idx('K')) + .idx($.type.literal(0)), + $.type('Parameters').generic( + $.type(symbolHandlerFactoriesType).idx('K'), + ), + ), + ), + ), + ), + ), + ) + .returns($.type('ReadonlyArray').generic(symbolHttpHandler)), + ), + ) + .prop(factoryResultOne, (p) => p.type(symbolHandlerFactoriesType)), + ); + plugin.node(factoryReturnType); + + const factoryFn = $.func(symbolFactory) + .export() + .param('config', (p) => p.type(symbolRequestHandlerOptions).assign($.object())) + .returns(symbolFactoryReturnType) + .do( + $.type + .alias(symbolHandler) + .generic('R') + .type( + $.type + .func() + .param('resolver', (p) => p.optional().type('R')) + .param('options', (p) => p.optional().type(symbolRequestHandlerOptions)) + .returns(symbolHttpHandler), + ), + $.func(symbolWrap) + .generic('R') + .param('handler', (p) => p.type($.type(symbolHandler).generic('R'))) + .returns($.type(symbolHandler).generic('R')) + .do( + $.return( + $.func() + .param('resolver') + .param('options') + .do( + $.return( + $('handler').call('resolver', $.object().spread('config').spread('options')), + ), + ), + ), + ), + $.const(symbolOne) + .type($.type(symbolFactoryReturnType).idx($.type.literal(factoryResultOne))) + .assign(oneObject), + $.const(symbolAll) + .type($.type(symbolFactoryReturnType).idx($.type.literal(factoryResultAll))) + .assign( + $.func() + .param('options', (p) => p.assign($.object())) + .do( + $.type + .alias(symbolOverrideValue) + .generic('R') + .type( + $.type.or( + $.type('R'), + $.type.tuple( + $.type.tupleMember('resolver').optional().type('R'), + $.type.tupleMember('options').optional().type(symbolRequestHandlerOptions), + ), + ), + ), + $.func(symbolInvoke) + .generic('R') + .param('fn', (p) => p.type($.type(symbolHandler).generic('R'))) + .param('override', (p) => + p.optional().type($.type(symbolOverrideValue).generic('R')), + ) + .returns(symbolHttpHandler) + .do( + $.return( + $.ternary($('Array').attr('isArray').call('override')) + .do($('fn').call($.spread('override'))) + .otherwise($('fn').call('override')), + ), + ), + $.const('overrides').assign($('options').attr('one').coalesce($.object())), + $.array( + ...sortHandlers(handlerInfo).map((info) => + $(symbolInvoke).call( + $(symbolOne).attr(info.name), + $('overrides').attr(info.name), + ), + ), + ).return(), + ), + ), + $.object().prop(factoryResultAll, symbolAll).prop(factoryResultOne, symbolOne).return(), + ); + plugin.node(factoryFn); +}; diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 4cf329cf67..1972e2d0e3 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -131,4 +131,4 @@ export { refToName, resolveRef, } from './utils/ref'; -export { parseUrl } from './utils/url'; +export { getBaseUrl, parseUrl } from './utils/url'; diff --git a/packages/shared/src/utils/url.ts b/packages/shared/src/utils/url.ts index 6eb335b799..e3bab22d0c 100644 --- a/packages/shared/src/utils/url.ts +++ b/packages/shared/src/utils/url.ts @@ -1,3 +1,5 @@ +import type { IR } from '../ir/types'; + const parseUrlRegExp = /^(([^:/?#]+):)?((\/\/)?([^:/?#]*)(:?([^/?#]*)))?([^?#]*)(\?([^#]*))?(#(.*))?/; @@ -8,6 +10,36 @@ interface Url { protocol: string; } +/** + * Resolve the base URL value based on the plugin configuration. + * + * The `baseUrl` config option can be: + * - `false` to disable using the base URL + * - a string to use as the base URL + * - a number to pick a server from the IR `servers` array + */ +function resolveBaseUrl(baseUrl: string | number | boolean, ir: IR.Model): string | undefined { + if (baseUrl === false) return; + if (typeof baseUrl === 'string') return baseUrl; + const servers = ir.servers ?? []; + return servers[typeof baseUrl === 'number' ? baseUrl : 0]?.url; +} + +/** + * Resolve the base URL string if it's a valid URL or path. + */ +export function getBaseUrl(config: string | number | boolean, ir: IR.Model): string | undefined { + const baseUrl = resolveBaseUrl(config, ir); + if (baseUrl === undefined) return; + if (baseUrl.includes('{')) return; + const url = parseUrl(baseUrl); + if (url.protocol && url.host) return baseUrl; + if (baseUrl !== '/' && baseUrl.startsWith('/')) { + return baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl; + } + return baseUrl; +} + export function parseUrl(value: string): Url { const errorResponse: Url = { host: '', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 010a9bf9c2..4a56df3555 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1596,6 +1596,18 @@ importers: specifier: 4.3.6 version: 4.3.6 + packages/openapi-ts-tests/msw/v2: + devDependencies: + '@hey-api/openapi-ts': + specifier: workspace:* + version: link:../../../openapi-ts + msw: + specifier: 2.12.14 + version: 2.12.14(@types/node@25.2.1)(typescript@5.9.3) + typescript: + specifier: 5.9.3 + version: 5.9.3 + packages/openapi-ts-tests/nestjs/v11: devDependencies: '@hey-api/openapi-ts': diff --git a/specs/3.1.x/response-example.yaml b/specs/3.1.x/response-example.yaml new file mode 100644 index 0000000000..1f8df45c75 --- /dev/null +++ b/specs/3.1.x/response-example.yaml @@ -0,0 +1,53 @@ +openapi: 3.1.1 +info: + title: OpenAPI 3.1.1 body response application/json inline example + version: 1 +paths: + /foo: + post: + requestBody: + content: + 'text/plain': + schema: + type: string + required: true + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + fullName: + type: string + age: + type: number + example: + fullName: 'John Doe' + age: 34 + '204': + description: SUCCESSFUL + get: + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Person' +components: + schemas: + Person: + type: object + properties: + firstName: + type: string + lastName: + type: string; + age: + type: number + example: + firstName: 'Marry' + lastName: 'Jane' + age: 30 diff --git a/specs/3.1.x/response-types.yaml b/specs/3.1.x/response-types.yaml new file mode 100644 index 0000000000..fde681eb30 --- /dev/null +++ b/specs/3.1.x/response-types.yaml @@ -0,0 +1,26 @@ +openapi: 3.1.0 +info: + title: OpenAPI 3.1.0 response types + version: '1' +paths: + /foo: + get: + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + name: + type: string + example: + name: 'Alice' + text/plain: + schema: + type: string + application/octet-stream: + schema: + type: string + format: binary diff --git a/vitest.config.ts b/vitest.config.ts index 8bd8e3eac1..803997d5c3 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -59,6 +59,15 @@ export default defineConfig({ setupFiles: ['./vitest.setup.ts'], }, }, + { + extends: true, + test: { + globalSetup: ['./test/globalTeardown.ts'], + name: '@test/openapi-ts-msw-v2', + root: 'packages/openapi-ts-tests/msw/v2', + setupFiles: ['./vitest.setup.ts'], + }, + }, { extends: true, test: {