diff options
| author | Dax <[email protected]> | 2025-12-07 19:04:14 -0500 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-12-07 19:04:14 -0500 |
| commit | ea7ec60f51f9fe3c6382f644c328188a43545b7b (patch) | |
| tree | 5204cbc059fe10b87c54ffd25782ad83710bdcdb /packages/sdk/js/src/v2 | |
| parent | 6667856ba5dac3e5dd77c7008cee2d09be894472 (diff) | |
| download | opencode-ea7ec60f51f9fe3c6382f644c328188a43545b7b.tar.gz opencode-ea7ec60f51f9fe3c6382f644c328188a43545b7b.zip | |
v2 SDK (#5216)
Co-authored-by: GitHub Action <[email protected]>
Diffstat (limited to 'packages/sdk/js/src/v2')
18 files changed, 8201 insertions, 0 deletions
diff --git a/packages/sdk/js/src/v2/client.ts b/packages/sdk/js/src/v2/client.ts new file mode 100644 index 000000000..806ad26e5 --- /dev/null +++ b/packages/sdk/js/src/v2/client.ts @@ -0,0 +1,30 @@ +export * from "./gen/types.gen.js" + +import { createClient } from "./gen/client/client.gen.js" +import { type Config } from "./gen/client/types.gen.js" +import { OpencodeClient } from "./gen/sdk.gen.js" +export { type Config as OpencodeClientConfig, OpencodeClient } + +export function createOpencodeClient(config?: Config & { directory?: string }) { + if (!config?.fetch) { + const customFetch: any = (req: any) => { + // @ts-ignore + req.timeout = false + return fetch(req) + } + config = { + ...config, + fetch: customFetch, + } + } + + if (config?.directory) { + config.headers = { + ...config.headers, + "x-opencode-directory": config.directory, + } + } + + const client = createClient(config) + return new OpencodeClient({ client }) +} diff --git a/packages/sdk/js/src/v2/gen/client.gen.ts b/packages/sdk/js/src/v2/gen/client.gen.ts new file mode 100644 index 000000000..0c110eca3 --- /dev/null +++ b/packages/sdk/js/src/v2/gen/client.gen.ts @@ -0,0 +1,18 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { type ClientOptions, type Config, createClient, createConfig } from "./client/index.js" +import type { ClientOptions as ClientOptions2 } from "./types.gen.js" + +/** + * 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<T extends ClientOptions = ClientOptions2> = ( + override?: Config<ClientOptions & T>, +) => Config<Required<ClientOptions> & T> + +export const client = createClient(createConfig<ClientOptions2>({ baseUrl: "http://localhost:4096" })) diff --git a/packages/sdk/js/src/v2/gen/client/client.gen.ts b/packages/sdk/js/src/v2/gen/client/client.gen.ts new file mode 100644 index 000000000..47f140342 --- /dev/null +++ b/packages/sdk/js/src/v2/gen/client/client.gen.ts @@ -0,0 +1,278 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { createSseClient } from "../core/serverSentEvents.gen.js" +import type { HttpMethod } from "../core/types.gen.js" +import { getValidRequestBody } from "../core/utils.gen.js" +import type { Client, Config, RequestOptions, ResolvedRequestOptions } from "./types.gen.js" +import { + buildUrl, + createConfig, + createInterceptors, + getParseAs, + mergeConfigs, + mergeHeaders, + setAuthParams, +} from "./utils.gen.js" + +type ReqInit = Omit<RequestInit, "body" | "headers"> & { + body?: any + headers: ReturnType<typeof mergeHeaders> +} + +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<Request, Response, unknown, ResolvedRequestOptions>() + + 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 "json": + case "text": + data = await response[parseAs]() + 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<HttpMethod>) => (options: RequestOptions) => request({ ...options, method }) + + const makeSseFn = (method: Uppercase<HttpMethod>) => 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<string, string>, + 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 + }, + 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/sdk/js/src/v2/gen/client/index.ts b/packages/sdk/js/src/v2/gen/client/index.ts new file mode 100644 index 000000000..0af63f330 --- /dev/null +++ b/packages/sdk/js/src/v2/gen/client/index.ts @@ -0,0 +1,25 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type { Auth } from "../core/auth.gen.js" +export type { QuerySerializerOptions } from "../core/bodySerializer.gen.js" +export { + formDataBodySerializer, + jsonBodySerializer, + urlSearchParamsBodySerializer, +} from "../core/bodySerializer.gen.js" +export { buildClientParams } from "../core/params.gen.js" +export { serializeQueryKeyValue } from "../core/queryKeySerializer.gen.js" +export { createClient } from "./client.gen.js" +export type { + Client, + ClientOptions, + Config, + CreateClientConfig, + Options, + RequestOptions, + RequestResult, + ResolvedRequestOptions, + ResponseStyle, + TDataShape, +} from "./types.gen.js" +export { createConfig, mergeHeaders } from "./utils.gen.js" diff --git a/packages/sdk/js/src/v2/gen/client/types.gen.ts b/packages/sdk/js/src/v2/gen/client/types.gen.ts new file mode 100644 index 000000000..e053aa406 --- /dev/null +++ b/packages/sdk/js/src/v2/gen/client/types.gen.ts @@ -0,0 +1,202 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Auth } from "../core/auth.gen.js" +import type { ServerSentEventsOptions, ServerSentEventsResult } from "../core/serverSentEvents.gen.js" +import type { Client as CoreClient, Config as CoreConfig } from "../core/types.gen.js" +import type { Middleware } from "./utils.gen.js" + +export type ResponseStyle = "data" | "fields" + +export interface Config<T extends ClientOptions = ClientOptions> + extends Omit<RequestInit, "body" | "headers" | "method">, + 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<TData>, + "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<string, unknown> + query?: Record<string, unknown> + /** + * Security mechanism(s) to use for the request. + */ + security?: ReadonlyArray<Auth> + url: Url +} + +export interface ResolvedRequestOptions< + TResponseStyle extends ResponseStyle = "fields", + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends RequestOptions<unknown, TResponseStyle, ThrowOnError, Url> { + 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<string, unknown> + ? TData[keyof TData] + : TData + : { + data: TData extends Record<string, unknown> ? TData[keyof TData] : TData + request: Request + response: Response + } + > + : Promise< + TResponseStyle extends "data" + ? (TData extends Record<string, unknown> ? TData[keyof TData] : TData) | undefined + : ( + | { + data: TData extends Record<string, unknown> ? TData[keyof TData] : TData + error: undefined + } + | { + data: undefined + error: TError extends Record<string, unknown> ? 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<RequestOptions<TData, TResponseStyle, ThrowOnError>, "method">, +) => RequestResult<TData, TError, ThrowOnError, TResponseStyle> + +type SseFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = "fields", +>( + options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, "method">, +) => Promise<ServerSentEventsResult<TData, TError>> + +type RequestFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = "fields", +>( + options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, "method"> & + Pick<Required<RequestOptions<TData, TResponseStyle, ThrowOnError>>, "method">, +) => RequestResult<TData, TError, ThrowOnError, TResponseStyle> + +type BuildUrlFn = < + TData extends { + body?: unknown + path?: Record<string, unknown> + query?: Record<string, unknown> + url: string + }, +>( + options: TData & Options<TData>, +) => string + +export type Client = CoreClient<RequestFn, Config, MethodFn, BuildUrlFn, SseFn> & { + interceptors: Middleware<Request, Response, unknown, ResolvedRequestOptions> +} + +/** + * 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<T extends ClientOptions = ClientOptions> = ( + override?: Config<ClientOptions & T>, +) => Config<Required<ClientOptions> & T> + +export interface TDataShape { + body?: unknown + headers?: unknown + path?: unknown + query?: unknown + url: string +} + +type OmitKeys<T, K> = Pick<T, Exclude<keyof T, K>> + +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean, + TResponse = unknown, + TResponseStyle extends ResponseStyle = "fields", +> = OmitKeys<RequestOptions<TResponse, TResponseStyle, ThrowOnError>, "body" | "path" | "query" | "url"> & + ([TData] extends [never] ? unknown : Omit<TData, "url">) diff --git a/packages/sdk/js/src/v2/gen/client/utils.gen.ts b/packages/sdk/js/src/v2/gen/client/utils.gen.ts new file mode 100644 index 000000000..3b1dfb787 --- /dev/null +++ b/packages/sdk/js/src/v2/gen/client/utils.gen.ts @@ -0,0 +1,289 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { getAuthToken } from "../core/auth.gen.js" +import type { QuerySerializerOptions } from "../core/bodySerializer.gen.js" +import { jsonBodySerializer } from "../core/bodySerializer.gen.js" +import { serializeArrayParam, serializeObjectParam, serializePrimitiveParam } from "../core/pathSerializer.gen.js" +import { getUrl } from "../core/utils.gen.js" +import type { Client, ClientOptions, Config, RequestOptions } from "./types.gen.js" + +export const createQuerySerializer = <T = unknown>({ 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<string, unknown>, + ...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<Config["parseAs"], "auto"> => { + 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<RequestOptions, "auth" | "query"> & { + 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<Required<RequestOptions>, "security"> & + Pick<RequestOptions, "auth" | "query"> & { + 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<Required<Config>["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<Err, Res, Req, Options> = ( + error: Err, + response: Res, + request: Req, + options: Options, +) => Err | Promise<Err> + +type ReqInterceptor<Req, Options> = (request: Req, options: Options) => Req | Promise<Req> + +type ResInterceptor<Res, Req, Options> = (response: Res, request: Req, options: Options) => Res | Promise<Res> + +class Interceptors<Interceptor> { + fns: Array<Interceptor | null> = [] + + 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<Req, Res, Err, Options> { + error: Interceptors<ErrInterceptor<Err, Res, Req, Options>> + request: Interceptors<ReqInterceptor<Req, Options>> + response: Interceptors<ResInterceptor<Res, Req, Options>> +} + +export const createInterceptors = <Req, Res, Err, Options>(): Middleware<Req, Res, Err, Options> => ({ + error: new Interceptors<ErrInterceptor<Err, Res, Req, Options>>(), + request: new Interceptors<ReqInterceptor<Req, Options>>(), + response: new Interceptors<ResInterceptor<Res, Req, Options>>(), +}) + +const defaultQuerySerializer = createQuerySerializer({ + allowReserved: false, + array: { + explode: true, + style: "form", + }, + object: { + explode: true, + style: "deepObject", + }, +}) + +const defaultHeaders = { + "Content-Type": "application/json", +} + +export const createConfig = <T extends ClientOptions = ClientOptions>( + override: Config<Omit<ClientOptions, keyof T> & T> = {}, +): Config<Omit<ClientOptions, keyof T> & T> => ({ + ...jsonBodySerializer, + headers: defaultHeaders, + parseAs: "auto", + querySerializer: defaultQuerySerializer, + ...override, +}) diff --git a/packages/sdk/js/src/v2/gen/core/auth.gen.ts b/packages/sdk/js/src/v2/gen/core/auth.gen.ts new file mode 100644 index 000000000..bc7b230f4 --- /dev/null +++ b/packages/sdk/js/src/v2/gen/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) | AuthToken, +): Promise<string | undefined> => { + 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/sdk/js/src/v2/gen/core/bodySerializer.gen.ts b/packages/sdk/js/src/v2/gen/core/bodySerializer.gen.ts new file mode 100644 index 000000000..9678fb08e --- /dev/null +++ b/packages/sdk/js/src/v2/gen/core/bodySerializer.gen.ts @@ -0,0 +1,82 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { ArrayStyle, ObjectStyle, SerializerOptions } from "./pathSerializer.gen.js" + +export type QuerySerializer = (query: Record<string, unknown>) => string + +export type BodySerializer = (body: any) => any + +type QuerySerializerOptionsObject = { + allowReserved?: boolean + array?: Partial<SerializerOptions<ArrayStyle>> + object?: Partial<SerializerOptions<ObjectStyle>> +} + +export type QuerySerializerOptions = QuerySerializerOptionsObject & { + /** + * Per-parameter serialization overrides. When provided, these settings + * override the global array/object settings for specific parameter names. + */ + parameters?: Record<string, QuerySerializerOptionsObject> +} + +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: <T extends Record<string, any> | Array<Record<string, any>>>(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: <T>(body: T): string => + JSON.stringify(body, (_key, value) => (typeof value === "bigint" ? value.toString() : value)), +} + +export const urlSearchParamsBodySerializer = { + bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>(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/sdk/js/src/v2/gen/core/params.gen.ts b/packages/sdk/js/src/v2/gen/core/params.gen.ts new file mode 100644 index 000000000..6e9d0b9ad --- /dev/null +++ b/packages/sdk/js/src/v2/gen/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<Slot, "body"> + /** + * 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<Slot, "body"> + /** + * 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<Record<Slot, boolean>> + args?: ReadonlyArray<Field> +} + +export type FieldsConfig = ReadonlyArray<Field | Fields> + +const extraPrefixesMap: Record<string, Slot> = { + $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<string, unknown> + path: Record<string, unknown> + query: Record<string, unknown> +} + +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<unknown>, 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<string, unknown>)[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<string, unknown>)[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<string, unknown>)[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<string, unknown>)[key] = value + break + } + } + } + } + } + } + } + + stripEmptySlots(params) + + return params +} diff --git a/packages/sdk/js/src/v2/gen/core/pathSerializer.gen.ts b/packages/sdk/js/src/v2/gen/core/pathSerializer.gen.ts new file mode 100644 index 000000000..96be3bc5a --- /dev/null +++ b/packages/sdk/js/src/v2/gen/core/pathSerializer.gen.ts @@ -0,0 +1,167 @@ +// This file is auto-generated by @hey-api/openapi-ts + +interface SerializeOptions<T> extends SerializePrimitiveOptions, SerializerOptions<T> {} + +interface SerializePrimitiveOptions { + allowReserved?: boolean + name: string +} + +export interface SerializerOptions<T> { + /** + * @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<ArraySeparatorStyle> & { + 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<ObjectSeparatorStyle> & { + value: Record<string, unknown> | 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/sdk/js/src/v2/gen/core/queryKeySerializer.gen.ts b/packages/sdk/js/src/v2/gen/core/queryKeySerializer.gen.ts new file mode 100644 index 000000000..320204aef --- /dev/null +++ b/packages/sdk/js/src/v2/gen/core/queryKeySerializer.gen.ts @@ -0,0 +1,111 @@ +// 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<string, unknown> => { + 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<string, JsonValue> = {} + + 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/sdk/js/src/v2/gen/core/serverSentEvents.gen.ts b/packages/sdk/js/src/v2/gen/core/serverSentEvents.gen.ts new file mode 100644 index 000000000..09ef3fb39 --- /dev/null +++ b/packages/sdk/js/src/v2/gen/core/serverSentEvents.gen.ts @@ -0,0 +1,237 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Config } from "./types.gen.js" + +export type ServerSentEventsOptions<TData = unknown> = Omit<RequestInit, "method"> & + Pick<Config, "method" | "responseTransformer" | "responseValidator"> & { + /** + * 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<Request> + /** + * 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<TData>) => 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<void> + url: string + } + +export interface StreamEvent<TData = unknown> { + data: TData + event?: string + id?: string + retry?: number +} + +export type ServerSentEventsResult<TData = unknown, TReturn = void, TNext = unknown> = { + stream: AsyncGenerator<TData extends Record<string, unknown> ? TData[keyof TData] : TData, TReturn, TNext> +} + +export const createSseClient = <TData = unknown>({ + onRequest, + onSseError, + onSseEvent, + responseTransformer, + responseValidator, + sseDefaultRetryDelay, + sseMaxRetryAttempts, + sseMaxRetryDelay, + sseSleepFn, + url, + ...options +}: ServerSentEventsOptions): ServerSentEventsResult<TData> => { + 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<string, string> | 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 + + const chunks = buffer.split("\n\n") + buffer = chunks.pop() ?? "" + + for (const chunk of chunks) { + const lines = chunk.split("\n") + const dataLines: Array<string> = [] + 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/sdk/js/src/v2/gen/core/types.gen.ts b/packages/sdk/js/src/v2/gen/core/types.gen.ts new file mode 100644 index 000000000..bfa77b8ac --- /dev/null +++ b/packages/sdk/js/src/v2/gen/core/types.gen.ts @@ -0,0 +1,86 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Auth, AuthToken } from "./auth.gen.js" +import type { BodySerializer, QuerySerializer, QuerySerializerOptions } from "./bodySerializer.gen.js" + +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) | 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<HttpMethod> + /** + * 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<unknown> + /** + * 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<unknown> + /** + * 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<unknown> +} + +type IsExactlyNeverOrNeverUndefined<T> = [T] extends [never] + ? true + : [T] extends [never | undefined] + ? [undefined] extends [T] + ? false + : true + : false + +export type OmitNever<T extends Record<string, unknown>> = { + [K in keyof T as IsExactlyNeverOrNeverUndefined<T[K]> extends true ? never : K]: T[K] +} diff --git a/packages/sdk/js/src/v2/gen/core/utils.gen.ts b/packages/sdk/js/src/v2/gen/core/utils.gen.ts new file mode 100644 index 000000000..8a45f7269 --- /dev/null +++ b/packages/sdk/js/src/v2/gen/core/utils.gen.ts @@ -0,0 +1,137 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { BodySerializer, QuerySerializer } from "./bodySerializer.gen.js" +import { + type ArraySeparatorStyle, + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from "./pathSerializer.gen.js" + +export interface PathSerializer { + path: Record<string, unknown> + 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<string, unknown>, + 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<string, unknown> + query?: Record<string, unknown> + 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/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts new file mode 100644 index 000000000..caf6c9d00 --- /dev/null +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -0,0 +1,2343 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { client } from "./client.gen.js" +import { buildClientParams, type Client, type Options as Options2, type TDataShape } from "./client/index.js" +import type { + AgentPartInput, + AppAgentsResponses, + AppLogErrors, + AppLogResponses, + Auth as Auth2, + AuthSetErrors, + AuthSetResponses, + CommandListResponses, + Config as Config2, + ConfigGetResponses, + ConfigProvidersResponses, + ConfigUpdateErrors, + ConfigUpdateResponses, + EventSubscribeResponses, + EventTuiCommandExecute, + EventTuiPromptAppend, + EventTuiToastShow, + FileListResponses, + FilePartInput, + FileReadResponses, + FileStatusResponses, + FindFilesResponses, + FindSymbolsResponses, + FindTextResponses, + FormatterStatusResponses, + GlobalEventResponses, + InstanceDisposeResponses, + LspStatusResponses, + McpAddErrors, + McpAddResponses, + McpAuthAuthenticateErrors, + McpAuthAuthenticateResponses, + McpAuthCallbackErrors, + McpAuthCallbackResponses, + McpAuthRemoveErrors, + McpAuthRemoveResponses, + McpAuthStartErrors, + McpAuthStartResponses, + McpLocalConfig, + McpRemoteConfig, + McpStatusResponses, + PathGetResponses, + PermissionRespondErrors, + PermissionRespondResponses, + ProjectCurrentResponses, + ProjectListResponses, + 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.js" + +export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = Options2< + TData, + ThrowOnError +> & { + /** + * 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<string, unknown> +} + +class HeyApiClient { + protected client: Client + + constructor(args?: { client?: Client }) { + this.client = args?.client ?? client + } +} + +class HeyApiRegistry<T> { + private readonly defaultKey = "default" + + private readonly instances: Map<string, T> = new Map() + + get(key?: string): T { + const instance = this.instances.get(key ?? this.defaultKey) + if (!instance) { + throw new Error(`No SDK client found. Create one with "new OpencodeClient()" to fix this error.`) + } + return instance + } + + set(value: T, key?: string): void { + this.instances.set(key ?? this.defaultKey, value) + } +} + +export class Global extends HeyApiClient { + /** + * Get events + */ + public event<ThrowOnError extends boolean = false>(options?: Options<never, ThrowOnError>) { + return (options?.client ?? this.client).sse.get<GlobalEventResponses, unknown, ThrowOnError>({ + url: "/global/event", + ...options, + }) + } +} + +export class Project extends HeyApiClient { + /** + * List all projects + */ + public list<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + return (options?.client ?? this.client).get<ProjectListResponses, unknown, ThrowOnError>({ + url: "/project", + ...options, + ...params, + }) + } + + /** + * Get the current project + */ + public current<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + return (options?.client ?? this.client).get<ProjectCurrentResponses, unknown, ThrowOnError>({ + url: "/project/current", + ...options, + ...params, + }) + } +} + +export class Pty extends HeyApiClient { + /** + * List all PTY sessions + */ + public list<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + return (options?.client ?? this.client).get<PtyListResponses, unknown, ThrowOnError>({ + url: "/pty", + ...options, + ...params, + }) + } + + /** + * Create a new PTY session + */ + public create<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + command?: string + args?: Array<string> + cwd?: string + title?: string + env?: { + [key: string]: string + } + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "body", key: "command" }, + { in: "body", key: "args" }, + { in: "body", key: "cwd" }, + { in: "body", key: "title" }, + { in: "body", key: "env" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post<PtyCreateResponses, PtyCreateErrors, ThrowOnError>({ + url: "/pty", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + + /** + * Remove a PTY session + */ + public remove<ThrowOnError extends boolean = false>( + parameters: { + ptyID: string + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "ptyID" }, + { in: "query", key: "directory" }, + ], + }, + ], + ) + return (options?.client ?? this.client).delete<PtyRemoveResponses, PtyRemoveErrors, ThrowOnError>({ + url: "/pty/{ptyID}", + ...options, + ...params, + }) + } + + /** + * Get PTY session info + */ + public get<ThrowOnError extends boolean = false>( + parameters: { + ptyID: string + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "ptyID" }, + { in: "query", key: "directory" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get<PtyGetResponses, PtyGetErrors, ThrowOnError>({ + url: "/pty/{ptyID}", + ...options, + ...params, + }) + } + + /** + * Update PTY session + */ + public update<ThrowOnError extends boolean = false>( + parameters: { + ptyID: string + directory?: string + title?: string + size?: { + rows: number + cols: number + } + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "ptyID" }, + { in: "query", key: "directory" }, + { in: "body", key: "title" }, + { in: "body", key: "size" }, + ], + }, + ], + ) + return (options?.client ?? this.client).put<PtyUpdateResponses, PtyUpdateErrors, ThrowOnError>({ + url: "/pty/{ptyID}", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + + /** + * Connect to a PTY session + */ + public connect<ThrowOnError extends boolean = false>( + parameters: { + ptyID: string + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "ptyID" }, + { in: "query", key: "directory" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get<PtyConnectResponses, PtyConnectErrors, ThrowOnError>({ + url: "/pty/{ptyID}/connect", + ...options, + ...params, + }) + } +} + +export class Config extends HeyApiClient { + /** + * Get config info + */ + public get<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + return (options?.client ?? this.client).get<ConfigGetResponses, unknown, ThrowOnError>({ + url: "/config", + ...options, + ...params, + }) + } + + /** + * Update config + */ + public update<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + config?: Config2 + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { key: "config", map: "body" }, + ], + }, + ], + ) + return (options?.client ?? this.client).patch<ConfigUpdateResponses, ConfigUpdateErrors, ThrowOnError>({ + url: "/config", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + + /** + * List all providers + */ + public providers<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + return (options?.client ?? this.client).get<ConfigProvidersResponses, unknown, ThrowOnError>({ + url: "/config/providers", + ...options, + ...params, + }) + } +} + +export class Tool extends HeyApiClient { + /** + * List all tool IDs (including built-in and dynamically registered) + */ + public ids<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + return (options?.client ?? this.client).get<ToolIdsResponses, ToolIdsErrors, ThrowOnError>({ + url: "/experimental/tool/ids", + ...options, + ...params, + }) + } + + /** + * List tools with JSON schema parameters for a provider/model + */ + public list<ThrowOnError extends boolean = false>( + parameters: { + directory?: string + provider: string + model: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "provider" }, + { in: "query", key: "model" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get<ToolListResponses, ToolListErrors, ThrowOnError>({ + url: "/experimental/tool", + ...options, + ...params, + }) + } +} + +export class Instance extends HeyApiClient { + /** + * Dispose the current instance + */ + public dispose<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + return (options?.client ?? this.client).post<InstanceDisposeResponses, unknown, ThrowOnError>({ + url: "/instance/dispose", + ...options, + ...params, + }) + } +} + +export class Path extends HeyApiClient { + /** + * Get the current path + */ + public get<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + return (options?.client ?? this.client).get<PathGetResponses, unknown, ThrowOnError>({ + url: "/path", + ...options, + ...params, + }) + } +} + +export class Vcs extends HeyApiClient { + /** + * Get VCS info for the current instance + */ + public get<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + return (options?.client ?? this.client).get<VcsGetResponses, unknown, ThrowOnError>({ + url: "/vcs", + ...options, + ...params, + }) + } +} + +export class Session extends HeyApiClient { + /** + * List all sessions + */ + public list<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + return (options?.client ?? this.client).get<SessionListResponses, unknown, ThrowOnError>({ + url: "/session", + ...options, + ...params, + }) + } + + /** + * Create a new session + */ + public create<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + parentID?: string + title?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "body", key: "parentID" }, + { in: "body", key: "title" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post<SessionCreateResponses, SessionCreateErrors, ThrowOnError>({ + url: "/session", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + + /** + * Get session status + */ + public status<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + return (options?.client ?? this.client).get<SessionStatusResponses, SessionStatusErrors, ThrowOnError>({ + url: "/session/status", + ...options, + ...params, + }) + } + + /** + * Delete a session and all its data + */ + public delete<ThrowOnError extends boolean = false>( + parameters: { + sessionID: string + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + ], + }, + ], + ) + return (options?.client ?? this.client).delete<SessionDeleteResponses, SessionDeleteErrors, ThrowOnError>({ + url: "/session/{sessionID}", + ...options, + ...params, + }) + } + + /** + * Get session + */ + public get<ThrowOnError extends boolean = false>( + parameters: { + sessionID: string + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get<SessionGetResponses, SessionGetErrors, ThrowOnError>({ + url: "/session/{sessionID}", + ...options, + ...params, + }) + } + + /** + * Update session properties + */ + public update<ThrowOnError extends boolean = false>( + parameters: { + sessionID: string + directory?: string + title?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + { in: "body", key: "title" }, + ], + }, + ], + ) + return (options?.client ?? this.client).patch<SessionUpdateResponses, SessionUpdateErrors, ThrowOnError>({ + url: "/session/{sessionID}", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + + /** + * Get a session's children + */ + public children<ThrowOnError extends boolean = false>( + parameters: { + sessionID: string + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get<SessionChildrenResponses, SessionChildrenErrors, ThrowOnError>({ + url: "/session/{sessionID}/children", + ...options, + ...params, + }) + } + + /** + * Get the todo list for a session + */ + public todo<ThrowOnError extends boolean = false>( + parameters: { + sessionID: string + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get<SessionTodoResponses, SessionTodoErrors, ThrowOnError>({ + url: "/session/{sessionID}/todo", + ...options, + ...params, + }) + } + + /** + * Analyze the app and create an AGENTS.md file + */ + public init<ThrowOnError extends boolean = false>( + parameters: { + sessionID: string + directory?: string + modelID?: string + providerID?: string + messageID?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + { in: "body", key: "modelID" }, + { in: "body", key: "providerID" }, + { in: "body", key: "messageID" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post<SessionInitResponses, SessionInitErrors, ThrowOnError>({ + url: "/session/{sessionID}/init", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + + /** + * Fork an existing session at a specific message + */ + public fork<ThrowOnError extends boolean = false>( + parameters: { + sessionID: string + directory?: string + messageID?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + { in: "body", key: "messageID" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post<SessionForkResponses, unknown, ThrowOnError>({ + url: "/session/{sessionID}/fork", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + + /** + * Abort a session + */ + public abort<ThrowOnError extends boolean = false>( + parameters: { + sessionID: string + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post<SessionAbortResponses, SessionAbortErrors, ThrowOnError>({ + url: "/session/{sessionID}/abort", + ...options, + ...params, + }) + } + + /** + * Unshare the session + */ + public unshare<ThrowOnError extends boolean = false>( + parameters: { + sessionID: string + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + ], + }, + ], + ) + return (options?.client ?? this.client).delete<SessionUnshareResponses, SessionUnshareErrors, ThrowOnError>({ + url: "/session/{sessionID}/share", + ...options, + ...params, + }) + } + + /** + * Share a session + */ + public share<ThrowOnError extends boolean = false>( + parameters: { + sessionID: string + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post<SessionShareResponses, SessionShareErrors, ThrowOnError>({ + url: "/session/{sessionID}/share", + ...options, + ...params, + }) + } + + /** + * Get the diff for this session + */ + public diff<ThrowOnError extends boolean = false>( + parameters: { + sessionID: string + directory?: string + messageID?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + { in: "query", key: "messageID" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get<SessionDiffResponses, SessionDiffErrors, ThrowOnError>({ + url: "/session/{sessionID}/diff", + ...options, + ...params, + }) + } + + /** + * Summarize the session + */ + public summarize<ThrowOnError extends boolean = false>( + parameters: { + sessionID: string + directory?: string + providerID?: string + modelID?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + { in: "body", key: "providerID" }, + { in: "body", key: "modelID" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post<SessionSummarizeResponses, SessionSummarizeErrors, ThrowOnError>({ + url: "/session/{sessionID}/summarize", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + + /** + * List messages for a session + */ + public messages<ThrowOnError extends boolean = false>( + parameters: { + sessionID: string + directory?: string + limit?: number + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + { in: "query", key: "limit" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get<SessionMessagesResponses, SessionMessagesErrors, ThrowOnError>({ + url: "/session/{sessionID}/message", + ...options, + ...params, + }) + } + + /** + * Create and send a new message to a session + */ + public prompt<ThrowOnError extends boolean = false>( + parameters: { + sessionID: string + directory?: string + messageID?: string + model?: { + providerID: string + modelID: string + } + agent?: string + noReply?: boolean + system?: string + tools?: { + [key: string]: boolean + } + parts?: Array<TextPartInput | FilePartInput | AgentPartInput | SubtaskPartInput> + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + { in: "body", key: "messageID" }, + { in: "body", key: "model" }, + { in: "body", key: "agent" }, + { in: "body", key: "noReply" }, + { in: "body", key: "system" }, + { in: "body", key: "tools" }, + { in: "body", key: "parts" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post<SessionPromptResponses, SessionPromptErrors, ThrowOnError>({ + url: "/session/{sessionID}/message", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + + /** + * Get a message from a session + */ + public message<ThrowOnError extends boolean = false>( + parameters: { + sessionID: string + messageID: string + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "path", key: "messageID" }, + { in: "query", key: "directory" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get<SessionMessageResponses, SessionMessageErrors, ThrowOnError>({ + url: "/session/{sessionID}/message/{messageID}", + ...options, + ...params, + }) + } + + /** + * Create and send a new message to a session, start if needed and return immediately + */ + public promptAsync<ThrowOnError extends boolean = false>( + parameters: { + sessionID: string + directory?: string + messageID?: string + model?: { + providerID: string + modelID: string + } + agent?: string + noReply?: boolean + system?: string + tools?: { + [key: string]: boolean + } + parts?: Array<TextPartInput | FilePartInput | AgentPartInput | SubtaskPartInput> + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + { in: "body", key: "messageID" }, + { in: "body", key: "model" }, + { in: "body", key: "agent" }, + { in: "body", key: "noReply" }, + { in: "body", key: "system" }, + { in: "body", key: "tools" }, + { in: "body", key: "parts" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post<SessionPromptAsyncResponses, SessionPromptAsyncErrors, ThrowOnError>({ + url: "/session/{sessionID}/prompt_async", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + + /** + * Send a new command to a session + */ + public command<ThrowOnError extends boolean = false>( + parameters: { + sessionID: string + directory?: string + messageID?: string + agent?: string + model?: string + arguments?: string + command?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + { in: "body", key: "messageID" }, + { in: "body", key: "agent" }, + { in: "body", key: "model" }, + { in: "body", key: "arguments" }, + { in: "body", key: "command" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post<SessionCommandResponses, SessionCommandErrors, ThrowOnError>({ + url: "/session/{sessionID}/command", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + + /** + * Run a shell command + */ + public shell<ThrowOnError extends boolean = false>( + parameters: { + sessionID: string + directory?: string + agent?: string + model?: { + providerID: string + modelID: string + } + command?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + { in: "body", key: "agent" }, + { in: "body", key: "model" }, + { in: "body", key: "command" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post<SessionShellResponses, SessionShellErrors, ThrowOnError>({ + url: "/session/{sessionID}/shell", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + + /** + * Revert a message + */ + public revert<ThrowOnError extends boolean = false>( + parameters: { + sessionID: string + directory?: string + messageID?: string + partID?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + { in: "body", key: "messageID" }, + { in: "body", key: "partID" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post<SessionRevertResponses, SessionRevertErrors, ThrowOnError>({ + url: "/session/{sessionID}/revert", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + + /** + * Restore all reverted messages + */ + public unrevert<ThrowOnError extends boolean = false>( + parameters: { + sessionID: string + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post<SessionUnrevertResponses, SessionUnrevertErrors, ThrowOnError>({ + url: "/session/{sessionID}/unrevert", + ...options, + ...params, + }) + } +} + +export class Permission extends HeyApiClient { + /** + * Respond to a permission request + */ + public respond<ThrowOnError extends boolean = false>( + parameters: { + sessionID: string + permissionID: string + directory?: string + response?: "once" | "always" | "reject" + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "path", key: "permissionID" }, + { in: "query", key: "directory" }, + { in: "body", key: "response" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post<PermissionRespondResponses, PermissionRespondErrors, ThrowOnError>({ + url: "/session/{sessionID}/permissions/{permissionID}", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } +} + +export class Command extends HeyApiClient { + /** + * List all commands + */ + public list<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + return (options?.client ?? this.client).get<CommandListResponses, unknown, ThrowOnError>({ + url: "/command", + ...options, + ...params, + }) + } +} + +export class Oauth extends HeyApiClient { + /** + * Authorize a provider using OAuth + */ + public authorize<ThrowOnError extends boolean = false>( + parameters: { + providerID: string + directory?: string + method?: number + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "providerID" }, + { in: "query", key: "directory" }, + { in: "body", key: "method" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post< + ProviderOauthAuthorizeResponses, + ProviderOauthAuthorizeErrors, + ThrowOnError + >({ + url: "/provider/{providerID}/oauth/authorize", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + + /** + * Handle OAuth callback for a provider + */ + public callback<ThrowOnError extends boolean = false>( + parameters: { + providerID: string + directory?: string + method?: number + code?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "providerID" }, + { in: "query", key: "directory" }, + { in: "body", key: "method" }, + { in: "body", key: "code" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post< + ProviderOauthCallbackResponses, + ProviderOauthCallbackErrors, + ThrowOnError + >({ + url: "/provider/{providerID}/oauth/callback", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } +} + +export class Provider extends HeyApiClient { + /** + * List all providers + */ + public list<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + return (options?.client ?? this.client).get<ProviderListResponses, unknown, ThrowOnError>({ + url: "/provider", + ...options, + ...params, + }) + } + + /** + * Get provider authentication methods + */ + public auth<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + return (options?.client ?? this.client).get<ProviderAuthResponses, unknown, ThrowOnError>({ + url: "/provider/auth", + ...options, + ...params, + }) + } + + oauth = new Oauth({ client: this.client }) +} + +export class Find extends HeyApiClient { + /** + * Find text in files + */ + public text<ThrowOnError extends boolean = false>( + parameters: { + directory?: string + pattern: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "pattern" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get<FindTextResponses, unknown, ThrowOnError>({ + url: "/find", + ...options, + ...params, + }) + } + + /** + * Find files + */ + public files<ThrowOnError extends boolean = false>( + parameters: { + directory?: string + query: string + dirs?: "true" | "false" + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "query" }, + { in: "query", key: "dirs" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get<FindFilesResponses, unknown, ThrowOnError>({ + url: "/find/file", + ...options, + ...params, + }) + } + + /** + * Find workspace symbols + */ + public symbols<ThrowOnError extends boolean = false>( + parameters: { + directory?: string + query: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "query" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get<FindSymbolsResponses, unknown, ThrowOnError>({ + url: "/find/symbol", + ...options, + ...params, + }) + } +} + +export class File extends HeyApiClient { + /** + * List files and directories + */ + public list<ThrowOnError extends boolean = false>( + parameters: { + directory?: string + path: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "path" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get<FileListResponses, unknown, ThrowOnError>({ + url: "/file", + ...options, + ...params, + }) + } + + /** + * Read a file + */ + public read<ThrowOnError extends boolean = false>( + parameters: { + directory?: string + path: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "path" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get<FileReadResponses, unknown, ThrowOnError>({ + url: "/file/content", + ...options, + ...params, + }) + } + + /** + * Get file status + */ + public status<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + return (options?.client ?? this.client).get<FileStatusResponses, unknown, ThrowOnError>({ + url: "/file/status", + ...options, + ...params, + }) + } +} + +export class App extends HeyApiClient { + /** + * Write a log entry to the server logs + */ + public log<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + service?: string + level?: "debug" | "info" | "error" | "warn" + message?: string + extra?: { + [key: string]: unknown + } + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "body", key: "service" }, + { in: "body", key: "level" }, + { in: "body", key: "message" }, + { in: "body", key: "extra" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post<AppLogResponses, AppLogErrors, ThrowOnError>({ + url: "/log", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + + /** + * List all agents + */ + public agents<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + return (options?.client ?? this.client).get<AppAgentsResponses, unknown, ThrowOnError>({ + url: "/agent", + ...options, + ...params, + }) + } +} + +export class Auth extends HeyApiClient { + /** + * Remove OAuth credentials for an MCP server + */ + public remove<ThrowOnError extends boolean = false>( + parameters: { + name: string + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "name" }, + { in: "query", key: "directory" }, + ], + }, + ], + ) + return (options?.client ?? this.client).delete<McpAuthRemoveResponses, McpAuthRemoveErrors, ThrowOnError>({ + url: "/mcp/{name}/auth", + ...options, + ...params, + }) + } + + /** + * Start OAuth authentication flow for an MCP server + */ + public start<ThrowOnError extends boolean = false>( + parameters: { + name: string + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "name" }, + { in: "query", key: "directory" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post<McpAuthStartResponses, McpAuthStartErrors, ThrowOnError>({ + url: "/mcp/{name}/auth", + ...options, + ...params, + }) + } + + /** + * Complete OAuth authentication with authorization code + */ + public callback<ThrowOnError extends boolean = false>( + parameters: { + name: string + directory?: string + code?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "name" }, + { in: "query", key: "directory" }, + { in: "body", key: "code" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post<McpAuthCallbackResponses, McpAuthCallbackErrors, ThrowOnError>({ + url: "/mcp/{name}/auth/callback", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + + /** + * Start OAuth flow and wait for callback (opens browser) + */ + public authenticate<ThrowOnError extends boolean = false>( + parameters: { + name: string + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "name" }, + { in: "query", key: "directory" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post<McpAuthAuthenticateResponses, McpAuthAuthenticateErrors, ThrowOnError>( + { + url: "/mcp/{name}/auth/authenticate", + ...options, + ...params, + }, + ) + } + + /** + * Set authentication credentials + */ + public set<ThrowOnError extends boolean = false>( + parameters: { + providerID: string + directory?: string + auth?: Auth2 + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "providerID" }, + { in: "query", key: "directory" }, + { key: "auth", map: "body" }, + ], + }, + ], + ) + return (options?.client ?? this.client).put<AuthSetResponses, AuthSetErrors, ThrowOnError>({ + url: "/auth/{providerID}", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } +} + +export class Mcp extends HeyApiClient { + /** + * Get MCP server status + */ + public status<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + return (options?.client ?? this.client).get<McpStatusResponses, unknown, ThrowOnError>({ + url: "/mcp", + ...options, + ...params, + }) + } + + /** + * Add MCP server dynamically + */ + public add<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + name?: string + config?: McpLocalConfig | McpRemoteConfig + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "body", key: "name" }, + { in: "body", key: "config" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post<McpAddResponses, McpAddErrors, ThrowOnError>({ + url: "/mcp", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + + auth = new Auth({ client: this.client }) +} + +export class Lsp extends HeyApiClient { + /** + * Get LSP server status + */ + public status<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + return (options?.client ?? this.client).get<LspStatusResponses, unknown, ThrowOnError>({ + url: "/lsp", + ...options, + ...params, + }) + } +} + +export class Formatter extends HeyApiClient { + /** + * Get formatter status + */ + public status<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + return (options?.client ?? this.client).get<FormatterStatusResponses, unknown, ThrowOnError>({ + url: "/formatter", + ...options, + ...params, + }) + } +} + +export class Control extends HeyApiClient { + /** + * Get the next TUI request from the queue + */ + public next<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + return (options?.client ?? this.client).get<TuiControlNextResponses, unknown, ThrowOnError>({ + url: "/tui/control/next", + ...options, + ...params, + }) + } + + /** + * Submit a response to the TUI request queue + */ + public response<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + body?: unknown + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }, { in: "body" }] }]) + return (options?.client ?? this.client).post<TuiControlResponseResponses, unknown, ThrowOnError>({ + url: "/tui/control/response", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } +} + +export class Tui extends HeyApiClient { + /** + * Append prompt to the TUI + */ + public appendPrompt<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + text?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "body", key: "text" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post<TuiAppendPromptResponses, TuiAppendPromptErrors, ThrowOnError>({ + url: "/tui/append-prompt", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + + /** + * Open the help dialog + */ + public openHelp<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + return (options?.client ?? this.client).post<TuiOpenHelpResponses, unknown, ThrowOnError>({ + url: "/tui/open-help", + ...options, + ...params, + }) + } + + /** + * Open the session dialog + */ + public openSessions<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + return (options?.client ?? this.client).post<TuiOpenSessionsResponses, unknown, ThrowOnError>({ + url: "/tui/open-sessions", + ...options, + ...params, + }) + } + + /** + * Open the theme dialog + */ + public openThemes<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + return (options?.client ?? this.client).post<TuiOpenThemesResponses, unknown, ThrowOnError>({ + url: "/tui/open-themes", + ...options, + ...params, + }) + } + + /** + * Open the model dialog + */ + public openModels<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + return (options?.client ?? this.client).post<TuiOpenModelsResponses, unknown, ThrowOnError>({ + url: "/tui/open-models", + ...options, + ...params, + }) + } + + /** + * Submit the prompt + */ + public submitPrompt<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + return (options?.client ?? this.client).post<TuiSubmitPromptResponses, unknown, ThrowOnError>({ + url: "/tui/submit-prompt", + ...options, + ...params, + }) + } + + /** + * Clear the prompt + */ + public clearPrompt<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + return (options?.client ?? this.client).post<TuiClearPromptResponses, unknown, ThrowOnError>({ + url: "/tui/clear-prompt", + ...options, + ...params, + }) + } + + /** + * Execute a TUI command (e.g. agent_cycle) + */ + public executeCommand<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + command?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "body", key: "command" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post<TuiExecuteCommandResponses, TuiExecuteCommandErrors, ThrowOnError>({ + url: "/tui/execute-command", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + + /** + * Show a toast notification in the TUI + */ + public showToast<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + title?: string + message?: string + variant?: "info" | "success" | "warning" | "error" + duration?: number + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "body", key: "title" }, + { in: "body", key: "message" }, + { in: "body", key: "variant" }, + { in: "body", key: "duration" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post<TuiShowToastResponses, unknown, ThrowOnError>({ + url: "/tui/show-toast", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + + /** + * Publish a TUI event + */ + public publish<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + body?: EventTuiPromptAppend | EventTuiCommandExecute | EventTuiToastShow + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }, { in: "body" }] }]) + return (options?.client ?? this.client).post<TuiPublishResponses, TuiPublishErrors, ThrowOnError>({ + url: "/tui/publish", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + + control = new Control({ client: this.client }) +} + +export class Event extends HeyApiClient { + /** + * Get events + */ + public subscribe<ThrowOnError extends boolean = false>( + parameters?: { + directory?: string + }, + options?: Options<never, ThrowOnError>, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + return (options?.client ?? this.client).sse.get<EventSubscribeResponses, unknown, ThrowOnError>({ + url: "/event", + ...options, + ...params, + }) + } +} + +export class OpencodeClient extends HeyApiClient { + public static readonly __registry = new HeyApiRegistry<OpencodeClient>() + + constructor(args?: { client?: Client; key?: string }) { + super(args) + OpencodeClient.__registry.set(this, args?.key) + } + + global = new Global({ client: this.client }) + + project = new Project({ client: this.client }) + + pty = new Pty({ client: this.client }) + + config = new Config({ client: this.client }) + + tool = new Tool({ client: this.client }) + + instance = new Instance({ client: this.client }) + + path = new Path({ client: this.client }) + + vcs = new Vcs({ client: this.client }) + + session = new Session({ client: this.client }) + + permission = new Permission({ client: this.client }) + + command = new Command({ client: this.client }) + + provider = new Provider({ client: this.client }) + + find = new Find({ client: this.client }) + + file = new File({ client: this.client }) + + app = new App({ client: this.client }) + + mcp = new Mcp({ client: this.client }) + + lsp = new Lsp({ client: this.client }) + + formatter = new Formatter({ client: this.client }) + + tui = new Tui({ client: this.client }) + + auth = new Auth({ client: this.client }) + + event = new Event({ client: this.client }) +} diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts new file mode 100644 index 000000000..8bbfa40a3 --- /dev/null +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -0,0 +1,3845 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type ClientOptions = { + baseUrl: `${string}://${string}` | (string & {}) +} + +export type EventServerInstanceDisposed = { + type: "server.instance.disposed" + properties: { + directory: string + } +} + +export type EventInstallationUpdated = { + type: "installation.updated" + properties: { + version: string + } +} + +export type EventInstallationUpdateAvailable = { + type: "installation.update-available" + properties: { + version: 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<FileDiff> + } + agent: string + model: { + providerID: string + modelID: string + } + system?: string + tools?: { + [key: string]: boolean + } +} + +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 + } +} + +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 + 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<FilePart> +} + +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<string> +} + +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 + } + | 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 Permission = { + id: string + type: string + pattern?: string | Array<string> + sessionID: string + messageID: string + callID?: string + title: string + metadata: { + [key: string]: unknown + } + time: { + created: number + } +} + +export type EventPermissionUpdated = { + type: "permission.updated" + properties: Permission +} + +export type EventPermissionReplied = { + type: "permission.replied" + properties: { + sessionID: string + permissionID: string + response: string + } +} + +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<Todo> + } +} + +export type EventCommandExecuted = { + type: "command.executed" + properties: { + name: string + sessionID: string + arguments: string + messageID: string + } +} + +export type Session = { + id: string + projectID: string + directory: string + parentID?: string + summary?: { + additions: number + deletions: number + files: number + diffs?: Array<FileDiff> + } + share?: { + url: string + } + title: string + version: string + time: { + created: number + updated: number + compacting?: number + } + 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<FileDiff> + } +} + +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 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 Pty = { + id: string + title: string + command: string + args: Array<string> + 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 Event = + | EventServerInstanceDisposed + | EventInstallationUpdated + | EventInstallationUpdateAvailable + | EventLspClientDiagnostics + | EventLspUpdated + | EventMessageUpdated + | EventMessageRemoved + | EventMessagePartUpdated + | EventMessagePartRemoved + | EventPermissionUpdated + | EventPermissionReplied + | EventSessionStatus + | EventSessionIdle + | EventSessionCompacted + | EventFileEdited + | EventTodoUpdated + | EventCommandExecuted + | EventSessionCreated + | EventSessionUpdated + | EventSessionDeleted + | EventSessionDiff + | EventSessionError + | EventFileWatcherUpdated + | EventVcsBranchUpdated + | EventTuiPromptAppend + | EventTuiCommandExecute + | EventTuiToastShow + | EventPtyCreated + | EventPtyUpdated + | EventPtyExited + | EventPtyDeleted + | EventServerConnected + +export type GlobalEvent = { + directory: string + payload: Event +} + +export type Project = { + id: string + worktree: string + vcsDir?: string + vcs?: "git" + time: { + created: number + initialized?: number + } +} + +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 + /** + * 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 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 + /** + * List available commands + */ + command_list?: string + /** + * List agents + */ + agent_list?: string + /** + * Next agent + */ + agent_cycle?: string + /** + * Previous agent + */ + agent_cycle_reverse?: string + /** + * Clear input field + */ + input_clear?: string + /** + * Forward delete + */ + input_forward_delete?: string + /** + * Paste from clipboard + */ + input_paste?: string + /** + * Submit input + */ + input_submit?: string + /** + * Insert newline in input + */ + input_newline?: 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 + /** + * Suspend terminal + */ + terminal_suspend?: string +} + +export type AgentConfig = { + model?: string + temperature?: number + top_p?: number + prompt?: string + tools?: { + [key: string]: boolean + } + disable?: boolean + /** + * Description of when to use the agent + */ + description?: string + mode?: "subagent" | "primary" | "all" + /** + * Hex color code for the agent (e.g., #FF5733) + */ + color?: string + /** + * Maximum number of agentic iterations before forcing text-only response + */ + maxSteps?: number + permission?: { + edit?: "ask" | "allow" | "deny" + bash?: + | "ask" + | "allow" + | "deny" + | { + [key: string]: "ask" | "allow" | "deny" + } + webfetch?: "ask" | "allow" | "deny" + doom_loop?: "ask" | "allow" | "deny" + external_directory?: "ask" | "allow" | "deny" + } + [key: string]: + | unknown + | string + | number + | { + [key: string]: boolean + } + | boolean + | "subagent" + | "primary" + | "all" + | string + | number + | { + edit?: "ask" | "allow" | "deny" + bash?: + | "ask" + | "allow" + | "deny" + | { + [key: string]: "ask" | "allow" | "deny" + } + webfetch?: "ask" | "allow" | "deny" + doom_loop?: "ask" | "allow" | "deny" + external_directory?: "ask" | "allow" | "deny" + } + | undefined +} + +export type ProviderConfig = { + api?: string + name?: string + env?: Array<string> + id?: string + npm?: string + models?: { + [key: string]: { + id?: string + name?: string + release_date?: string + attachment?: boolean + reasoning?: boolean + temperature?: boolean + tool_call?: boolean + 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 + } + } + } + whitelist?: Array<string> + blacklist?: Array<string> + 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<string> + /** + * 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 + /** + * 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" + } + /** + * Command configuration, see https://opencode.ai/docs/commands + */ + command?: { + [key: string]: { + template: string + description?: string + agent?: string + model?: string + subtask?: boolean + } + } + watcher?: { + ignore?: Array<string> + } + plugin?: Array<string> + 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<string> + /** + * When set, ONLY these providers will be enabled. All other providers will be ignored + */ + enabled_providers?: Array<string> + /** + * 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 + /** + * 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 + [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<string> + environment?: { + [key: string]: string + } + extensions?: Array<string> + } + } + lsp?: + | false + | { + [key: string]: + | { + disabled: true + } + | { + command: Array<string> + extensions?: Array<string> + disabled?: boolean + env?: { + [key: string]: string + } + initialization?: { + [key: string]: unknown + } + } + } + /** + * Additional instruction files or patterns to include + */ + instructions?: Array<string> + layout?: LayoutConfig + permission?: { + edit?: "ask" | "allow" | "deny" + bash?: + | "ask" + | "allow" + | "deny" + | { + [key: string]: "ask" | "allow" | "deny" + } + webfetch?: "ask" | "allow" | "deny" + doom_loop?: "ask" | "allow" | "deny" + external_directory?: "ask" | "allow" | "deny" + } + tools?: { + [key: string]: boolean + } + enterprise?: { + /** + * Enterprise URL + */ + url?: string + } + experimental?: { + hook?: { + file_edited?: { + [key: string]: Array<{ + command: Array<string> + environment?: { + [key: string]: string + } + }> + } + session_completed?: Array<{ + command: Array<string> + 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<string> + } +} + +export type ToolIds = Array<string> + +export type ToolListItem = { + id: string + description: string + parameters: unknown +} + +export type ToolList = Array<ToolListItem> + +export type Path = { + 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 +} + +export type Command = { + name: string + description?: string + agent?: string + model?: string + template: string + subtask?: boolean +} + +export type Model = { + id: string + providerID: string + api: { + id: string + url: string + npm: string + } + name: 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 + } + } + 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 + } +} + +export type Provider = { + id: string + name: string + source: "env" | "config" | "custom" | "api" + env: Array<string> + 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<string> + }> + 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" + builtIn: boolean + topP?: number + temperature?: number + color?: string + permission: { + edit: "ask" | "allow" | "deny" + bash: { + [key: string]: "ask" | "allow" | "deny" + } + webfetch?: "ask" | "allow" | "deny" + doom_loop?: "ask" | "allow" | "deny" + external_directory?: "ask" | "allow" | "deny" + } + model?: { + modelID: string + providerID: string + } + prompt?: string + tools: { + [key: string]: boolean + } + options: { + [key: string]: unknown + } + maxSteps?: 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<string> + 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 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 ProjectListData = { + body?: never + path?: never + query?: { + directory?: string + } + url: "/project" +} + +export type ProjectListResponses = { + /** + * List of projects + */ + 200: Array<Project> +} + +export type ProjectListResponse = ProjectListResponses[keyof ProjectListResponses] + +export type ProjectCurrentData = { + body?: never + path?: never + query?: { + directory?: string + } + url: "/project/current" +} + +export type ProjectCurrentResponses = { + /** + * Current project + */ + 200: Project +} + +export type ProjectCurrentResponse = ProjectCurrentResponses[keyof ProjectCurrentResponses] + +export type PtyListData = { + body?: never + path?: never + query?: { + directory?: string + } + url: "/pty" +} + +export type PtyListResponses = { + /** + * List of sessions + */ + 200: Array<Pty> +} + +export type PtyListResponse = PtyListResponses[keyof PtyListResponses] + +export type PtyCreateData = { + body?: { + command?: string + args?: Array<string> + 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<Session> +} + +export type SessionListResponse = SessionListResponses[keyof SessionListResponses] + +export type SessionCreateData = { + body?: { + parentID?: string + title?: string + } + 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 + } + 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<Session> +} + +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<Todo> +} + +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<FileDiff> +} + +export type SessionDiffResponse = SessionDiffResponses[keyof SessionDiffResponses] + +export type SessionSummarizeData = { + body?: { + providerID: string + modelID: string + } + 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<Part> + }> +} + +export type SessionMessagesResponse = SessionMessagesResponses[keyof SessionMessagesResponses] + +export type SessionPromptData = { + body?: { + messageID?: string + model?: { + providerID: string + modelID: string + } + agent?: string + noReply?: boolean + system?: string + tools?: { + [key: string]: boolean + } + parts: Array<TextPartInput | FilePartInput | AgentPartInput | SubtaskPartInput> + } + 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<Part> + } +} + +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<Part> + } +} + +export type SessionMessageResponse = SessionMessageResponses[keyof SessionMessageResponses] + +export type SessionPromptAsyncData = { + body?: { + messageID?: string + model?: { + providerID: string + modelID: string + } + agent?: string + noReply?: boolean + system?: string + tools?: { + [key: string]: boolean + } + parts: Array<TextPartInput | FilePartInput | AgentPartInput | SubtaskPartInput> + } + 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 + } + 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<Part> + } +} + +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 CommandListData = { + body?: never + path?: never + query?: { + directory?: string + } + url: "/command" +} + +export type CommandListResponses = { + /** + * List of commands + */ + 200: Array<Command> +} + +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<Provider> + 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<string> + id: string + npm?: string + models: { + [key: string]: { + id: string + name: string + release_date: string + attachment: boolean + reasoning: boolean + temperature: boolean + tool_call: boolean + 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 + } + } + } + }> + default: { + [key: string]: string + } + connected: Array<string> + } +} + +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<ProviderAuthMethod> + } +} + +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" + } + url: "/find/file" +} + +export type FindFilesResponses = { + /** + * File paths + */ + 200: Array<string> +} + +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<Symbol> +} + +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<FileNode> +} + +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<File> +} + +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<Agent> +} + +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 LspStatusData = { + body?: never + path?: never + query?: { + directory?: string + } + url: "/lsp" +} + +export type LspStatusResponses = { + /** + * LSP server status + */ + 200: Array<LspStatus> +} + +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<FormatterStatus> +} + +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/sdk/js/src/v2/index.ts b/packages/sdk/js/src/v2/index.ts new file mode 100644 index 000000000..d044f5ad6 --- /dev/null +++ b/packages/sdk/js/src/v2/index.ts @@ -0,0 +1,21 @@ +export * from "./client.js" +export * from "./server.js" + +import { createOpencodeClient } from "./client.js" +import { createOpencodeServer } from "./server.js" +import type { ServerOptions } from "./server.js" + +export async function createOpencode(options?: ServerOptions) { + const server = await createOpencodeServer({ + ...options, + }) + + const client = createOpencodeClient({ + baseUrl: server.url, + }) + + return { + client, + server, + } +} diff --git a/packages/sdk/js/src/v2/server.ts b/packages/sdk/js/src/v2/server.ts new file mode 100644 index 000000000..a09e14ab2 --- /dev/null +++ b/packages/sdk/js/src/v2/server.ts @@ -0,0 +1,120 @@ +import { spawn } from "node:child_process" +import { type Config } from "./gen/types.gen.js" + +export type ServerOptions = { + hostname?: string + port?: number + signal?: AbortSignal + timeout?: number + config?: Config +} + +export type TuiOptions = { + project?: string + model?: string + session?: string + agent?: string + signal?: AbortSignal + config?: Config +} + +export async function createOpencodeServer(options?: ServerOptions) { + options = Object.assign( + { + hostname: "127.0.0.1", + port: 4096, + timeout: 5000, + }, + options ?? {}, + ) + + const proc = spawn(`opencode`, [`serve`, `--hostname=${options.hostname}`, `--port=${options.port}`], { + signal: options.signal, + env: { + ...process.env, + OPENCODE_CONFIG_CONTENT: JSON.stringify(options.config ?? {}), + }, + }) + + const url = await new Promise<string>((resolve, reject) => { + const id = setTimeout(() => { + reject(new Error(`Timeout waiting for server to start after ${options.timeout}ms`)) + }, options.timeout) + let output = "" + proc.stdout?.on("data", (chunk) => { + output += chunk.toString() + const lines = output.split("\n") + for (const line of lines) { + if (line.startsWith("opencode server listening")) { + const match = line.match(/on\s+(https?:\/\/[^\s]+)/) + if (!match) { + throw new Error(`Failed to parse server url from output: ${line}`) + } + clearTimeout(id) + resolve(match[1]!) + return + } + } + }) + proc.stderr?.on("data", (chunk) => { + output += chunk.toString() + }) + proc.on("exit", (code) => { + clearTimeout(id) + let msg = `Server exited with code ${code}` + if (output.trim()) { + msg += `\nServer output: ${output}` + } + reject(new Error(msg)) + }) + proc.on("error", (error) => { + clearTimeout(id) + reject(error) + }) + if (options.signal) { + options.signal.addEventListener("abort", () => { + clearTimeout(id) + reject(new Error("Aborted")) + }) + } + }) + + return { + url, + close() { + proc.kill() + }, + } +} + +export function createOpencodeTui(options?: TuiOptions) { + const args = [] + + if (options?.project) { + args.push(`--project=${options.project}`) + } + if (options?.model) { + args.push(`--model=${options.model}`) + } + if (options?.session) { + args.push(`--session=${options.session}`) + } + if (options?.agent) { + args.push(`--agent=${options.agent}`) + } + + const proc = spawn(`opencode`, args, { + signal: options?.signal, + stdio: "inherit", + env: { + ...process.env, + OPENCODE_CONFIG_CONTENT: JSON.stringify(options?.config ?? {}), + }, + }) + + return { + close() { + proc.kill() + }, + } +} |
