diff --git a/docs/openapi-ts/plugins/sdk.md b/docs/openapi-ts/plugins/sdk.md index 68ffc157a8..1db341e5b0 100644 --- a/docs/openapi-ts/plugins/sdk.md +++ b/docs/openapi-ts/plugins/sdk.md @@ -38,6 +38,51 @@ export default { The SDK plugin supports a wide range of configuration options. This guide focuses on two main SDK formats: tree-shakeable functions and instantiable classes, but you can apply the same concepts to create more advanced configurations. +## Parameters + +By default, SDK methods accept parameters as a single object when using `paramsStructure: 'flat'`. If you want positional path params (for example `remove(id, { ... })`), enable `positionalPathParams`. + +::: code-group + +```ts [example] +export const projectUpdate = ( + projectId: string, + parameters: { + name?: string; + }, + options?: Options, +) => { + const params = buildClientParams( + [projectId, parameters], + [ + { + args: [ + { in: 'path', key: 'projectId' }, + { in: 'body', key: 'name' }, + ], + }, + ], + ); + /** ... */ +}; +``` + +```js [config] +export default { + input: 'hey-api/backend', + output: 'src/client', + plugins: [ + { + name: '@hey-api/sdk', + paramsStructure: 'flat', + positionalPathParams: true, + }, + ], +}; +``` + +::: + ## Flat This is the default setting. Flat SDKs support tree-shaking, which can lead to a reduced bundle size. You select flat mode by setting `operations.strategy` to `flat`. diff --git a/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/client.gen.ts b/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/client.gen.ts new file mode 100644 index 0000000000..cab3c70195 --- /dev/null +++ b/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/client.gen.ts @@ -0,0 +1,16 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { type ClientOptions, type Config, createClient, createConfig } from './client'; +import type { ClientOptions as ClientOptions2 } from './types.gen'; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = (override?: Config) => Config & T>; + +export const client = createClient(createConfig()); diff --git a/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/client/client.gen.ts b/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/client/client.gen.ts new file mode 100644 index 0000000000..d2e55a1449 --- /dev/null +++ b/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/client/client.gen.ts @@ -0,0 +1,288 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { createSseClient } from '../core/serverSentEvents.gen'; +import type { HttpMethod } from '../core/types.gen'; +import { getValidRequestBody } from '../core/utils.gen'; +import type { Client, Config, RequestOptions, ResolvedRequestOptions } from './types.gen'; +import { + buildUrl, + createConfig, + createInterceptors, + getParseAs, + mergeConfigs, + mergeHeaders, + setAuthParams, +} from './utils.gen'; + +type ReqInit = Omit & { + body?: any; + headers: ReturnType; +}; + +export const createClient = (config: Config = {}): Client => { + let _config = mergeConfigs(createConfig(), config); + + const getConfig = (): Config => ({ ..._config }); + + const setConfig = (config: Config): Config => { + _config = mergeConfigs(_config, config); + return getConfig(); + }; + + const interceptors = createInterceptors(); + + const beforeRequest = async (options: RequestOptions) => { + const opts = { + ..._config, + ...options, + fetch: options.fetch ?? _config.fetch ?? globalThis.fetch, + headers: mergeHeaders(_config.headers, options.headers), + serializedBody: undefined, + }; + + if (opts.security) { + await setAuthParams({ + ...opts, + security: opts.security, + }); + } + + if (opts.requestValidator) { + await opts.requestValidator(opts); + } + + if (opts.body !== undefined && opts.bodySerializer) { + opts.serializedBody = opts.bodySerializer(opts.body); + } + + // remove Content-Type header if body is empty to avoid sending invalid requests + if (opts.body === undefined || opts.serializedBody === '') { + opts.headers.delete('Content-Type'); + } + + const url = buildUrl(opts); + + return { opts, url }; + }; + + const request: Client['request'] = async (options) => { + // @ts-expect-error + const { opts, url } = await beforeRequest(options); + const requestInit: ReqInit = { + redirect: 'follow', + ...opts, + body: getValidRequestBody(opts), + }; + + let request = new Request(url, requestInit); + + for (const fn of interceptors.request.fns) { + if (fn) { + request = await fn(request, opts); + } + } + + // fetch must be assigned here, otherwise it would throw the error: + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation + const _fetch = opts.fetch!; + let response: Response; + + try { + response = await _fetch(request); + } catch (error) { + // Handle fetch exceptions (AbortError, network errors, etc.) + let finalError = error; + + for (const fn of interceptors.error.fns) { + if (fn) { + finalError = (await fn(error, undefined as any, request, opts)) as unknown; + } + } + + finalError = finalError || ({} as unknown); + + if (opts.throwOnError) { + throw finalError; + } + + // Return error response + return opts.responseStyle === 'data' + ? undefined + : { + error: finalError, + request, + response: undefined as any, + }; + } + + for (const fn of interceptors.response.fns) { + if (fn) { + response = await fn(response, request, opts); + } + } + + const result = { + request, + response, + }; + + if (response.ok) { + const parseAs = + (opts.parseAs === 'auto' + ? getParseAs(response.headers.get('Content-Type')) + : opts.parseAs) ?? 'json'; + + if (response.status === 204 || response.headers.get('Content-Length') === '0') { + let emptyData: any; + switch (parseAs) { + case 'arrayBuffer': + case 'blob': + case 'text': + emptyData = await response[parseAs](); + break; + case 'formData': + emptyData = new FormData(); + break; + case 'stream': + emptyData = response.body; + break; + case 'json': + default: + emptyData = {}; + break; + } + return opts.responseStyle === 'data' + ? emptyData + : { + data: emptyData, + ...result, + }; + } + + let data: any; + switch (parseAs) { + case 'arrayBuffer': + case 'blob': + case 'formData': + case 'text': + data = await response[parseAs](); + break; + case 'json': { + // Some servers return 200 with no Content-Length and empty body. + // response.json() would throw; read as text and parse if non-empty. + const text = await response.text(); + data = text ? JSON.parse(text) : {}; + break; + } + case 'stream': + return opts.responseStyle === 'data' + ? response.body + : { + data: response.body, + ...result, + }; + } + + if (parseAs === 'json') { + if (opts.responseValidator) { + await opts.responseValidator(data); + } + + if (opts.responseTransformer) { + data = await opts.responseTransformer(data); + } + } + + return opts.responseStyle === 'data' + ? data + : { + data, + ...result, + }; + } + + const textError = await response.text(); + let jsonError: unknown; + + try { + jsonError = JSON.parse(textError); + } catch { + // noop + } + + const error = jsonError ?? textError; + let finalError = error; + + for (const fn of interceptors.error.fns) { + if (fn) { + finalError = (await fn(error, response, request, opts)) as string; + } + } + + finalError = finalError || ({} as string); + + if (opts.throwOnError) { + throw finalError; + } + + // TODO: we probably want to return error and improve types + return opts.responseStyle === 'data' + ? undefined + : { + error: finalError, + ...result, + }; + }; + + const makeMethodFn = (method: Uppercase) => (options: RequestOptions) => + request({ ...options, method }); + + const makeSseFn = (method: Uppercase) => async (options: RequestOptions) => { + const { opts, url } = await beforeRequest(options); + return createSseClient({ + ...opts, + body: opts.body as BodyInit | null | undefined, + headers: opts.headers as unknown as Record, + method, + onRequest: async (url, init) => { + let request = new Request(url, init); + for (const fn of interceptors.request.fns) { + if (fn) { + request = await fn(request, opts); + } + } + return request; + }, + serializedBody: getValidRequestBody(opts) as BodyInit | null | undefined, + url, + }); + }; + + return { + buildUrl, + connect: makeMethodFn('CONNECT'), + delete: makeMethodFn('DELETE'), + get: makeMethodFn('GET'), + getConfig, + head: makeMethodFn('HEAD'), + interceptors, + options: makeMethodFn('OPTIONS'), + patch: makeMethodFn('PATCH'), + post: makeMethodFn('POST'), + put: makeMethodFn('PUT'), + request, + setConfig, + sse: { + connect: makeSseFn('CONNECT'), + delete: makeSseFn('DELETE'), + get: makeSseFn('GET'), + head: makeSseFn('HEAD'), + options: makeSseFn('OPTIONS'), + patch: makeSseFn('PATCH'), + post: makeSseFn('POST'), + put: makeSseFn('PUT'), + trace: makeSseFn('TRACE'), + }, + trace: makeMethodFn('TRACE'), + } as Client; +}; diff --git a/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/client/index.ts b/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/client/index.ts new file mode 100644 index 0000000000..b295edeca0 --- /dev/null +++ b/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/client/index.ts @@ -0,0 +1,25 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type { Auth } from '../core/auth.gen'; +export type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +export { + formDataBodySerializer, + jsonBodySerializer, + urlSearchParamsBodySerializer, +} from '../core/bodySerializer.gen'; +export { buildClientParams } from '../core/params.gen'; +export { serializeQueryKeyValue } from '../core/queryKeySerializer.gen'; +export { createClient } from './client.gen'; +export type { + Client, + ClientOptions, + Config, + CreateClientConfig, + Options, + RequestOptions, + RequestResult, + ResolvedRequestOptions, + ResponseStyle, + TDataShape, +} from './types.gen'; +export { createConfig, mergeHeaders } from './utils.gen'; diff --git a/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/client/types.gen.ts b/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/client/types.gen.ts new file mode 100644 index 0000000000..cb6d0d54a0 --- /dev/null +++ b/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/client/types.gen.ts @@ -0,0 +1,213 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Auth } from '../core/auth.gen'; +import type { + ServerSentEventsOptions, + ServerSentEventsResult, +} from '../core/serverSentEvents.gen'; +import type { Client as CoreClient, Config as CoreConfig } from '../core/types.gen'; +import type { Middleware } from './utils.gen'; + +export type ResponseStyle = 'data' | 'fields'; + +export interface Config + extends Omit, CoreConfig { + /** + * Base URL for all requests made by this client. + */ + baseUrl?: T['baseUrl']; + /** + * Fetch API implementation. You can use this option to provide a custom + * fetch instance. + * + * @default globalThis.fetch + */ + fetch?: typeof fetch; + /** + * Please don't use the Fetch client for Next.js applications. The `next` + * options won't have any effect. + * + * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. + */ + next?: never; + /** + * Return the response data parsed in a specified format. By default, `auto` + * will infer the appropriate method from the `Content-Type` response header. + * You can override this behavior with any of the {@link Body} methods. + * Select `stream` if you don't want to parse response data at all. + * + * @default 'auto' + */ + parseAs?: 'arrayBuffer' | 'auto' | 'blob' | 'formData' | 'json' | 'stream' | 'text'; + /** + * Should we return only data or multiple fields (data, error, response, etc.)? + * + * @default 'fields' + */ + responseStyle?: ResponseStyle; + /** + * Throw an error instead of returning it in the response? + * + * @default false + */ + throwOnError?: T['throwOnError']; +} + +export interface RequestOptions< + TData = unknown, + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> + extends + Config<{ + responseStyle: TResponseStyle; + throwOnError: ThrowOnError; + }>, + Pick< + ServerSentEventsOptions, + | 'onSseError' + | 'onSseEvent' + | 'sseDefaultRetryDelay' + | 'sseMaxRetryAttempts' + | 'sseMaxRetryDelay' + > { + /** + * Any body that you want to add to your request. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} + */ + body?: unknown; + path?: Record; + query?: Record; + /** + * Security mechanism(s) to use for the request. + */ + security?: ReadonlyArray; + url: Url; +} + +export interface ResolvedRequestOptions< + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends RequestOptions { + serializedBody?: string; +} + +export type RequestResult< + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = ThrowOnError extends true + ? Promise< + TResponseStyle extends 'data' + ? TData extends Record + ? TData[keyof TData] + : TData + : { + data: TData extends Record ? TData[keyof TData] : TData; + request: Request; + response: Response; + } + > + : Promise< + TResponseStyle extends 'data' + ? (TData extends Record ? TData[keyof TData] : TData) | undefined + : ( + | { + data: TData extends Record ? TData[keyof TData] : TData; + error: undefined; + } + | { + data: undefined; + error: TError extends Record ? TError[keyof TError] : TError; + } + ) & { + request: Request; + response: Response; + } + >; + +export interface ClientOptions { + baseUrl?: string; + responseStyle?: ResponseStyle; + throwOnError?: boolean; +} + +type MethodFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => RequestResult; + +type SseFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => Promise>; + +type RequestFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'> & + Pick>, 'method'>, +) => RequestResult; + +type BuildUrlFn = < + TData extends { + body?: unknown; + path?: Record; + query?: Record; + url: string; + }, +>( + options: TData & Options, +) => string; + +export type Client = CoreClient & { + interceptors: Middleware; +}; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = ( + override?: Config, +) => Config & T>; + +export interface TDataShape { + body?: unknown; + headers?: unknown; + path?: unknown; + query?: unknown; + url: string; +} + +type OmitKeys = Pick>; + +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean, + TResponse = unknown, + TResponseStyle extends ResponseStyle = 'fields', +> = OmitKeys< + RequestOptions, + 'body' | 'path' | 'query' | 'url' +> & + ([TData] extends [never] ? unknown : Omit); diff --git a/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/client/utils.gen.ts b/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/client/utils.gen.ts new file mode 100644 index 0000000000..b4bd2435ce --- /dev/null +++ b/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/client/utils.gen.ts @@ -0,0 +1,316 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { getAuthToken } from '../core/auth.gen'; +import type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +import { jsonBodySerializer } from '../core/bodySerializer.gen'; +import { + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from '../core/pathSerializer.gen'; +import { getUrl } from '../core/utils.gen'; +import type { Client, ClientOptions, Config, RequestOptions } from './types.gen'; + +export const createQuerySerializer = ({ + parameters = {}, + ...args +}: QuerySerializerOptions = {}) => { + const querySerializer = (queryParams: T) => { + const search: string[] = []; + if (queryParams && typeof queryParams === 'object') { + for (const name in queryParams) { + const value = queryParams[name]; + + if (value === undefined || value === null) { + continue; + } + + const options = parameters[name] || args; + + if (Array.isArray(value)) { + const serializedArray = serializeArrayParam({ + allowReserved: options.allowReserved, + explode: true, + name, + style: 'form', + value, + ...options.array, + }); + if (serializedArray) search.push(serializedArray); + } else if (typeof value === 'object') { + const serializedObject = serializeObjectParam({ + allowReserved: options.allowReserved, + explode: true, + name, + style: 'deepObject', + value: value as Record, + ...options.object, + }); + if (serializedObject) search.push(serializedObject); + } else { + const serializedPrimitive = serializePrimitiveParam({ + allowReserved: options.allowReserved, + name, + value: value as string, + }); + if (serializedPrimitive) search.push(serializedPrimitive); + } + } + } + return search.join('&'); + }; + return querySerializer; +}; + +/** + * Infers parseAs value from provided Content-Type header. + */ +export const getParseAs = (contentType: string | null): Exclude => { + if (!contentType) { + // If no Content-Type header is provided, the best we can do is return the raw response body, + // which is effectively the same as the 'stream' option. + return 'stream'; + } + + const cleanContent = contentType.split(';')[0]?.trim(); + + if (!cleanContent) { + return; + } + + if (cleanContent.startsWith('application/json') || cleanContent.endsWith('+json')) { + return 'json'; + } + + if (cleanContent === 'multipart/form-data') { + return 'formData'; + } + + if ( + ['application/', 'audio/', 'image/', 'video/'].some((type) => cleanContent.startsWith(type)) + ) { + return 'blob'; + } + + if (cleanContent.startsWith('text/')) { + return 'text'; + } + + return; +}; + +const checkForExistence = ( + options: Pick & { + headers: Headers; + }, + name?: string, +): boolean => { + if (!name) { + return false; + } + if ( + options.headers.has(name) || + options.query?.[name] || + options.headers.get('Cookie')?.includes(`${name}=`) + ) { + return true; + } + return false; +}; + +export const setAuthParams = async ({ + security, + ...options +}: Pick, 'security'> & + Pick & { + headers: Headers; + }) => { + for (const auth of security) { + if (checkForExistence(options, auth.name)) { + continue; + } + + const token = await getAuthToken(auth, options.auth); + + if (!token) { + continue; + } + + const name = auth.name ?? 'Authorization'; + + switch (auth.in) { + case 'query': + if (!options.query) { + options.query = {}; + } + options.query[name] = token; + break; + case 'cookie': + options.headers.append('Cookie', `${name}=${token}`); + break; + case 'header': + default: + options.headers.set(name, token); + break; + } + } +}; + +export const buildUrl: Client['buildUrl'] = (options) => + getUrl({ + baseUrl: options.baseUrl as string, + path: options.path, + query: options.query, + querySerializer: + typeof options.querySerializer === 'function' + ? options.querySerializer + : createQuerySerializer(options.querySerializer), + url: options.url, + }); + +export const mergeConfigs = (a: Config, b: Config): Config => { + const config = { ...a, ...b }; + if (config.baseUrl?.endsWith('/')) { + config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); + } + config.headers = mergeHeaders(a.headers, b.headers); + return config; +}; + +const headersEntries = (headers: Headers): Array<[string, string]> => { + const entries: Array<[string, string]> = []; + headers.forEach((value, key) => { + entries.push([key, value]); + }); + return entries; +}; + +export const mergeHeaders = ( + ...headers: Array['headers'] | undefined> +): Headers => { + const mergedHeaders = new Headers(); + for (const header of headers) { + if (!header) { + continue; + } + + const iterator = header instanceof Headers ? headersEntries(header) : Object.entries(header); + + for (const [key, value] of iterator) { + if (value === null) { + mergedHeaders.delete(key); + } else if (Array.isArray(value)) { + for (const v of value) { + mergedHeaders.append(key, v as string); + } + } else if (value !== undefined) { + // assume object headers are meant to be JSON stringified, i.e. their + // content value in OpenAPI specification is 'application/json' + mergedHeaders.set( + key, + typeof value === 'object' ? JSON.stringify(value) : (value as string), + ); + } + } + } + return mergedHeaders; +}; + +type ErrInterceptor = ( + error: Err, + response: Res, + request: Req, + options: Options, +) => Err | Promise; + +type ReqInterceptor = (request: Req, options: Options) => Req | Promise; + +type ResInterceptor = ( + response: Res, + request: Req, + options: Options, +) => Res | Promise; + +class Interceptors { + fns: Array = []; + + clear(): void { + this.fns = []; + } + + eject(id: number | Interceptor): void { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = null; + } + } + + exists(id: number | Interceptor): boolean { + const index = this.getInterceptorIndex(id); + return Boolean(this.fns[index]); + } + + getInterceptorIndex(id: number | Interceptor): number { + if (typeof id === 'number') { + return this.fns[id] ? id : -1; + } + return this.fns.indexOf(id); + } + + update(id: number | Interceptor, fn: Interceptor): number | Interceptor | false { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = fn; + return id; + } + return false; + } + + use(fn: Interceptor): number { + this.fns.push(fn); + return this.fns.length - 1; + } +} + +export interface Middleware { + error: Interceptors>; + request: Interceptors>; + response: Interceptors>; +} + +export const createInterceptors = (): Middleware< + Req, + Res, + Err, + Options +> => ({ + error: new Interceptors>(), + request: new Interceptors>(), + response: new Interceptors>(), +}); + +const defaultQuerySerializer = createQuerySerializer({ + allowReserved: false, + array: { + explode: true, + style: 'form', + }, + object: { + explode: true, + style: 'deepObject', + }, +}); + +const defaultHeaders = { + 'Content-Type': 'application/json', +}; + +export const createConfig = ( + override: Config & T> = {}, +): Config & T> => ({ + ...jsonBodySerializer, + headers: defaultHeaders, + parseAs: 'auto', + querySerializer: defaultQuerySerializer, + ...override, +}); diff --git a/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/core/auth.gen.ts b/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/core/auth.gen.ts new file mode 100644 index 0000000000..3ebf994788 --- /dev/null +++ b/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/core/auth.gen.ts @@ -0,0 +1,41 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type AuthToken = string | undefined; + +export interface Auth { + /** + * Which part of the request do we use to send the auth? + * + * @default 'header' + */ + in?: 'header' | 'query' | 'cookie'; + /** + * Header or query parameter name. + * + * @default 'Authorization' + */ + name?: string; + scheme?: 'basic' | 'bearer'; + type: 'apiKey' | 'http'; +} + +export const getAuthToken = async ( + auth: Auth, + callback: ((auth: Auth) => Promise | AuthToken) | AuthToken, +): Promise => { + const token = typeof callback === 'function' ? await callback(auth) : callback; + + if (!token) { + return; + } + + if (auth.scheme === 'bearer') { + return `Bearer ${token}`; + } + + if (auth.scheme === 'basic') { + return `Basic ${btoa(token)}`; + } + + return token; +}; diff --git a/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/core/bodySerializer.gen.ts b/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/core/bodySerializer.gen.ts new file mode 100644 index 0000000000..8ad92c9ffd --- /dev/null +++ b/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/core/bodySerializer.gen.ts @@ -0,0 +1,84 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { ArrayStyle, ObjectStyle, SerializerOptions } from './pathSerializer.gen'; + +export type QuerySerializer = (query: Record) => string; + +export type BodySerializer = (body: any) => any; + +type QuerySerializerOptionsObject = { + allowReserved?: boolean; + array?: Partial>; + object?: Partial>; +}; + +export type QuerySerializerOptions = QuerySerializerOptionsObject & { + /** + * Per-parameter serialization overrides. When provided, these settings + * override the global array/object settings for specific parameter names. + */ + parameters?: Record; +}; + +const serializeFormDataPair = (data: FormData, key: string, value: unknown): void => { + if (typeof value === 'string' || value instanceof Blob) { + data.append(key, value); + } else if (value instanceof Date) { + data.append(key, value.toISOString()); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +const serializeUrlSearchParamsPair = (data: URLSearchParams, key: string, value: unknown): void => { + if (typeof value === 'string') { + data.append(key, value); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +export const formDataBodySerializer = { + bodySerializer: | Array>>( + body: T, + ): FormData => { + const data = new FormData(); + + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeFormDataPair(data, key, v)); + } else { + serializeFormDataPair(data, key, value); + } + }); + + return data; + }, +}; + +export const jsonBodySerializer = { + bodySerializer: (body: T): string => + JSON.stringify(body, (_key, value) => (typeof value === 'bigint' ? value.toString() : value)), +}; + +export const urlSearchParamsBodySerializer = { + bodySerializer: | Array>>(body: T): string => { + const data = new URLSearchParams(); + + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); + } else { + serializeUrlSearchParamsPair(data, key, value); + } + }); + + return data.toString(); + }, +}; diff --git a/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/core/params.gen.ts b/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/core/params.gen.ts new file mode 100644 index 0000000000..6099cab1b4 --- /dev/null +++ b/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/core/params.gen.ts @@ -0,0 +1,169 @@ +// This file is auto-generated by @hey-api/openapi-ts + +type Slot = 'body' | 'headers' | 'path' | 'query'; + +export type Field = + | { + in: Exclude; + /** + * Field name. This is the name we want the user to see and use. + */ + key: string; + /** + * Field mapped name. This is the name we want to use in the request. + * If omitted, we use the same value as `key`. + */ + map?: string; + } + | { + in: Extract; + /** + * Key isn't required for bodies. + */ + key?: string; + map?: string; + } + | { + /** + * Field name. This is the name we want the user to see and use. + */ + key: string; + /** + * Field mapped name. This is the name we want to use in the request. + * If `in` is omitted, `map` aliases `key` to the transport layer. + */ + map: Slot; + }; + +export interface Fields { + allowExtra?: Partial>; + args?: ReadonlyArray; +} + +export type FieldsConfig = ReadonlyArray; + +const extraPrefixesMap: Record = { + $body_: 'body', + $headers_: 'headers', + $path_: 'path', + $query_: 'query', +}; +const extraPrefixes = Object.entries(extraPrefixesMap); + +type KeyMap = Map< + string, + | { + in: Slot; + map?: string; + } + | { + in?: never; + map: Slot; + } +>; + +const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => { + if (!map) { + map = new Map(); + } + + for (const config of fields) { + if ('in' in config) { + if (config.key) { + map.set(config.key, { + in: config.in, + map: config.map, + }); + } + } else if ('key' in config) { + map.set(config.key, { + map: config.map, + }); + } else if (config.args) { + buildKeyMap(config.args, map); + } + } + + return map; +}; + +interface Params { + body: unknown; + headers: Record; + path: Record; + query: Record; +} + +const stripEmptySlots = (params: Params) => { + for (const [slot, value] of Object.entries(params)) { + if (value && typeof value === 'object' && !Object.keys(value).length) { + delete params[slot as Slot]; + } + } +}; + +export const buildClientParams = (args: ReadonlyArray, fields: FieldsConfig) => { + const params: Params = { + body: {}, + headers: {}, + path: {}, + query: {}, + }; + + const map = buildKeyMap(fields); + + let config: FieldsConfig[number] | undefined; + + for (const [index, arg] of args.entries()) { + if (fields[index]) { + config = fields[index]; + } + + if (!config) { + continue; + } + + if ('in' in config) { + if (config.key) { + const field = map.get(config.key)!; + const name = field.map || config.key; + if (field.in) { + (params[field.in] as Record)[name] = arg; + } + } else { + params.body = arg; + } + } else { + for (const [key, value] of Object.entries(arg ?? {})) { + const field = map.get(key); + + if (field) { + if (field.in) { + const name = field.map || key; + (params[field.in] as Record)[name] = value; + } else { + params[field.map] = value; + } + } else { + const extra = extraPrefixes.find(([prefix]) => key.startsWith(prefix)); + + if (extra) { + const [prefix, slot] = extra; + (params[slot] as Record)[key.slice(prefix.length)] = value; + } else if ('allowExtra' in config && config.allowExtra) { + for (const [slot, allowed] of Object.entries(config.allowExtra)) { + if (allowed) { + (params[slot as Slot] as Record)[key] = value; + break; + } + } + } + } + } + } + } + + stripEmptySlots(params); + + return params; +}; diff --git a/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/core/pathSerializer.gen.ts b/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/core/pathSerializer.gen.ts new file mode 100644 index 0000000000..994b2848c6 --- /dev/null +++ b/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/core/pathSerializer.gen.ts @@ -0,0 +1,171 @@ +// This file is auto-generated by @hey-api/openapi-ts + +interface SerializeOptions extends SerializePrimitiveOptions, SerializerOptions {} + +interface SerializePrimitiveOptions { + allowReserved?: boolean; + name: string; +} + +export interface SerializerOptions { + /** + * @default true + */ + explode: boolean; + style: T; +} + +export type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; +export type ArraySeparatorStyle = ArrayStyle | MatrixStyle; +type MatrixStyle = 'label' | 'matrix' | 'simple'; +export type ObjectStyle = 'form' | 'deepObject'; +type ObjectSeparatorStyle = ObjectStyle | MatrixStyle; + +interface SerializePrimitiveParam extends SerializePrimitiveOptions { + value: string; +} + +export const separatorArrayExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'form': + return ','; + case 'pipeDelimited': + return '|'; + case 'spaceDelimited': + return '%20'; + default: + return ','; + } +}; + +export const separatorObjectExplode = (style: ObjectSeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const serializeArrayParam = ({ + allowReserved, + explode, + name, + style, + value, +}: SerializeOptions & { + value: unknown[]; +}) => { + if (!explode) { + const joinedValues = ( + allowReserved ? value : value.map((v) => encodeURIComponent(v as string)) + ).join(separatorArrayNoExplode(style)); + switch (style) { + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + case 'simple': + return joinedValues; + default: + return `${name}=${joinedValues}`; + } + } + + const separator = separatorArrayExplode(style); + const joinedValues = value + .map((v) => { + if (style === 'label' || style === 'simple') { + return allowReserved ? v : encodeURIComponent(v as string); + } + + return serializePrimitiveParam({ + allowReserved, + name, + value: v as string, + }); + }) + .join(separator); + return style === 'label' || style === 'matrix' ? separator + joinedValues : joinedValues; +}; + +export const serializePrimitiveParam = ({ + allowReserved, + name, + value, +}: SerializePrimitiveParam) => { + if (value === undefined || value === null) { + return ''; + } + + if (typeof value === 'object') { + throw new Error( + 'Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.', + ); + } + + return `${name}=${allowReserved ? value : encodeURIComponent(value)}`; +}; + +export const serializeObjectParam = ({ + allowReserved, + explode, + name, + style, + value, + valueOnly, +}: SerializeOptions & { + value: Record | Date; + valueOnly?: boolean; +}) => { + if (value instanceof Date) { + return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`; + } + + if (style !== 'deepObject' && !explode) { + let values: string[] = []; + Object.entries(value).forEach(([key, v]) => { + values = [...values, key, allowReserved ? (v as string) : encodeURIComponent(v as string)]; + }); + const joinedValues = values.join(','); + switch (style) { + case 'form': + return `${name}=${joinedValues}`; + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + default: + return joinedValues; + } + } + + const separator = separatorObjectExplode(style); + const joinedValues = Object.entries(value) + .map(([key, v]) => + serializePrimitiveParam({ + allowReserved, + name: style === 'deepObject' ? `${name}[${key}]` : key, + value: v as string, + }), + ) + .join(separator); + return style === 'label' || style === 'matrix' ? separator + joinedValues : joinedValues; +}; diff --git a/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/core/queryKeySerializer.gen.ts b/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/core/queryKeySerializer.gen.ts new file mode 100644 index 0000000000..5000df606f --- /dev/null +++ b/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/core/queryKeySerializer.gen.ts @@ -0,0 +1,117 @@ +// This file is auto-generated by @hey-api/openapi-ts + +/** + * JSON-friendly union that mirrors what Pinia Colada can hash. + */ +export type JsonValue = + | null + | string + | number + | boolean + | JsonValue[] + | { [key: string]: JsonValue }; + +/** + * Replacer that converts non-JSON values (bigint, Date, etc.) to safe substitutes. + */ +export const queryKeyJsonReplacer = (_key: string, value: unknown) => { + if (value === undefined || typeof value === 'function' || typeof value === 'symbol') { + return undefined; + } + if (typeof value === 'bigint') { + return value.toString(); + } + if (value instanceof Date) { + return value.toISOString(); + } + return value; +}; + +/** + * Safely stringifies a value and parses it back into a JsonValue. + */ +export const stringifyToJsonValue = (input: unknown): JsonValue | undefined => { + try { + const json = JSON.stringify(input, queryKeyJsonReplacer); + if (json === undefined) { + return undefined; + } + return JSON.parse(json) as JsonValue; + } catch { + return undefined; + } +}; + +/** + * Detects plain objects (including objects with a null prototype). + */ +const isPlainObject = (value: unknown): value is Record => { + if (value === null || typeof value !== 'object') { + return false; + } + const prototype = Object.getPrototypeOf(value as object); + return prototype === Object.prototype || prototype === null; +}; + +/** + * Turns URLSearchParams into a sorted JSON object for deterministic keys. + */ +const serializeSearchParams = (params: URLSearchParams): JsonValue => { + const entries = Array.from(params.entries()).sort(([a], [b]) => a.localeCompare(b)); + const result: Record = {}; + + for (const [key, value] of entries) { + const existing = result[key]; + if (existing === undefined) { + result[key] = value; + continue; + } + + if (Array.isArray(existing)) { + (existing as string[]).push(value); + } else { + result[key] = [existing, value]; + } + } + + return result; +}; + +/** + * Normalizes any accepted value into a JSON-friendly shape for query keys. + */ +export const serializeQueryKeyValue = (value: unknown): JsonValue | undefined => { + if (value === null) { + return null; + } + + if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { + return value; + } + + if (value === undefined || typeof value === 'function' || typeof value === 'symbol') { + return undefined; + } + + if (typeof value === 'bigint') { + return value.toString(); + } + + if (value instanceof Date) { + return value.toISOString(); + } + + if (Array.isArray(value)) { + return stringifyToJsonValue(value); + } + + if (typeof URLSearchParams !== 'undefined' && value instanceof URLSearchParams) { + return serializeSearchParams(value); + } + + if (isPlainObject(value)) { + return stringifyToJsonValue(value); + } + + return undefined; +}; diff --git a/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/core/serverSentEvents.gen.ts b/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/core/serverSentEvents.gen.ts new file mode 100644 index 0000000000..6aa6cf02a4 --- /dev/null +++ b/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/core/serverSentEvents.gen.ts @@ -0,0 +1,243 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Config } from './types.gen'; + +export type ServerSentEventsOptions = Omit & + Pick & { + /** + * Fetch API implementation. You can use this option to provide a custom + * fetch instance. + * + * @default globalThis.fetch + */ + fetch?: typeof fetch; + /** + * Implementing clients can call request interceptors inside this hook. + */ + onRequest?: (url: string, init: RequestInit) => Promise; + /** + * Callback invoked when a network or parsing error occurs during streaming. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param error The error that occurred. + */ + onSseError?: (error: unknown) => void; + /** + * Callback invoked when an event is streamed from the server. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param event Event streamed from the server. + * @returns Nothing (void). + */ + onSseEvent?: (event: StreamEvent) => void; + serializedBody?: RequestInit['body']; + /** + * Default retry delay in milliseconds. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 3000 + */ + sseDefaultRetryDelay?: number; + /** + * Maximum number of retry attempts before giving up. + */ + sseMaxRetryAttempts?: number; + /** + * Maximum retry delay in milliseconds. + * + * Applies only when exponential backoff is used. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 30000 + */ + sseMaxRetryDelay?: number; + /** + * Optional sleep function for retry backoff. + * + * Defaults to using `setTimeout`. + */ + sseSleepFn?: (ms: number) => Promise; + url: string; + }; + +export interface StreamEvent { + data: TData; + event?: string; + id?: string; + retry?: number; +} + +export type ServerSentEventsResult = { + stream: AsyncGenerator< + TData extends Record ? TData[keyof TData] : TData, + TReturn, + TNext + >; +}; + +export const createSseClient = ({ + onRequest, + onSseError, + onSseEvent, + responseTransformer, + responseValidator, + sseDefaultRetryDelay, + sseMaxRetryAttempts, + sseMaxRetryDelay, + sseSleepFn, + url, + ...options +}: ServerSentEventsOptions): ServerSentEventsResult => { + let lastEventId: string | undefined; + + const sleep = sseSleepFn ?? ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms))); + + const createStream = async function* () { + let retryDelay: number = sseDefaultRetryDelay ?? 3000; + let attempt = 0; + const signal = options.signal ?? new AbortController().signal; + + while (true) { + if (signal.aborted) break; + + attempt++; + + const headers = + options.headers instanceof Headers + ? options.headers + : new Headers(options.headers as Record | undefined); + + if (lastEventId !== undefined) { + headers.set('Last-Event-ID', lastEventId); + } + + try { + const requestInit: RequestInit = { + redirect: 'follow', + ...options, + body: options.serializedBody, + headers, + signal, + }; + let request = new Request(url, requestInit); + if (onRequest) { + request = await onRequest(url, requestInit); + } + // fetch must be assigned here, otherwise it would throw the error: + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation + const _fetch = options.fetch ?? globalThis.fetch; + const response = await _fetch(request); + + if (!response.ok) throw new Error(`SSE failed: ${response.status} ${response.statusText}`); + + if (!response.body) throw new Error('No body in SSE response'); + + const reader = response.body.pipeThrough(new TextDecoderStream()).getReader(); + + let buffer = ''; + + const abortHandler = () => { + try { + reader.cancel(); + } catch { + // noop + } + }; + + signal.addEventListener('abort', abortHandler); + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + buffer += value; + // Normalize line endings: CRLF -> LF, then CR -> LF + buffer = buffer.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); + + const chunks = buffer.split('\n\n'); + buffer = chunks.pop() ?? ''; + + for (const chunk of chunks) { + const lines = chunk.split('\n'); + const dataLines: Array = []; + let eventName: string | undefined; + + for (const line of lines) { + if (line.startsWith('data:')) { + dataLines.push(line.replace(/^data:\s*/, '')); + } else if (line.startsWith('event:')) { + eventName = line.replace(/^event:\s*/, ''); + } else if (line.startsWith('id:')) { + lastEventId = line.replace(/^id:\s*/, ''); + } else if (line.startsWith('retry:')) { + const parsed = Number.parseInt(line.replace(/^retry:\s*/, ''), 10); + if (!Number.isNaN(parsed)) { + retryDelay = parsed; + } + } + } + + let data: unknown; + let parsedJson = false; + + if (dataLines.length) { + const rawData = dataLines.join('\n'); + try { + data = JSON.parse(rawData); + parsedJson = true; + } catch { + data = rawData; + } + } + + if (parsedJson) { + if (responseValidator) { + await responseValidator(data); + } + + if (responseTransformer) { + data = await responseTransformer(data); + } + } + + onSseEvent?.({ + data, + event: eventName, + id: lastEventId, + retry: retryDelay, + }); + + if (dataLines.length) { + yield data as any; + } + } + } + } finally { + signal.removeEventListener('abort', abortHandler); + reader.releaseLock(); + } + + break; // exit loop on normal completion + } catch (error) { + // connection failed or aborted; retry after delay + onSseError?.(error); + + if (sseMaxRetryAttempts !== undefined && attempt >= sseMaxRetryAttempts) { + break; // stop after firing error + } + + // exponential backoff: double retry each attempt, cap at 30s + const backoff = Math.min(retryDelay * 2 ** (attempt - 1), sseMaxRetryDelay ?? 30000); + await sleep(backoff); + } + } + }; + + const stream = createStream(); + + return { stream }; +}; diff --git a/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/core/types.gen.ts b/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/core/types.gen.ts new file mode 100644 index 0000000000..97463257e4 --- /dev/null +++ b/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/core/types.gen.ts @@ -0,0 +1,104 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Auth, AuthToken } from './auth.gen'; +import type { BodySerializer, QuerySerializer, QuerySerializerOptions } from './bodySerializer.gen'; + +export type HttpMethod = + | 'connect' + | 'delete' + | 'get' + | 'head' + | 'options' + | 'patch' + | 'post' + | 'put' + | 'trace'; + +export type Client< + RequestFn = never, + Config = unknown, + MethodFn = never, + BuildUrlFn = never, + SseFn = never, +> = { + /** + * Returns the final request URL. + */ + buildUrl: BuildUrlFn; + getConfig: () => Config; + request: RequestFn; + setConfig: (config: Config) => Config; +} & { + [K in HttpMethod]: MethodFn; +} & ([SseFn] extends [never] ? { sse?: never } : { sse: { [K in HttpMethod]: SseFn } }); + +export interface Config { + /** + * Auth token or a function returning auth token. The resolved value will be + * added to the request payload as defined by its `security` array. + */ + auth?: ((auth: Auth) => Promise | AuthToken) | AuthToken; + /** + * A function for serializing request body parameter. By default, + * {@link JSON.stringify()} will be used. + */ + bodySerializer?: BodySerializer | null; + /** + * An object containing any HTTP headers that you want to pre-populate your + * `Headers` object with. + * + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} + */ + headers?: + | RequestInit['headers'] + | Record< + string, + string | number | boolean | (string | number | boolean)[] | null | undefined | unknown + >; + /** + * The request method. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} + */ + method?: Uppercase; + /** + * A function for serializing request query parameters. By default, arrays + * will be exploded in form style, objects will be exploded in deepObject + * style, and reserved characters are percent-encoded. + * + * This method will have no effect if the native `paramsSerializer()` Axios + * API function is used. + * + * {@link https://swagger.io/docs/specification/serialization/#query View examples} + */ + querySerializer?: QuerySerializer | QuerySerializerOptions; + /** + * A function validating request data. This is useful if you want to ensure + * the request conforms to the desired shape, so it can be safely sent to + * the server. + */ + requestValidator?: (data: unknown) => Promise; + /** + * A function transforming response data before it's returned. This is useful + * for post-processing data, e.g. converting ISO strings into Date objects. + */ + responseTransformer?: (data: unknown) => Promise; + /** + * A function validating response data. This is useful if you want to ensure + * the response conforms to the desired shape, so it can be safely passed to + * the transformers and returned to the user. + */ + responseValidator?: (data: unknown) => Promise; +} + +type IsExactlyNeverOrNeverUndefined = [T] extends [never] + ? true + : [T] extends [never | undefined] + ? [undefined] extends [T] + ? false + : true + : false; + +export type OmitNever> = { + [K in keyof T as IsExactlyNeverOrNeverUndefined extends true ? never : K]: T[K]; +}; diff --git a/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/core/utils.gen.ts b/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/core/utils.gen.ts new file mode 100644 index 0000000000..e7ddbe3541 --- /dev/null +++ b/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/core/utils.gen.ts @@ -0,0 +1,140 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { BodySerializer, QuerySerializer } from './bodySerializer.gen'; +import { + type ArraySeparatorStyle, + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from './pathSerializer.gen'; + +export interface PathSerializer { + path: Record; + url: string; +} + +export const PATH_PARAM_RE = /\{[^{}]+\}/g; + +export const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { + let url = _url; + const matches = _url.match(PATH_PARAM_RE); + if (matches) { + for (const match of matches) { + let explode = false; + let name = match.substring(1, match.length - 1); + let style: ArraySeparatorStyle = 'simple'; + + if (name.endsWith('*')) { + explode = true; + name = name.substring(0, name.length - 1); + } + + if (name.startsWith('.')) { + name = name.substring(1); + style = 'label'; + } else if (name.startsWith(';')) { + name = name.substring(1); + style = 'matrix'; + } + + const value = path[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + url = url.replace(match, serializeArrayParam({ explode, name, style, value })); + continue; + } + + if (typeof value === 'object') { + url = url.replace( + match, + serializeObjectParam({ + explode, + name, + style, + value: value as Record, + valueOnly: true, + }), + ); + continue; + } + + if (style === 'matrix') { + url = url.replace( + match, + `;${serializePrimitiveParam({ + name, + value: value as string, + })}`, + ); + continue; + } + + const replaceValue = encodeURIComponent( + style === 'label' ? `.${value as string}` : (value as string), + ); + url = url.replace(match, replaceValue); + } + } + return url; +}; + +export const getUrl = ({ + baseUrl, + path, + query, + querySerializer, + url: _url, +}: { + baseUrl?: string; + path?: Record; + query?: Record; + querySerializer: QuerySerializer; + url: string; +}) => { + const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; + let url = (baseUrl ?? '') + pathUrl; + if (path) { + url = defaultPathSerializer({ path, url }); + } + let search = query ? querySerializer(query) : ''; + if (search.startsWith('?')) { + search = search.substring(1); + } + if (search) { + url += `?${search}`; + } + return url; +}; + +export function getValidRequestBody(options: { + body?: unknown; + bodySerializer?: BodySerializer | null; + serializedBody?: unknown; +}) { + const hasBody = options.body !== undefined; + const isSerializedBody = hasBody && options.bodySerializer; + + if (isSerializedBody) { + if ('serializedBody' in options) { + const hasSerializedBody = + options.serializedBody !== undefined && options.serializedBody !== ''; + + return hasSerializedBody ? options.serializedBody : null; + } + + // not all clients implement a serializedBody property (i.e. client-axios) + return options.body !== '' ? options.body : null; + } + + // plain/text body + if (hasBody) { + return options.body; + } + + // no body was provided + return undefined; +} diff --git a/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/index.ts b/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/index.ts new file mode 100644 index 0000000000..c1f98cb47e --- /dev/null +++ b/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/index.ts @@ -0,0 +1,4 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export { appAgents, appLog, authSet, commandList, configGet, configProviders, configUpdate, eventSubscribe, fileList, fileRead, fileStatus, findFiles, findSymbols, findText, formatterStatus, globalDispose, globalEvent, globalHealth, instanceDispose, lspStatus, mcpAdd, mcpAuthAuthenticate, mcpAuthCallback, mcpAuthRemove, mcpAuthStart, mcpConnect, mcpDisconnect, mcpStatus, type Options, partDelete, partUpdate, pathGet, permissionList, permissionReply, permissionRespond, projectCurrent, projectList, projectUpdate, providerAuth, providerList, providerOauthAuthorize, providerOauthCallback, ptyConnect, ptyCreate, ptyGet, ptyList, ptyRemove, ptyUpdate, sessionAbort, sessionChildren, sessionCommand, sessionCreate, sessionDelete, sessionDiff, sessionFork, sessionGet, sessionInit, sessionList, sessionMessage, sessionMessages, sessionPrompt, sessionPromptAsync, sessionRevert, sessionShare, sessionShell, sessionStatus, sessionSummarize, sessionTodo, sessionUnrevert, sessionUnshare, sessionUpdate, toolIds, toolList, tuiAppendPrompt, tuiClearPrompt, tuiControlNext, tuiControlResponse, tuiExecuteCommand, tuiOpenHelp, tuiOpenModels, tuiOpenSessions, tuiOpenThemes, tuiPublish, tuiShowToast, tuiSubmitPrompt, vcsGet } from './sdk.gen'; +export type { Agent, AgentConfig, AgentPart, AgentPartInput, ApiAuth, ApiError, AppAgentsData, AppAgentsResponse, AppAgentsResponses, AppLogData, AppLogError, AppLogErrors, AppLogResponse, AppLogResponses, AssistantMessage, Auth, AuthSetData, AuthSetError, AuthSetErrors, AuthSetResponse, AuthSetResponses, BadRequestError, ClientOptions, Command, CommandListData, CommandListResponse, CommandListResponses, CompactionPart, Config, ConfigGetData, ConfigGetResponse, ConfigGetResponses, ConfigProvidersData, ConfigProvidersResponse, ConfigProvidersResponses, ConfigUpdateData, ConfigUpdateError, ConfigUpdateErrors, ConfigUpdateResponse, ConfigUpdateResponses, Event, EventCommandExecuted, EventFileEdited, EventFileWatcherUpdated, EventGlobalDisposed, EventInstallationUpdateAvailable, EventInstallationUpdated, EventLspClientDiagnostics, EventLspUpdated, EventMcpToolsChanged, EventMessagePartRemoved, EventMessagePartUpdated, EventMessageRemoved, EventMessageUpdated, EventPermissionAsked, EventPermissionReplied, EventProjectUpdated, EventPtyCreated, EventPtyDeleted, EventPtyExited, EventPtyUpdated, EventServerConnected, EventServerInstanceDisposed, EventSessionCompacted, EventSessionCreated, EventSessionDeleted, EventSessionDiff, EventSessionError, EventSessionIdle, EventSessionStatus, EventSessionUpdated, EventSubscribeData, EventSubscribeResponse, EventSubscribeResponses, EventTodoUpdated, EventTuiCommandExecute, EventTuiPromptAppend, EventTuiToastShow, EventVcsBranchUpdated, File, FileContent, FileDiff, FileListData, FileListResponse, FileListResponses, FileNode, FilePart, FilePartInput, FilePartSource, FilePartSourceText, FileReadData, FileReadResponse, FileReadResponses, FileSource, FileStatusData, FileStatusResponse, FileStatusResponses, FindFilesData, FindFilesResponse, FindFilesResponses, FindSymbolsData, FindSymbolsResponse, FindSymbolsResponses, FindTextData, FindTextResponse, FindTextResponses, FormatterStatus, FormatterStatusData, FormatterStatusResponse, FormatterStatusResponses, GlobalDisposeData, GlobalDisposeResponse, GlobalDisposeResponses, GlobalEvent, GlobalEventData, GlobalEventResponse, GlobalEventResponses, GlobalHealthData, GlobalHealthResponse, GlobalHealthResponses, InstanceDisposeData, InstanceDisposeResponse, InstanceDisposeResponses, KeybindsConfig, LayoutConfig, LogLevel, LspStatus, LspStatusData, LspStatusResponse, LspStatusResponses, McpAddData, McpAddError, McpAddErrors, McpAddResponse, McpAddResponses, McpAuthAuthenticateData, McpAuthAuthenticateError, McpAuthAuthenticateErrors, McpAuthAuthenticateResponse, McpAuthAuthenticateResponses, McpAuthCallbackData, McpAuthCallbackError, McpAuthCallbackErrors, McpAuthCallbackResponse, McpAuthCallbackResponses, McpAuthRemoveData, McpAuthRemoveError, McpAuthRemoveErrors, McpAuthRemoveResponse, McpAuthRemoveResponses, McpAuthStartData, McpAuthStartError, McpAuthStartErrors, McpAuthStartResponse, McpAuthStartResponses, McpConnectData, McpConnectResponse, McpConnectResponses, McpDisconnectData, McpDisconnectResponse, McpDisconnectResponses, McpLocalConfig, McpOAuthConfig, McpRemoteConfig, McpStatus, McpStatusConnected, McpStatusData, McpStatusDisabled, McpStatusFailed, McpStatusNeedsAuth, McpStatusNeedsClientRegistration, McpStatusResponse, McpStatusResponses, Message, MessageAbortedError, MessageOutputLengthError, Model, NotFoundError, OAuth, Part, PartDeleteData, PartDeleteError, PartDeleteErrors, PartDeleteResponse, PartDeleteResponses, PartUpdateData, PartUpdateError, PartUpdateErrors, PartUpdateResponse, PartUpdateResponses, PatchPart, Path, PathGetData, PathGetResponse, PathGetResponses, PermissionAction, PermissionActionConfig, PermissionConfig, PermissionListData, PermissionListResponse, PermissionListResponses, PermissionObjectConfig, PermissionReplyData, PermissionReplyError, PermissionReplyErrors, PermissionReplyResponse, PermissionReplyResponses, PermissionRequest, PermissionRespondData, PermissionRespondError, PermissionRespondErrors, PermissionRespondResponse, PermissionRespondResponses, PermissionRule, PermissionRuleConfig, PermissionRuleset, Project, ProjectCurrentData, ProjectCurrentResponse, ProjectCurrentResponses, ProjectListData, ProjectListResponse, ProjectListResponses, ProjectUpdateData, ProjectUpdateError, ProjectUpdateErrors, ProjectUpdateResponse, ProjectUpdateResponses, Provider, ProviderAuthAuthorization, ProviderAuthData, ProviderAuthError, ProviderAuthMethod, ProviderAuthResponse, ProviderAuthResponses, ProviderConfig, ProviderListData, ProviderListResponse, ProviderListResponses, ProviderOauthAuthorizeData, ProviderOauthAuthorizeError, ProviderOauthAuthorizeErrors, ProviderOauthAuthorizeResponse, ProviderOauthAuthorizeResponses, ProviderOauthCallbackData, ProviderOauthCallbackError, ProviderOauthCallbackErrors, ProviderOauthCallbackResponse, ProviderOauthCallbackResponses, Pty, PtyConnectData, PtyConnectError, PtyConnectErrors, PtyConnectResponse, PtyConnectResponses, PtyCreateData, PtyCreateError, PtyCreateErrors, PtyCreateResponse, PtyCreateResponses, PtyGetData, PtyGetError, PtyGetErrors, PtyGetResponse, PtyGetResponses, PtyListData, PtyListResponse, PtyListResponses, PtyRemoveData, PtyRemoveError, PtyRemoveErrors, PtyRemoveResponse, PtyRemoveResponses, PtyUpdateData, PtyUpdateError, PtyUpdateErrors, PtyUpdateResponse, PtyUpdateResponses, Range, ReasoningPart, RetryPart, ServerConfig, Session, SessionAbortData, SessionAbortError, SessionAbortErrors, SessionAbortResponse, SessionAbortResponses, SessionChildrenData, SessionChildrenError, SessionChildrenErrors, SessionChildrenResponse, SessionChildrenResponses, SessionCommandData, SessionCommandError, SessionCommandErrors, SessionCommandResponse, SessionCommandResponses, SessionCreateData, SessionCreateError, SessionCreateErrors, SessionCreateResponse, SessionCreateResponses, SessionDeleteData, SessionDeleteError, SessionDeleteErrors, SessionDeleteResponse, SessionDeleteResponses, SessionDiffData, SessionDiffError, SessionDiffErrors, SessionDiffResponse, SessionDiffResponses, SessionForkData, SessionForkResponse, SessionForkResponses, SessionGetData, SessionGetError, SessionGetErrors, SessionGetResponse, SessionGetResponses, SessionInitData, SessionInitError, SessionInitErrors, SessionInitResponse, SessionInitResponses, SessionListData, SessionListResponse, SessionListResponses, SessionMessageData, SessionMessageError, SessionMessageErrors, SessionMessageResponse, SessionMessageResponses, SessionMessagesData, SessionMessagesError, SessionMessagesErrors, SessionMessagesResponse, SessionMessagesResponses, SessionPromptAsyncData, SessionPromptAsyncError, SessionPromptAsyncErrors, SessionPromptAsyncResponse, SessionPromptAsyncResponses, SessionPromptData, SessionPromptError, SessionPromptErrors, SessionPromptResponse, SessionPromptResponses, SessionRevertData, SessionRevertError, SessionRevertErrors, SessionRevertResponse, SessionRevertResponses, SessionShareData, SessionShareError, SessionShareErrors, SessionShareResponse, SessionShareResponses, SessionShellData, SessionShellError, SessionShellErrors, SessionShellResponse, SessionShellResponses, SessionStatus, SessionStatusData, SessionStatusError, SessionStatusErrors, SessionStatusResponse, SessionStatusResponses, SessionSummarizeData, SessionSummarizeError, SessionSummarizeErrors, SessionSummarizeResponse, SessionSummarizeResponses, SessionTodoData, SessionTodoError, SessionTodoErrors, SessionTodoResponse, SessionTodoResponses, SessionUnrevertData, SessionUnrevertError, SessionUnrevertErrors, SessionUnrevertResponse, SessionUnrevertResponses, SessionUnshareData, SessionUnshareError, SessionUnshareErrors, SessionUnshareResponse, SessionUnshareResponses, SessionUpdateData, SessionUpdateError, SessionUpdateErrors, SessionUpdateResponse, SessionUpdateResponses, SnapshotPart, StepFinishPart, StepStartPart, SubtaskPartInput, Symbol, SymbolSource, TextPart, TextPartInput, Todo, ToolIds, ToolIdsData, ToolIdsError, ToolIdsErrors, ToolIdsResponse, ToolIdsResponses, ToolList, ToolListData, ToolListError, ToolListErrors, ToolListItem, ToolListResponse, ToolListResponses, ToolPart, ToolState, ToolStateCompleted, ToolStateError, ToolStatePending, ToolStateRunning, TuiAppendPromptData, TuiAppendPromptError, TuiAppendPromptErrors, TuiAppendPromptResponse, TuiAppendPromptResponses, TuiClearPromptData, TuiClearPromptResponse, TuiClearPromptResponses, TuiControlNextData, TuiControlNextResponse, TuiControlNextResponses, TuiControlResponseData, TuiControlResponseResponse, TuiControlResponseResponses, TuiExecuteCommandData, TuiExecuteCommandError, TuiExecuteCommandErrors, TuiExecuteCommandResponse, TuiExecuteCommandResponses, TuiOpenHelpData, TuiOpenHelpResponse, TuiOpenHelpResponses, TuiOpenModelsData, TuiOpenModelsResponse, TuiOpenModelsResponses, TuiOpenSessionsData, TuiOpenSessionsResponse, TuiOpenSessionsResponses, TuiOpenThemesData, TuiOpenThemesResponse, TuiOpenThemesResponses, TuiPublishData, TuiPublishError, TuiPublishErrors, TuiPublishResponse, TuiPublishResponses, TuiShowToastData, TuiShowToastResponse, TuiShowToastResponses, TuiSubmitPromptData, TuiSubmitPromptResponse, TuiSubmitPromptResponses, UnknownError, UserMessage, VcsGetData, VcsGetResponse, VcsGetResponses, VcsInfo, WellKnownAuth } from './types.gen'; diff --git a/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/sdk.gen.ts b/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/sdk.gen.ts new file mode 100644 index 0000000000..e69df9dbf6 --- /dev/null +++ b/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/sdk.gen.ts @@ -0,0 +1,1667 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { buildClientParams, type Client, type Options as Options2, type TDataShape } from './client'; +import { client } from './client.gen'; +import type { AgentPartInput, AppAgentsResponses, AppLogErrors, AppLogResponses, Auth, AuthSetErrors, AuthSetResponses, CommandListResponses, Config, ConfigGetResponses, ConfigProvidersResponses, ConfigUpdateErrors, ConfigUpdateResponses, EventSubscribeResponses, EventTuiCommandExecute, EventTuiPromptAppend, EventTuiToastShow, FileListResponses, FilePartInput, FileReadResponses, FileStatusResponses, FindFilesResponses, FindSymbolsResponses, FindTextResponses, FormatterStatusResponses, GlobalDisposeResponses, GlobalEventResponses, GlobalHealthResponses, InstanceDisposeResponses, LspStatusResponses, McpAddErrors, McpAddResponses, McpAuthAuthenticateErrors, McpAuthAuthenticateResponses, McpAuthCallbackErrors, McpAuthCallbackResponses, McpAuthRemoveErrors, McpAuthRemoveResponses, McpAuthStartErrors, McpAuthStartResponses, McpConnectResponses, McpDisconnectResponses, McpLocalConfig, McpRemoteConfig, McpStatusResponses, Part, PartDeleteErrors, PartDeleteResponses, PartUpdateErrors, PartUpdateResponses, PathGetResponses, PermissionListResponses, PermissionReplyErrors, PermissionReplyResponses, PermissionRespondErrors, PermissionRespondResponses, PermissionRuleset, ProjectCurrentResponses, ProjectListResponses, ProjectUpdateErrors, ProjectUpdateResponses, ProviderAuthResponses, ProviderListResponses, ProviderOauthAuthorizeErrors, ProviderOauthAuthorizeResponses, ProviderOauthCallbackErrors, ProviderOauthCallbackResponses, PtyConnectErrors, PtyConnectResponses, PtyCreateErrors, PtyCreateResponses, PtyGetErrors, PtyGetResponses, PtyListResponses, PtyRemoveErrors, PtyRemoveResponses, PtyUpdateErrors, PtyUpdateResponses, SessionAbortErrors, SessionAbortResponses, SessionChildrenErrors, SessionChildrenResponses, SessionCommandErrors, SessionCommandResponses, SessionCreateErrors, SessionCreateResponses, SessionDeleteErrors, SessionDeleteResponses, SessionDiffErrors, SessionDiffResponses, SessionForkResponses, SessionGetErrors, SessionGetResponses, SessionInitErrors, SessionInitResponses, SessionListResponses, SessionMessageErrors, SessionMessageResponses, SessionMessagesErrors, SessionMessagesResponses, SessionPromptAsyncErrors, SessionPromptAsyncResponses, SessionPromptErrors, SessionPromptResponses, SessionRevertErrors, SessionRevertResponses, SessionShareErrors, SessionShareResponses, SessionShellErrors, SessionShellResponses, SessionStatusErrors, SessionStatusResponses, SessionSummarizeErrors, SessionSummarizeResponses, SessionTodoErrors, SessionTodoResponses, SessionUnrevertErrors, SessionUnrevertResponses, SessionUnshareErrors, SessionUnshareResponses, SessionUpdateErrors, SessionUpdateResponses, SubtaskPartInput, TextPartInput, ToolIdsErrors, ToolIdsResponses, ToolListErrors, ToolListResponses, TuiAppendPromptErrors, TuiAppendPromptResponses, TuiClearPromptResponses, TuiControlNextResponses, TuiControlResponseResponses, TuiExecuteCommandErrors, TuiExecuteCommandResponses, TuiOpenHelpResponses, TuiOpenModelsResponses, TuiOpenSessionsResponses, TuiOpenThemesResponses, TuiPublishErrors, TuiPublishResponses, TuiShowToastResponses, TuiSubmitPromptResponses, VcsGetResponses } from './types.gen'; + +export type Options = Options2 & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; + /** + * You can pass arbitrary values through the `meta` object. This can be + * used to access values that aren't defined as part of the SDK function. + */ + meta?: Record; +}; + +/** + * Get health + * + * Get health information about the OpenCode server. + */ +export const globalHealth = (options?: Options) => (options?.client ?? client).get({ url: '/global/health', ...options }); + +/** + * Get global events + * + * Subscribe to global events from the OpenCode system using server-sent events. + */ +export const globalEvent = (options?: Options) => (options?.client ?? client).sse.get({ url: '/global/event', ...options }); + +/** + * Dispose instance + * + * Clean up and dispose all OpenCode instances, releasing all resources. + */ +export const globalDispose = (options?: Options) => (options?.client ?? client).post({ url: '/global/dispose', ...options }); + +/** + * List all projects + * + * Get a list of projects that have been opened with OpenCode. + */ +export const projectList = (parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).get({ + url: '/project', + ...options, + ...params + }); +}; + +/** + * Get current project + * + * Retrieve the currently active project that OpenCode is working with. + */ +export const projectCurrent = (parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).get({ + url: '/project/current', + ...options, + ...params + }); +}; + +/** + * Update project + * + * Update project properties such as name, icon and color. + */ +export const projectUpdate = (projectID: string, parameters?: { + directory?: string; + name?: string; + icon?: { + url?: string; + color?: string; + }; +}, options?: Options) => { + const params = buildClientParams([projectID, parameters], [{ in: 'path', key: 'projectID' }, { args: [ + { in: 'query', key: 'directory' }, + { in: 'body', key: 'name' }, + { in: 'body', key: 'icon' } + ] }]); + return (options?.client ?? client).patch({ + url: '/project/{projectID}', + ...options, + ...params, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + ...params.headers + } + }); +}; + +/** + * List PTY sessions + * + * Get a list of all active pseudo-terminal (PTY) sessions managed by OpenCode. + */ +export const ptyList = (parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).get({ + url: '/pty', + ...options, + ...params + }); +}; + +/** + * Create PTY session + * + * Create a new pseudo-terminal (PTY) session for running shell commands and processes. + */ +export const ptyCreate = (parameters?: { + directory?: string; + command?: string; + args?: Array; + cwd?: string; + title?: string; + env?: { + [key: string]: string; + }; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).post({ + url: '/pty', + ...options, + ...params, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + ...params.headers + } + }); +}; + +/** + * Remove PTY session + * + * Remove and terminate a specific pseudo-terminal (PTY) session. + */ +export const ptyRemove = (ptyID: string, parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([ptyID, parameters], [{ in: 'path', key: 'ptyID' }, { args: [{ in: 'query', key: 'directory' }] }]); + return (options?.client ?? client).delete({ + url: '/pty/{ptyID}', + ...options, + ...params + }); +}; + +/** + * Get PTY session + * + * Retrieve detailed information about a specific pseudo-terminal (PTY) session. + */ +export const ptyGet = (ptyID: string, parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([ptyID, parameters], [{ in: 'path', key: 'ptyID' }, { args: [{ in: 'query', key: 'directory' }] }]); + return (options?.client ?? client).get({ + url: '/pty/{ptyID}', + ...options, + ...params + }); +}; + +/** + * Update PTY session + * + * Update properties of an existing pseudo-terminal (PTY) session. + */ +export const ptyUpdate = (ptyID: string, parameters?: { + directory?: string; + title?: string; + size?: { + rows: number; + cols: number; + }; +}, options?: Options) => { + const params = buildClientParams([ptyID, parameters], [{ in: 'path', key: 'ptyID' }, { args: [ + { in: 'query', key: 'directory' }, + { in: 'body', key: 'title' }, + { in: 'body', key: 'size' } + ] }]); + return (options?.client ?? client).put({ + url: '/pty/{ptyID}', + ...options, + ...params, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + ...params.headers + } + }); +}; + +/** + * Connect to PTY session + * + * Establish a WebSocket connection to interact with a pseudo-terminal (PTY) session in real-time. + */ +export const ptyConnect = (ptyID: string, parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([ptyID, parameters], [{ in: 'path', key: 'ptyID' }, { args: [{ in: 'query', key: 'directory' }] }]); + return (options?.client ?? client).get({ + url: '/pty/{ptyID}/connect', + ...options, + ...params + }); +}; + +/** + * Get configuration + * + * Retrieve the current OpenCode configuration settings and preferences. + */ +export const configGet = (parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).get({ + url: '/config', + ...options, + ...params + }); +}; + +/** + * Update configuration + * + * Update OpenCode configuration settings and preferences. + */ +export const configUpdate = (parameters?: { + directory?: string; + config?: Config; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).patch({ + url: '/config', + ...options, + ...params, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + ...params.headers + } + }); +}; + +/** + * List tool IDs + * + * Get a list of all available tool IDs, including both built-in tools and dynamically registered tools. + */ +export const toolIds = (parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).get({ + url: '/experimental/tool/ids', + ...options, + ...params + }); +}; + +/** + * List tools + * + * Get a list of available tools with their JSON schema parameters for a specific provider and model combination. + */ +export const toolList = (parameters: { + directory?: string; + provider: string; + model: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).get({ + url: '/experimental/tool', + ...options, + ...params + }); +}; + +/** + * Dispose instance + * + * Clean up and dispose the current OpenCode instance, releasing all resources. + */ +export const instanceDispose = (parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).post({ + url: '/instance/dispose', + ...options, + ...params + }); +}; + +/** + * Get paths + * + * Retrieve the current working directory and related path information for the OpenCode instance. + */ +export const pathGet = (parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).get({ + url: '/path', + ...options, + ...params + }); +}; + +/** + * Get VCS info + * + * Retrieve version control system (VCS) information for the current project, such as git branch. + */ +export const vcsGet = (parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).get({ + url: '/vcs', + ...options, + ...params + }); +}; + +/** + * List sessions + * + * Get a list of all OpenCode sessions, sorted by most recently updated. + */ +export const sessionList = (parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).get({ + url: '/session', + ...options, + ...params + }); +}; + +/** + * Create session + * + * Create a new OpenCode session for interacting with AI assistants and managing conversations. + */ +export const sessionCreate = (parameters?: { + directory?: string; + parentID?: string; + title?: string; + permission?: PermissionRuleset; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).post({ + url: '/session', + ...options, + ...params, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + ...params.headers + } + }); +}; + +/** + * Get session status + * + * Retrieve the current status of all sessions, including active, idle, and completed states. + */ +export const sessionStatus = (parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).get({ + url: '/session/status', + ...options, + ...params + }); +}; + +/** + * Delete session + * + * Delete a session and permanently remove all associated data, including messages and history. + */ +export const sessionDelete = (sessionID: string, parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([sessionID, parameters], [{ in: 'path', key: 'sessionID' }, { args: [{ in: 'query', key: 'directory' }] }]); + return (options?.client ?? client).delete({ + url: '/session/{sessionID}', + ...options, + ...params + }); +}; + +/** + * Get session + * + * Retrieve detailed information about a specific OpenCode session. + */ +export const sessionGet = (sessionID: string, parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([sessionID, parameters], [{ in: 'path', key: 'sessionID' }, { args: [{ in: 'query', key: 'directory' }] }]); + return (options?.client ?? client).get({ + url: '/session/{sessionID}', + ...options, + ...params + }); +}; + +/** + * Update session + * + * Update properties of an existing session, such as title or other metadata. + */ +export const sessionUpdate = (sessionID: string, parameters?: { + directory?: string; + title?: string; + time?: { + archived?: number; + }; +}, options?: Options) => { + const params = buildClientParams([sessionID, parameters], [{ in: 'path', key: 'sessionID' }, { args: [ + { in: 'query', key: 'directory' }, + { in: 'body', key: 'title' }, + { in: 'body', key: 'time' } + ] }]); + return (options?.client ?? client).patch({ + url: '/session/{sessionID}', + ...options, + ...params, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + ...params.headers + } + }); +}; + +/** + * Get session children + * + * Retrieve all child sessions that were forked from the specified parent session. + */ +export const sessionChildren = (sessionID: string, parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([sessionID, parameters], [{ in: 'path', key: 'sessionID' }, { args: [{ in: 'query', key: 'directory' }] }]); + return (options?.client ?? client).get({ + url: '/session/{sessionID}/children', + ...options, + ...params + }); +}; + +/** + * Get session todos + * + * Retrieve the todo list associated with a specific session, showing tasks and action items. + */ +export const sessionTodo = (sessionID: string, parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([sessionID, parameters], [{ in: 'path', key: 'sessionID' }, { args: [{ in: 'query', key: 'directory' }] }]); + return (options?.client ?? client).get({ + url: '/session/{sessionID}/todo', + ...options, + ...params + }); +}; + +/** + * Initialize session + * + * Analyze the current application and create an AGENTS.md file with project-specific agent configurations. + */ +export const sessionInit = (sessionID: string, parameters?: { + directory?: string; + modelID?: string; + providerID?: string; + messageID?: string; +}, options?: Options) => { + const params = buildClientParams([sessionID, parameters], [{ in: 'path', key: 'sessionID' }, { args: [ + { in: 'query', key: 'directory' }, + { in: 'body', key: 'modelID' }, + { in: 'body', key: 'providerID' }, + { in: 'body', key: 'messageID' } + ] }]); + return (options?.client ?? client).post({ + url: '/session/{sessionID}/init', + ...options, + ...params, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + ...params.headers + } + }); +}; + +/** + * Fork session + * + * Create a new session by forking an existing session at a specific message point. + */ +export const sessionFork = (sessionID: string, parameters?: { + directory?: string; + messageID?: string; +}, options?: Options) => { + const params = buildClientParams([sessionID, parameters], [{ in: 'path', key: 'sessionID' }, { args: [{ in: 'query', key: 'directory' }, { in: 'body', key: 'messageID' }] }]); + return (options?.client ?? client).post({ + url: '/session/{sessionID}/fork', + ...options, + ...params, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + ...params.headers + } + }); +}; + +/** + * Abort session + * + * Abort an active session and stop any ongoing AI processing or command execution. + */ +export const sessionAbort = (sessionID: string, parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([sessionID, parameters], [{ in: 'path', key: 'sessionID' }, { args: [{ in: 'query', key: 'directory' }] }]); + return (options?.client ?? client).post({ + url: '/session/{sessionID}/abort', + ...options, + ...params + }); +}; + +/** + * Unshare session + * + * Remove the shareable link for a session, making it private again. + */ +export const sessionUnshare = (sessionID: string, parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([sessionID, parameters], [{ in: 'path', key: 'sessionID' }, { args: [{ in: 'query', key: 'directory' }] }]); + return (options?.client ?? client).delete({ + url: '/session/{sessionID}/share', + ...options, + ...params + }); +}; + +/** + * Share session + * + * Create a shareable link for a session, allowing others to view the conversation. + */ +export const sessionShare = (sessionID: string, parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([sessionID, parameters], [{ in: 'path', key: 'sessionID' }, { args: [{ in: 'query', key: 'directory' }] }]); + return (options?.client ?? client).post({ + url: '/session/{sessionID}/share', + ...options, + ...params + }); +}; + +/** + * Get session diff + * + * Get all file changes (diffs) made during this session. + */ +export const sessionDiff = (sessionID: string, parameters?: { + directory?: string; + messageID?: string; +}, options?: Options) => { + const params = buildClientParams([sessionID, parameters], [{ in: 'path', key: 'sessionID' }, { args: [{ in: 'query', key: 'directory' }, { in: 'query', key: 'messageID' }] }]); + return (options?.client ?? client).get({ + url: '/session/{sessionID}/diff', + ...options, + ...params + }); +}; + +/** + * Summarize session + * + * Generate a concise summary of the session using AI compaction to preserve key information. + */ +export const sessionSummarize = (sessionID: string, parameters?: { + directory?: string; + providerID?: string; + modelID?: string; + auto?: boolean; +}, options?: Options) => { + const params = buildClientParams([sessionID, parameters], [{ in: 'path', key: 'sessionID' }, { args: [ + { in: 'query', key: 'directory' }, + { in: 'body', key: 'providerID' }, + { in: 'body', key: 'modelID' }, + { in: 'body', key: 'auto' } + ] }]); + return (options?.client ?? client).post({ + url: '/session/{sessionID}/summarize', + ...options, + ...params, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + ...params.headers + } + }); +}; + +/** + * Get session messages + * + * Retrieve all messages in a session, including user prompts and AI responses. + */ +export const sessionMessages = (sessionID: string, parameters?: { + directory?: string; + limit?: number; +}, options?: Options) => { + const params = buildClientParams([sessionID, parameters], [{ in: 'path', key: 'sessionID' }, { args: [{ in: 'query', key: 'directory' }, { in: 'query', key: 'limit' }] }]); + return (options?.client ?? client).get({ + url: '/session/{sessionID}/message', + ...options, + ...params + }); +}; + +/** + * Send message + * + * Create and send a new message to a session, streaming the AI response. + */ +export const sessionPrompt = (sessionID: string, parameters?: { + directory?: string; + messageID?: string; + model?: { + providerID: string; + modelID: string; + }; + agent?: string; + noReply?: boolean; + tools?: { + [key: string]: boolean; + }; + system?: string; + variant?: string; + parts?: Array; +}, options?: Options) => { + const params = buildClientParams([sessionID, parameters], [{ in: 'path', key: 'sessionID' }, { args: [ + { in: 'query', key: 'directory' }, + { in: 'body', key: 'messageID' }, + { in: 'body', key: 'model' }, + { in: 'body', key: 'agent' }, + { in: 'body', key: 'noReply' }, + { in: 'body', key: 'tools' }, + { in: 'body', key: 'system' }, + { in: 'body', key: 'variant' }, + { in: 'body', key: 'parts' } + ] }]); + return (options?.client ?? client).post({ + url: '/session/{sessionID}/message', + ...options, + ...params, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + ...params.headers + } + }); +}; + +/** + * Get message + * + * Retrieve a specific message from a session by its message ID. + */ +export const sessionMessage = (parameters: { + sessionID: string; + messageID: string; + directory?: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).get({ + url: '/session/{sessionID}/message/{messageID}', + ...options, + ...params + }); +}; + +/** + * Delete a part from a message + */ +export const partDelete = (parameters: { + sessionID: string; + messageID: string; + partID: string; + directory?: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).delete({ + url: '/session/{sessionID}/message/{messageID}/part/{partID}', + ...options, + ...params + }); +}; + +/** + * Update a part in a message + */ +export const partUpdate = (parameters: { + sessionID: string; + messageID: string; + partID: string; + directory?: string; + part?: Part; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).patch({ + url: '/session/{sessionID}/message/{messageID}/part/{partID}', + ...options, + ...params, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + ...params.headers + } + }); +}; + +/** + * Send async message + * + * Create and send a new message to a session asynchronously, starting the session if needed and returning immediately. + */ +export const sessionPromptAsync = (sessionID: string, parameters?: { + directory?: string; + messageID?: string; + model?: { + providerID: string; + modelID: string; + }; + agent?: string; + noReply?: boolean; + tools?: { + [key: string]: boolean; + }; + system?: string; + variant?: string; + parts?: Array; +}, options?: Options) => { + const params = buildClientParams([sessionID, parameters], [{ in: 'path', key: 'sessionID' }, { args: [ + { in: 'query', key: 'directory' }, + { in: 'body', key: 'messageID' }, + { in: 'body', key: 'model' }, + { in: 'body', key: 'agent' }, + { in: 'body', key: 'noReply' }, + { in: 'body', key: 'tools' }, + { in: 'body', key: 'system' }, + { in: 'body', key: 'variant' }, + { in: 'body', key: 'parts' } + ] }]); + return (options?.client ?? client).post({ + url: '/session/{sessionID}/prompt_async', + ...options, + ...params, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + ...params.headers + } + }); +}; + +/** + * Send command + * + * Send a new command to a session for execution by the AI assistant. + */ +export const sessionCommand = (sessionID: string, parameters?: { + directory?: string; + messageID?: string; + agent?: string; + model?: string; + arguments?: string; + command?: string; + variant?: string; +}, options?: Options) => { + const params = buildClientParams([sessionID, parameters], [{ in: 'path', key: 'sessionID' }, { args: [ + { in: 'query', key: 'directory' }, + { in: 'body', key: 'messageID' }, + { in: 'body', key: 'agent' }, + { in: 'body', key: 'model' }, + { in: 'body', key: 'arguments' }, + { in: 'body', key: 'command' }, + { in: 'body', key: 'variant' } + ] }]); + return (options?.client ?? client).post({ + url: '/session/{sessionID}/command', + ...options, + ...params, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + ...params.headers + } + }); +}; + +/** + * Run shell command + * + * Execute a shell command within the session context and return the AI's response. + */ +export const sessionShell = (sessionID: string, parameters?: { + directory?: string; + agent?: string; + model?: { + providerID: string; + modelID: string; + }; + command?: string; +}, options?: Options) => { + const params = buildClientParams([sessionID, parameters], [{ in: 'path', key: 'sessionID' }, { args: [ + { in: 'query', key: 'directory' }, + { in: 'body', key: 'agent' }, + { in: 'body', key: 'model' }, + { in: 'body', key: 'command' } + ] }]); + return (options?.client ?? client).post({ + url: '/session/{sessionID}/shell', + ...options, + ...params, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + ...params.headers + } + }); +}; + +/** + * Revert message + * + * Revert a specific message in a session, undoing its effects and restoring the previous state. + */ +export const sessionRevert = (sessionID: string, parameters?: { + directory?: string; + messageID?: string; + partID?: string; +}, options?: Options) => { + const params = buildClientParams([sessionID, parameters], [{ in: 'path', key: 'sessionID' }, { args: [ + { in: 'query', key: 'directory' }, + { in: 'body', key: 'messageID' }, + { in: 'body', key: 'partID' } + ] }]); + return (options?.client ?? client).post({ + url: '/session/{sessionID}/revert', + ...options, + ...params, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + ...params.headers + } + }); +}; + +/** + * Restore reverted messages + * + * Restore all previously reverted messages in a session. + */ +export const sessionUnrevert = (sessionID: string, parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([sessionID, parameters], [{ in: 'path', key: 'sessionID' }, { args: [{ in: 'query', key: 'directory' }] }]); + return (options?.client ?? client).post({ + url: '/session/{sessionID}/unrevert', + ...options, + ...params + }); +}; + +/** + * Respond to permission + * + * Approve or deny a permission request from the AI assistant. + * + * @deprecated + */ +export const permissionRespond = (parameters: { + sessionID: string; + permissionID: string; + directory?: string; + response?: 'once' | 'always' | 'reject'; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).post({ + url: '/session/{sessionID}/permissions/{permissionID}', + ...options, + ...params, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + ...params.headers + } + }); +}; + +/** + * Respond to permission request + * + * Approve or deny a permission request from the AI assistant. + */ +export const permissionReply = (requestID: string, parameters?: { + directory?: string; + reply?: 'once' | 'always' | 'reject'; +}, options?: Options) => { + const params = buildClientParams([requestID, parameters], [{ in: 'path', key: 'requestID' }, { args: [{ in: 'query', key: 'directory' }, { in: 'body', key: 'reply' }] }]); + return (options?.client ?? client).post({ + url: '/permission/{requestID}/reply', + ...options, + ...params, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + ...params.headers + } + }); +}; + +/** + * List pending permissions + * + * Get all pending permission requests across all sessions. + */ +export const permissionList = (parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).get({ + url: '/permission', + ...options, + ...params + }); +}; + +/** + * List commands + * + * Get a list of all available commands in the OpenCode system. + */ +export const commandList = (parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).get({ + url: '/command', + ...options, + ...params + }); +}; + +/** + * List config providers + * + * Get a list of all configured AI providers and their default models. + */ +export const configProviders = (parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).get({ + url: '/config/providers', + ...options, + ...params + }); +}; + +/** + * List providers + * + * Get a list of all available AI providers, including both available and connected ones. + */ +export const providerList = (parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).get({ + url: '/provider', + ...options, + ...params + }); +}; + +/** + * Get provider auth methods + * + * Retrieve available authentication methods for all AI providers. + */ +export const providerAuth = (parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).get({ + url: '/provider/auth', + ...options, + ...params + }); +}; + +/** + * OAuth authorize + * + * Initiate OAuth authorization for a specific AI provider to get an authorization URL. + */ +export const providerOauthAuthorize = (providerID: string, parameters?: { + directory?: string; + method?: number; +}, options?: Options) => { + const params = buildClientParams([providerID, parameters], [{ in: 'path', key: 'providerID' }, { args: [{ in: 'query', key: 'directory' }, { in: 'body', key: 'method' }] }]); + return (options?.client ?? client).post({ + url: '/provider/{providerID}/oauth/authorize', + ...options, + ...params, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + ...params.headers + } + }); +}; + +/** + * OAuth callback + * + * Handle the OAuth callback from a provider after user authorization. + */ +export const providerOauthCallback = (providerID: string, parameters?: { + directory?: string; + method?: number; + code?: string; +}, options?: Options) => { + const params = buildClientParams([providerID, parameters], [{ in: 'path', key: 'providerID' }, { args: [ + { in: 'query', key: 'directory' }, + { in: 'body', key: 'method' }, + { in: 'body', key: 'code' } + ] }]); + return (options?.client ?? client).post({ + url: '/provider/{providerID}/oauth/callback', + ...options, + ...params, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + ...params.headers + } + }); +}; + +/** + * Find text + * + * Search for text patterns across files in the project using ripgrep. + */ +export const findText = (parameters: { + directory?: string; + pattern: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).get({ + url: '/find', + ...options, + ...params + }); +}; + +/** + * Find files + * + * Search for files or directories by name or pattern in the project directory. + */ +export const findFiles = (parameters: { + directory?: string; + query: string; + dirs?: 'true' | 'false'; + type?: 'file' | 'directory'; + limit?: number; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).get({ + url: '/find/file', + ...options, + ...params + }); +}; + +/** + * Find symbols + * + * Search for workspace symbols like functions, classes, and variables using LSP. + */ +export const findSymbols = (parameters: { + directory?: string; + query: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).get({ + url: '/find/symbol', + ...options, + ...params + }); +}; + +/** + * List files + * + * List files and directories in a specified path. + */ +export const fileList = (parameters: { + directory?: string; + path: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).get({ + url: '/file', + ...options, + ...params + }); +}; + +/** + * Read file + * + * Read the content of a specified file. + */ +export const fileRead = (parameters: { + directory?: string; + path: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).get({ + url: '/file/content', + ...options, + ...params + }); +}; + +/** + * Get file status + * + * Get the git status of all files in the project. + */ +export const fileStatus = (parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).get({ + url: '/file/status', + ...options, + ...params + }); +}; + +/** + * Write log + * + * Write a log entry to the server logs with specified level and metadata. + */ +export const appLog = (parameters?: { + directory?: string; + service?: string; + level?: 'debug' | 'info' | 'error' | 'warn'; + message?: string; + extra?: { + [key: string]: unknown; + }; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).post({ + url: '/log', + ...options, + ...params, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + ...params.headers + } + }); +}; + +/** + * List agents + * + * Get a list of all available AI agents in the OpenCode system. + */ +export const appAgents = (parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).get({ + url: '/agent', + ...options, + ...params + }); +}; + +/** + * Get MCP status + * + * Get the status of all Model Context Protocol (MCP) servers. + */ +export const mcpStatus = (parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).get({ + url: '/mcp', + ...options, + ...params + }); +}; + +/** + * Add MCP server + * + * Dynamically add a new Model Context Protocol (MCP) server to the system. + */ +export const mcpAdd = (parameters?: { + directory?: string; + name?: string; + config?: McpLocalConfig | McpRemoteConfig; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).post({ + url: '/mcp', + ...options, + ...params, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + ...params.headers + } + }); +}; + +/** + * Remove MCP OAuth + * + * Remove OAuth credentials for an MCP server + */ +export const mcpAuthRemove = (name: string, parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([name, parameters], [{ in: 'path', key: 'name' }, { args: [{ in: 'query', key: 'directory' }] }]); + return (options?.client ?? client).delete({ + url: '/mcp/{name}/auth', + ...options, + ...params + }); +}; + +/** + * Start MCP OAuth + * + * Start OAuth authentication flow for a Model Context Protocol (MCP) server. + */ +export const mcpAuthStart = (name: string, parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([name, parameters], [{ in: 'path', key: 'name' }, { args: [{ in: 'query', key: 'directory' }] }]); + return (options?.client ?? client).post({ + url: '/mcp/{name}/auth', + ...options, + ...params + }); +}; + +/** + * Complete MCP OAuth + * + * Complete OAuth authentication for a Model Context Protocol (MCP) server using the authorization code. + */ +export const mcpAuthCallback = (name: string, parameters?: { + directory?: string; + code?: string; +}, options?: Options) => { + const params = buildClientParams([name, parameters], [{ in: 'path', key: 'name' }, { args: [{ in: 'query', key: 'directory' }, { in: 'body', key: 'code' }] }]); + return (options?.client ?? client).post({ + url: '/mcp/{name}/auth/callback', + ...options, + ...params, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + ...params.headers + } + }); +}; + +/** + * Authenticate MCP OAuth + * + * Start OAuth flow and wait for callback (opens browser) + */ +export const mcpAuthAuthenticate = (name: string, parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([name, parameters], [{ in: 'path', key: 'name' }, { args: [{ in: 'query', key: 'directory' }] }]); + return (options?.client ?? client).post({ + url: '/mcp/{name}/auth/authenticate', + ...options, + ...params + }); +}; + +/** + * Connect an MCP server + */ +export const mcpConnect = (name: string, parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([name, parameters], [{ in: 'path', key: 'name' }, { args: [{ in: 'query', key: 'directory' }] }]); + return (options?.client ?? client).post({ + url: '/mcp/{name}/connect', + ...options, + ...params + }); +}; + +/** + * Disconnect an MCP server + */ +export const mcpDisconnect = (name: string, parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([name, parameters], [{ in: 'path', key: 'name' }, { args: [{ in: 'query', key: 'directory' }] }]); + return (options?.client ?? client).post({ + url: '/mcp/{name}/disconnect', + ...options, + ...params + }); +}; + +/** + * Get LSP status + * + * Get LSP server status + */ +export const lspStatus = (parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).get({ + url: '/lsp', + ...options, + ...params + }); +}; + +/** + * Get formatter status + * + * Get formatter status + */ +export const formatterStatus = (parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).get({ + url: '/formatter', + ...options, + ...params + }); +}; + +/** + * Append TUI prompt + * + * Append prompt to the TUI + */ +export const tuiAppendPrompt = (parameters?: { + directory?: string; + text?: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).post({ + url: '/tui/append-prompt', + ...options, + ...params, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + ...params.headers + } + }); +}; + +/** + * Open help dialog + * + * Open the help dialog in the TUI to display user assistance information. + */ +export const tuiOpenHelp = (parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).post({ + url: '/tui/open-help', + ...options, + ...params + }); +}; + +/** + * Open sessions dialog + * + * Open the session dialog + */ +export const tuiOpenSessions = (parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).post({ + url: '/tui/open-sessions', + ...options, + ...params + }); +}; + +/** + * Open themes dialog + * + * Open the theme dialog + */ +export const tuiOpenThemes = (parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).post({ + url: '/tui/open-themes', + ...options, + ...params + }); +}; + +/** + * Open models dialog + * + * Open the model dialog + */ +export const tuiOpenModels = (parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).post({ + url: '/tui/open-models', + ...options, + ...params + }); +}; + +/** + * Submit TUI prompt + * + * Submit the prompt + */ +export const tuiSubmitPrompt = (parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).post({ + url: '/tui/submit-prompt', + ...options, + ...params + }); +}; + +/** + * Clear TUI prompt + * + * Clear the prompt + */ +export const tuiClearPrompt = (parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).post({ + url: '/tui/clear-prompt', + ...options, + ...params + }); +}; + +/** + * Execute TUI command + * + * Execute a TUI command (e.g. agent_cycle) + */ +export const tuiExecuteCommand = (parameters?: { + directory?: string; + command?: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).post({ + url: '/tui/execute-command', + ...options, + ...params, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + ...params.headers + } + }); +}; + +/** + * Show TUI toast + * + * Show a toast notification in the TUI + */ +export const tuiShowToast = (parameters?: { + directory?: string; + title?: string; + message?: string; + variant?: 'info' | 'success' | 'warning' | 'error'; + duration?: number; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).post({ + url: '/tui/show-toast', + ...options, + ...params, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + ...params.headers + } + }); +}; + +/** + * Publish TUI event + * + * Publish a TUI event + */ +export const tuiPublish = (parameters?: { + directory?: string; + body?: EventTuiPromptAppend | EventTuiCommandExecute | EventTuiToastShow; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).post({ + url: '/tui/publish', + ...options, + ...params, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + ...params.headers + } + }); +}; + +/** + * Get next TUI request + * + * Retrieve the next TUI (Terminal User Interface) request from the queue for processing. + */ +export const tuiControlNext = (parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).get({ + url: '/tui/control/next', + ...options, + ...params + }); +}; + +/** + * Submit TUI response + * + * Submit a response to the TUI request queue to complete a pending request. + */ +export const tuiControlResponse = (parameters?: { + directory?: string; + body?: unknown; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).post({ + url: '/tui/control/response', + ...options, + ...params, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + ...params.headers + } + }); +}; + +/** + * Set auth credentials + * + * Set authentication credentials + */ +export const authSet = (providerID: string, parameters?: { + directory?: string; + auth?: Auth; +}, options?: Options) => { + const params = buildClientParams([providerID, parameters], [{ in: 'path', key: 'providerID' }, { args: [{ in: 'query', key: 'directory' }, { key: 'auth', map: 'body' }] }]); + return (options?.client ?? client).put({ + url: '/auth/{providerID}', + ...options, + ...params, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + ...params.headers + } + }); +}; + +/** + * Subscribe to events + * + * Get events + */ +export const eventSubscribe = (parameters?: { + directory?: string; +}, options?: Options) => { + const params = buildClientParams([parameters], [{}]); + return (options?.client ?? client).sse.get({ + url: '/event', + ...options, + ...params + }); +}; diff --git a/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/types.gen.ts b/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/types.gen.ts new file mode 100644 index 0000000000..e1be81f3ec --- /dev/null +++ b/packages/openapi-ts-tests/sdks/__snapshots__/opencode/flat-positional/types.gen.ts @@ -0,0 +1,4326 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type ClientOptions = { + baseUrl: `${string}://${string}` | (string & {}); +}; + +export type EventInstallationUpdated = { + type: 'installation.updated'; + properties: { + version: string; + }; +}; + +export type EventInstallationUpdateAvailable = { + type: 'installation.update-available'; + properties: { + version: string; + }; +}; + +export type Project = { + id: string; + worktree: string; + vcs?: 'git'; + name?: string; + icon?: { + url?: string; + color?: string; + }; + time: { + created: number; + updated: number; + initialized?: number; + }; +}; + +export type EventProjectUpdated = { + type: 'project.updated'; + properties: Project; +}; + +export type EventServerInstanceDisposed = { + type: 'server.instance.disposed'; + properties: { + directory: string; + }; +}; + +export type EventLspClientDiagnostics = { + type: 'lsp.client.diagnostics'; + properties: { + serverID: string; + path: string; + }; +}; + +export type EventLspUpdated = { + type: 'lsp.updated'; + properties: { + [key: string]: unknown; + }; +}; + +export type FileDiff = { + file: string; + before: string; + after: string; + additions: number; + deletions: number; +}; + +export type UserMessage = { + id: string; + sessionID: string; + role: 'user'; + time: { + created: number; + }; + summary?: { + title?: string; + body?: string; + diffs: Array; + }; + agent: string; + model: { + providerID: string; + modelID: string; + }; + system?: string; + tools?: { + [key: string]: boolean; + }; + variant?: string; +}; + +export type ProviderAuthError = { + name: 'ProviderAuthError'; + data: { + providerID: string; + message: string; + }; +}; + +export type UnknownError = { + name: 'UnknownError'; + data: { + message: string; + }; +}; + +export type MessageOutputLengthError = { + name: 'MessageOutputLengthError'; + data: { + [key: string]: unknown; + }; +}; + +export type MessageAbortedError = { + name: 'MessageAbortedError'; + data: { + message: string; + }; +}; + +export type ApiError = { + name: 'APIError'; + data: { + message: string; + statusCode?: number; + isRetryable: boolean; + responseHeaders?: { + [key: string]: string; + }; + responseBody?: string; + metadata?: { + [key: string]: string; + }; + }; +}; + +export type AssistantMessage = { + id: string; + sessionID: string; + role: 'assistant'; + time: { + created: number; + completed?: number; + }; + error?: ProviderAuthError | UnknownError | MessageOutputLengthError | MessageAbortedError | ApiError; + parentID: string; + modelID: string; + providerID: string; + mode: string; + agent: string; + path: { + cwd: string; + root: string; + }; + summary?: boolean; + cost: number; + tokens: { + input: number; + output: number; + reasoning: number; + cache: { + read: number; + write: number; + }; + }; + finish?: string; +}; + +export type Message = UserMessage | AssistantMessage; + +export type EventMessageUpdated = { + type: 'message.updated'; + properties: { + info: Message; + }; +}; + +export type EventMessageRemoved = { + type: 'message.removed'; + properties: { + sessionID: string; + messageID: string; + }; +}; + +export type TextPart = { + id: string; + sessionID: string; + messageID: string; + type: 'text'; + text: string; + synthetic?: boolean; + ignored?: boolean; + time?: { + start: number; + end?: number; + }; + metadata?: { + [key: string]: unknown; + }; +}; + +export type ReasoningPart = { + id: string; + sessionID: string; + messageID: string; + type: 'reasoning'; + text: string; + metadata?: { + [key: string]: unknown; + }; + time: { + start: number; + end?: number; + }; +}; + +export type FilePartSourceText = { + value: string; + start: number; + end: number; +}; + +export type FileSource = { + text: FilePartSourceText; + type: 'file'; + path: string; +}; + +export type Range = { + start: { + line: number; + character: number; + }; + end: { + line: number; + character: number; + }; +}; + +export type SymbolSource = { + text: FilePartSourceText; + type: 'symbol'; + path: string; + range: Range; + name: string; + kind: number; +}; + +export type FilePartSource = FileSource | SymbolSource; + +export type FilePart = { + id: string; + sessionID: string; + messageID: string; + type: 'file'; + mime: string; + filename?: string; + url: string; + source?: FilePartSource; +}; + +export type ToolStatePending = { + status: 'pending'; + input: { + [key: string]: unknown; + }; + raw: string; +}; + +export type ToolStateRunning = { + status: 'running'; + input: { + [key: string]: unknown; + }; + title?: string; + metadata?: { + [key: string]: unknown; + }; + time: { + start: number; + }; +}; + +export type ToolStateCompleted = { + status: 'completed'; + input: { + [key: string]: unknown; + }; + output: string; + title: string; + metadata: { + [key: string]: unknown; + }; + time: { + start: number; + end: number; + compacted?: number; + }; + attachments?: Array; +}; + +export type ToolStateError = { + status: 'error'; + input: { + [key: string]: unknown; + }; + error: string; + metadata?: { + [key: string]: unknown; + }; + time: { + start: number; + end: number; + }; +}; + +export type ToolState = ToolStatePending | ToolStateRunning | ToolStateCompleted | ToolStateError; + +export type ToolPart = { + id: string; + sessionID: string; + messageID: string; + type: 'tool'; + callID: string; + tool: string; + state: ToolState; + metadata?: { + [key: string]: unknown; + }; +}; + +export type StepStartPart = { + id: string; + sessionID: string; + messageID: string; + type: 'step-start'; + snapshot?: string; +}; + +export type StepFinishPart = { + id: string; + sessionID: string; + messageID: string; + type: 'step-finish'; + reason: string; + snapshot?: string; + cost: number; + tokens: { + input: number; + output: number; + reasoning: number; + cache: { + read: number; + write: number; + }; + }; +}; + +export type SnapshotPart = { + id: string; + sessionID: string; + messageID: string; + type: 'snapshot'; + snapshot: string; +}; + +export type PatchPart = { + id: string; + sessionID: string; + messageID: string; + type: 'patch'; + hash: string; + files: Array; +}; + +export type AgentPart = { + id: string; + sessionID: string; + messageID: string; + type: 'agent'; + name: string; + source?: { + value: string; + start: number; + end: number; + }; +}; + +export type RetryPart = { + id: string; + sessionID: string; + messageID: string; + type: 'retry'; + attempt: number; + error: ApiError; + time: { + created: number; + }; +}; + +export type CompactionPart = { + id: string; + sessionID: string; + messageID: string; + type: 'compaction'; + auto: boolean; +}; + +export type Part = TextPart | { + id: string; + sessionID: string; + messageID: string; + type: 'subtask'; + prompt: string; + description: string; + agent: string; + command?: string; +} | ReasoningPart | FilePart | ToolPart | StepStartPart | StepFinishPart | SnapshotPart | PatchPart | AgentPart | RetryPart | CompactionPart; + +export type EventMessagePartUpdated = { + type: 'message.part.updated'; + properties: { + part: Part; + delta?: string; + }; +}; + +export type EventMessagePartRemoved = { + type: 'message.part.removed'; + properties: { + sessionID: string; + messageID: string; + partID: string; + }; +}; + +export type PermissionRequest = { + id: string; + sessionID: string; + permission: string; + patterns: Array; + metadata: { + [key: string]: unknown; + }; + always: Array; + tool?: { + messageID: string; + callID: string; + }; +}; + +export type EventPermissionAsked = { + type: 'permission.asked'; + properties: PermissionRequest; +}; + +export type EventPermissionReplied = { + type: 'permission.replied'; + properties: { + sessionID: string; + requestID: string; + reply: 'once' | 'always' | 'reject'; + }; +}; + +export type SessionStatus = { + type: 'idle'; +} | { + type: 'retry'; + attempt: number; + message: string; + next: number; +} | { + type: 'busy'; +}; + +export type EventSessionStatus = { + type: 'session.status'; + properties: { + sessionID: string; + status: SessionStatus; + }; +}; + +export type EventSessionIdle = { + type: 'session.idle'; + properties: { + sessionID: string; + }; +}; + +export type EventSessionCompacted = { + type: 'session.compacted'; + properties: { + sessionID: string; + }; +}; + +export type EventFileEdited = { + type: 'file.edited'; + properties: { + file: string; + }; +}; + +export type Todo = { + /** + * Brief description of the task + */ + content: string; + /** + * Current status of the task: pending, in_progress, completed, cancelled + */ + status: string; + /** + * Priority level of the task: high, medium, low + */ + priority: string; + /** + * Unique identifier for the todo item + */ + id: string; +}; + +export type EventTodoUpdated = { + type: 'todo.updated'; + properties: { + sessionID: string; + todos: Array; + }; +}; + +export type EventTuiPromptAppend = { + type: 'tui.prompt.append'; + properties: { + text: string; + }; +}; + +export type EventTuiCommandExecute = { + type: 'tui.command.execute'; + properties: { + command: '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' | string; + }; +}; + +export type EventTuiToastShow = { + type: 'tui.toast.show'; + properties: { + title?: string; + message: string; + variant: 'info' | 'success' | 'warning' | 'error'; + /** + * Duration in milliseconds + */ + duration?: number; + }; +}; + +export type EventMcpToolsChanged = { + type: 'mcp.tools.changed'; + properties: { + server: string; + }; +}; + +export type EventCommandExecuted = { + type: 'command.executed'; + properties: { + name: string; + sessionID: string; + arguments: string; + messageID: string; + }; +}; + +export type PermissionAction = 'allow' | 'deny' | 'ask'; + +export type PermissionRule = { + permission: string; + pattern: string; + action: PermissionAction; +}; + +export type PermissionRuleset = Array; + +export type Session = { + id: string; + projectID: string; + directory: string; + parentID?: string; + summary?: { + additions: number; + deletions: number; + files: number; + diffs?: Array; + }; + share?: { + url: string; + }; + title: string; + version: string; + time: { + created: number; + updated: number; + compacting?: number; + archived?: number; + }; + permission?: PermissionRuleset; + revert?: { + messageID: string; + partID?: string; + snapshot?: string; + diff?: string; + }; +}; + +export type EventSessionCreated = { + type: 'session.created'; + properties: { + info: Session; + }; +}; + +export type EventSessionUpdated = { + type: 'session.updated'; + properties: { + info: Session; + }; +}; + +export type EventSessionDeleted = { + type: 'session.deleted'; + properties: { + info: Session; + }; +}; + +export type EventSessionDiff = { + type: 'session.diff'; + properties: { + sessionID: string; + diff: Array; + }; +}; + +export type EventSessionError = { + type: 'session.error'; + properties: { + sessionID?: string; + error?: ProviderAuthError | UnknownError | MessageOutputLengthError | MessageAbortedError | ApiError; + }; +}; + +export type EventFileWatcherUpdated = { + type: 'file.watcher.updated'; + properties: { + file: string; + event: 'add' | 'change' | 'unlink'; + }; +}; + +export type EventVcsBranchUpdated = { + type: 'vcs.branch.updated'; + properties: { + branch?: string; + }; +}; + +export type Pty = { + id: string; + title: string; + command: string; + args: Array; + cwd: string; + status: 'running' | 'exited'; + pid: number; +}; + +export type EventPtyCreated = { + type: 'pty.created'; + properties: { + info: Pty; + }; +}; + +export type EventPtyUpdated = { + type: 'pty.updated'; + properties: { + info: Pty; + }; +}; + +export type EventPtyExited = { + type: 'pty.exited'; + properties: { + id: string; + exitCode: number; + }; +}; + +export type EventPtyDeleted = { + type: 'pty.deleted'; + properties: { + id: string; + }; +}; + +export type EventServerConnected = { + type: 'server.connected'; + properties: { + [key: string]: unknown; + }; +}; + +export type EventGlobalDisposed = { + type: 'global.disposed'; + properties: { + [key: string]: unknown; + }; +}; + +export type Event = EventInstallationUpdated | EventInstallationUpdateAvailable | EventProjectUpdated | EventServerInstanceDisposed | EventLspClientDiagnostics | EventLspUpdated | EventMessageUpdated | EventMessageRemoved | EventMessagePartUpdated | EventMessagePartRemoved | EventPermissionAsked | EventPermissionReplied | EventSessionStatus | EventSessionIdle | EventSessionCompacted | EventFileEdited | EventTodoUpdated | EventTuiPromptAppend | EventTuiCommandExecute | EventTuiToastShow | EventMcpToolsChanged | EventCommandExecuted | EventSessionCreated | EventSessionUpdated | EventSessionDeleted | EventSessionDiff | EventSessionError | EventFileWatcherUpdated | EventVcsBranchUpdated | EventPtyCreated | EventPtyUpdated | EventPtyExited | EventPtyDeleted | EventServerConnected | EventGlobalDisposed; + +export type GlobalEvent = { + directory: string; + payload: Event; +}; + +export type BadRequestError = { + data: unknown; + errors: Array<{ + [key: string]: unknown; + }>; + success: false; +}; + +export type NotFoundError = { + name: 'NotFoundError'; + data: { + message: string; + }; +}; + +/** + * Custom keybind configurations + */ +export type KeybindsConfig = { + /** + * Leader key for keybind combinations + */ + leader?: string; + /** + * Exit the application + */ + app_exit?: string; + /** + * Open external editor + */ + editor_open?: string; + /** + * List available themes + */ + theme_list?: string; + /** + * Toggle sidebar + */ + sidebar_toggle?: string; + /** + * Toggle session scrollbar + */ + scrollbar_toggle?: string; + /** + * Toggle username visibility + */ + username_toggle?: string; + /** + * View status + */ + status_view?: string; + /** + * Export session to editor + */ + session_export?: string; + /** + * Create a new session + */ + session_new?: string; + /** + * List all sessions + */ + session_list?: string; + /** + * Show session timeline + */ + session_timeline?: string; + /** + * Fork session from message + */ + session_fork?: string; + /** + * Rename session + */ + session_rename?: string; + /** + * Share current session + */ + session_share?: string; + /** + * Unshare current session + */ + session_unshare?: string; + /** + * Interrupt current session + */ + session_interrupt?: string; + /** + * Compact the session + */ + session_compact?: string; + /** + * Scroll messages up by one page + */ + messages_page_up?: string; + /** + * Scroll messages down by one page + */ + messages_page_down?: string; + /** + * Scroll messages up by half page + */ + messages_half_page_up?: string; + /** + * Scroll messages down by half page + */ + messages_half_page_down?: string; + /** + * Navigate to first message + */ + messages_first?: string; + /** + * Navigate to last message + */ + messages_last?: string; + /** + * Navigate to next message + */ + messages_next?: string; + /** + * Navigate to previous message + */ + messages_previous?: string; + /** + * Navigate to last user message + */ + messages_last_user?: string; + /** + * Copy message + */ + messages_copy?: string; + /** + * Undo message + */ + messages_undo?: string; + /** + * Redo message + */ + messages_redo?: string; + /** + * Toggle code block concealment in messages + */ + messages_toggle_conceal?: string; + /** + * Toggle tool details visibility + */ + tool_details?: string; + /** + * List available models + */ + model_list?: string; + /** + * Next recently used model + */ + model_cycle_recent?: string; + /** + * Previous recently used model + */ + model_cycle_recent_reverse?: string; + /** + * Next favorite model + */ + model_cycle_favorite?: string; + /** + * Previous favorite model + */ + model_cycle_favorite_reverse?: string; + /** + * List available commands + */ + command_list?: string; + /** + * List agents + */ + agent_list?: string; + /** + * Next agent + */ + agent_cycle?: string; + /** + * Previous agent + */ + agent_cycle_reverse?: string; + /** + * Cycle model variants + */ + variant_cycle?: string; + /** + * Clear input field + */ + input_clear?: string; + /** + * Paste from clipboard + */ + input_paste?: string; + /** + * Submit input + */ + input_submit?: string; + /** + * Insert newline in input + */ + input_newline?: string; + /** + * Move cursor left in input + */ + input_move_left?: string; + /** + * Move cursor right in input + */ + input_move_right?: string; + /** + * Move cursor up in input + */ + input_move_up?: string; + /** + * Move cursor down in input + */ + input_move_down?: string; + /** + * Select left in input + */ + input_select_left?: string; + /** + * Select right in input + */ + input_select_right?: string; + /** + * Select up in input + */ + input_select_up?: string; + /** + * Select down in input + */ + input_select_down?: string; + /** + * Move to start of line in input + */ + input_line_home?: string; + /** + * Move to end of line in input + */ + input_line_end?: string; + /** + * Select to start of line in input + */ + input_select_line_home?: string; + /** + * Select to end of line in input + */ + input_select_line_end?: string; + /** + * Move to start of visual line in input + */ + input_visual_line_home?: string; + /** + * Move to end of visual line in input + */ + input_visual_line_end?: string; + /** + * Select to start of visual line in input + */ + input_select_visual_line_home?: string; + /** + * Select to end of visual line in input + */ + input_select_visual_line_end?: string; + /** + * Move to start of buffer in input + */ + input_buffer_home?: string; + /** + * Move to end of buffer in input + */ + input_buffer_end?: string; + /** + * Select to start of buffer in input + */ + input_select_buffer_home?: string; + /** + * Select to end of buffer in input + */ + input_select_buffer_end?: string; + /** + * Delete line in input + */ + input_delete_line?: string; + /** + * Delete to end of line in input + */ + input_delete_to_line_end?: string; + /** + * Delete to start of line in input + */ + input_delete_to_line_start?: string; + /** + * Backspace in input + */ + input_backspace?: string; + /** + * Delete character in input + */ + input_delete?: string; + /** + * Undo in input + */ + input_undo?: string; + /** + * Redo in input + */ + input_redo?: string; + /** + * Move word forward in input + */ + input_word_forward?: string; + /** + * Move word backward in input + */ + input_word_backward?: string; + /** + * Select word forward in input + */ + input_select_word_forward?: string; + /** + * Select word backward in input + */ + input_select_word_backward?: string; + /** + * Delete word forward in input + */ + input_delete_word_forward?: string; + /** + * Delete word backward in input + */ + input_delete_word_backward?: string; + /** + * Previous history item + */ + history_previous?: string; + /** + * Next history item + */ + history_next?: string; + /** + * Next child session + */ + session_child_cycle?: string; + /** + * Previous child session + */ + session_child_cycle_reverse?: string; + /** + * Go to parent session + */ + session_parent?: string; + /** + * Suspend terminal + */ + terminal_suspend?: string; + /** + * Toggle terminal title + */ + terminal_title_toggle?: string; + /** + * Toggle tips on home screen + */ + tips_toggle?: string; +}; + +/** + * Log level + */ +export type LogLevel = 'DEBUG' | 'INFO' | 'WARN' | 'ERROR'; + +/** + * Server configuration for opencode serve and web commands + */ +export type ServerConfig = { + /** + * Port to listen on + */ + port?: number; + /** + * Hostname to listen on + */ + hostname?: string; + /** + * Enable mDNS service discovery + */ + mdns?: boolean; + /** + * Additional domains to allow for CORS + */ + cors?: Array; +}; + +export type PermissionActionConfig = 'ask' | 'allow' | 'deny'; + +export type PermissionObjectConfig = { + [key: string]: PermissionActionConfig; +}; + +export type PermissionRuleConfig = PermissionActionConfig | PermissionObjectConfig; + +export type PermissionConfig = { + read?: PermissionRuleConfig; + edit?: PermissionRuleConfig; + glob?: PermissionRuleConfig; + grep?: PermissionRuleConfig; + list?: PermissionRuleConfig; + bash?: PermissionRuleConfig; + task?: PermissionRuleConfig; + external_directory?: PermissionRuleConfig; + todowrite?: PermissionActionConfig; + todoread?: PermissionActionConfig; + webfetch?: PermissionActionConfig; + websearch?: PermissionActionConfig; + codesearch?: PermissionActionConfig; + lsp?: PermissionRuleConfig; + doom_loop?: PermissionActionConfig; + [key: string]: PermissionRuleConfig | PermissionActionConfig | undefined; +} | PermissionActionConfig; + +export type AgentConfig = { + model?: string; + temperature?: number; + top_p?: number; + prompt?: string; + /** + * @deprecated Use 'permission' field instead + */ + tools?: { + [key: string]: boolean; + }; + disable?: boolean; + /** + * Description of when to use the agent + */ + description?: string; + mode?: 'subagent' | 'primary' | 'all'; + options?: { + [key: string]: unknown; + }; + /** + * Hex color code for the agent (e.g., #FF5733) + */ + color?: string; + /** + * Maximum number of agentic iterations before forcing text-only response + */ + steps?: number; + /** + * @deprecated Use 'steps' field instead. + */ + maxSteps?: number; + permission?: PermissionConfig; + [key: string]: unknown | string | number | { + [key: string]: boolean; + } | boolean | 'subagent' | 'primary' | 'all' | { + [key: string]: unknown; + } | string | number | PermissionConfig | undefined; +}; + +export type ProviderConfig = { + api?: string; + name?: string; + env?: Array; + id?: string; + npm?: string; + models?: { + [key: string]: { + id?: string; + name?: string; + family?: string; + release_date?: string; + attachment?: boolean; + reasoning?: boolean; + temperature?: boolean; + tool_call?: boolean; + interleaved?: true | { + field: 'reasoning_content' | 'reasoning_details'; + }; + cost?: { + input: number; + output: number; + cache_read?: number; + cache_write?: number; + context_over_200k?: { + input: number; + output: number; + cache_read?: number; + cache_write?: number; + }; + }; + limit?: { + context: number; + output: number; + }; + modalities?: { + input: Array<'text' | 'audio' | 'image' | 'video' | 'pdf'>; + output: Array<'text' | 'audio' | 'image' | 'video' | 'pdf'>; + }; + experimental?: boolean; + status?: 'alpha' | 'beta' | 'deprecated'; + options?: { + [key: string]: unknown; + }; + headers?: { + [key: string]: string; + }; + provider?: { + npm: string; + }; + /** + * Variant-specific configuration + */ + variants?: { + [key: string]: { + /** + * Disable this variant for the model + */ + disabled?: boolean; + [key: string]: unknown | boolean | undefined; + }; + }; + }; + }; + whitelist?: Array; + blacklist?: Array; + options?: { + apiKey?: string; + baseURL?: string; + /** + * GitHub Enterprise URL for copilot authentication + */ + enterpriseUrl?: string; + /** + * Enable promptCacheKey for this provider (default false) + */ + setCacheKey?: boolean; + /** + * Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout. + */ + timeout?: number | false; + [key: string]: unknown | string | boolean | number | false | undefined; + }; +}; + +export type McpLocalConfig = { + /** + * Type of MCP server connection + */ + type: 'local'; + /** + * Command and arguments to run the MCP server + */ + command: Array; + /** + * Environment variables to set when running the MCP server + */ + environment?: { + [key: string]: string; + }; + /** + * Enable or disable the MCP server on startup + */ + enabled?: boolean; + /** + * Timeout in ms for fetching tools from the MCP server. Defaults to 5000 (5 seconds) if not specified. + */ + timeout?: number; +}; + +export type McpOAuthConfig = { + /** + * OAuth client ID. If not provided, dynamic client registration (RFC 7591) will be attempted. + */ + clientId?: string; + /** + * OAuth client secret (if required by the authorization server) + */ + clientSecret?: string; + /** + * OAuth scopes to request during authorization + */ + scope?: string; +}; + +export type McpRemoteConfig = { + /** + * Type of MCP server connection + */ + type: 'remote'; + /** + * URL of the remote MCP server + */ + url: string; + /** + * Enable or disable the MCP server on startup + */ + enabled?: boolean; + /** + * Headers to send with the request + */ + headers?: { + [key: string]: string; + }; + /** + * OAuth authentication configuration for the MCP server. Set to false to disable OAuth auto-detection. + */ + oauth?: McpOAuthConfig | false; + /** + * Timeout in ms for fetching tools from the MCP server. Defaults to 5000 (5 seconds) if not specified. + */ + timeout?: number; +}; + +/** + * @deprecated Always uses stretch layout. + */ +export type LayoutConfig = 'auto' | 'stretch'; + +export type Config = { + /** + * JSON schema reference for configuration validation + */ + $schema?: string; + /** + * Theme name to use for the interface + */ + theme?: string; + keybinds?: KeybindsConfig; + logLevel?: LogLevel; + /** + * TUI specific settings + */ + tui?: { + /** + * TUI scroll speed + */ + scroll_speed?: number; + /** + * Scroll acceleration settings + */ + scroll_acceleration?: { + /** + * Enable scroll acceleration + */ + enabled: boolean; + }; + /** + * Control diff rendering style: 'auto' adapts to terminal width, 'stacked' always shows single column + */ + diff_style?: 'auto' | 'stacked'; + }; + server?: ServerConfig; + /** + * Command configuration, see https://opencode.ai/docs/commands + */ + command?: { + [key: string]: { + template: string; + description?: string; + agent?: string; + model?: string; + subtask?: boolean; + }; + }; + watcher?: { + ignore?: Array; + }; + plugin?: Array; + snapshot?: boolean; + /** + * Control sharing behavior:'manual' allows manual sharing via commands, 'auto' enables automatic sharing, 'disabled' disables all sharing + */ + share?: 'manual' | 'auto' | 'disabled'; + /** + * @deprecated Use 'share' field instead. Share newly created sessions automatically + */ + autoshare?: boolean; + /** + * Automatically update to the latest version. Set to true to auto-update, false to disable, or 'notify' to show update notifications + */ + autoupdate?: boolean | 'notify'; + /** + * Disable providers that are loaded automatically + */ + disabled_providers?: Array; + /** + * When set, ONLY these providers will be enabled. All other providers will be ignored + */ + enabled_providers?: Array; + /** + * Model to use in the format of provider/model, eg anthropic/claude-2 + */ + model?: string; + /** + * Small model to use for tasks like title generation in the format of provider/model + */ + small_model?: string; + /** + * 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. + */ + default_agent?: string; + /** + * Custom username to display in conversations instead of system username + */ + username?: string; + /** + * @deprecated Use `agent` field instead. + */ + mode?: { + build?: AgentConfig; + plan?: AgentConfig; + [key: string]: AgentConfig | undefined; + }; + /** + * Agent configuration, see https://opencode.ai/docs/agent + */ + agent?: { + plan?: AgentConfig; + build?: AgentConfig; + general?: AgentConfig; + explore?: AgentConfig; + title?: AgentConfig; + summary?: AgentConfig; + compaction?: AgentConfig; + [key: string]: AgentConfig | undefined; + }; + /** + * Custom provider configurations and model overrides + */ + provider?: { + [key: string]: ProviderConfig; + }; + /** + * MCP (Model Context Protocol) server configurations + */ + mcp?: { + [key: string]: McpLocalConfig | McpRemoteConfig; + }; + formatter?: false | { + [key: string]: { + disabled?: boolean; + command?: Array; + environment?: { + [key: string]: string; + }; + extensions?: Array; + }; + }; + lsp?: false | { + [key: string]: { + disabled: true; + } | { + command: Array; + extensions?: Array; + disabled?: boolean; + env?: { + [key: string]: string; + }; + initialization?: { + [key: string]: unknown; + }; + }; + }; + /** + * Additional instruction files or patterns to include + */ + instructions?: Array; + layout?: LayoutConfig; + permission?: PermissionConfig; + tools?: { + [key: string]: boolean; + }; + enterprise?: { + /** + * Enterprise URL + */ + url?: string; + }; + compaction?: { + /** + * Enable automatic compaction when context is full (default: true) + */ + auto?: boolean; + /** + * Enable pruning of old tool outputs (default: true) + */ + prune?: boolean; + }; + experimental?: { + hook?: { + file_edited?: { + [key: string]: Array<{ + command: Array; + environment?: { + [key: string]: string; + }; + }>; + }; + session_completed?: Array<{ + command: Array; + environment?: { + [key: string]: string; + }; + }>; + }; + /** + * Number of retries for chat completions on failure + */ + chatMaxRetries?: number; + disable_paste_summary?: boolean; + /** + * Enable the batch tool + */ + batch_tool?: boolean; + /** + * Enable OpenTelemetry spans for AI SDK calls (using the 'experimental_telemetry' flag) + */ + openTelemetry?: boolean; + /** + * Tools that should only be available to primary agents. + */ + primary_tools?: Array; + /** + * Continue the agent loop when a tool call is denied + */ + continue_loop_on_deny?: boolean; + /** + * Timeout in milliseconds for model context protocol (MCP) requests + */ + mcp_timeout?: number; + }; +}; + +export type ToolIds = Array; + +export type ToolListItem = { + id: string; + description: string; + parameters: unknown; +}; + +export type ToolList = Array; + +export type Path = { + home: string; + state: string; + config: string; + worktree: string; + directory: string; +}; + +export type VcsInfo = { + branch: string; +}; + +export type TextPartInput = { + id?: string; + type: 'text'; + text: string; + synthetic?: boolean; + ignored?: boolean; + time?: { + start: number; + end?: number; + }; + metadata?: { + [key: string]: unknown; + }; +}; + +export type FilePartInput = { + id?: string; + type: 'file'; + mime: string; + filename?: string; + url: string; + source?: FilePartSource; +}; + +export type AgentPartInput = { + id?: string; + type: 'agent'; + name: string; + source?: { + value: string; + start: number; + end: number; + }; +}; + +export type SubtaskPartInput = { + id?: string; + type: 'subtask'; + prompt: string; + description: string; + agent: string; + command?: string; +}; + +export type Command = { + name: string; + description?: string; + agent?: string; + model?: string; + mcp?: boolean; + template: string; + subtask?: boolean; + hints: Array; +}; + +export type Model = { + id: string; + providerID: string; + api: { + id: string; + url: string; + npm: string; + }; + name: string; + family?: string; + capabilities: { + temperature: boolean; + reasoning: boolean; + attachment: boolean; + toolcall: boolean; + input: { + text: boolean; + audio: boolean; + image: boolean; + video: boolean; + pdf: boolean; + }; + output: { + text: boolean; + audio: boolean; + image: boolean; + video: boolean; + pdf: boolean; + }; + interleaved: boolean | { + field: 'reasoning_content' | 'reasoning_details'; + }; + }; + cost: { + input: number; + output: number; + cache: { + read: number; + write: number; + }; + experimentalOver200K?: { + input: number; + output: number; + cache: { + read: number; + write: number; + }; + }; + }; + limit: { + context: number; + output: number; + }; + status: 'alpha' | 'beta' | 'deprecated' | 'active'; + options: { + [key: string]: unknown; + }; + headers: { + [key: string]: string; + }; + release_date: string; + variants?: { + [key: string]: { + [key: string]: unknown; + }; + }; +}; + +export type Provider = { + id: string; + name: string; + source: 'env' | 'config' | 'custom' | 'api'; + env: Array; + key?: string; + options: { + [key: string]: unknown; + }; + models: { + [key: string]: Model; + }; +}; + +export type ProviderAuthMethod = { + type: 'oauth' | 'api'; + label: string; +}; + +export type ProviderAuthAuthorization = { + url: string; + method: 'auto' | 'code'; + instructions: string; +}; + +export type Symbol = { + name: string; + kind: number; + location: { + uri: string; + range: Range; + }; +}; + +export type FileNode = { + name: string; + path: string; + absolute: string; + type: 'file' | 'directory'; + ignored: boolean; +}; + +export type FileContent = { + type: 'text'; + content: string; + diff?: string; + patch?: { + oldFileName: string; + newFileName: string; + oldHeader?: string; + newHeader?: string; + hunks: Array<{ + oldStart: number; + oldLines: number; + newStart: number; + newLines: number; + lines: Array; + }>; + index?: string; + }; + encoding?: 'base64'; + mimeType?: string; +}; + +export type File = { + path: string; + added: number; + removed: number; + status: 'added' | 'deleted' | 'modified'; +}; + +export type Agent = { + name: string; + description?: string; + mode: 'subagent' | 'primary' | 'all'; + native?: boolean; + hidden?: boolean; + topP?: number; + temperature?: number; + color?: string; + permission: PermissionRuleset; + model?: { + modelID: string; + providerID: string; + }; + prompt?: string; + options: { + [key: string]: unknown; + }; + steps?: number; +}; + +export type McpStatusConnected = { + status: 'connected'; +}; + +export type McpStatusDisabled = { + status: 'disabled'; +}; + +export type McpStatusFailed = { + status: 'failed'; + error: string; +}; + +export type McpStatusNeedsAuth = { + status: 'needs_auth'; +}; + +export type McpStatusNeedsClientRegistration = { + status: 'needs_client_registration'; + error: string; +}; + +export type McpStatus = McpStatusConnected | McpStatusDisabled | McpStatusFailed | McpStatusNeedsAuth | McpStatusNeedsClientRegistration; + +export type LspStatus = { + id: string; + name: string; + root: string; + status: 'connected' | 'error'; +}; + +export type FormatterStatus = { + name: string; + extensions: Array; + enabled: boolean; +}; + +export type OAuth = { + type: 'oauth'; + refresh: string; + access: string; + expires: number; + enterpriseUrl?: string; +}; + +export type ApiAuth = { + type: 'api'; + key: string; +}; + +export type WellKnownAuth = { + type: 'wellknown'; + key: string; + token: string; +}; + +export type Auth = OAuth | ApiAuth | WellKnownAuth; + +export type GlobalHealthData = { + body?: never; + path?: never; + query?: never; + url: '/global/health'; +}; + +export type GlobalHealthResponses = { + /** + * Health information + */ + 200: { + healthy: true; + version: string; + }; +}; + +export type GlobalHealthResponse = GlobalHealthResponses[keyof GlobalHealthResponses]; + +export type GlobalEventData = { + body?: never; + path?: never; + query?: never; + url: '/global/event'; +}; + +export type GlobalEventResponses = { + /** + * Event stream + */ + 200: GlobalEvent; +}; + +export type GlobalEventResponse = GlobalEventResponses[keyof GlobalEventResponses]; + +export type GlobalDisposeData = { + body?: never; + path?: never; + query?: never; + url: '/global/dispose'; +}; + +export type GlobalDisposeResponses = { + /** + * Global disposed + */ + 200: boolean; +}; + +export type GlobalDisposeResponse = GlobalDisposeResponses[keyof GlobalDisposeResponses]; + +export type ProjectListData = { + body?: never; + path?: never; + query?: { + directory?: string; + }; + url: '/project'; +}; + +export type ProjectListResponses = { + /** + * List of projects + */ + 200: Array; +}; + +export type ProjectListResponse = ProjectListResponses[keyof ProjectListResponses]; + +export type ProjectCurrentData = { + body?: never; + path?: never; + query?: { + directory?: string; + }; + url: '/project/current'; +}; + +export type ProjectCurrentResponses = { + /** + * Current project information + */ + 200: Project; +}; + +export type ProjectCurrentResponse = ProjectCurrentResponses[keyof ProjectCurrentResponses]; + +export type ProjectUpdateData = { + body?: { + name?: string; + icon?: { + url?: string; + color?: string; + }; + }; + path: { + projectID: string; + }; + query?: { + directory?: string; + }; + url: '/project/{projectID}'; +}; + +export type ProjectUpdateErrors = { + /** + * Bad request + */ + 400: BadRequestError; + /** + * Not found + */ + 404: NotFoundError; +}; + +export type ProjectUpdateError = ProjectUpdateErrors[keyof ProjectUpdateErrors]; + +export type ProjectUpdateResponses = { + /** + * Updated project information + */ + 200: Project; +}; + +export type ProjectUpdateResponse = ProjectUpdateResponses[keyof ProjectUpdateResponses]; + +export type PtyListData = { + body?: never; + path?: never; + query?: { + directory?: string; + }; + url: '/pty'; +}; + +export type PtyListResponses = { + /** + * List of sessions + */ + 200: Array; +}; + +export type PtyListResponse = PtyListResponses[keyof PtyListResponses]; + +export type PtyCreateData = { + body?: { + command?: string; + args?: Array; + cwd?: string; + title?: string; + env?: { + [key: string]: string; + }; + }; + path?: never; + query?: { + directory?: string; + }; + url: '/pty'; +}; + +export type PtyCreateErrors = { + /** + * Bad request + */ + 400: BadRequestError; +}; + +export type PtyCreateError = PtyCreateErrors[keyof PtyCreateErrors]; + +export type PtyCreateResponses = { + /** + * Created session + */ + 200: Pty; +}; + +export type PtyCreateResponse = PtyCreateResponses[keyof PtyCreateResponses]; + +export type PtyRemoveData = { + body?: never; + path: { + ptyID: string; + }; + query?: { + directory?: string; + }; + url: '/pty/{ptyID}'; +}; + +export type PtyRemoveErrors = { + /** + * Not found + */ + 404: NotFoundError; +}; + +export type PtyRemoveError = PtyRemoveErrors[keyof PtyRemoveErrors]; + +export type PtyRemoveResponses = { + /** + * Session removed + */ + 200: boolean; +}; + +export type PtyRemoveResponse = PtyRemoveResponses[keyof PtyRemoveResponses]; + +export type PtyGetData = { + body?: never; + path: { + ptyID: string; + }; + query?: { + directory?: string; + }; + url: '/pty/{ptyID}'; +}; + +export type PtyGetErrors = { + /** + * Not found + */ + 404: NotFoundError; +}; + +export type PtyGetError = PtyGetErrors[keyof PtyGetErrors]; + +export type PtyGetResponses = { + /** + * Session info + */ + 200: Pty; +}; + +export type PtyGetResponse = PtyGetResponses[keyof PtyGetResponses]; + +export type PtyUpdateData = { + body?: { + title?: string; + size?: { + rows: number; + cols: number; + }; + }; + path: { + ptyID: string; + }; + query?: { + directory?: string; + }; + url: '/pty/{ptyID}'; +}; + +export type PtyUpdateErrors = { + /** + * Bad request + */ + 400: BadRequestError; +}; + +export type PtyUpdateError = PtyUpdateErrors[keyof PtyUpdateErrors]; + +export type PtyUpdateResponses = { + /** + * Updated session + */ + 200: Pty; +}; + +export type PtyUpdateResponse = PtyUpdateResponses[keyof PtyUpdateResponses]; + +export type PtyConnectData = { + body?: never; + path: { + ptyID: string; + }; + query?: { + directory?: string; + }; + url: '/pty/{ptyID}/connect'; +}; + +export type PtyConnectErrors = { + /** + * Not found + */ + 404: NotFoundError; +}; + +export type PtyConnectError = PtyConnectErrors[keyof PtyConnectErrors]; + +export type PtyConnectResponses = { + /** + * Connected session + */ + 200: boolean; +}; + +export type PtyConnectResponse = PtyConnectResponses[keyof PtyConnectResponses]; + +export type ConfigGetData = { + body?: never; + path?: never; + query?: { + directory?: string; + }; + url: '/config'; +}; + +export type ConfigGetResponses = { + /** + * Get config info + */ + 200: Config; +}; + +export type ConfigGetResponse = ConfigGetResponses[keyof ConfigGetResponses]; + +export type ConfigUpdateData = { + body?: Config; + path?: never; + query?: { + directory?: string; + }; + url: '/config'; +}; + +export type ConfigUpdateErrors = { + /** + * Bad request + */ + 400: BadRequestError; +}; + +export type ConfigUpdateError = ConfigUpdateErrors[keyof ConfigUpdateErrors]; + +export type ConfigUpdateResponses = { + /** + * Successfully updated config + */ + 200: Config; +}; + +export type ConfigUpdateResponse = ConfigUpdateResponses[keyof ConfigUpdateResponses]; + +export type ToolIdsData = { + body?: never; + path?: never; + query?: { + directory?: string; + }; + url: '/experimental/tool/ids'; +}; + +export type ToolIdsErrors = { + /** + * Bad request + */ + 400: BadRequestError; +}; + +export type ToolIdsError = ToolIdsErrors[keyof ToolIdsErrors]; + +export type ToolIdsResponses = { + /** + * Tool IDs + */ + 200: ToolIds; +}; + +export type ToolIdsResponse = ToolIdsResponses[keyof ToolIdsResponses]; + +export type ToolListData = { + body?: never; + path?: never; + query: { + directory?: string; + provider: string; + model: string; + }; + url: '/experimental/tool'; +}; + +export type ToolListErrors = { + /** + * Bad request + */ + 400: BadRequestError; +}; + +export type ToolListError = ToolListErrors[keyof ToolListErrors]; + +export type ToolListResponses = { + /** + * Tools + */ + 200: ToolList; +}; + +export type ToolListResponse = ToolListResponses[keyof ToolListResponses]; + +export type InstanceDisposeData = { + body?: never; + path?: never; + query?: { + directory?: string; + }; + url: '/instance/dispose'; +}; + +export type InstanceDisposeResponses = { + /** + * Instance disposed + */ + 200: boolean; +}; + +export type InstanceDisposeResponse = InstanceDisposeResponses[keyof InstanceDisposeResponses]; + +export type PathGetData = { + body?: never; + path?: never; + query?: { + directory?: string; + }; + url: '/path'; +}; + +export type PathGetResponses = { + /** + * Path + */ + 200: Path; +}; + +export type PathGetResponse = PathGetResponses[keyof PathGetResponses]; + +export type VcsGetData = { + body?: never; + path?: never; + query?: { + directory?: string; + }; + url: '/vcs'; +}; + +export type VcsGetResponses = { + /** + * VCS info + */ + 200: VcsInfo; +}; + +export type VcsGetResponse = VcsGetResponses[keyof VcsGetResponses]; + +export type SessionListData = { + body?: never; + path?: never; + query?: { + directory?: string; + }; + url: '/session'; +}; + +export type SessionListResponses = { + /** + * List of sessions + */ + 200: Array; +}; + +export type SessionListResponse = SessionListResponses[keyof SessionListResponses]; + +export type SessionCreateData = { + body?: { + parentID?: string; + title?: string; + permission?: PermissionRuleset; + }; + path?: never; + query?: { + directory?: string; + }; + url: '/session'; +}; + +export type SessionCreateErrors = { + /** + * Bad request + */ + 400: BadRequestError; +}; + +export type SessionCreateError = SessionCreateErrors[keyof SessionCreateErrors]; + +export type SessionCreateResponses = { + /** + * Successfully created session + */ + 200: Session; +}; + +export type SessionCreateResponse = SessionCreateResponses[keyof SessionCreateResponses]; + +export type SessionStatusData = { + body?: never; + path?: never; + query?: { + directory?: string; + }; + url: '/session/status'; +}; + +export type SessionStatusErrors = { + /** + * Bad request + */ + 400: BadRequestError; +}; + +export type SessionStatusError = SessionStatusErrors[keyof SessionStatusErrors]; + +export type SessionStatusResponses = { + /** + * Get session status + */ + 200: { + [key: string]: SessionStatus; + }; +}; + +export type SessionStatusResponse = SessionStatusResponses[keyof SessionStatusResponses]; + +export type SessionDeleteData = { + body?: never; + path: { + sessionID: string; + }; + query?: { + directory?: string; + }; + url: '/session/{sessionID}'; +}; + +export type SessionDeleteErrors = { + /** + * Bad request + */ + 400: BadRequestError; + /** + * Not found + */ + 404: NotFoundError; +}; + +export type SessionDeleteError = SessionDeleteErrors[keyof SessionDeleteErrors]; + +export type SessionDeleteResponses = { + /** + * Successfully deleted session + */ + 200: boolean; +}; + +export type SessionDeleteResponse = SessionDeleteResponses[keyof SessionDeleteResponses]; + +export type SessionGetData = { + body?: never; + path: { + sessionID: string; + }; + query?: { + directory?: string; + }; + url: '/session/{sessionID}'; +}; + +export type SessionGetErrors = { + /** + * Bad request + */ + 400: BadRequestError; + /** + * Not found + */ + 404: NotFoundError; +}; + +export type SessionGetError = SessionGetErrors[keyof SessionGetErrors]; + +export type SessionGetResponses = { + /** + * Get session + */ + 200: Session; +}; + +export type SessionGetResponse = SessionGetResponses[keyof SessionGetResponses]; + +export type SessionUpdateData = { + body?: { + title?: string; + time?: { + archived?: number; + }; + }; + path: { + sessionID: string; + }; + query?: { + directory?: string; + }; + url: '/session/{sessionID}'; +}; + +export type SessionUpdateErrors = { + /** + * Bad request + */ + 400: BadRequestError; + /** + * Not found + */ + 404: NotFoundError; +}; + +export type SessionUpdateError = SessionUpdateErrors[keyof SessionUpdateErrors]; + +export type SessionUpdateResponses = { + /** + * Successfully updated session + */ + 200: Session; +}; + +export type SessionUpdateResponse = SessionUpdateResponses[keyof SessionUpdateResponses]; + +export type SessionChildrenData = { + body?: never; + path: { + sessionID: string; + }; + query?: { + directory?: string; + }; + url: '/session/{sessionID}/children'; +}; + +export type SessionChildrenErrors = { + /** + * Bad request + */ + 400: BadRequestError; + /** + * Not found + */ + 404: NotFoundError; +}; + +export type SessionChildrenError = SessionChildrenErrors[keyof SessionChildrenErrors]; + +export type SessionChildrenResponses = { + /** + * List of children + */ + 200: Array; +}; + +export type SessionChildrenResponse = SessionChildrenResponses[keyof SessionChildrenResponses]; + +export type SessionTodoData = { + body?: never; + path: { + /** + * Session ID + */ + sessionID: string; + }; + query?: { + directory?: string; + }; + url: '/session/{sessionID}/todo'; +}; + +export type SessionTodoErrors = { + /** + * Bad request + */ + 400: BadRequestError; + /** + * Not found + */ + 404: NotFoundError; +}; + +export type SessionTodoError = SessionTodoErrors[keyof SessionTodoErrors]; + +export type SessionTodoResponses = { + /** + * Todo list + */ + 200: Array; +}; + +export type SessionTodoResponse = SessionTodoResponses[keyof SessionTodoResponses]; + +export type SessionInitData = { + body?: { + modelID: string; + providerID: string; + messageID: string; + }; + path: { + /** + * Session ID + */ + sessionID: string; + }; + query?: { + directory?: string; + }; + url: '/session/{sessionID}/init'; +}; + +export type SessionInitErrors = { + /** + * Bad request + */ + 400: BadRequestError; + /** + * Not found + */ + 404: NotFoundError; +}; + +export type SessionInitError = SessionInitErrors[keyof SessionInitErrors]; + +export type SessionInitResponses = { + /** + * 200 + */ + 200: boolean; +}; + +export type SessionInitResponse = SessionInitResponses[keyof SessionInitResponses]; + +export type SessionForkData = { + body?: { + messageID?: string; + }; + path: { + sessionID: string; + }; + query?: { + directory?: string; + }; + url: '/session/{sessionID}/fork'; +}; + +export type SessionForkResponses = { + /** + * 200 + */ + 200: Session; +}; + +export type SessionForkResponse = SessionForkResponses[keyof SessionForkResponses]; + +export type SessionAbortData = { + body?: never; + path: { + sessionID: string; + }; + query?: { + directory?: string; + }; + url: '/session/{sessionID}/abort'; +}; + +export type SessionAbortErrors = { + /** + * Bad request + */ + 400: BadRequestError; + /** + * Not found + */ + 404: NotFoundError; +}; + +export type SessionAbortError = SessionAbortErrors[keyof SessionAbortErrors]; + +export type SessionAbortResponses = { + /** + * Aborted session + */ + 200: boolean; +}; + +export type SessionAbortResponse = SessionAbortResponses[keyof SessionAbortResponses]; + +export type SessionUnshareData = { + body?: never; + path: { + sessionID: string; + }; + query?: { + directory?: string; + }; + url: '/session/{sessionID}/share'; +}; + +export type SessionUnshareErrors = { + /** + * Bad request + */ + 400: BadRequestError; + /** + * Not found + */ + 404: NotFoundError; +}; + +export type SessionUnshareError = SessionUnshareErrors[keyof SessionUnshareErrors]; + +export type SessionUnshareResponses = { + /** + * Successfully unshared session + */ + 200: Session; +}; + +export type SessionUnshareResponse = SessionUnshareResponses[keyof SessionUnshareResponses]; + +export type SessionShareData = { + body?: never; + path: { + sessionID: string; + }; + query?: { + directory?: string; + }; + url: '/session/{sessionID}/share'; +}; + +export type SessionShareErrors = { + /** + * Bad request + */ + 400: BadRequestError; + /** + * Not found + */ + 404: NotFoundError; +}; + +export type SessionShareError = SessionShareErrors[keyof SessionShareErrors]; + +export type SessionShareResponses = { + /** + * Successfully shared session + */ + 200: Session; +}; + +export type SessionShareResponse = SessionShareResponses[keyof SessionShareResponses]; + +export type SessionDiffData = { + body?: never; + path: { + /** + * Session ID + */ + sessionID: string; + }; + query?: { + directory?: string; + messageID?: string; + }; + url: '/session/{sessionID}/diff'; +}; + +export type SessionDiffErrors = { + /** + * Bad request + */ + 400: BadRequestError; + /** + * Not found + */ + 404: NotFoundError; +}; + +export type SessionDiffError = SessionDiffErrors[keyof SessionDiffErrors]; + +export type SessionDiffResponses = { + /** + * List of diffs + */ + 200: Array; +}; + +export type SessionDiffResponse = SessionDiffResponses[keyof SessionDiffResponses]; + +export type SessionSummarizeData = { + body?: { + providerID: string; + modelID: string; + auto?: boolean; + }; + path: { + /** + * Session ID + */ + sessionID: string; + }; + query?: { + directory?: string; + }; + url: '/session/{sessionID}/summarize'; +}; + +export type SessionSummarizeErrors = { + /** + * Bad request + */ + 400: BadRequestError; + /** + * Not found + */ + 404: NotFoundError; +}; + +export type SessionSummarizeError = SessionSummarizeErrors[keyof SessionSummarizeErrors]; + +export type SessionSummarizeResponses = { + /** + * Summarized session + */ + 200: boolean; +}; + +export type SessionSummarizeResponse = SessionSummarizeResponses[keyof SessionSummarizeResponses]; + +export type SessionMessagesData = { + body?: never; + path: { + /** + * Session ID + */ + sessionID: string; + }; + query?: { + directory?: string; + limit?: number; + }; + url: '/session/{sessionID}/message'; +}; + +export type SessionMessagesErrors = { + /** + * Bad request + */ + 400: BadRequestError; + /** + * Not found + */ + 404: NotFoundError; +}; + +export type SessionMessagesError = SessionMessagesErrors[keyof SessionMessagesErrors]; + +export type SessionMessagesResponses = { + /** + * List of messages + */ + 200: Array<{ + info: Message; + parts: Array; + }>; +}; + +export type SessionMessagesResponse = SessionMessagesResponses[keyof SessionMessagesResponses]; + +export type SessionPromptData = { + body?: { + messageID?: string; + model?: { + providerID: string; + modelID: string; + }; + agent?: string; + noReply?: boolean; + /** + * @deprecated tools and permissions have been merged, you can set permissions on the session itself now + */ + tools?: { + [key: string]: boolean; + }; + system?: string; + variant?: string; + parts: Array; + }; + path: { + /** + * Session ID + */ + sessionID: string; + }; + query?: { + directory?: string; + }; + url: '/session/{sessionID}/message'; +}; + +export type SessionPromptErrors = { + /** + * Bad request + */ + 400: BadRequestError; + /** + * Not found + */ + 404: NotFoundError; +}; + +export type SessionPromptError = SessionPromptErrors[keyof SessionPromptErrors]; + +export type SessionPromptResponses = { + /** + * Created message + */ + 200: { + info: AssistantMessage; + parts: Array; + }; +}; + +export type SessionPromptResponse = SessionPromptResponses[keyof SessionPromptResponses]; + +export type SessionMessageData = { + body?: never; + path: { + /** + * Session ID + */ + sessionID: string; + /** + * Message ID + */ + messageID: string; + }; + query?: { + directory?: string; + }; + url: '/session/{sessionID}/message/{messageID}'; +}; + +export type SessionMessageErrors = { + /** + * Bad request + */ + 400: BadRequestError; + /** + * Not found + */ + 404: NotFoundError; +}; + +export type SessionMessageError = SessionMessageErrors[keyof SessionMessageErrors]; + +export type SessionMessageResponses = { + /** + * Message + */ + 200: { + info: Message; + parts: Array; + }; +}; + +export type SessionMessageResponse = SessionMessageResponses[keyof SessionMessageResponses]; + +export type PartDeleteData = { + body?: never; + path: { + /** + * Session ID + */ + sessionID: string; + /** + * Message ID + */ + messageID: string; + /** + * Part ID + */ + partID: string; + }; + query?: { + directory?: string; + }; + url: '/session/{sessionID}/message/{messageID}/part/{partID}'; +}; + +export type PartDeleteErrors = { + /** + * Bad request + */ + 400: BadRequestError; + /** + * Not found + */ + 404: NotFoundError; +}; + +export type PartDeleteError = PartDeleteErrors[keyof PartDeleteErrors]; + +export type PartDeleteResponses = { + /** + * Successfully deleted part + */ + 200: boolean; +}; + +export type PartDeleteResponse = PartDeleteResponses[keyof PartDeleteResponses]; + +export type PartUpdateData = { + body?: Part; + path: { + /** + * Session ID + */ + sessionID: string; + /** + * Message ID + */ + messageID: string; + /** + * Part ID + */ + partID: string; + }; + query?: { + directory?: string; + }; + url: '/session/{sessionID}/message/{messageID}/part/{partID}'; +}; + +export type PartUpdateErrors = { + /** + * Bad request + */ + 400: BadRequestError; + /** + * Not found + */ + 404: NotFoundError; +}; + +export type PartUpdateError = PartUpdateErrors[keyof PartUpdateErrors]; + +export type PartUpdateResponses = { + /** + * Successfully updated part + */ + 200: Part; +}; + +export type PartUpdateResponse = PartUpdateResponses[keyof PartUpdateResponses]; + +export type SessionPromptAsyncData = { + body?: { + messageID?: string; + model?: { + providerID: string; + modelID: string; + }; + agent?: string; + noReply?: boolean; + /** + * @deprecated tools and permissions have been merged, you can set permissions on the session itself now + */ + tools?: { + [key: string]: boolean; + }; + system?: string; + variant?: string; + parts: Array; + }; + path: { + /** + * Session ID + */ + sessionID: string; + }; + query?: { + directory?: string; + }; + url: '/session/{sessionID}/prompt_async'; +}; + +export type SessionPromptAsyncErrors = { + /** + * Bad request + */ + 400: BadRequestError; + /** + * Not found + */ + 404: NotFoundError; +}; + +export type SessionPromptAsyncError = SessionPromptAsyncErrors[keyof SessionPromptAsyncErrors]; + +export type SessionPromptAsyncResponses = { + /** + * Prompt accepted + */ + 204: void; +}; + +export type SessionPromptAsyncResponse = SessionPromptAsyncResponses[keyof SessionPromptAsyncResponses]; + +export type SessionCommandData = { + body?: { + messageID?: string; + agent?: string; + model?: string; + arguments: string; + command: string; + variant?: string; + }; + path: { + /** + * Session ID + */ + sessionID: string; + }; + query?: { + directory?: string; + }; + url: '/session/{sessionID}/command'; +}; + +export type SessionCommandErrors = { + /** + * Bad request + */ + 400: BadRequestError; + /** + * Not found + */ + 404: NotFoundError; +}; + +export type SessionCommandError = SessionCommandErrors[keyof SessionCommandErrors]; + +export type SessionCommandResponses = { + /** + * Created message + */ + 200: { + info: AssistantMessage; + parts: Array; + }; +}; + +export type SessionCommandResponse = SessionCommandResponses[keyof SessionCommandResponses]; + +export type SessionShellData = { + body?: { + agent: string; + model?: { + providerID: string; + modelID: string; + }; + command: string; + }; + path: { + /** + * Session ID + */ + sessionID: string; + }; + query?: { + directory?: string; + }; + url: '/session/{sessionID}/shell'; +}; + +export type SessionShellErrors = { + /** + * Bad request + */ + 400: BadRequestError; + /** + * Not found + */ + 404: NotFoundError; +}; + +export type SessionShellError = SessionShellErrors[keyof SessionShellErrors]; + +export type SessionShellResponses = { + /** + * Created message + */ + 200: AssistantMessage; +}; + +export type SessionShellResponse = SessionShellResponses[keyof SessionShellResponses]; + +export type SessionRevertData = { + body?: { + messageID: string; + partID?: string; + }; + path: { + sessionID: string; + }; + query?: { + directory?: string; + }; + url: '/session/{sessionID}/revert'; +}; + +export type SessionRevertErrors = { + /** + * Bad request + */ + 400: BadRequestError; + /** + * Not found + */ + 404: NotFoundError; +}; + +export type SessionRevertError = SessionRevertErrors[keyof SessionRevertErrors]; + +export type SessionRevertResponses = { + /** + * Updated session + */ + 200: Session; +}; + +export type SessionRevertResponse = SessionRevertResponses[keyof SessionRevertResponses]; + +export type SessionUnrevertData = { + body?: never; + path: { + sessionID: string; + }; + query?: { + directory?: string; + }; + url: '/session/{sessionID}/unrevert'; +}; + +export type SessionUnrevertErrors = { + /** + * Bad request + */ + 400: BadRequestError; + /** + * Not found + */ + 404: NotFoundError; +}; + +export type SessionUnrevertError = SessionUnrevertErrors[keyof SessionUnrevertErrors]; + +export type SessionUnrevertResponses = { + /** + * Updated session + */ + 200: Session; +}; + +export type SessionUnrevertResponse = SessionUnrevertResponses[keyof SessionUnrevertResponses]; + +export type PermissionRespondData = { + body?: { + response: 'once' | 'always' | 'reject'; + }; + path: { + sessionID: string; + permissionID: string; + }; + query?: { + directory?: string; + }; + url: '/session/{sessionID}/permissions/{permissionID}'; +}; + +export type PermissionRespondErrors = { + /** + * Bad request + */ + 400: BadRequestError; + /** + * Not found + */ + 404: NotFoundError; +}; + +export type PermissionRespondError = PermissionRespondErrors[keyof PermissionRespondErrors]; + +export type PermissionRespondResponses = { + /** + * Permission processed successfully + */ + 200: boolean; +}; + +export type PermissionRespondResponse = PermissionRespondResponses[keyof PermissionRespondResponses]; + +export type PermissionReplyData = { + body?: { + reply: 'once' | 'always' | 'reject'; + }; + path: { + requestID: string; + }; + query?: { + directory?: string; + }; + url: '/permission/{requestID}/reply'; +}; + +export type PermissionReplyErrors = { + /** + * Bad request + */ + 400: BadRequestError; + /** + * Not found + */ + 404: NotFoundError; +}; + +export type PermissionReplyError = PermissionReplyErrors[keyof PermissionReplyErrors]; + +export type PermissionReplyResponses = { + /** + * Permission processed successfully + */ + 200: boolean; +}; + +export type PermissionReplyResponse = PermissionReplyResponses[keyof PermissionReplyResponses]; + +export type PermissionListData = { + body?: never; + path?: never; + query?: { + directory?: string; + }; + url: '/permission'; +}; + +export type PermissionListResponses = { + /** + * List of pending permissions + */ + 200: Array; +}; + +export type PermissionListResponse = PermissionListResponses[keyof PermissionListResponses]; + +export type CommandListData = { + body?: never; + path?: never; + query?: { + directory?: string; + }; + url: '/command'; +}; + +export type CommandListResponses = { + /** + * List of commands + */ + 200: Array; +}; + +export type CommandListResponse = CommandListResponses[keyof CommandListResponses]; + +export type ConfigProvidersData = { + body?: never; + path?: never; + query?: { + directory?: string; + }; + url: '/config/providers'; +}; + +export type ConfigProvidersResponses = { + /** + * List of providers + */ + 200: { + providers: Array; + default: { + [key: string]: string; + }; + }; +}; + +export type ConfigProvidersResponse = ConfigProvidersResponses[keyof ConfigProvidersResponses]; + +export type ProviderListData = { + body?: never; + path?: never; + query?: { + directory?: string; + }; + url: '/provider'; +}; + +export type ProviderListResponses = { + /** + * List of providers + */ + 200: { + all: Array<{ + api?: string; + name: string; + env: Array; + id: string; + npm?: string; + models: { + [key: string]: { + id: string; + name: string; + family?: string; + release_date: string; + attachment: boolean; + reasoning: boolean; + temperature: boolean; + tool_call: boolean; + interleaved?: true | { + field: 'reasoning_content' | 'reasoning_details'; + }; + cost?: { + input: number; + output: number; + cache_read?: number; + cache_write?: number; + context_over_200k?: { + input: number; + output: number; + cache_read?: number; + cache_write?: number; + }; + }; + limit: { + context: number; + output: number; + }; + modalities?: { + input: Array<'text' | 'audio' | 'image' | 'video' | 'pdf'>; + output: Array<'text' | 'audio' | 'image' | 'video' | 'pdf'>; + }; + experimental?: boolean; + status?: 'alpha' | 'beta' | 'deprecated'; + options: { + [key: string]: unknown; + }; + headers?: { + [key: string]: string; + }; + provider?: { + npm: string; + }; + variants?: { + [key: string]: { + [key: string]: unknown; + }; + }; + }; + }; + }>; + default: { + [key: string]: string; + }; + connected: Array; + }; +}; + +export type ProviderListResponse = ProviderListResponses[keyof ProviderListResponses]; + +export type ProviderAuthData = { + body?: never; + path?: never; + query?: { + directory?: string; + }; + url: '/provider/auth'; +}; + +export type ProviderAuthResponses = { + /** + * Provider auth methods + */ + 200: { + [key: string]: Array; + }; +}; + +export type ProviderAuthResponse = ProviderAuthResponses[keyof ProviderAuthResponses]; + +export type ProviderOauthAuthorizeData = { + body?: { + /** + * Auth method index + */ + method: number; + }; + path: { + /** + * Provider ID + */ + providerID: string; + }; + query?: { + directory?: string; + }; + url: '/provider/{providerID}/oauth/authorize'; +}; + +export type ProviderOauthAuthorizeErrors = { + /** + * Bad request + */ + 400: BadRequestError; +}; + +export type ProviderOauthAuthorizeError = ProviderOauthAuthorizeErrors[keyof ProviderOauthAuthorizeErrors]; + +export type ProviderOauthAuthorizeResponses = { + /** + * Authorization URL and method + */ + 200: ProviderAuthAuthorization; +}; + +export type ProviderOauthAuthorizeResponse = ProviderOauthAuthorizeResponses[keyof ProviderOauthAuthorizeResponses]; + +export type ProviderOauthCallbackData = { + body?: { + /** + * Auth method index + */ + method: number; + /** + * OAuth authorization code + */ + code?: string; + }; + path: { + /** + * Provider ID + */ + providerID: string; + }; + query?: { + directory?: string; + }; + url: '/provider/{providerID}/oauth/callback'; +}; + +export type ProviderOauthCallbackErrors = { + /** + * Bad request + */ + 400: BadRequestError; +}; + +export type ProviderOauthCallbackError = ProviderOauthCallbackErrors[keyof ProviderOauthCallbackErrors]; + +export type ProviderOauthCallbackResponses = { + /** + * OAuth callback processed successfully + */ + 200: boolean; +}; + +export type ProviderOauthCallbackResponse = ProviderOauthCallbackResponses[keyof ProviderOauthCallbackResponses]; + +export type FindTextData = { + body?: never; + path?: never; + query: { + directory?: string; + pattern: string; + }; + url: '/find'; +}; + +export type FindTextResponses = { + /** + * Matches + */ + 200: Array<{ + path: { + text: string; + }; + lines: { + text: string; + }; + line_number: number; + absolute_offset: number; + submatches: Array<{ + match: { + text: string; + }; + start: number; + end: number; + }>; + }>; +}; + +export type FindTextResponse = FindTextResponses[keyof FindTextResponses]; + +export type FindFilesData = { + body?: never; + path?: never; + query: { + directory?: string; + query: string; + dirs?: 'true' | 'false'; + type?: 'file' | 'directory'; + limit?: number; + }; + url: '/find/file'; +}; + +export type FindFilesResponses = { + /** + * File paths + */ + 200: Array; +}; + +export type FindFilesResponse = FindFilesResponses[keyof FindFilesResponses]; + +export type FindSymbolsData = { + body?: never; + path?: never; + query: { + directory?: string; + query: string; + }; + url: '/find/symbol'; +}; + +export type FindSymbolsResponses = { + /** + * Symbols + */ + 200: Array; +}; + +export type FindSymbolsResponse = FindSymbolsResponses[keyof FindSymbolsResponses]; + +export type FileListData = { + body?: never; + path?: never; + query: { + directory?: string; + path: string; + }; + url: '/file'; +}; + +export type FileListResponses = { + /** + * Files and directories + */ + 200: Array; +}; + +export type FileListResponse = FileListResponses[keyof FileListResponses]; + +export type FileReadData = { + body?: never; + path?: never; + query: { + directory?: string; + path: string; + }; + url: '/file/content'; +}; + +export type FileReadResponses = { + /** + * File content + */ + 200: FileContent; +}; + +export type FileReadResponse = FileReadResponses[keyof FileReadResponses]; + +export type FileStatusData = { + body?: never; + path?: never; + query?: { + directory?: string; + }; + url: '/file/status'; +}; + +export type FileStatusResponses = { + /** + * File status + */ + 200: Array; +}; + +export type FileStatusResponse = FileStatusResponses[keyof FileStatusResponses]; + +export type AppLogData = { + body?: { + /** + * Service name for the log entry + */ + service: string; + /** + * Log level + */ + level: 'debug' | 'info' | 'error' | 'warn'; + /** + * Log message + */ + message: string; + /** + * Additional metadata for the log entry + */ + extra?: { + [key: string]: unknown; + }; + }; + path?: never; + query?: { + directory?: string; + }; + url: '/log'; +}; + +export type AppLogErrors = { + /** + * Bad request + */ + 400: BadRequestError; +}; + +export type AppLogError = AppLogErrors[keyof AppLogErrors]; + +export type AppLogResponses = { + /** + * Log entry written successfully + */ + 200: boolean; +}; + +export type AppLogResponse = AppLogResponses[keyof AppLogResponses]; + +export type AppAgentsData = { + body?: never; + path?: never; + query?: { + directory?: string; + }; + url: '/agent'; +}; + +export type AppAgentsResponses = { + /** + * List of agents + */ + 200: Array; +}; + +export type AppAgentsResponse = AppAgentsResponses[keyof AppAgentsResponses]; + +export type McpStatusData = { + body?: never; + path?: never; + query?: { + directory?: string; + }; + url: '/mcp'; +}; + +export type McpStatusResponses = { + /** + * MCP server status + */ + 200: { + [key: string]: McpStatus; + }; +}; + +export type McpStatusResponse = McpStatusResponses[keyof McpStatusResponses]; + +export type McpAddData = { + body?: { + name: string; + config: McpLocalConfig | McpRemoteConfig; + }; + path?: never; + query?: { + directory?: string; + }; + url: '/mcp'; +}; + +export type McpAddErrors = { + /** + * Bad request + */ + 400: BadRequestError; +}; + +export type McpAddError = McpAddErrors[keyof McpAddErrors]; + +export type McpAddResponses = { + /** + * MCP server added successfully + */ + 200: { + [key: string]: McpStatus; + }; +}; + +export type McpAddResponse = McpAddResponses[keyof McpAddResponses]; + +export type McpAuthRemoveData = { + body?: never; + path: { + name: string; + }; + query?: { + directory?: string; + }; + url: '/mcp/{name}/auth'; +}; + +export type McpAuthRemoveErrors = { + /** + * Not found + */ + 404: NotFoundError; +}; + +export type McpAuthRemoveError = McpAuthRemoveErrors[keyof McpAuthRemoveErrors]; + +export type McpAuthRemoveResponses = { + /** + * OAuth credentials removed + */ + 200: { + success: true; + }; +}; + +export type McpAuthRemoveResponse = McpAuthRemoveResponses[keyof McpAuthRemoveResponses]; + +export type McpAuthStartData = { + body?: never; + path: { + name: string; + }; + query?: { + directory?: string; + }; + url: '/mcp/{name}/auth'; +}; + +export type McpAuthStartErrors = { + /** + * Bad request + */ + 400: BadRequestError; + /** + * Not found + */ + 404: NotFoundError; +}; + +export type McpAuthStartError = McpAuthStartErrors[keyof McpAuthStartErrors]; + +export type McpAuthStartResponses = { + /** + * OAuth flow started + */ + 200: { + /** + * URL to open in browser for authorization + */ + authorizationUrl: string; + }; +}; + +export type McpAuthStartResponse = McpAuthStartResponses[keyof McpAuthStartResponses]; + +export type McpAuthCallbackData = { + body?: { + /** + * Authorization code from OAuth callback + */ + code: string; + }; + path: { + name: string; + }; + query?: { + directory?: string; + }; + url: '/mcp/{name}/auth/callback'; +}; + +export type McpAuthCallbackErrors = { + /** + * Bad request + */ + 400: BadRequestError; + /** + * Not found + */ + 404: NotFoundError; +}; + +export type McpAuthCallbackError = McpAuthCallbackErrors[keyof McpAuthCallbackErrors]; + +export type McpAuthCallbackResponses = { + /** + * OAuth authentication completed + */ + 200: McpStatus; +}; + +export type McpAuthCallbackResponse = McpAuthCallbackResponses[keyof McpAuthCallbackResponses]; + +export type McpAuthAuthenticateData = { + body?: never; + path: { + name: string; + }; + query?: { + directory?: string; + }; + url: '/mcp/{name}/auth/authenticate'; +}; + +export type McpAuthAuthenticateErrors = { + /** + * Bad request + */ + 400: BadRequestError; + /** + * Not found + */ + 404: NotFoundError; +}; + +export type McpAuthAuthenticateError = McpAuthAuthenticateErrors[keyof McpAuthAuthenticateErrors]; + +export type McpAuthAuthenticateResponses = { + /** + * OAuth authentication completed + */ + 200: McpStatus; +}; + +export type McpAuthAuthenticateResponse = McpAuthAuthenticateResponses[keyof McpAuthAuthenticateResponses]; + +export type McpConnectData = { + body?: never; + path: { + name: string; + }; + query?: { + directory?: string; + }; + url: '/mcp/{name}/connect'; +}; + +export type McpConnectResponses = { + /** + * MCP server connected successfully + */ + 200: boolean; +}; + +export type McpConnectResponse = McpConnectResponses[keyof McpConnectResponses]; + +export type McpDisconnectData = { + body?: never; + path: { + name: string; + }; + query?: { + directory?: string; + }; + url: '/mcp/{name}/disconnect'; +}; + +export type McpDisconnectResponses = { + /** + * MCP server disconnected successfully + */ + 200: boolean; +}; + +export type McpDisconnectResponse = McpDisconnectResponses[keyof McpDisconnectResponses]; + +export type LspStatusData = { + body?: never; + path?: never; + query?: { + directory?: string; + }; + url: '/lsp'; +}; + +export type LspStatusResponses = { + /** + * LSP server status + */ + 200: Array; +}; + +export type LspStatusResponse = LspStatusResponses[keyof LspStatusResponses]; + +export type FormatterStatusData = { + body?: never; + path?: never; + query?: { + directory?: string; + }; + url: '/formatter'; +}; + +export type FormatterStatusResponses = { + /** + * Formatter status + */ + 200: Array; +}; + +export type FormatterStatusResponse = FormatterStatusResponses[keyof FormatterStatusResponses]; + +export type TuiAppendPromptData = { + body?: { + text: string; + }; + path?: never; + query?: { + directory?: string; + }; + url: '/tui/append-prompt'; +}; + +export type TuiAppendPromptErrors = { + /** + * Bad request + */ + 400: BadRequestError; +}; + +export type TuiAppendPromptError = TuiAppendPromptErrors[keyof TuiAppendPromptErrors]; + +export type TuiAppendPromptResponses = { + /** + * Prompt processed successfully + */ + 200: boolean; +}; + +export type TuiAppendPromptResponse = TuiAppendPromptResponses[keyof TuiAppendPromptResponses]; + +export type TuiOpenHelpData = { + body?: never; + path?: never; + query?: { + directory?: string; + }; + url: '/tui/open-help'; +}; + +export type TuiOpenHelpResponses = { + /** + * Help dialog opened successfully + */ + 200: boolean; +}; + +export type TuiOpenHelpResponse = TuiOpenHelpResponses[keyof TuiOpenHelpResponses]; + +export type TuiOpenSessionsData = { + body?: never; + path?: never; + query?: { + directory?: string; + }; + url: '/tui/open-sessions'; +}; + +export type TuiOpenSessionsResponses = { + /** + * Session dialog opened successfully + */ + 200: boolean; +}; + +export type TuiOpenSessionsResponse = TuiOpenSessionsResponses[keyof TuiOpenSessionsResponses]; + +export type TuiOpenThemesData = { + body?: never; + path?: never; + query?: { + directory?: string; + }; + url: '/tui/open-themes'; +}; + +export type TuiOpenThemesResponses = { + /** + * Theme dialog opened successfully + */ + 200: boolean; +}; + +export type TuiOpenThemesResponse = TuiOpenThemesResponses[keyof TuiOpenThemesResponses]; + +export type TuiOpenModelsData = { + body?: never; + path?: never; + query?: { + directory?: string; + }; + url: '/tui/open-models'; +}; + +export type TuiOpenModelsResponses = { + /** + * Model dialog opened successfully + */ + 200: boolean; +}; + +export type TuiOpenModelsResponse = TuiOpenModelsResponses[keyof TuiOpenModelsResponses]; + +export type TuiSubmitPromptData = { + body?: never; + path?: never; + query?: { + directory?: string; + }; + url: '/tui/submit-prompt'; +}; + +export type TuiSubmitPromptResponses = { + /** + * Prompt submitted successfully + */ + 200: boolean; +}; + +export type TuiSubmitPromptResponse = TuiSubmitPromptResponses[keyof TuiSubmitPromptResponses]; + +export type TuiClearPromptData = { + body?: never; + path?: never; + query?: { + directory?: string; + }; + url: '/tui/clear-prompt'; +}; + +export type TuiClearPromptResponses = { + /** + * Prompt cleared successfully + */ + 200: boolean; +}; + +export type TuiClearPromptResponse = TuiClearPromptResponses[keyof TuiClearPromptResponses]; + +export type TuiExecuteCommandData = { + body?: { + command: string; + }; + path?: never; + query?: { + directory?: string; + }; + url: '/tui/execute-command'; +}; + +export type TuiExecuteCommandErrors = { + /** + * Bad request + */ + 400: BadRequestError; +}; + +export type TuiExecuteCommandError = TuiExecuteCommandErrors[keyof TuiExecuteCommandErrors]; + +export type TuiExecuteCommandResponses = { + /** + * Command executed successfully + */ + 200: boolean; +}; + +export type TuiExecuteCommandResponse = TuiExecuteCommandResponses[keyof TuiExecuteCommandResponses]; + +export type TuiShowToastData = { + body?: { + title?: string; + message: string; + variant: 'info' | 'success' | 'warning' | 'error'; + /** + * Duration in milliseconds + */ + duration?: number; + }; + path?: never; + query?: { + directory?: string; + }; + url: '/tui/show-toast'; +}; + +export type TuiShowToastResponses = { + /** + * Toast notification shown successfully + */ + 200: boolean; +}; + +export type TuiShowToastResponse = TuiShowToastResponses[keyof TuiShowToastResponses]; + +export type TuiPublishData = { + body?: EventTuiPromptAppend | EventTuiCommandExecute | EventTuiToastShow; + path?: never; + query?: { + directory?: string; + }; + url: '/tui/publish'; +}; + +export type TuiPublishErrors = { + /** + * Bad request + */ + 400: BadRequestError; +}; + +export type TuiPublishError = TuiPublishErrors[keyof TuiPublishErrors]; + +export type TuiPublishResponses = { + /** + * Event published successfully + */ + 200: boolean; +}; + +export type TuiPublishResponse = TuiPublishResponses[keyof TuiPublishResponses]; + +export type TuiControlNextData = { + body?: never; + path?: never; + query?: { + directory?: string; + }; + url: '/tui/control/next'; +}; + +export type TuiControlNextResponses = { + /** + * Next TUI request + */ + 200: { + path: string; + body: unknown; + }; +}; + +export type TuiControlNextResponse = TuiControlNextResponses[keyof TuiControlNextResponses]; + +export type TuiControlResponseData = { + body?: unknown; + path?: never; + query?: { + directory?: string; + }; + url: '/tui/control/response'; +}; + +export type TuiControlResponseResponses = { + /** + * Response submitted successfully + */ + 200: boolean; +}; + +export type TuiControlResponseResponse = TuiControlResponseResponses[keyof TuiControlResponseResponses]; + +export type AuthSetData = { + body?: Auth; + path: { + providerID: string; + }; + query?: { + directory?: string; + }; + url: '/auth/{providerID}'; +}; + +export type AuthSetErrors = { + /** + * Bad request + */ + 400: BadRequestError; +}; + +export type AuthSetError = AuthSetErrors[keyof AuthSetErrors]; + +export type AuthSetResponses = { + /** + * Successfully set authentication credentials + */ + 200: boolean; +}; + +export type AuthSetResponse = AuthSetResponses[keyof AuthSetResponses]; + +export type EventSubscribeData = { + body?: never; + path?: never; + query?: { + directory?: string; + }; + url: '/event'; +}; + +export type EventSubscribeResponses = { + /** + * Event stream + */ + 200: Event; +}; + +export type EventSubscribeResponse = EventSubscribeResponses[keyof EventSubscribeResponses]; diff --git a/packages/openapi-ts-tests/sdks/test/opencode.test.ts b/packages/openapi-ts-tests/sdks/test/opencode.test.ts index 8d9b04cb8f..13741d0bd1 100644 --- a/packages/openapi-ts-tests/sdks/test/opencode.test.ts +++ b/packages/openapi-ts-tests/sdks/test/opencode.test.ts @@ -48,6 +48,20 @@ describe(`SDK: ${namespace}`, () => { }), description: 'flat', }, + { + config: createConfig({ + input: specPath, + output: 'flat-positional', + plugins: [ + { + name: '@hey-api/sdk', + paramsStructure: 'flat', + positionalPathParams: true, + }, + ], + }), + description: 'flat positional path params', + }, { config: createConfig({ input: specPath, diff --git a/packages/openapi-ts/src/plugins/@hey-api/sdk/config.ts b/packages/openapi-ts/src/plugins/@hey-api/sdk/config.ts index 78c64f1e60..b502ccb48c 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/sdk/config.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/sdk/config.ts @@ -11,6 +11,7 @@ export const defaultConfig: HeyApiSdkPlugin['Config'] = { client: true, exportFromIndex: true, paramsStructure: 'grouped', + positionalPathParams: false, responseStyle: 'fields', transformer: false, validator: false, diff --git a/packages/openapi-ts/src/plugins/@hey-api/sdk/shared/operation.ts b/packages/openapi-ts/src/plugins/@hey-api/sdk/shared/operation.ts index a103ac78f4..f6418dcae3 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/sdk/shared/operation.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/sdk/shared/operation.ts @@ -6,6 +6,7 @@ import { statusCodeToGroup } from '@hey-api/shared'; import { getTypedConfig } from '../../../../config/utils'; import { getClientPlugin } from '../../../../plugins/@hey-api/client-core/utils'; import { $ } from '../../../../ts-dsl'; +import type { TypeTsDsl } from '../../../../ts-dsl/base'; import type { Field, Fields } from '../../client-core/bundle/params'; import type { HeyApiSdkPlugin } from '../types'; import { isInstance } from '../v1/node'; @@ -73,6 +74,7 @@ export const operationOptionsType = ({ type OperationParameters = { argNames: Array; fields: Array; + fieldsByArgument: boolean; parameters: Array>; }; @@ -88,6 +90,7 @@ export function operationParameters({ const result: OperationParameters = { argNames: [], fields: [], + fieldsByArgument: false, parameters: [], }; @@ -97,37 +100,131 @@ export function operationParameters({ if (plugin.config.paramsStructure === 'flat') { const signature = getSignatureParameters({ operation, plugin }); - const flatParams = $.type.object(); if (signature) { - let isParametersRequired = false; - - for (const key in signature.parameters) { - const parameter = signature.parameters[key]!; - if (parameter.isRequired) { - isParametersRequired = true; + const usePositionalPathParams = plugin.config.positionalPathParams; + + let appliedPositionalPathParams = false; + + if (usePositionalPathParams && operation.parameters?.path) { + const pathEntries = Object.entries(operation.parameters.path); + if (pathEntries.length === 1) { + const [, pathParameter] = pathEntries[0]!; + const pathSignature = Object.values(signature.parameters).find((parameter) => { + const originalName = parameter.originalName ?? parameter.name; + return originalName === pathParameter.name; + }); + if (pathSignature) { + const nonPathFields = signature.fields.filter( + (field) => !('in' in field && field.in === 'path'), + ); + + if (nonPathFields.length) { + appliedPositionalPathParams = true; + // Add positional path arg name in the SDK signature. + result.argNames.push(pathSignature.name); + // Record the field mapping for the positional path arg. + result.fields.push({ + in: 'path', + key: pathSignature.name, + ...(pathSignature.originalName ? { map: pathSignature.originalName } : {}), + }); + // Build the TypeScript type for the positional path arg. + const pathType = pluginTypeScript.api.schemaToType({ + plugin: pluginTypeScript, + schema: pathSignature.schema, + state: refs({ + path: [], + }), + }); + result.parameters.push( + $.param(pathSignature.name, (p) => + p.required(pathSignature.isRequired).type(pathType as TypeTsDsl), + ), + ); + + const flatParams = $.type.object(); + let isParametersRequired = false; + + for (const key in signature.parameters) { + if (key === pathSignature.name) { + continue; + } + const parameter = signature.parameters[key]!; + if (parameter.isRequired) { + isParametersRequired = true; + } + const paramType = pluginTypeScript.api.schemaToType({ + plugin: pluginTypeScript, + schema: parameter.schema, + state: refs({ + path: [], + }), + }); + flatParams.prop(parameter.name, (p) => + p.required(parameter.isRequired).type(paramType as TypeTsDsl), + ); + } + + result.argNames.push('parameters'); + result.fields.push({ + args: nonPathFields, + }); + + result.parameters.push( + $.param('parameters', (p) => p.required(isParametersRequired).type(flatParams)), + ); + + // Fields are aligned to arguments (positional path + parameters). + result.fieldsByArgument = true; + } + } } - flatParams.prop(parameter.name, (p) => - p.required(parameter.isRequired).type( - pluginTypeScript.api.schemaToType({ - plugin: pluginTypeScript, - schema: parameter.schema, - state: refs({ - path: [], - }), - }), - ), - ); } - result.argNames.push('parameters'); - for (const field of signature.fields) { - result.fields.push(field); + // If we applied positional path params but had no other fields, + // fall back to flat signature so the output matches snapshots. + if (appliedPositionalPathParams && !result.fieldsByArgument) { + appliedPositionalPathParams = false; } + if (!appliedPositionalPathParams) { + const flatParams = $.type.object(); + let isParametersRequired = false; + + for (const key in signature.parameters) { + const parameter = signature.parameters[key]!; + if (parameter.isRequired) { + isParametersRequired = true; + } + const paramType = pluginTypeScript.api.schemaToType({ + plugin: pluginTypeScript, + schema: parameter.schema, + state: refs({ + path: [], + }), + }); + flatParams.prop(parameter.name, (p) => + p.required(parameter.isRequired).type(paramType as TypeTsDsl), + ); + } + + // Add the single parameters argument to the signature. + result.argNames.push('parameters'); + if (usePositionalPathParams) { + // Keep fields empty so we emit `[{}]` for buildClientParams. + result.fields.push({}); + result.fieldsByArgument = true; + } else { + // Store the full field mapping for the flattened object. + result.fields.push(...signature.fields); + // Keep fieldsByArgument false so we wrap fields as args later. + result.fieldsByArgument = false; + } - result.parameters.push( - $.param('parameters', (p) => p.required(isParametersRequired).type(flatParams)), - ); + result.parameters.push( + $.param('parameters', (p) => p.required(isParametersRequired).type(flatParams)), + ); + } } } @@ -389,31 +486,19 @@ export function operationStatements({ if (hasParams) { const args: Array> = []; - const config: Array> = []; for (const argName of opParameters.argNames) { args.push($(argName)); } - for (const field of opParameters.fields) { - const shape = $.object(); - if ('in' in field) { - shape.prop('in', $.literal(field.in)); - } - if ('key' in field) { - if (field.key) { - shape.prop('key', $.literal(field.key)); - } - if (field.map) { - shape.prop('map', $.literal(field.map)); - } - } - config.push(shape); - } + // Align fields config with argument order when required. + const fieldsConfig = opParameters.fieldsByArgument + ? opParameters.fields + : [{ args: opParameters.fields }]; + const symbol = plugin.external('client.buildClientParams'); statements.push( - $.const('params').assign( - $(symbol).call($.array(...args), $.array($.object().prop('args', $.array(...config)))), - ), + $.const('params').assign($(symbol).call($.array(...args), $.fromValue(fieldsConfig))), ); + reqOptions.spread('params'); } diff --git a/packages/openapi-ts/src/plugins/@hey-api/sdk/types.ts b/packages/openapi-ts/src/plugins/@hey-api/sdk/types.ts index 9cfbb6c5e7..5514b73773 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/sdk/types.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/sdk/types.ts @@ -69,6 +69,14 @@ export type UserConfig = Plugin.Name<'@hey-api/sdk'> & * @default 'grouped' */ paramsStructure?: 'flat' | 'grouped'; + /** + * Emit path parameters as positional arguments when using `paramsStructure: 'flat'`. + * + * When enabled, the SDK signature becomes `fn(pathParam, parameters?, options?)`. + * + * @default false + */ + positionalPathParams?: boolean; /** * **This feature works only with the Fetch client** * @@ -239,6 +247,14 @@ export type Config = Plugin.Name<'@hey-api/sdk'> & * @default 'grouped' */ paramsStructure: 'flat' | 'grouped'; + /** + * Emit path parameters as positional arguments when using `paramsStructure: 'flat'`. + * + * When enabled, the SDK signature becomes `fn(pathParam, parameters?, options?)`. + * + * @default false + */ + positionalPathParams: boolean; /** * **This feature works only with the Fetch client** *