ci: new publish method (#1451)

This commit is contained in:
Dax
2025-07-31 01:00:29 -04:00
committed by GitHub
parent b09ebf4645
commit 33cef075d2
190 changed files with 16142 additions and 13342 deletions

View File

@@ -1,2 +0,0 @@
/** @deprecated Import from ./core/api-promise instead */
export * from './core/api-promise';

View File

@@ -1,862 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import type { RequestInit, RequestInfo, BodyInit } from './internal/builtin-types';
import type { HTTPMethod, PromiseOrValue, MergedRequestInit, FinalizedRequestInit } from './internal/types';
import { uuid4 } from './internal/utils/uuid';
import { validatePositiveInteger, isAbsoluteURL, safeJSON } from './internal/utils/values';
import { sleep } from './internal/utils/sleep';
export type { Logger, LogLevel } from './internal/utils/log';
import { castToError, isAbortError } from './internal/errors';
import type { APIResponseProps } from './internal/parse';
import { getPlatformHeaders } from './internal/detect-platform';
import * as Shims from './internal/shims';
import * as Opts from './internal/request-options';
import { VERSION } from './version';
import * as Errors from './core/error';
import * as Uploads from './core/uploads';
import * as API from './resources/index';
import { APIPromise } from './core/api-promise';
import {
App,
AppInitResponse,
AppLogParams,
AppLogResponse,
AppModesResponse,
AppProvidersResponse,
AppResource,
Mode,
Model,
Provider,
} from './resources/app';
import {
Config,
ConfigResource,
KeybindsConfig,
McpLocalConfig,
McpRemoteConfig,
ModeConfig,
} from './resources/config';
import { Event, EventListResponse } from './resources/event';
import { File, FileReadParams, FileReadResponse, FileResource, FileStatusResponse } from './resources/file';
import {
Find,
FindFilesParams,
FindFilesResponse,
FindSymbolsParams,
FindSymbolsResponse,
FindTextParams,
FindTextResponse,
Symbol,
} from './resources/find';
import {
AssistantMessage,
FilePart,
FilePartInput,
FilePartSource,
FilePartSourceText,
FileSource,
Message,
Part,
Session,
SessionAbortResponse,
SessionChatParams,
SessionDeleteResponse,
SessionInitParams,
SessionInitResponse,
SessionListResponse,
SessionMessagesResponse,
SessionResource,
SessionRevertParams,
SessionSummarizeParams,
SessionSummarizeResponse,
SnapshotPart,
StepFinishPart,
StepStartPart,
SymbolSource,
TextPart,
TextPartInput,
ToolPart,
ToolStateCompleted,
ToolStateError,
ToolStatePending,
ToolStateRunning,
UserMessage,
} from './resources/session';
import { Tui, TuiAppendPromptParams, TuiAppendPromptResponse, TuiOpenHelpResponse } from './resources/tui';
import { type Fetch } from './internal/builtin-types';
import { HeadersLike, NullableHeaders, buildHeaders } from './internal/headers';
import { FinalRequestOptions, RequestOptions } from './internal/request-options';
import { readEnv } from './internal/utils/env';
import {
type LogLevel,
type Logger,
formatRequestDetails,
loggerFor,
parseLogLevel,
} from './internal/utils/log';
import { isEmptyObj } from './internal/utils/values';
export interface ClientOptions {
/**
* Override the default base URL for the API, e.g., "https://api.example.com/v2/"
*
* Defaults to process.env['OPENCODE_BASE_URL'].
*/
baseURL?: string | null | undefined;
/**
* The maximum amount of time (in milliseconds) that the client should wait for a response
* from the server before timing out a single request.
*
* Note that request timeouts are retried by default, so in a worst-case scenario you may wait
* much longer than this timeout before the promise succeeds or fails.
*
* @unit milliseconds
*/
timeout?: number | undefined;
/**
* Additional `RequestInit` options to be passed to `fetch` calls.
* Properties will be overridden by per-request `fetchOptions`.
*/
fetchOptions?: MergedRequestInit | undefined;
/**
* Specify a custom `fetch` function implementation.
*
* If not provided, we expect that `fetch` is defined globally.
*/
fetch?: Fetch | undefined;
/**
* The maximum number of times that the client will retry a request in case of a
* temporary failure, like a network error or a 5XX error from the server.
*
* @default 2
*/
maxRetries?: number | undefined;
/**
* Default headers to include with every request to the API.
*
* These can be removed in individual requests by explicitly setting the
* header to `null` in request options.
*/
defaultHeaders?: HeadersLike | undefined;
/**
* Default query parameters to include with every request to the API.
*
* These can be removed in individual requests by explicitly setting the
* param to `undefined` in request options.
*/
defaultQuery?: Record<string, string | undefined> | undefined;
/**
* Set the log level.
*
* Defaults to process.env['OPENCODE_LOG'] or 'warn' if it isn't set.
*/
logLevel?: LogLevel | undefined;
/**
* Set the logger.
*
* Defaults to globalThis.console.
*/
logger?: Logger | undefined;
}
/**
* API Client for interfacing with the Opencode API.
*/
export class Opencode {
baseURL: string;
maxRetries: number;
timeout: number;
logger: Logger | undefined;
logLevel: LogLevel | undefined;
fetchOptions: MergedRequestInit | undefined;
private fetch: Fetch;
#encoder: Opts.RequestEncoder;
protected idempotencyHeader?: string;
private _options: ClientOptions;
/**
* API Client for interfacing with the Opencode API.
*
* @param {string} [opts.baseURL=process.env['OPENCODE_BASE_URL'] ?? http://localhost:54321] - Override the default base URL for the API.
* @param {number} [opts.timeout=1 minute] - The maximum amount of time (in milliseconds) the client will wait for a response before timing out.
* @param {MergedRequestInit} [opts.fetchOptions] - Additional `RequestInit` options to be passed to `fetch` calls.
* @param {Fetch} [opts.fetch] - Specify a custom `fetch` function implementation.
* @param {number} [opts.maxRetries=2] - The maximum number of times the client will retry a request.
* @param {HeadersLike} opts.defaultHeaders - Default headers to include with every request to the API.
* @param {Record<string, string | undefined>} opts.defaultQuery - Default query parameters to include with every request to the API.
*/
constructor({ baseURL = readEnv('OPENCODE_BASE_URL'), ...opts }: ClientOptions = {}) {
const options: ClientOptions = {
...opts,
baseURL: baseURL || `http://localhost:54321`,
};
this.baseURL = options.baseURL!;
this.timeout = options.timeout ?? Opencode.DEFAULT_TIMEOUT /* 1 minute */;
this.logger = options.logger ?? console;
const defaultLogLevel = 'warn';
// Set default logLevel early so that we can log a warning in parseLogLevel.
this.logLevel = defaultLogLevel;
this.logLevel =
parseLogLevel(options.logLevel, 'ClientOptions.logLevel', this) ??
parseLogLevel(readEnv('OPENCODE_LOG'), "process.env['OPENCODE_LOG']", this) ??
defaultLogLevel;
this.fetchOptions = options.fetchOptions;
this.maxRetries = options.maxRetries ?? 2;
this.fetch = options.fetch ?? Shims.getDefaultFetch();
this.#encoder = Opts.FallbackEncoder;
this._options = options;
}
/**
* Create a new client instance re-using the same options given to the current client with optional overriding.
*/
withOptions(options: Partial<ClientOptions>): this {
const client = new (this.constructor as any as new (props: ClientOptions) => typeof this)({
...this._options,
baseURL: this.baseURL,
maxRetries: this.maxRetries,
timeout: this.timeout,
logger: this.logger,
logLevel: this.logLevel,
fetch: this.fetch,
fetchOptions: this.fetchOptions,
...options,
});
return client;
}
/**
* Check whether the base URL is set to its default.
*/
#baseURLOverridden(): boolean {
return this.baseURL !== 'http://localhost:54321';
}
protected defaultQuery(): Record<string, string | undefined> | undefined {
return this._options.defaultQuery;
}
protected validateHeaders({ values, nulls }: NullableHeaders) {
return;
}
/**
* Basic re-implementation of `qs.stringify` for primitive types.
*/
protected stringifyQuery(query: Record<string, unknown>): string {
return Object.entries(query)
.filter(([_, value]) => typeof value !== 'undefined')
.map(([key, value]) => {
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
}
if (value === null) {
return `${encodeURIComponent(key)}=`;
}
throw new Errors.OpencodeError(
`Cannot stringify type ${typeof value}; Expected string, number, boolean, or null. If you need to pass nested query parameters, you can manually encode them, e.g. { query: { 'foo[key1]': value1, 'foo[key2]': value2 } }, and please open a GitHub issue requesting better support for your use case.`,
);
})
.join('&');
}
private getUserAgent(): string {
return `${this.constructor.name}/JS ${VERSION}`;
}
protected defaultIdempotencyKey(): string {
return `stainless-node-retry-${uuid4()}`;
}
protected makeStatusError(
status: number,
error: Object,
message: string | undefined,
headers: Headers,
): Errors.APIError {
return Errors.APIError.generate(status, error, message, headers);
}
buildURL(
path: string,
query: Record<string, unknown> | null | undefined,
defaultBaseURL?: string | undefined,
): string {
const baseURL = (!this.#baseURLOverridden() && defaultBaseURL) || this.baseURL;
const url =
isAbsoluteURL(path) ?
new URL(path)
: new URL(baseURL + (baseURL.endsWith('/') && path.startsWith('/') ? path.slice(1) : path));
const defaultQuery = this.defaultQuery();
if (!isEmptyObj(defaultQuery)) {
query = { ...defaultQuery, ...query };
}
if (typeof query === 'object' && query && !Array.isArray(query)) {
url.search = this.stringifyQuery(query as Record<string, unknown>);
}
return url.toString();
}
/**
* Used as a callback for mutating the given `FinalRequestOptions` object.
*/
protected async prepareOptions(options: FinalRequestOptions): Promise<void> {}
/**
* Used as a callback for mutating the given `RequestInit` object.
*
* This is useful for cases where you want to add certain headers based off of
* the request properties, e.g. `method` or `url`.
*/
protected async prepareRequest(
request: RequestInit,
{ url, options }: { url: string; options: FinalRequestOptions },
): Promise<void> {}
get<Rsp>(path: string, opts?: PromiseOrValue<RequestOptions>): APIPromise<Rsp> {
return this.methodRequest('get', path, opts);
}
post<Rsp>(path: string, opts?: PromiseOrValue<RequestOptions>): APIPromise<Rsp> {
return this.methodRequest('post', path, opts);
}
patch<Rsp>(path: string, opts?: PromiseOrValue<RequestOptions>): APIPromise<Rsp> {
return this.methodRequest('patch', path, opts);
}
put<Rsp>(path: string, opts?: PromiseOrValue<RequestOptions>): APIPromise<Rsp> {
return this.methodRequest('put', path, opts);
}
delete<Rsp>(path: string, opts?: PromiseOrValue<RequestOptions>): APIPromise<Rsp> {
return this.methodRequest('delete', path, opts);
}
private methodRequest<Rsp>(
method: HTTPMethod,
path: string,
opts?: PromiseOrValue<RequestOptions>,
): APIPromise<Rsp> {
return this.request(
Promise.resolve(opts).then((opts) => {
return { method, path, ...opts };
}),
);
}
request<Rsp>(
options: PromiseOrValue<FinalRequestOptions>,
remainingRetries: number | null = null,
): APIPromise<Rsp> {
return new APIPromise(this, this.makeRequest(options, remainingRetries, undefined));
}
private async makeRequest(
optionsInput: PromiseOrValue<FinalRequestOptions>,
retriesRemaining: number | null,
retryOfRequestLogID: string | undefined,
): Promise<APIResponseProps> {
const options = await optionsInput;
const maxRetries = options.maxRetries ?? this.maxRetries;
if (retriesRemaining == null) {
retriesRemaining = maxRetries;
}
await this.prepareOptions(options);
const { req, url, timeout } = await this.buildRequest(options, {
retryCount: maxRetries - retriesRemaining,
});
await this.prepareRequest(req, { url, options });
/** Not an API request ID, just for correlating local log entries. */
const requestLogID = 'log_' + ((Math.random() * (1 << 24)) | 0).toString(16).padStart(6, '0');
const retryLogStr = retryOfRequestLogID === undefined ? '' : `, retryOf: ${retryOfRequestLogID}`;
const startTime = Date.now();
loggerFor(this).debug(
`[${requestLogID}] sending request`,
formatRequestDetails({
retryOfRequestLogID,
method: options.method,
url,
options,
headers: req.headers,
}),
);
if (options.signal?.aborted) {
throw new Errors.APIUserAbortError();
}
const controller = new AbortController();
const response = await this.fetchWithTimeout(url, req, timeout, controller).catch(castToError);
const headersTime = Date.now();
if (response instanceof Error) {
const retryMessage = `retrying, ${retriesRemaining} attempts remaining`;
if (options.signal?.aborted) {
throw new Errors.APIUserAbortError();
}
// detect native connection timeout errors
// deno throws "TypeError: error sending request for url (https://example/): client error (Connect): tcp connect error: Operation timed out (os error 60): Operation timed out (os error 60)"
// undici throws "TypeError: fetch failed" with cause "ConnectTimeoutError: Connect Timeout Error (attempted address: example:443, timeout: 1ms)"
// others do not provide enough information to distinguish timeouts from other connection errors
const isTimeout =
isAbortError(response) ||
/timed? ?out/i.test(String(response) + ('cause' in response ? String(response.cause) : ''));
if (retriesRemaining) {
loggerFor(this).info(
`[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} - ${retryMessage}`,
);
loggerFor(this).debug(
`[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} (${retryMessage})`,
formatRequestDetails({
retryOfRequestLogID,
url,
durationMs: headersTime - startTime,
message: response.message,
}),
);
return this.retryRequest(options, retriesRemaining, retryOfRequestLogID ?? requestLogID);
}
loggerFor(this).info(
`[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} - error; no more retries left`,
);
loggerFor(this).debug(
`[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} (error; no more retries left)`,
formatRequestDetails({
retryOfRequestLogID,
url,
durationMs: headersTime - startTime,
message: response.message,
}),
);
if (isTimeout) {
throw new Errors.APIConnectionTimeoutError();
}
throw new Errors.APIConnectionError({ cause: response });
}
const responseInfo = `[${requestLogID}${retryLogStr}] ${req.method} ${url} ${
response.ok ? 'succeeded' : 'failed'
} with status ${response.status} in ${headersTime - startTime}ms`;
if (!response.ok) {
const shouldRetry = await this.shouldRetry(response);
if (retriesRemaining && shouldRetry) {
const retryMessage = `retrying, ${retriesRemaining} attempts remaining`;
// We don't need the body of this response.
await Shims.CancelReadableStream(response.body);
loggerFor(this).info(`${responseInfo} - ${retryMessage}`);
loggerFor(this).debug(
`[${requestLogID}] response error (${retryMessage})`,
formatRequestDetails({
retryOfRequestLogID,
url: response.url,
status: response.status,
headers: response.headers,
durationMs: headersTime - startTime,
}),
);
return this.retryRequest(
options,
retriesRemaining,
retryOfRequestLogID ?? requestLogID,
response.headers,
);
}
const retryMessage = shouldRetry ? `error; no more retries left` : `error; not retryable`;
loggerFor(this).info(`${responseInfo} - ${retryMessage}`);
const errText = await response.text().catch((err: any) => castToError(err).message);
const errJSON = safeJSON(errText);
const errMessage = errJSON ? undefined : errText;
loggerFor(this).debug(
`[${requestLogID}] response error (${retryMessage})`,
formatRequestDetails({
retryOfRequestLogID,
url: response.url,
status: response.status,
headers: response.headers,
message: errMessage,
durationMs: Date.now() - startTime,
}),
);
const err = this.makeStatusError(response.status, errJSON, errMessage, response.headers);
throw err;
}
loggerFor(this).info(responseInfo);
loggerFor(this).debug(
`[${requestLogID}] response start`,
formatRequestDetails({
retryOfRequestLogID,
url: response.url,
status: response.status,
headers: response.headers,
durationMs: headersTime - startTime,
}),
);
return { response, options, controller, requestLogID, retryOfRequestLogID, startTime };
}
async fetchWithTimeout(
url: RequestInfo,
init: RequestInit | undefined,
ms: number,
controller: AbortController,
): Promise<Response> {
const { signal, method, ...options } = init || {};
if (signal) signal.addEventListener('abort', () => controller.abort());
const timeout = setTimeout(() => controller.abort(), ms);
const isReadableBody =
((globalThis as any).ReadableStream && options.body instanceof (globalThis as any).ReadableStream) ||
(typeof options.body === 'object' && options.body !== null && Symbol.asyncIterator in options.body);
const fetchOptions: RequestInit = {
signal: controller.signal as any,
...(isReadableBody ? { duplex: 'half' } : {}),
method: 'GET',
...options,
};
if (method) {
// Custom methods like 'patch' need to be uppercased
// See https://github.com/nodejs/undici/issues/2294
fetchOptions.method = method.toUpperCase();
}
try {
// use undefined this binding; fetch errors if bound to something else in browser/cloudflare
return await this.fetch.call(undefined, url, fetchOptions);
} finally {
clearTimeout(timeout);
}
}
private async shouldRetry(response: Response): Promise<boolean> {
// Note this is not a standard header.
const shouldRetryHeader = response.headers.get('x-should-retry');
// If the server explicitly says whether or not to retry, obey.
if (shouldRetryHeader === 'true') return true;
if (shouldRetryHeader === 'false') return false;
// Retry on request timeouts.
if (response.status === 408) return true;
// Retry on lock timeouts.
if (response.status === 409) return true;
// Retry on rate limits.
if (response.status === 429) return true;
// Retry internal errors.
if (response.status >= 500) return true;
return false;
}
private async retryRequest(
options: FinalRequestOptions,
retriesRemaining: number,
requestLogID: string,
responseHeaders?: Headers | undefined,
): Promise<APIResponseProps> {
let timeoutMillis: number | undefined;
// Note the `retry-after-ms` header may not be standard, but is a good idea and we'd like proactive support for it.
const retryAfterMillisHeader = responseHeaders?.get('retry-after-ms');
if (retryAfterMillisHeader) {
const timeoutMs = parseFloat(retryAfterMillisHeader);
if (!Number.isNaN(timeoutMs)) {
timeoutMillis = timeoutMs;
}
}
// About the Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
const retryAfterHeader = responseHeaders?.get('retry-after');
if (retryAfterHeader && !timeoutMillis) {
const timeoutSeconds = parseFloat(retryAfterHeader);
if (!Number.isNaN(timeoutSeconds)) {
timeoutMillis = timeoutSeconds * 1000;
} else {
timeoutMillis = Date.parse(retryAfterHeader) - Date.now();
}
}
// If the API asks us to wait a certain amount of time (and it's a reasonable amount),
// just do what it says, but otherwise calculate a default
if (!(timeoutMillis && 0 <= timeoutMillis && timeoutMillis < 60 * 1000)) {
const maxRetries = options.maxRetries ?? this.maxRetries;
timeoutMillis = this.calculateDefaultRetryTimeoutMillis(retriesRemaining, maxRetries);
}
await sleep(timeoutMillis);
return this.makeRequest(options, retriesRemaining - 1, requestLogID);
}
private calculateDefaultRetryTimeoutMillis(retriesRemaining: number, maxRetries: number): number {
const initialRetryDelay = 0.5;
const maxRetryDelay = 8.0;
const numRetries = maxRetries - retriesRemaining;
// Apply exponential backoff, but not more than the max.
const sleepSeconds = Math.min(initialRetryDelay * Math.pow(2, numRetries), maxRetryDelay);
// Apply some jitter, take up to at most 25 percent of the retry time.
const jitter = 1 - Math.random() * 0.25;
return sleepSeconds * jitter * 1000;
}
async buildRequest(
inputOptions: FinalRequestOptions,
{ retryCount = 0 }: { retryCount?: number } = {},
): Promise<{ req: FinalizedRequestInit; url: string; timeout: number }> {
const options = { ...inputOptions };
const { method, path, query, defaultBaseURL } = options;
const url = this.buildURL(path!, query as Record<string, unknown>, defaultBaseURL);
if ('timeout' in options) validatePositiveInteger('timeout', options.timeout);
options.timeout = options.timeout ?? this.timeout;
const { bodyHeaders, body } = this.buildBody({ options });
const reqHeaders = await this.buildHeaders({ options: inputOptions, method, bodyHeaders, retryCount });
const req: FinalizedRequestInit = {
method,
headers: reqHeaders,
...(options.signal && { signal: options.signal }),
...((globalThis as any).ReadableStream &&
body instanceof (globalThis as any).ReadableStream && { duplex: 'half' }),
...(body && { body }),
...((this.fetchOptions as any) ?? {}),
...((options.fetchOptions as any) ?? {}),
};
return { req, url, timeout: options.timeout };
}
private async buildHeaders({
options,
method,
bodyHeaders,
retryCount,
}: {
options: FinalRequestOptions;
method: HTTPMethod;
bodyHeaders: HeadersLike;
retryCount: number;
}): Promise<Headers> {
let idempotencyHeaders: HeadersLike = {};
if (this.idempotencyHeader && method !== 'get') {
if (!options.idempotencyKey) options.idempotencyKey = this.defaultIdempotencyKey();
idempotencyHeaders[this.idempotencyHeader] = options.idempotencyKey;
}
const headers = buildHeaders([
idempotencyHeaders,
{
Accept: 'application/json',
'User-Agent': this.getUserAgent(),
'X-Stainless-Retry-Count': String(retryCount),
...(options.timeout ? { 'X-Stainless-Timeout': String(Math.trunc(options.timeout / 1000)) } : {}),
...getPlatformHeaders(),
},
this._options.defaultHeaders,
bodyHeaders,
options.headers,
]);
this.validateHeaders(headers);
return headers.values;
}
private buildBody({ options: { body, headers: rawHeaders } }: { options: FinalRequestOptions }): {
bodyHeaders: HeadersLike;
body: BodyInit | undefined;
} {
if (!body) {
return { bodyHeaders: undefined, body: undefined };
}
const headers = buildHeaders([rawHeaders]);
if (
// Pass raw type verbatim
ArrayBuffer.isView(body) ||
body instanceof ArrayBuffer ||
body instanceof DataView ||
(typeof body === 'string' &&
// Preserve legacy string encoding behavior for now
headers.values.has('content-type')) ||
// `Blob` is superset of `File`
body instanceof Blob ||
// `FormData` -> `multipart/form-data`
body instanceof FormData ||
// `URLSearchParams` -> `application/x-www-form-urlencoded`
body instanceof URLSearchParams ||
// Send chunked stream (each chunk has own `length`)
((globalThis as any).ReadableStream && body instanceof (globalThis as any).ReadableStream)
) {
return { bodyHeaders: undefined, body: body as BodyInit };
} else if (
typeof body === 'object' &&
(Symbol.asyncIterator in body ||
(Symbol.iterator in body && 'next' in body && typeof body.next === 'function'))
) {
return { bodyHeaders: undefined, body: Shims.ReadableStreamFrom(body as AsyncIterable<Uint8Array>) };
} else {
return this.#encoder({ body, headers });
}
}
static Opencode = this;
static DEFAULT_TIMEOUT = 60000; // 1 minute
static OpencodeError = Errors.OpencodeError;
static APIError = Errors.APIError;
static APIConnectionError = Errors.APIConnectionError;
static APIConnectionTimeoutError = Errors.APIConnectionTimeoutError;
static APIUserAbortError = Errors.APIUserAbortError;
static NotFoundError = Errors.NotFoundError;
static ConflictError = Errors.ConflictError;
static RateLimitError = Errors.RateLimitError;
static BadRequestError = Errors.BadRequestError;
static AuthenticationError = Errors.AuthenticationError;
static InternalServerError = Errors.InternalServerError;
static PermissionDeniedError = Errors.PermissionDeniedError;
static UnprocessableEntityError = Errors.UnprocessableEntityError;
static toFile = Uploads.toFile;
event: API.Event = new API.Event(this);
app: API.AppResource = new API.AppResource(this);
find: API.Find = new API.Find(this);
file: API.FileResource = new API.FileResource(this);
config: API.ConfigResource = new API.ConfigResource(this);
session: API.SessionResource = new API.SessionResource(this);
tui: API.Tui = new API.Tui(this);
}
Opencode.Event = Event;
Opencode.AppResource = AppResource;
Opencode.Find = Find;
Opencode.FileResource = FileResource;
Opencode.ConfigResource = ConfigResource;
Opencode.SessionResource = SessionResource;
Opencode.Tui = Tui;
export declare namespace Opencode {
export type RequestOptions = Opts.RequestOptions;
export { Event as Event, type EventListResponse as EventListResponse };
export {
AppResource as AppResource,
type App as App,
type Mode as Mode,
type Model as Model,
type Provider as Provider,
type AppInitResponse as AppInitResponse,
type AppLogResponse as AppLogResponse,
type AppModesResponse as AppModesResponse,
type AppProvidersResponse as AppProvidersResponse,
type AppLogParams as AppLogParams,
};
export {
Find as Find,
type Symbol as Symbol,
type FindFilesResponse as FindFilesResponse,
type FindSymbolsResponse as FindSymbolsResponse,
type FindTextResponse as FindTextResponse,
type FindFilesParams as FindFilesParams,
type FindSymbolsParams as FindSymbolsParams,
type FindTextParams as FindTextParams,
};
export {
FileResource as FileResource,
type File as File,
type FileReadResponse as FileReadResponse,
type FileStatusResponse as FileStatusResponse,
type FileReadParams as FileReadParams,
};
export {
ConfigResource as ConfigResource,
type Config as Config,
type KeybindsConfig as KeybindsConfig,
type McpLocalConfig as McpLocalConfig,
type McpRemoteConfig as McpRemoteConfig,
type ModeConfig as ModeConfig,
};
export {
SessionResource as SessionResource,
type AssistantMessage as AssistantMessage,
type FilePart as FilePart,
type FilePartInput as FilePartInput,
type FilePartSource as FilePartSource,
type FilePartSourceText as FilePartSourceText,
type FileSource as FileSource,
type Message as Message,
type Part as Part,
type Session as Session,
type SnapshotPart as SnapshotPart,
type StepFinishPart as StepFinishPart,
type StepStartPart as StepStartPart,
type SymbolSource as SymbolSource,
type TextPart as TextPart,
type TextPartInput as TextPartInput,
type ToolPart as ToolPart,
type ToolStateCompleted as ToolStateCompleted,
type ToolStateError as ToolStateError,
type ToolStatePending as ToolStatePending,
type ToolStateRunning as ToolStateRunning,
type UserMessage as UserMessage,
type SessionListResponse as SessionListResponse,
type SessionDeleteResponse as SessionDeleteResponse,
type SessionAbortResponse as SessionAbortResponse,
type SessionInitResponse as SessionInitResponse,
type SessionMessagesResponse as SessionMessagesResponse,
type SessionSummarizeResponse as SessionSummarizeResponse,
type SessionChatParams as SessionChatParams,
type SessionInitParams as SessionInitParams,
type SessionRevertParams as SessionRevertParams,
type SessionSummarizeParams as SessionSummarizeParams,
};
export {
Tui as Tui,
type TuiAppendPromptResponse as TuiAppendPromptResponse,
type TuiOpenHelpResponse as TuiOpenHelpResponse,
type TuiAppendPromptParams as TuiAppendPromptParams,
};
export type MessageAbortedError = API.MessageAbortedError;
export type ProviderAuthError = API.ProviderAuthError;
export type UnknownError = API.UnknownError;
}

View File

@@ -1,3 +0,0 @@
# `core`
This directory holds public modules implementing non-resource-specific SDK functionality.

View File

@@ -1,92 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { type Opencode } from '../client';
import { type PromiseOrValue } from '../internal/types';
import { APIResponseProps, defaultParseResponse } from '../internal/parse';
/**
* A subclass of `Promise` providing additional helper methods
* for interacting with the SDK.
*/
export class APIPromise<T> extends Promise<T> {
private parsedPromise: Promise<T> | undefined;
#client: Opencode;
constructor(
client: Opencode,
private responsePromise: Promise<APIResponseProps>,
private parseResponse: (
client: Opencode,
props: APIResponseProps,
) => PromiseOrValue<T> = defaultParseResponse,
) {
super((resolve) => {
// this is maybe a bit weird but this has to be a no-op to not implicitly
// parse the response body; instead .then, .catch, .finally are overridden
// to parse the response
resolve(null as any);
});
this.#client = client;
}
_thenUnwrap<U>(transform: (data: T, props: APIResponseProps) => U): APIPromise<U> {
return new APIPromise(this.#client, this.responsePromise, async (client, props) =>
transform(await this.parseResponse(client, props), props),
);
}
/**
* Gets the raw `Response` instance instead of parsing the response
* data.
*
* If you want to parse the response body but still get the `Response`
* instance, you can use {@link withResponse()}.
*
* 👋 Getting the wrong TypeScript type for `Response`?
* Try setting `"moduleResolution": "NodeNext"` or add `"lib": ["DOM"]`
* to your `tsconfig.json`.
*/
asResponse(): Promise<Response> {
return this.responsePromise.then((p) => p.response);
}
/**
* Gets the parsed response data and the raw `Response` instance.
*
* If you just want to get the raw `Response` instance without parsing it,
* you can use {@link asResponse()}.
*
* 👋 Getting the wrong TypeScript type for `Response`?
* Try setting `"moduleResolution": "NodeNext"` or add `"lib": ["DOM"]`
* to your `tsconfig.json`.
*/
async withResponse(): Promise<{ data: T; response: Response }> {
const [data, response] = await Promise.all([this.parse(), this.asResponse()]);
return { data, response };
}
private parse(): Promise<T> {
if (!this.parsedPromise) {
this.parsedPromise = this.responsePromise.then((data) => this.parseResponse(this.#client, data));
}
return this.parsedPromise;
}
override then<TResult1 = T, TResult2 = never>(
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null,
): Promise<TResult1 | TResult2> {
return this.parse().then(onfulfilled, onrejected);
}
override catch<TResult = never>(
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null,
): Promise<T | TResult> {
return this.parse().catch(onrejected);
}
override finally(onfinally?: (() => void) | undefined | null): Promise<T> {
return this.parse().finally(onfinally);
}
}

View File

@@ -1,130 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { castToError } from '../internal/errors';
export class OpencodeError extends Error {}
export class APIError<
TStatus extends number | undefined = number | undefined,
THeaders extends Headers | undefined = Headers | undefined,
TError extends Object | undefined = Object | undefined,
> extends OpencodeError {
/** HTTP status for the response that caused the error */
readonly status: TStatus;
/** HTTP headers for the response that caused the error */
readonly headers: THeaders;
/** JSON body of the response that caused the error */
readonly error: TError;
constructor(status: TStatus, error: TError, message: string | undefined, headers: THeaders) {
super(`${APIError.makeMessage(status, error, message)}`);
this.status = status;
this.headers = headers;
this.error = error;
}
private static makeMessage(status: number | undefined, error: any, message: string | undefined) {
const msg =
error?.message ?
typeof error.message === 'string' ?
error.message
: JSON.stringify(error.message)
: error ? JSON.stringify(error)
: message;
if (status && msg) {
return `${status} ${msg}`;
}
if (status) {
return `${status} status code (no body)`;
}
if (msg) {
return msg;
}
return '(no status code or body)';
}
static generate(
status: number | undefined,
errorResponse: Object | undefined,
message: string | undefined,
headers: Headers | undefined,
): APIError {
if (!status || !headers) {
return new APIConnectionError({ message, cause: castToError(errorResponse) });
}
const error = errorResponse as Record<string, any>;
if (status === 400) {
return new BadRequestError(status, error, message, headers);
}
if (status === 401) {
return new AuthenticationError(status, error, message, headers);
}
if (status === 403) {
return new PermissionDeniedError(status, error, message, headers);
}
if (status === 404) {
return new NotFoundError(status, error, message, headers);
}
if (status === 409) {
return new ConflictError(status, error, message, headers);
}
if (status === 422) {
return new UnprocessableEntityError(status, error, message, headers);
}
if (status === 429) {
return new RateLimitError(status, error, message, headers);
}
if (status >= 500) {
return new InternalServerError(status, error, message, headers);
}
return new APIError(status, error, message, headers);
}
}
export class APIUserAbortError extends APIError<undefined, undefined, undefined> {
constructor({ message }: { message?: string } = {}) {
super(undefined, undefined, message || 'Request was aborted.', undefined);
}
}
export class APIConnectionError extends APIError<undefined, undefined, undefined> {
constructor({ message, cause }: { message?: string | undefined; cause?: Error | undefined }) {
super(undefined, undefined, message || 'Connection error.', undefined);
// in some environments the 'cause' property is already declared
// @ts-ignore
if (cause) this.cause = cause;
}
}
export class APIConnectionTimeoutError extends APIConnectionError {
constructor({ message }: { message?: string } = {}) {
super({ message: message ?? 'Request timed out.' });
}
}
export class BadRequestError extends APIError<400, Headers> {}
export class AuthenticationError extends APIError<401, Headers> {}
export class PermissionDeniedError extends APIError<403, Headers> {}
export class NotFoundError extends APIError<404, Headers> {}
export class ConflictError extends APIError<409, Headers> {}
export class UnprocessableEntityError extends APIError<422, Headers> {}
export class RateLimitError extends APIError<429, Headers> {}
export class InternalServerError extends APIError<number, Headers> {}

View File

@@ -1,11 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import type { Opencode } from '../client';
export abstract class APIResource {
protected _client: Opencode;
constructor(client: Opencode) {
this._client = client;
}
}

View File

@@ -1,315 +0,0 @@
import { OpencodeError } from './error';
import { type ReadableStream } from '../internal/shim-types';
import { makeReadableStream } from '../internal/shims';
import { findDoubleNewlineIndex, LineDecoder } from '../internal/decoders/line';
import { ReadableStreamToAsyncIterable } from '../internal/shims';
import { isAbortError } from '../internal/errors';
import { encodeUTF8 } from '../internal/utils/bytes';
import { loggerFor } from '../internal/utils/log';
import type { Opencode } from '../client';
type Bytes = string | ArrayBuffer | Uint8Array | null | undefined;
export type ServerSentEvent = {
event: string | null;
data: string;
raw: string[];
};
export class Stream<Item> implements AsyncIterable<Item> {
controller: AbortController;
#client: Opencode | undefined;
constructor(
private iterator: () => AsyncIterator<Item>,
controller: AbortController,
client?: Opencode,
) {
this.controller = controller;
this.#client = client;
}
static fromSSEResponse<Item>(
response: Response,
controller: AbortController,
client?: Opencode,
): Stream<Item> {
let consumed = false;
const logger = client ? loggerFor(client) : console;
async function* iterator(): AsyncIterator<Item, any, undefined> {
if (consumed) {
throw new OpencodeError('Cannot iterate over a consumed stream, use `.tee()` to split the stream.');
}
consumed = true;
let done = false;
try {
for await (const sse of _iterSSEMessages(response, controller)) {
try {
yield JSON.parse(sse.data);
} catch (e) {
logger.error(`Could not parse message into JSON:`, sse.data);
logger.error(`From chunk:`, sse.raw);
throw e;
}
}
done = true;
} catch (e) {
// If the user calls `stream.controller.abort()`, we should exit without throwing.
if (isAbortError(e)) return;
throw e;
} finally {
// If the user `break`s, abort the ongoing request.
if (!done) controller.abort();
}
}
return new Stream(iterator, controller, client);
}
/**
* Generates a Stream from a newline-separated ReadableStream
* where each item is a JSON value.
*/
static fromReadableStream<Item>(
readableStream: ReadableStream,
controller: AbortController,
client?: Opencode,
): Stream<Item> {
let consumed = false;
async function* iterLines(): AsyncGenerator<string, void, unknown> {
const lineDecoder = new LineDecoder();
const iter = ReadableStreamToAsyncIterable<Bytes>(readableStream);
for await (const chunk of iter) {
for (const line of lineDecoder.decode(chunk)) {
yield line;
}
}
for (const line of lineDecoder.flush()) {
yield line;
}
}
async function* iterator(): AsyncIterator<Item, any, undefined> {
if (consumed) {
throw new OpencodeError('Cannot iterate over a consumed stream, use `.tee()` to split the stream.');
}
consumed = true;
let done = false;
try {
for await (const line of iterLines()) {
if (done) continue;
if (line) yield JSON.parse(line);
}
done = true;
} catch (e) {
// If the user calls `stream.controller.abort()`, we should exit without throwing.
if (isAbortError(e)) return;
throw e;
} finally {
// If the user `break`s, abort the ongoing request.
if (!done) controller.abort();
}
}
return new Stream(iterator, controller, client);
}
[Symbol.asyncIterator](): AsyncIterator<Item> {
return this.iterator();
}
/**
* Splits the stream into two streams which can be
* independently read from at different speeds.
*/
tee(): [Stream<Item>, Stream<Item>] {
const left: Array<Promise<IteratorResult<Item>>> = [];
const right: Array<Promise<IteratorResult<Item>>> = [];
const iterator = this.iterator();
const teeIterator = (queue: Array<Promise<IteratorResult<Item>>>): AsyncIterator<Item> => {
return {
next: () => {
if (queue.length === 0) {
const result = iterator.next();
left.push(result);
right.push(result);
}
return queue.shift()!;
},
};
};
return [
new Stream(() => teeIterator(left), this.controller, this.#client),
new Stream(() => teeIterator(right), this.controller, this.#client),
];
}
/**
* Converts this stream to a newline-separated ReadableStream of
* JSON stringified values in the stream
* which can be turned back into a Stream with `Stream.fromReadableStream()`.
*/
toReadableStream(): ReadableStream {
const self = this;
let iter: AsyncIterator<Item>;
return makeReadableStream({
async start() {
iter = self[Symbol.asyncIterator]();
},
async pull(ctrl: any) {
try {
const { value, done } = await iter.next();
if (done) return ctrl.close();
const bytes = encodeUTF8(JSON.stringify(value) + '\n');
ctrl.enqueue(bytes);
} catch (err) {
ctrl.error(err);
}
},
async cancel() {
await iter.return?.();
},
});
}
}
export async function* _iterSSEMessages(
response: Response,
controller: AbortController,
): AsyncGenerator<ServerSentEvent, void, unknown> {
if (!response.body) {
controller.abort();
if (
typeof (globalThis as any).navigator !== 'undefined' &&
(globalThis as any).navigator.product === 'ReactNative'
) {
throw new OpencodeError(
`The default react-native fetch implementation does not support streaming. Please use expo/fetch: https://docs.expo.dev/versions/latest/sdk/expo/#expofetch-api`,
);
}
throw new OpencodeError(`Attempted to iterate over a response with no body`);
}
const sseDecoder = new SSEDecoder();
const lineDecoder = new LineDecoder();
const iter = ReadableStreamToAsyncIterable<Bytes>(response.body);
for await (const sseChunk of iterSSEChunks(iter)) {
for (const line of lineDecoder.decode(sseChunk)) {
const sse = sseDecoder.decode(line);
if (sse) yield sse;
}
}
for (const line of lineDecoder.flush()) {
const sse = sseDecoder.decode(line);
if (sse) yield sse;
}
}
/**
* Given an async iterable iterator, iterates over it and yields full
* SSE chunks, i.e. yields when a double new-line is encountered.
*/
async function* iterSSEChunks(iterator: AsyncIterableIterator<Bytes>): AsyncGenerator<Uint8Array> {
let data = new Uint8Array();
for await (const chunk of iterator) {
if (chunk == null) {
continue;
}
const binaryChunk =
chunk instanceof ArrayBuffer ? new Uint8Array(chunk)
: typeof chunk === 'string' ? encodeUTF8(chunk)
: chunk;
let newData = new Uint8Array(data.length + binaryChunk.length);
newData.set(data);
newData.set(binaryChunk, data.length);
data = newData;
let patternIndex;
while ((patternIndex = findDoubleNewlineIndex(data)) !== -1) {
yield data.slice(0, patternIndex);
data = data.slice(patternIndex);
}
}
if (data.length > 0) {
yield data;
}
}
class SSEDecoder {
private data: string[];
private event: string | null;
private chunks: string[];
constructor() {
this.event = null;
this.data = [];
this.chunks = [];
}
decode(line: string) {
if (line.endsWith('\r')) {
line = line.substring(0, line.length - 1);
}
if (!line) {
// empty line and we didn't previously encounter any messages
if (!this.event && !this.data.length) return null;
const sse: ServerSentEvent = {
event: this.event,
data: this.data.join('\n'),
raw: this.chunks,
};
this.event = null;
this.data = [];
this.chunks = [];
return sse;
}
this.chunks.push(line);
if (line.startsWith(':')) {
return null;
}
let [fieldname, _, value] = partition(line, ':');
if (value.startsWith(' ')) {
value = value.substring(1);
}
if (fieldname === 'event') {
this.event = value;
} else if (fieldname === 'data') {
this.data.push(value);
}
return null;
}
}
function partition(str: string, delimiter: string): [string, string, string] {
const index = str.indexOf(delimiter);
if (index !== -1) {
return [str.substring(0, index), delimiter, str.substring(index + delimiter.length)];
}
return [str, '', ''];
}

View File

@@ -1,2 +0,0 @@
export { type Uploadable } from '../internal/uploads';
export { toFile, type ToFileInput } from '../internal/to-file';

View File

@@ -1,2 +0,0 @@
/** @deprecated Import from ./core/error instead */
export * from './core/error';

View File

@@ -1,22 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export { Opencode as default } from './client';
export { type Uploadable, toFile } from './core/uploads';
export { APIPromise } from './core/api-promise';
export { Opencode, type ClientOptions } from './client';
export {
OpencodeError,
APIError,
APIConnectionError,
APIConnectionTimeoutError,
APIUserAbortError,
NotFoundError,
ConflictError,
RateLimitError,
BadRequestError,
AuthenticationError,
InternalServerError,
PermissionDeniedError,
UnprocessableEntityError,
} from './core/error';

View File

@@ -1,3 +0,0 @@
# `internal`
The modules in this directory are not importable outside this package and will change between releases.

View File

@@ -1,93 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export type Fetch = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
/**
* An alias to the builtin `RequestInit` type so we can
* easily alias it in import statements if there are name clashes.
*
* https://developer.mozilla.org/docs/Web/API/RequestInit
*/
type _RequestInit = RequestInit;
/**
* An alias to the builtin `Response` type so we can
* easily alias it in import statements if there are name clashes.
*
* https://developer.mozilla.org/docs/Web/API/Response
*/
type _Response = Response;
/**
* The type for the first argument to `fetch`.
*
* https://developer.mozilla.org/docs/Web/API/Window/fetch#resource
*/
type _RequestInfo = Request | URL | string;
/**
* The type for constructing `RequestInit` Headers.
*
* https://developer.mozilla.org/docs/Web/API/RequestInit#setting_headers
*/
type _HeadersInit = RequestInit['headers'];
/**
* The type for constructing `RequestInit` body.
*
* https://developer.mozilla.org/docs/Web/API/RequestInit#body
*/
type _BodyInit = RequestInit['body'];
/**
* An alias to the builtin `Array<T>` type so we can
* easily alias it in import statements if there are name clashes.
*/
type _Array<T> = Array<T>;
/**
* An alias to the builtin `Record<K, T>` type so we can
* easily alias it in import statements if there are name clashes.
*/
type _Record<K extends keyof any, T> = Record<K, T>;
export type {
_Array as Array,
_BodyInit as BodyInit,
_HeadersInit as HeadersInit,
_Record as Record,
_RequestInfo as RequestInfo,
_RequestInit as RequestInit,
_Response as Response,
};
/**
* A copy of the builtin `EndingType` type as it isn't fully supported in certain
* environments and attempting to reference the global version will error.
*
* https://github.com/microsoft/TypeScript/blob/49ad1a3917a0ea57f5ff248159256e12bb1cb705/src/lib/dom.generated.d.ts#L27941
*/
type EndingType = 'native' | 'transparent';
/**
* A copy of the builtin `BlobPropertyBag` type as it isn't fully supported in certain
* environments and attempting to reference the global version will error.
*
* https://github.com/microsoft/TypeScript/blob/49ad1a3917a0ea57f5ff248159256e12bb1cb705/src/lib/dom.generated.d.ts#L154
* https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob#options
*/
export interface BlobPropertyBag {
endings?: EndingType;
type?: string;
}
/**
* A copy of the builtin `FilePropertyBag` type as it isn't fully supported in certain
* environments and attempting to reference the global version will error.
*
* https://github.com/microsoft/TypeScript/blob/49ad1a3917a0ea57f5ff248159256e12bb1cb705/src/lib/dom.generated.d.ts#L503
* https://developer.mozilla.org/en-US/docs/Web/API/File/File#options
*/
export interface FilePropertyBag extends BlobPropertyBag {
lastModified?: number;
}

View File

@@ -1,135 +0,0 @@
import { concatBytes, decodeUTF8, encodeUTF8 } from '../utils/bytes';
export type Bytes = string | ArrayBuffer | Uint8Array | null | undefined;
/**
* A re-implementation of httpx's `LineDecoder` in Python that handles incrementally
* reading lines from text.
*
* https://github.com/encode/httpx/blob/920333ea98118e9cf617f246905d7b202510941c/httpx/_decoders.py#L258
*/
export class LineDecoder {
// prettier-ignore
static NEWLINE_CHARS = new Set(['\n', '\r']);
static NEWLINE_REGEXP = /\r\n|[\n\r]/g;
#buffer: Uint8Array;
#carriageReturnIndex: number | null;
constructor() {
this.#buffer = new Uint8Array();
this.#carriageReturnIndex = null;
}
decode(chunk: Bytes): string[] {
if (chunk == null) {
return [];
}
const binaryChunk =
chunk instanceof ArrayBuffer ? new Uint8Array(chunk)
: typeof chunk === 'string' ? encodeUTF8(chunk)
: chunk;
this.#buffer = concatBytes([this.#buffer, binaryChunk]);
const lines: string[] = [];
let patternIndex;
while ((patternIndex = findNewlineIndex(this.#buffer, this.#carriageReturnIndex)) != null) {
if (patternIndex.carriage && this.#carriageReturnIndex == null) {
// skip until we either get a corresponding `\n`, a new `\r` or nothing
this.#carriageReturnIndex = patternIndex.index;
continue;
}
// we got double \r or \rtext\n
if (
this.#carriageReturnIndex != null &&
(patternIndex.index !== this.#carriageReturnIndex + 1 || patternIndex.carriage)
) {
lines.push(decodeUTF8(this.#buffer.subarray(0, this.#carriageReturnIndex - 1)));
this.#buffer = this.#buffer.subarray(this.#carriageReturnIndex);
this.#carriageReturnIndex = null;
continue;
}
const endIndex =
this.#carriageReturnIndex !== null ? patternIndex.preceding - 1 : patternIndex.preceding;
const line = decodeUTF8(this.#buffer.subarray(0, endIndex));
lines.push(line);
this.#buffer = this.#buffer.subarray(patternIndex.index);
this.#carriageReturnIndex = null;
}
return lines;
}
flush(): string[] {
if (!this.#buffer.length) {
return [];
}
return this.decode('\n');
}
}
/**
* This function searches the buffer for the end patterns, (\r or \n)
* and returns an object with the index preceding the matched newline and the
* index after the newline char. `null` is returned if no new line is found.
*
* ```ts
* findNewLineIndex('abc\ndef') -> { preceding: 2, index: 3 }
* ```
*/
function findNewlineIndex(
buffer: Uint8Array,
startIndex: number | null,
): { preceding: number; index: number; carriage: boolean } | null {
const newline = 0x0a; // \n
const carriage = 0x0d; // \r
for (let i = startIndex ?? 0; i < buffer.length; i++) {
if (buffer[i] === newline) {
return { preceding: i, index: i + 1, carriage: false };
}
if (buffer[i] === carriage) {
return { preceding: i, index: i + 1, carriage: true };
}
}
return null;
}
export function findDoubleNewlineIndex(buffer: Uint8Array): number {
// This function searches the buffer for the end patterns (\r\r, \n\n, \r\n\r\n)
// and returns the index right after the first occurrence of any pattern,
// or -1 if none of the patterns are found.
const newline = 0x0a; // \n
const carriage = 0x0d; // \r
for (let i = 0; i < buffer.length - 1; i++) {
if (buffer[i] === newline && buffer[i + 1] === newline) {
// \n\n
return i + 2;
}
if (buffer[i] === carriage && buffer[i + 1] === carriage) {
// \r\r
return i + 2;
}
if (
buffer[i] === carriage &&
buffer[i + 1] === newline &&
i + 3 < buffer.length &&
buffer[i + 2] === carriage &&
buffer[i + 3] === newline
) {
// \r\n\r\n
return i + 4;
}
}
return -1;
}

View File

@@ -1,196 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { VERSION } from '../version';
export const isRunningInBrowser = () => {
return (
// @ts-ignore
typeof window !== 'undefined' &&
// @ts-ignore
typeof window.document !== 'undefined' &&
// @ts-ignore
typeof navigator !== 'undefined'
);
};
type DetectedPlatform = 'deno' | 'node' | 'edge' | 'unknown';
/**
* Note this does not detect 'browser'; for that, use getBrowserInfo().
*/
function getDetectedPlatform(): DetectedPlatform {
if (typeof Deno !== 'undefined' && Deno.build != null) {
return 'deno';
}
if (typeof EdgeRuntime !== 'undefined') {
return 'edge';
}
if (
Object.prototype.toString.call(
typeof (globalThis as any).process !== 'undefined' ? (globalThis as any).process : 0,
) === '[object process]'
) {
return 'node';
}
return 'unknown';
}
declare const Deno: any;
declare const EdgeRuntime: any;
type Arch = 'x32' | 'x64' | 'arm' | 'arm64' | `other:${string}` | 'unknown';
type PlatformName =
| 'MacOS'
| 'Linux'
| 'Windows'
| 'FreeBSD'
| 'OpenBSD'
| 'iOS'
| 'Android'
| `Other:${string}`
| 'Unknown';
type Browser = 'ie' | 'edge' | 'chrome' | 'firefox' | 'safari';
type PlatformProperties = {
'X-Stainless-Lang': 'js';
'X-Stainless-Package-Version': string;
'X-Stainless-OS': PlatformName;
'X-Stainless-Arch': Arch;
'X-Stainless-Runtime': 'node' | 'deno' | 'edge' | `browser:${Browser}` | 'unknown';
'X-Stainless-Runtime-Version': string;
};
const getPlatformProperties = (): PlatformProperties => {
const detectedPlatform = getDetectedPlatform();
if (detectedPlatform === 'deno') {
return {
'X-Stainless-Lang': 'js',
'X-Stainless-Package-Version': VERSION,
'X-Stainless-OS': normalizePlatform(Deno.build.os),
'X-Stainless-Arch': normalizeArch(Deno.build.arch),
'X-Stainless-Runtime': 'deno',
'X-Stainless-Runtime-Version':
typeof Deno.version === 'string' ? Deno.version : Deno.version?.deno ?? 'unknown',
};
}
if (typeof EdgeRuntime !== 'undefined') {
return {
'X-Stainless-Lang': 'js',
'X-Stainless-Package-Version': VERSION,
'X-Stainless-OS': 'Unknown',
'X-Stainless-Arch': `other:${EdgeRuntime}`,
'X-Stainless-Runtime': 'edge',
'X-Stainless-Runtime-Version': (globalThis as any).process.version,
};
}
// Check if Node.js
if (detectedPlatform === 'node') {
return {
'X-Stainless-Lang': 'js',
'X-Stainless-Package-Version': VERSION,
'X-Stainless-OS': normalizePlatform((globalThis as any).process.platform ?? 'unknown'),
'X-Stainless-Arch': normalizeArch((globalThis as any).process.arch ?? 'unknown'),
'X-Stainless-Runtime': 'node',
'X-Stainless-Runtime-Version': (globalThis as any).process.version ?? 'unknown',
};
}
const browserInfo = getBrowserInfo();
if (browserInfo) {
return {
'X-Stainless-Lang': 'js',
'X-Stainless-Package-Version': VERSION,
'X-Stainless-OS': 'Unknown',
'X-Stainless-Arch': 'unknown',
'X-Stainless-Runtime': `browser:${browserInfo.browser}`,
'X-Stainless-Runtime-Version': browserInfo.version,
};
}
// TODO add support for Cloudflare workers, etc.
return {
'X-Stainless-Lang': 'js',
'X-Stainless-Package-Version': VERSION,
'X-Stainless-OS': 'Unknown',
'X-Stainless-Arch': 'unknown',
'X-Stainless-Runtime': 'unknown',
'X-Stainless-Runtime-Version': 'unknown',
};
};
type BrowserInfo = {
browser: Browser;
version: string;
};
declare const navigator: { userAgent: string } | undefined;
// Note: modified from https://github.com/JS-DevTools/host-environment/blob/b1ab79ecde37db5d6e163c050e54fe7d287d7c92/src/isomorphic.browser.ts
function getBrowserInfo(): BrowserInfo | null {
if (typeof navigator === 'undefined' || !navigator) {
return null;
}
// NOTE: The order matters here!
const browserPatterns = [
{ key: 'edge' as const, pattern: /Edge(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ },
{ key: 'ie' as const, pattern: /MSIE(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ },
{ key: 'ie' as const, pattern: /Trident(?:.*rv\:(\d+)\.(\d+)(?:\.(\d+))?)?/ },
{ key: 'chrome' as const, pattern: /Chrome(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ },
{ key: 'firefox' as const, pattern: /Firefox(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ },
{ key: 'safari' as const, pattern: /(?:Version\W+(\d+)\.(\d+)(?:\.(\d+))?)?(?:\W+Mobile\S*)?\W+Safari/ },
];
// Find the FIRST matching browser
for (const { key, pattern } of browserPatterns) {
const match = pattern.exec(navigator.userAgent);
if (match) {
const major = match[1] || 0;
const minor = match[2] || 0;
const patch = match[3] || 0;
return { browser: key, version: `${major}.${minor}.${patch}` };
}
}
return null;
}
const normalizeArch = (arch: string): Arch => {
// Node docs:
// - https://nodejs.org/api/process.html#processarch
// Deno docs:
// - https://doc.deno.land/deno/stable/~/Deno.build
if (arch === 'x32') return 'x32';
if (arch === 'x86_64' || arch === 'x64') return 'x64';
if (arch === 'arm') return 'arm';
if (arch === 'aarch64' || arch === 'arm64') return 'arm64';
if (arch) return `other:${arch}`;
return 'unknown';
};
const normalizePlatform = (platform: string): PlatformName => {
// Node platforms:
// - https://nodejs.org/api/process.html#processplatform
// Deno platforms:
// - https://doc.deno.land/deno/stable/~/Deno.build
// - https://github.com/denoland/deno/issues/14799
platform = platform.toLowerCase();
// NOTE: this iOS check is untested and may not work
// Node does not work natively on IOS, there is a fork at
// https://github.com/nodejs-mobile/nodejs-mobile
// however it is unknown at the time of writing how to detect if it is running
if (platform.includes('ios')) return 'iOS';
if (platform === 'android') return 'Android';
if (platform === 'darwin') return 'MacOS';
if (platform === 'win32') return 'Windows';
if (platform === 'freebsd') return 'FreeBSD';
if (platform === 'openbsd') return 'OpenBSD';
if (platform === 'linux') return 'Linux';
if (platform) return `Other:${platform}`;
return 'Unknown';
};
let _platformHeaders: PlatformProperties;
export const getPlatformHeaders = () => {
return (_platformHeaders ??= getPlatformProperties());
};

View File

@@ -1,33 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export function isAbortError(err: unknown) {
return (
typeof err === 'object' &&
err !== null &&
// Spec-compliant fetch implementations
(('name' in err && (err as any).name === 'AbortError') ||
// Expo fetch
('message' in err && String((err as any).message).includes('FetchRequestCanceledException')))
);
}
export const castToError = (err: any): Error => {
if (err instanceof Error) return err;
if (typeof err === 'object' && err !== null) {
try {
if (Object.prototype.toString.call(err) === '[object Error]') {
// @ts-ignore - not all envs have native support for cause yet
const error = new Error(err.message, err.cause ? { cause: err.cause } : {});
if (err.stack) error.stack = err.stack;
// @ts-ignore - not all envs have native support for cause yet
if (err.cause && !error.cause) error.cause = err.cause;
if (err.name) error.name = err.name;
return error;
}
} catch {}
try {
return new Error(JSON.stringify(err));
} catch {}
}
return new Error(err);
};

View File

@@ -1,97 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { isReadonlyArray } from './utils/values';
type HeaderValue = string | undefined | null;
export type HeadersLike =
| Headers
| readonly HeaderValue[][]
| Record<string, HeaderValue | readonly HeaderValue[]>
| undefined
| null
| NullableHeaders;
const brand_privateNullableHeaders = /* @__PURE__ */ Symbol('brand.privateNullableHeaders');
/**
* @internal
* Users can pass explicit nulls to unset default headers. When we parse them
* into a standard headers type we need to preserve that information.
*/
export type NullableHeaders = {
/** Brand check, prevent users from creating a NullableHeaders. */
[brand_privateNullableHeaders]: true;
/** Parsed headers. */
values: Headers;
/** Set of lowercase header names explicitly set to null. */
nulls: Set<string>;
};
function* iterateHeaders(headers: HeadersLike): IterableIterator<readonly [string, string | null]> {
if (!headers) return;
if (brand_privateNullableHeaders in headers) {
const { values, nulls } = headers;
yield* values.entries();
for (const name of nulls) {
yield [name, null];
}
return;
}
let shouldClear = false;
let iter: Iterable<readonly (HeaderValue | readonly HeaderValue[])[]>;
if (headers instanceof Headers) {
iter = headers.entries();
} else if (isReadonlyArray(headers)) {
iter = headers;
} else {
shouldClear = true;
iter = Object.entries(headers ?? {});
}
for (let row of iter) {
const name = row[0];
if (typeof name !== 'string') throw new TypeError('expected header name to be a string');
const values = isReadonlyArray(row[1]) ? row[1] : [row[1]];
let didClear = false;
for (const value of values) {
if (value === undefined) continue;
// Objects keys always overwrite older headers, they never append.
// Yield a null to clear the header before adding the new values.
if (shouldClear && !didClear) {
didClear = true;
yield [name, null];
}
yield [name, value];
}
}
}
export const buildHeaders = (newHeaders: HeadersLike[]): NullableHeaders => {
const targetHeaders = new Headers();
const nullHeaders = new Set<string>();
for (const headers of newHeaders) {
const seenHeaders = new Set<string>();
for (const [name, value] of iterateHeaders(headers)) {
const lowerName = name.toLowerCase();
if (!seenHeaders.has(lowerName)) {
targetHeaders.delete(name);
seenHeaders.add(lowerName);
}
if (value === null) {
targetHeaders.delete(name);
nullHeaders.add(lowerName);
} else {
targetHeaders.append(name, value);
nullHeaders.delete(lowerName);
}
}
}
return { [brand_privateNullableHeaders]: true, values: targetHeaders, nulls: nullHeaders };
};
export const isEmptyHeaders = (headers: HeadersLike) => {
for (const _ of iterateHeaders(headers)) return false;
return true;
};

View File

@@ -1,64 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import type { FinalRequestOptions } from './request-options';
import { Stream } from '../core/streaming';
import { type Opencode } from '../client';
import { formatRequestDetails, loggerFor } from './utils/log';
export type APIResponseProps = {
response: Response;
options: FinalRequestOptions;
controller: AbortController;
requestLogID: string;
retryOfRequestLogID: string | undefined;
startTime: number;
};
export async function defaultParseResponse<T>(client: Opencode, props: APIResponseProps): Promise<T> {
const { response, requestLogID, retryOfRequestLogID, startTime } = props;
const body = await (async () => {
if (props.options.stream) {
loggerFor(client).debug('response', response.status, response.url, response.headers, response.body);
// Note: there is an invariant here that isn't represented in the type system
// that if you set `stream: true` the response type must also be `Stream<T>`
if (props.options.__streamClass) {
return props.options.__streamClass.fromSSEResponse(response, props.controller, client) as any;
}
return Stream.fromSSEResponse(response, props.controller, client) as any;
}
// fetch refuses to read the body when the status code is 204.
if (response.status === 204) {
return null as T;
}
if (props.options.__binaryResponse) {
return response as unknown as T;
}
const contentType = response.headers.get('content-type');
const mediaType = contentType?.split(';')[0]?.trim();
const isJSON = mediaType?.includes('application/json') || mediaType?.endsWith('+json');
if (isJSON) {
const json = await response.json();
return json as T;
}
const text = await response.text();
return text as unknown as T;
})();
loggerFor(client).debug(
`[${requestLogID}] response parsed`,
formatRequestDetails({
retryOfRequestLogID,
url: response.url,
status: response.status,
body,
durationMs: Date.now() - startTime,
}),
);
return body;
}

View File

@@ -1,93 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { NullableHeaders } from './headers';
import type { BodyInit } from './builtin-types';
import { Stream } from '../core/streaming';
import type { HTTPMethod, MergedRequestInit } from './types';
import { type HeadersLike } from './headers';
export type FinalRequestOptions = RequestOptions & { method: HTTPMethod; path: string };
export type RequestOptions = {
/**
* The HTTP method for the request (e.g., 'get', 'post', 'put', 'delete').
*/
method?: HTTPMethod;
/**
* The URL path for the request.
*
* @example "/v1/foo"
*/
path?: string;
/**
* Query parameters to include in the request URL.
*/
query?: object | undefined | null;
/**
* The request body. Can be a string, JSON object, FormData, or other supported types.
*/
body?: unknown;
/**
* HTTP headers to include with the request. Can be a Headers object, plain object, or array of tuples.
*/
headers?: HeadersLike;
/**
* The maximum number of times that the client will retry a request in case of a
* temporary failure, like a network error or a 5XX error from the server.
*
* @default 2
*/
maxRetries?: number;
stream?: boolean | undefined;
/**
* The maximum amount of time (in milliseconds) that the client should wait for a response
* from the server before timing out a single request.
*
* @unit milliseconds
*/
timeout?: number;
/**
* Additional `RequestInit` options to be passed to the underlying `fetch` call.
* These options will be merged with the client's default fetch options.
*/
fetchOptions?: MergedRequestInit;
/**
* An AbortSignal that can be used to cancel the request.
*/
signal?: AbortSignal | undefined | null;
/**
* A unique key for this request to enable idempotency.
*/
idempotencyKey?: string;
/**
* Override the default base URL for this specific request.
*/
defaultBaseURL?: string | undefined;
__binaryResponse?: boolean | undefined;
__streamClass?: typeof Stream;
};
export type EncodedContent = { bodyHeaders: HeadersLike; body: BodyInit };
export type RequestEncoder = (request: { headers: NullableHeaders; body: unknown }) => EncodedContent;
export const FallbackEncoder: RequestEncoder = ({ headers, body }) => {
return {
bodyHeaders: {
'content-type': 'application/json',
},
body: JSON.stringify(body),
};
};

View File

@@ -1,26 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
/**
* Shims for types that we can't always rely on being available globally.
*
* Note: these only exist at the type-level, there is no corresponding runtime
* version for any of these symbols.
*/
type NeverToAny<T> = T extends never ? any : T;
/** @ts-ignore */
type _DOMReadableStream<R = any> = globalThis.ReadableStream<R>;
/** @ts-ignore */
type _NodeReadableStream<R = any> = import('stream/web').ReadableStream<R>;
type _ConditionalNodeReadableStream<R = any> =
typeof globalThis extends { ReadableStream: any } ? never : _NodeReadableStream<R>;
type _ReadableStream<R = any> = NeverToAny<
| ([0] extends [1 & _DOMReadableStream<R>] ? never : _DOMReadableStream<R>)
| ([0] extends [1 & _ConditionalNodeReadableStream<R>] ? never : _ConditionalNodeReadableStream<R>)
>;
export type { _ReadableStream as ReadableStream };

View File

@@ -1,107 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
/**
* This module provides internal shims and utility functions for environments where certain Node.js or global types may not be available.
*
* These are used to ensure we can provide a consistent behaviour between different JavaScript environments and good error
* messages in cases where an environment isn't fully supported.
*/
import type { Fetch } from './builtin-types';
import type { ReadableStream } from './shim-types';
export function getDefaultFetch(): Fetch {
if (typeof fetch !== 'undefined') {
return fetch as any;
}
throw new Error(
'`fetch` is not defined as a global; Either pass `fetch` to the client, `new Opencode({ fetch })` or polyfill the global, `globalThis.fetch = fetch`',
);
}
type ReadableStreamArgs = ConstructorParameters<typeof ReadableStream>;
export function makeReadableStream(...args: ReadableStreamArgs): ReadableStream {
const ReadableStream = (globalThis as any).ReadableStream;
if (typeof ReadableStream === 'undefined') {
// Note: All of the platforms / runtimes we officially support already define
// `ReadableStream` as a global, so this should only ever be hit on unsupported runtimes.
throw new Error(
'`ReadableStream` is not defined as a global; You will need to polyfill it, `globalThis.ReadableStream = ReadableStream`',
);
}
return new ReadableStream(...args);
}
export function ReadableStreamFrom<T>(iterable: Iterable<T> | AsyncIterable<T>): ReadableStream<T> {
let iter: AsyncIterator<T> | Iterator<T> =
Symbol.asyncIterator in iterable ? iterable[Symbol.asyncIterator]() : iterable[Symbol.iterator]();
return makeReadableStream({
start() {},
async pull(controller: any) {
const { done, value } = await iter.next();
if (done) {
controller.close();
} else {
controller.enqueue(value);
}
},
async cancel() {
await iter.return?.();
},
});
}
/**
* Most browsers don't yet have async iterable support for ReadableStream,
* and Node has a very different way of reading bytes from its "ReadableStream".
*
* This polyfill was pulled from https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1627354490
*/
export function ReadableStreamToAsyncIterable<T>(stream: any): AsyncIterableIterator<T> {
if (stream[Symbol.asyncIterator]) return stream;
const reader = stream.getReader();
return {
async next() {
try {
const result = await reader.read();
if (result?.done) reader.releaseLock(); // release lock when stream becomes closed
return result;
} catch (e) {
reader.releaseLock(); // release lock when stream becomes errored
throw e;
}
},
async return() {
const cancelPromise = reader.cancel();
reader.releaseLock();
await cancelPromise;
return { done: true, value: undefined };
},
[Symbol.asyncIterator]() {
return this;
},
};
}
/**
* Cancels a ReadableStream we don't need to consume.
* See https://undici.nodejs.org/#/?id=garbage-collection
*/
export async function CancelReadableStream(stream: any): Promise<void> {
if (stream === null || typeof stream !== 'object') return;
if (stream[Symbol.asyncIterator]) {
await stream[Symbol.asyncIterator]().return?.();
return;
}
const reader = stream.getReader();
const cancelPromise = reader.cancel();
reader.releaseLock();
await cancelPromise;
}

View File

@@ -1,154 +0,0 @@
import { BlobPart, getName, makeFile, isAsyncIterable } from './uploads';
import type { FilePropertyBag } from './builtin-types';
import { checkFileSupport } from './uploads';
type BlobLikePart = string | ArrayBuffer | ArrayBufferView | BlobLike | DataView;
/**
* Intended to match DOM Blob, node-fetch Blob, node:buffer Blob, etc.
* Don't add arrayBuffer here, node-fetch doesn't have it
*/
interface BlobLike {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/size) */
readonly size: number;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/type) */
readonly type: string;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/text) */
text(): Promise<string>;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/slice) */
slice(start?: number, end?: number): BlobLike;
}
/**
* This check adds the arrayBuffer() method type because it is available and used at runtime
*/
const isBlobLike = (value: any): value is BlobLike & { arrayBuffer(): Promise<ArrayBuffer> } =>
value != null &&
typeof value === 'object' &&
typeof value.size === 'number' &&
typeof value.type === 'string' &&
typeof value.text === 'function' &&
typeof value.slice === 'function' &&
typeof value.arrayBuffer === 'function';
/**
* Intended to match DOM File, node:buffer File, undici File, etc.
*/
interface FileLike extends BlobLike {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/lastModified) */
readonly lastModified: number;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/name) */
readonly name?: string | undefined;
}
/**
* This check adds the arrayBuffer() method type because it is available and used at runtime
*/
const isFileLike = (value: any): value is FileLike & { arrayBuffer(): Promise<ArrayBuffer> } =>
value != null &&
typeof value === 'object' &&
typeof value.name === 'string' &&
typeof value.lastModified === 'number' &&
isBlobLike(value);
/**
* Intended to match DOM Response, node-fetch Response, undici Response, etc.
*/
export interface ResponseLike {
url: string;
blob(): Promise<BlobLike>;
}
const isResponseLike = (value: any): value is ResponseLike =>
value != null &&
typeof value === 'object' &&
typeof value.url === 'string' &&
typeof value.blob === 'function';
export type ToFileInput =
| FileLike
| ResponseLike
| Exclude<BlobLikePart, string>
| AsyncIterable<BlobLikePart>;
/**
* Helper for creating a {@link File} to pass to an SDK upload method from a variety of different data formats
* @param value the raw content of the file. Can be an {@link Uploadable}, {@link BlobLikePart}, or {@link AsyncIterable} of {@link BlobLikePart}s
* @param {string=} name the name of the file. If omitted, toFile will try to determine a file name from bits if possible
* @param {Object=} options additional properties
* @param {string=} options.type the MIME type of the content
* @param {number=} options.lastModified the last modified timestamp
* @returns a {@link File} with the given properties
*/
export async function toFile(
value: ToFileInput | PromiseLike<ToFileInput>,
name?: string | null | undefined,
options?: FilePropertyBag | undefined,
): Promise<File> {
checkFileSupport();
// If it's a promise, resolve it.
value = await value;
// If we've been given a `File` we don't need to do anything
if (isFileLike(value)) {
if (value instanceof File) {
return value;
}
return makeFile([await value.arrayBuffer()], value.name);
}
if (isResponseLike(value)) {
const blob = await value.blob();
name ||= new URL(value.url).pathname.split(/[\\/]/).pop();
return makeFile(await getBytes(blob), name, options);
}
const parts = await getBytes(value);
name ||= getName(value);
if (!options?.type) {
const type = parts.find((part) => typeof part === 'object' && 'type' in part && part.type);
if (typeof type === 'string') {
options = { ...options, type };
}
}
return makeFile(parts, name, options);
}
async function getBytes(value: BlobLikePart | AsyncIterable<BlobLikePart>): Promise<Array<BlobPart>> {
let parts: Array<BlobPart> = [];
if (
typeof value === 'string' ||
ArrayBuffer.isView(value) || // includes Uint8Array, Buffer, etc.
value instanceof ArrayBuffer
) {
parts.push(value);
} else if (isBlobLike(value)) {
parts.push(value instanceof Blob ? value : await value.arrayBuffer());
} else if (
isAsyncIterable(value) // includes Readable, ReadableStream, etc.
) {
for await (const chunk of value) {
parts.push(...(await getBytes(chunk as BlobLikePart))); // TODO, consider validating?
}
} else {
const constructor = value?.constructor?.name;
throw new Error(
`Unexpected data type: ${typeof value}${
constructor ? `; constructor: ${constructor}` : ''
}${propsForError(value)}`,
);
}
return parts;
}
function propsForError(value: unknown): string {
if (typeof value !== 'object' || value === null) return '';
const props = Object.getOwnPropertyNames(value);
return `; props: [${props.map((p) => `"${p}"`).join(', ')}]`;
}

View File

@@ -1,95 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export type PromiseOrValue<T> = T | Promise<T>;
export type HTTPMethod = 'get' | 'post' | 'put' | 'patch' | 'delete';
export type KeysEnum<T> = { [P in keyof Required<T>]: true };
export type FinalizedRequestInit = RequestInit & { headers: Headers };
type NotAny<T> = [0] extends [1 & T] ? never : T;
/**
* Some environments overload the global fetch function, and Parameters<T> only gets the last signature.
*/
type OverloadedParameters<T> =
T extends (
{
(...args: infer A): unknown;
(...args: infer B): unknown;
(...args: infer C): unknown;
(...args: infer D): unknown;
}
) ?
A | B | C | D
: T extends (
{
(...args: infer A): unknown;
(...args: infer B): unknown;
(...args: infer C): unknown;
}
) ?
A | B | C
: T extends (
{
(...args: infer A): unknown;
(...args: infer B): unknown;
}
) ?
A | B
: T extends (...args: infer A) => unknown ? A
: never;
/* eslint-disable */
/**
* These imports attempt to get types from a parent package's dependencies.
* Unresolved bare specifiers can trigger [automatic type acquisition][1] in some projects, which
* would cause typescript to show types not present at runtime. To avoid this, we import
* directly from parent node_modules folders.
*
* We need to check multiple levels because we don't know what directory structure we'll be in.
* For example, pnpm generates directories like this:
* ```
* node_modules
* ├── .pnpm
* │ └── pkg@1.0.0
* │ └── node_modules
* │ └── pkg
* │ └── internal
* │ └── types.d.ts
* ├── pkg -> .pnpm/pkg@1.0.0/node_modules/pkg
* └── undici
* ```
*
* [1]: https://www.typescriptlang.org/tsconfig/#typeAcquisition
*/
/** @ts-ignore For users with \@types/node */
type UndiciTypesRequestInit = NotAny<import('../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../../../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../../../../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../../../node_modules/undici-types/index.d.ts').RequestInit>;
/** @ts-ignore For users with undici */
type UndiciRequestInit = NotAny<import('../node_modules/undici/index.d.ts').RequestInit> | NotAny<import('../../node_modules/undici/index.d.ts').RequestInit> | NotAny<import('../../../node_modules/undici/index.d.ts').RequestInit> | NotAny<import('../../../../node_modules/undici/index.d.ts').RequestInit> | NotAny<import('../../../../../node_modules/undici/index.d.ts').RequestInit> | NotAny<import('../../../../../../node_modules/undici/index.d.ts').RequestInit> | NotAny<import('../../../../../../../node_modules/undici/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../node_modules/undici/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../../node_modules/undici/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../../../node_modules/undici/index.d.ts').RequestInit>;
/** @ts-ignore For users with \@types/bun */
type BunRequestInit = globalThis.FetchRequestInit;
/** @ts-ignore For users with node-fetch@2 */
type NodeFetch2RequestInit = NotAny<import('../node_modules/@types/node-fetch/index.d.ts').RequestInit> | NotAny<import('../../node_modules/@types/node-fetch/index.d.ts').RequestInit> | NotAny<import('../../../node_modules/@types/node-fetch/index.d.ts').RequestInit> | NotAny<import('../../../../node_modules/@types/node-fetch/index.d.ts').RequestInit> | NotAny<import('../../../../../node_modules/@types/node-fetch/index.d.ts').RequestInit> | NotAny<import('../../../../../../node_modules/@types/node-fetch/index.d.ts').RequestInit> | NotAny<import('../../../../../../../node_modules/@types/node-fetch/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../node_modules/@types/node-fetch/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../../node_modules/@types/node-fetch/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../../../node_modules/@types/node-fetch/index.d.ts').RequestInit>;
/** @ts-ignore For users with node-fetch@3, doesn't need file extension because types are at ./@types/index.d.ts */
type NodeFetch3RequestInit = NotAny<import('../node_modules/node-fetch').RequestInit> | NotAny<import('../../node_modules/node-fetch').RequestInit> | NotAny<import('../../../node_modules/node-fetch').RequestInit> | NotAny<import('../../../../node_modules/node-fetch').RequestInit> | NotAny<import('../../../../../node_modules/node-fetch').RequestInit> | NotAny<import('../../../../../../node_modules/node-fetch').RequestInit> | NotAny<import('../../../../../../../node_modules/node-fetch').RequestInit> | NotAny<import('../../../../../../../../node_modules/node-fetch').RequestInit> | NotAny<import('../../../../../../../../../node_modules/node-fetch').RequestInit> | NotAny<import('../../../../../../../../../../node_modules/node-fetch').RequestInit>;
/** @ts-ignore For users who use Deno */
type FetchRequestInit = NonNullable<OverloadedParameters<typeof fetch>[1]>;
/* eslint-enable */
type RequestInits =
| NotAny<UndiciTypesRequestInit>
| NotAny<UndiciRequestInit>
| NotAny<BunRequestInit>
| NotAny<NodeFetch2RequestInit>
| NotAny<NodeFetch3RequestInit>
| NotAny<RequestInit>
| NotAny<FetchRequestInit>;
/**
* This type contains `RequestInit` options that may be available on the current runtime,
* including per-platform extensions like `dispatcher`, `agent`, `client`, etc.
*/
export type MergedRequestInit = RequestInits &
/** We don't include these in the types as they'll be overridden for every request. */
Partial<Record<'body' | 'headers' | 'method' | 'signal', never>>;

View File

@@ -1,187 +0,0 @@
import { type RequestOptions } from './request-options';
import type { FilePropertyBag, Fetch } from './builtin-types';
import type { Opencode } from '../client';
import { ReadableStreamFrom } from './shims';
export type BlobPart = string | ArrayBuffer | ArrayBufferView | Blob | DataView;
type FsReadStream = AsyncIterable<Uint8Array> & { path: string | { toString(): string } };
// https://github.com/oven-sh/bun/issues/5980
interface BunFile extends Blob {
readonly name?: string | undefined;
}
export const checkFileSupport = () => {
if (typeof File === 'undefined') {
const { process } = globalThis as any;
const isOldNode =
typeof process?.versions?.node === 'string' && parseInt(process.versions.node.split('.')) < 20;
throw new Error(
'`File` is not defined as a global, which is required for file uploads.' +
(isOldNode ?
" Update to Node 20 LTS or newer, or set `globalThis.File` to `import('node:buffer').File`."
: ''),
);
}
};
/**
* Typically, this is a native "File" class.
*
* We provide the {@link toFile} utility to convert a variety of objects
* into the File class.
*
* For convenience, you can also pass a fetch Response, or in Node,
* the result of fs.createReadStream().
*/
export type Uploadable = File | Response | FsReadStream | BunFile;
/**
* Construct a `File` instance. This is used to ensure a helpful error is thrown
* for environments that don't define a global `File` yet.
*/
export function makeFile(
fileBits: BlobPart[],
fileName: string | undefined,
options?: FilePropertyBag,
): File {
checkFileSupport();
return new File(fileBits as any, fileName ?? 'unknown_file', options);
}
export function getName(value: any): string | undefined {
return (
(
(typeof value === 'object' &&
value !== null &&
(('name' in value && value.name && String(value.name)) ||
('url' in value && value.url && String(value.url)) ||
('filename' in value && value.filename && String(value.filename)) ||
('path' in value && value.path && String(value.path)))) ||
''
)
.split(/[\\/]/)
.pop() || undefined
);
}
export const isAsyncIterable = (value: any): value is AsyncIterable<any> =>
value != null && typeof value === 'object' && typeof value[Symbol.asyncIterator] === 'function';
/**
* Returns a multipart/form-data request if any part of the given request body contains a File / Blob value.
* Otherwise returns the request as is.
*/
export const maybeMultipartFormRequestOptions = async (
opts: RequestOptions,
fetch: Opencode | Fetch,
): Promise<RequestOptions> => {
if (!hasUploadableValue(opts.body)) return opts;
return { ...opts, body: await createForm(opts.body, fetch) };
};
type MultipartFormRequestOptions = Omit<RequestOptions, 'body'> & { body: unknown };
export const multipartFormRequestOptions = async (
opts: MultipartFormRequestOptions,
fetch: Opencode | Fetch,
): Promise<RequestOptions> => {
return { ...opts, body: await createForm(opts.body, fetch) };
};
const supportsFormDataMap = /* @__PURE__ */ new WeakMap<Fetch, Promise<boolean>>();
/**
* node-fetch doesn't support the global FormData object in recent node versions. Instead of sending
* properly-encoded form data, it just stringifies the object, resulting in a request body of "[object FormData]".
* This function detects if the fetch function provided supports the global FormData object to avoid
* confusing error messages later on.
*/
function supportsFormData(fetchObject: Opencode | Fetch): Promise<boolean> {
const fetch: Fetch = typeof fetchObject === 'function' ? fetchObject : (fetchObject as any).fetch;
const cached = supportsFormDataMap.get(fetch);
if (cached) return cached;
const promise = (async () => {
try {
const FetchResponse = (
'Response' in fetch ?
fetch.Response
: (await fetch('data:,')).constructor) as typeof Response;
const data = new FormData();
if (data.toString() === (await new FetchResponse(data).text())) {
return false;
}
return true;
} catch {
// avoid false negatives
return true;
}
})();
supportsFormDataMap.set(fetch, promise);
return promise;
}
export const createForm = async <T = Record<string, unknown>>(
body: T | undefined,
fetch: Opencode | Fetch,
): Promise<FormData> => {
if (!(await supportsFormData(fetch))) {
throw new TypeError(
'The provided fetch function does not support file uploads with the current global FormData class.',
);
}
const form = new FormData();
await Promise.all(Object.entries(body || {}).map(([key, value]) => addFormValue(form, key, value)));
return form;
};
// We check for Blob not File because Bun.File doesn't inherit from File,
// but they both inherit from Blob and have a `name` property at runtime.
const isNamedBlob = (value: unknown) => value instanceof Blob && 'name' in value;
const isUploadable = (value: unknown) =>
typeof value === 'object' &&
value !== null &&
(value instanceof Response || isAsyncIterable(value) || isNamedBlob(value));
const hasUploadableValue = (value: unknown): boolean => {
if (isUploadable(value)) return true;
if (Array.isArray(value)) return value.some(hasUploadableValue);
if (value && typeof value === 'object') {
for (const k in value) {
if (hasUploadableValue((value as any)[k])) return true;
}
}
return false;
};
const addFormValue = async (form: FormData, key: string, value: unknown): Promise<void> => {
if (value === undefined) return;
if (value == null) {
throw new TypeError(
`Received null for "${key}"; to pass null in FormData, you must use the string 'null'`,
);
}
// TODO: make nested formats configurable
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
form.append(key, String(value));
} else if (value instanceof Response) {
form.append(key, makeFile([await value.blob()], getName(value)));
} else if (isAsyncIterable(value)) {
form.append(key, makeFile([await new Response(ReadableStreamFrom(value)).blob()], getName(value)));
} else if (isNamedBlob(value)) {
form.append(key, value, getName(value));
} else if (Array.isArray(value)) {
await Promise.all(value.map((entry) => addFormValue(form, key + '[]', entry)));
} else if (typeof value === 'object') {
await Promise.all(
Object.entries(value).map(([name, prop]) => addFormValue(form, `${key}[${name}]`, prop)),
);
} else {
throw new TypeError(
`Invalid value given to form, expected a string, number, boolean, object, Array, File or Blob but got ${value} instead`,
);
}
};

View File

@@ -1,8 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export * from './utils/values';
export * from './utils/base64';
export * from './utils/env';
export * from './utils/log';
export * from './utils/uuid';
export * from './utils/sleep';

View File

@@ -1,40 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { OpencodeError } from '../../core/error';
import { encodeUTF8 } from './bytes';
export const toBase64 = (data: string | Uint8Array | null | undefined): string => {
if (!data) return '';
if (typeof (globalThis as any).Buffer !== 'undefined') {
return (globalThis as any).Buffer.from(data).toString('base64');
}
if (typeof data === 'string') {
data = encodeUTF8(data);
}
if (typeof btoa !== 'undefined') {
return btoa(String.fromCharCode.apply(null, data as any));
}
throw new OpencodeError('Cannot generate base64 string; Expected `Buffer` or `btoa` to be defined');
};
export const fromBase64 = (str: string): Uint8Array => {
if (typeof (globalThis as any).Buffer !== 'undefined') {
const buf = (globalThis as any).Buffer.from(str, 'base64');
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
}
if (typeof atob !== 'undefined') {
const bstr = atob(str);
const buf = new Uint8Array(bstr.length);
for (let i = 0; i < bstr.length; i++) {
buf[i] = bstr.charCodeAt(i);
}
return buf;
}
throw new OpencodeError('Cannot decode base64 string; Expected `Buffer` or `atob` to be defined');
};

View File

@@ -1,32 +0,0 @@
export function concatBytes(buffers: Uint8Array[]): Uint8Array {
let length = 0;
for (const buffer of buffers) {
length += buffer.length;
}
const output = new Uint8Array(length);
let index = 0;
for (const buffer of buffers) {
output.set(buffer, index);
index += buffer.length;
}
return output;
}
let encodeUTF8_: (str: string) => Uint8Array;
export function encodeUTF8(str: string) {
let encoder;
return (
encodeUTF8_ ??
((encoder = new (globalThis as any).TextEncoder()), (encodeUTF8_ = encoder.encode.bind(encoder)))
)(str);
}
let decodeUTF8_: (bytes: Uint8Array) => string;
export function decodeUTF8(bytes: Uint8Array) {
let decoder;
return (
decodeUTF8_ ??
((decoder = new (globalThis as any).TextDecoder()), (decodeUTF8_ = decoder.decode.bind(decoder)))
)(bytes);
}

View File

@@ -1,18 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
/**
* Read an environment variable.
*
* Trims beginning and trailing whitespace.
*
* Will return undefined if the environment variable doesn't exist or cannot be accessed.
*/
export const readEnv = (env: string): string | undefined => {
if (typeof (globalThis as any).process !== 'undefined') {
return (globalThis as any).process.env?.[env]?.trim() ?? undefined;
}
if (typeof (globalThis as any).Deno !== 'undefined') {
return (globalThis as any).Deno.env?.get?.(env)?.trim();
}
return undefined;
};

View File

@@ -1,126 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { hasOwn } from './values';
import { type Opencode } from '../../client';
import { RequestOptions } from '../request-options';
type LogFn = (message: string, ...rest: unknown[]) => void;
export type Logger = {
error: LogFn;
warn: LogFn;
info: LogFn;
debug: LogFn;
};
export type LogLevel = 'off' | 'error' | 'warn' | 'info' | 'debug';
const levelNumbers = {
off: 0,
error: 200,
warn: 300,
info: 400,
debug: 500,
};
export const parseLogLevel = (
maybeLevel: string | undefined,
sourceName: string,
client: Opencode,
): LogLevel | undefined => {
if (!maybeLevel) {
return undefined;
}
if (hasOwn(levelNumbers, maybeLevel)) {
return maybeLevel;
}
loggerFor(client).warn(
`${sourceName} was set to ${JSON.stringify(maybeLevel)}, expected one of ${JSON.stringify(
Object.keys(levelNumbers),
)}`,
);
return undefined;
};
function noop() {}
function makeLogFn(fnLevel: keyof Logger, logger: Logger | undefined, logLevel: LogLevel) {
if (!logger || levelNumbers[fnLevel] > levelNumbers[logLevel]) {
return noop;
} else {
// Don't wrap logger functions, we want the stacktrace intact!
return logger[fnLevel].bind(logger);
}
}
const noopLogger = {
error: noop,
warn: noop,
info: noop,
debug: noop,
};
let cachedLoggers = /* @__PURE__ */ new WeakMap<Logger, [LogLevel, Logger]>();
export function loggerFor(client: Opencode): Logger {
const logger = client.logger;
const logLevel = client.logLevel ?? 'off';
if (!logger) {
return noopLogger;
}
const cachedLogger = cachedLoggers.get(logger);
if (cachedLogger && cachedLogger[0] === logLevel) {
return cachedLogger[1];
}
const levelLogger = {
error: makeLogFn('error', logger, logLevel),
warn: makeLogFn('warn', logger, logLevel),
info: makeLogFn('info', logger, logLevel),
debug: makeLogFn('debug', logger, logLevel),
};
cachedLoggers.set(logger, [logLevel, levelLogger]);
return levelLogger;
}
export const formatRequestDetails = (details: {
options?: RequestOptions | undefined;
headers?: Headers | Record<string, string> | undefined;
retryOfRequestLogID?: string | undefined;
retryOf?: string | undefined;
url?: string | undefined;
status?: number | undefined;
method?: string | undefined;
durationMs?: number | undefined;
message?: unknown;
body?: unknown;
}) => {
if (details.options) {
details.options = { ...details.options };
delete details.options['headers']; // redundant + leaks internals
}
if (details.headers) {
details.headers = Object.fromEntries(
(details.headers instanceof Headers ? [...details.headers] : Object.entries(details.headers)).map(
([name, value]) => [
name,
(
name.toLowerCase() === 'authorization' ||
name.toLowerCase() === 'cookie' ||
name.toLowerCase() === 'set-cookie'
) ?
'***'
: value,
],
),
);
}
if ('retryOfRequestLogID' in details) {
if (details.retryOfRequestLogID) {
details.retryOf = details.retryOfRequestLogID;
}
delete details.retryOfRequestLogID;
}
return details;
};

View File

@@ -1,88 +0,0 @@
import { OpencodeError } from '../../core/error';
/**
* Percent-encode everything that isn't safe to have in a path without encoding safe chars.
*
* Taken from https://datatracker.ietf.org/doc/html/rfc3986#section-3.3:
* > unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
* > sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
* > pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
*/
export function encodeURIPath(str: string) {
return str.replace(/[^A-Za-z0-9\-._~!$&'()*+,;=:@]+/g, encodeURIComponent);
}
const EMPTY = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null));
export const createPathTagFunction = (pathEncoder = encodeURIPath) =>
function path(statics: readonly string[], ...params: readonly unknown[]): string {
// If there are no params, no processing is needed.
if (statics.length === 1) return statics[0]!;
let postPath = false;
const invalidSegments = [];
const path = statics.reduce((previousValue, currentValue, index) => {
if (/[?#]/.test(currentValue)) {
postPath = true;
}
const value = params[index];
let encoded = (postPath ? encodeURIComponent : pathEncoder)('' + value);
if (
index !== params.length &&
(value == null ||
(typeof value === 'object' &&
// handle values from other realms
value.toString ===
Object.getPrototypeOf(Object.getPrototypeOf((value as any).hasOwnProperty ?? EMPTY) ?? EMPTY)
?.toString))
) {
encoded = value + '';
invalidSegments.push({
start: previousValue.length + currentValue.length,
length: encoded.length,
error: `Value of type ${Object.prototype.toString
.call(value)
.slice(8, -1)} is not a valid path parameter`,
});
}
return previousValue + currentValue + (index === params.length ? '' : encoded);
}, '');
const pathOnly = path.split(/[?#]/, 1)[0]!;
const invalidSegmentPattern = /(?<=^|\/)(?:\.|%2e){1,2}(?=\/|$)/gi;
let match;
// Find all invalid segments
while ((match = invalidSegmentPattern.exec(pathOnly)) !== null) {
invalidSegments.push({
start: match.index,
length: match[0].length,
error: `Value "${match[0]}" can\'t be safely passed as a path parameter`,
});
}
invalidSegments.sort((a, b) => a.start - b.start);
if (invalidSegments.length > 0) {
let lastEnd = 0;
const underline = invalidSegments.reduce((acc, segment) => {
const spaces = ' '.repeat(segment.start - lastEnd);
const arrows = '^'.repeat(segment.length);
lastEnd = segment.start + segment.length;
return acc + spaces + arrows;
}, '');
throw new OpencodeError(
`Path parameters result in path with invalid segments:\n${invalidSegments
.map((e) => e.error)
.join('\n')}\n${path}\n${underline}`,
);
}
return path;
};
/**
* URI-encodes path params and ensures no unsafe /./ or /../ path segments are introduced.
*/
export const path = /* @__PURE__ */ createPathTagFunction(encodeURIPath);

View File

@@ -1,3 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export const sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));

View File

@@ -1,17 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
/**
* https://stackoverflow.com/a/2117523
*/
export let uuid4 = function () {
const { crypto } = globalThis as any;
if (crypto?.randomUUID) {
uuid4 = crypto.randomUUID.bind(crypto);
return crypto.randomUUID();
}
const u8 = new Uint8Array(1);
const randomByte = crypto ? () => crypto.getRandomValues(u8)[0]! : () => (Math.random() * 0xff) & 0xff;
return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, (c) =>
(+c ^ (randomByte() & (15 >> (+c / 4)))).toString(16),
);
};

View File

@@ -1,105 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { OpencodeError } from '../../core/error';
// https://url.spec.whatwg.org/#url-scheme-string
const startsWithSchemeRegexp = /^[a-z][a-z0-9+.-]*:/i;
export const isAbsoluteURL = (url: string): boolean => {
return startsWithSchemeRegexp.test(url);
};
export let isArray = (val: unknown): val is unknown[] => ((isArray = Array.isArray), isArray(val));
export let isReadonlyArray = isArray as (val: unknown) => val is readonly unknown[];
/** Returns an object if the given value isn't an object, otherwise returns as-is */
export function maybeObj(x: unknown): object {
if (typeof x !== 'object') {
return {};
}
return x ?? {};
}
// https://stackoverflow.com/a/34491287
export function isEmptyObj(obj: Object | null | undefined): boolean {
if (!obj) return true;
for (const _k in obj) return false;
return true;
}
// https://eslint.org/docs/latest/rules/no-prototype-builtins
export function hasOwn<T extends object = object>(obj: T, key: PropertyKey): key is keyof T {
return Object.prototype.hasOwnProperty.call(obj, key);
}
export function isObj(obj: unknown): obj is Record<string, unknown> {
return obj != null && typeof obj === 'object' && !Array.isArray(obj);
}
export const ensurePresent = <T>(value: T | null | undefined): T => {
if (value == null) {
throw new OpencodeError(`Expected a value to be given but received ${value} instead.`);
}
return value;
};
export const validatePositiveInteger = (name: string, n: unknown): number => {
if (typeof n !== 'number' || !Number.isInteger(n)) {
throw new OpencodeError(`${name} must be an integer`);
}
if (n < 0) {
throw new OpencodeError(`${name} must be a positive integer`);
}
return n;
};
export const coerceInteger = (value: unknown): number => {
if (typeof value === 'number') return Math.round(value);
if (typeof value === 'string') return parseInt(value, 10);
throw new OpencodeError(`Could not coerce ${value} (type: ${typeof value}) into a number`);
};
export const coerceFloat = (value: unknown): number => {
if (typeof value === 'number') return value;
if (typeof value === 'string') return parseFloat(value);
throw new OpencodeError(`Could not coerce ${value} (type: ${typeof value}) into a number`);
};
export const coerceBoolean = (value: unknown): boolean => {
if (typeof value === 'boolean') return value;
if (typeof value === 'string') return value === 'true';
return Boolean(value);
};
export const maybeCoerceInteger = (value: unknown): number | undefined => {
if (value === undefined) {
return undefined;
}
return coerceInteger(value);
};
export const maybeCoerceFloat = (value: unknown): number | undefined => {
if (value === undefined) {
return undefined;
}
return coerceFloat(value);
};
export const maybeCoerceBoolean = (value: unknown): boolean | undefined => {
if (value === undefined) {
return undefined;
}
return coerceBoolean(value);
};
export const safeJSON = (text: string) => {
try {
return JSON.parse(text);
} catch (err) {
return undefined;
}
};

View File

@@ -1,4 +0,0 @@
File generated from our OpenAPI spec by Stainless.
This directory can be used to store custom files to expand the SDK.
It is ignored by Stainless code generation and its content (other than this keep file) won't be touched.

View File

@@ -1,2 +0,0 @@
/** @deprecated Import from ./core/resource instead */
export * from './core/resource';

View File

@@ -1 +0,0 @@
export * from './resources/index';

View File

@@ -1,192 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { APIResource } from '../core/resource';
import { APIPromise } from '../core/api-promise';
import { RequestOptions } from '../internal/request-options';
export class AppResource extends APIResource {
/**
* Get app info
*/
get(options?: RequestOptions): APIPromise<App> {
return this._client.get('/app', options);
}
/**
* Initialize the app
*/
init(options?: RequestOptions): APIPromise<AppInitResponse> {
return this._client.post('/app/init', options);
}
/**
* Write a log entry to the server logs
*/
log(body: AppLogParams, options?: RequestOptions): APIPromise<AppLogResponse> {
return this._client.post('/log', { body, ...options });
}
/**
* List all modes
*/
modes(options?: RequestOptions): APIPromise<AppModesResponse> {
return this._client.get('/mode', options);
}
/**
* List all providers
*/
providers(options?: RequestOptions): APIPromise<AppProvidersResponse> {
return this._client.get('/config/providers', options);
}
}
export interface App {
git: boolean;
hostname: string;
path: App.Path;
time: App.Time;
}
export namespace App {
export interface Path {
config: string;
cwd: string;
data: string;
root: string;
state: string;
}
export interface Time {
initialized?: number;
}
}
export interface Mode {
name: string;
tools: { [key: string]: boolean };
model?: Mode.Model;
prompt?: string;
temperature?: number;
}
export namespace Mode {
export interface Model {
modelID: string;
providerID: string;
}
}
export interface Model {
id: string;
attachment: boolean;
cost: Model.Cost;
limit: Model.Limit;
name: string;
options: { [key: string]: unknown };
reasoning: boolean;
release_date: string;
temperature: boolean;
tool_call: boolean;
}
export namespace Model {
export interface Cost {
input: number;
output: number;
cache_read?: number;
cache_write?: number;
}
export interface Limit {
context: number;
output: number;
}
}
export interface Provider {
id: string;
env: Array<string>;
models: { [key: string]: Model };
name: string;
api?: string;
npm?: string;
}
export type AppInitResponse = boolean;
export type AppLogResponse = boolean;
export type AppModesResponse = Array<Mode>;
export interface AppProvidersResponse {
default: { [key: string]: string };
providers: Array<Provider>;
}
export interface AppLogParams {
/**
* Log level
*/
level: 'debug' | 'info' | 'error' | 'warn';
/**
* Log message
*/
message: string;
/**
* Service name for the log entry
*/
service: string;
/**
* Additional metadata for the log entry
*/
extra?: { [key: string]: unknown };
}
export declare namespace AppResource {
export {
type App as App,
type Mode as Mode,
type Model as Model,
type Provider as Provider,
type AppInitResponse as AppInitResponse,
type AppLogResponse as AppLogResponse,
type AppModesResponse as AppModesResponse,
type AppProvidersResponse as AppProvidersResponse,
type AppLogParams as AppLogParams,
};
}

View File

@@ -1,492 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { APIResource } from '../core/resource';
import * as ConfigAPI from './config';
import { APIPromise } from '../core/api-promise';
import { RequestOptions } from '../internal/request-options';
export class ConfigResource extends APIResource {
/**
* Get config info
*/
get(options?: RequestOptions): APIPromise<Config> {
return this._client.get('/config', options);
}
}
export interface Config {
/**
* JSON schema reference for configuration validation
*/
$schema?: string;
/**
* Modes configuration, see https://opencode.ai/docs/modes
*/
agent?: Config.Agent;
/**
* @deprecated Use 'share' field instead. Share newly created sessions
* automatically
*/
autoshare?: boolean;
/**
* Automatically update to the latest version
*/
autoupdate?: boolean;
/**
* Disable providers that are loaded automatically
*/
disabled_providers?: Array<string>;
experimental?: Config.Experimental;
/**
* Additional instruction files or patterns to include
*/
instructions?: Array<string>;
/**
* Custom keybind configurations
*/
keybinds?: KeybindsConfig;
/**
* @deprecated Always uses stretch layout.
*/
layout?: 'auto' | 'stretch';
/**
* MCP (Model Context Protocol) server configurations
*/
mcp?: { [key: string]: McpLocalConfig | McpRemoteConfig };
/**
* Modes configuration, see https://opencode.ai/docs/modes
*/
mode?: Config.Mode;
/**
* Model to use in the format of provider/model, eg anthropic/claude-2
*/
model?: string;
/**
* Custom provider configurations and model overrides
*/
provider?: { [key: string]: Config.Provider };
/**
* Control sharing behavior:'manual' allows manual sharing via commands, 'auto'
* enables automatic sharing, 'disabled' disables all sharing
*/
share?: 'manual' | 'auto' | 'disabled';
/**
* Small model to use for tasks like summarization and title generation in the
* format of provider/model
*/
small_model?: string;
/**
* Theme name to use for the interface
*/
theme?: string;
/**
* Custom username to display in conversations instead of system username
*/
username?: string;
}
export namespace Config {
/**
* Modes configuration, see https://opencode.ai/docs/modes
*/
export interface Agent {
general?: Agent.General;
[k: string]: Agent.AgentConfig | undefined;
}
export namespace Agent {
export interface General extends ConfigAPI.ModeConfig {
description: string;
}
export interface AgentConfig extends ConfigAPI.ModeConfig {
description: string;
}
}
export interface AgentConfig extends ConfigAPI.ModeConfig {
description: string;
}
export interface AgentConfig extends ConfigAPI.ModeConfig {
description: string;
}
export interface Experimental {
hook?: Experimental.Hook;
}
export namespace Experimental {
export interface Hook {
file_edited?: { [key: string]: Array<Hook.FileEdited> };
session_completed?: Array<Hook.SessionCompleted>;
}
export namespace Hook {
export interface FileEdited {
command: Array<string>;
environment?: { [key: string]: string };
}
export interface SessionCompleted {
command: Array<string>;
environment?: { [key: string]: string };
}
}
}
/**
* Modes configuration, see https://opencode.ai/docs/modes
*/
export interface Mode {
build?: ConfigAPI.ModeConfig;
plan?: ConfigAPI.ModeConfig;
[k: string]: ConfigAPI.ModeConfig | undefined;
}
export interface Provider {
models: { [key: string]: Provider.Models };
id?: string;
api?: string;
env?: Array<string>;
name?: string;
npm?: string;
options?: Provider.Options;
}
export namespace Provider {
export interface Models {
id?: string;
attachment?: boolean;
cost?: Models.Cost;
limit?: Models.Limit;
name?: string;
options?: { [key: string]: unknown };
reasoning?: boolean;
release_date?: string;
temperature?: boolean;
tool_call?: boolean;
}
export namespace Models {
export interface Cost {
input: number;
output: number;
cache_read?: number;
cache_write?: number;
}
export interface Limit {
context: number;
output: number;
}
}
export interface Options {
apiKey?: string;
baseURL?: string;
[k: string]: unknown;
}
}
}
export interface KeybindsConfig {
/**
* Exit the application
*/
app_exit: string;
/**
* Show help dialog
*/
app_help: string;
/**
* Open external editor
*/
editor_open: string;
/**
* Close file
*/
file_close: string;
/**
* Split/unified diff
*/
file_diff_toggle: string;
/**
* List files
*/
file_list: string;
/**
* Search file
*/
file_search: string;
/**
* Clear input field
*/
input_clear: string;
/**
* Insert newline in input
*/
input_newline: string;
/**
* Paste from clipboard
*/
input_paste: string;
/**
* Submit input
*/
input_submit: string;
/**
* Leader key for keybind combinations
*/
leader: string;
/**
* Copy message
*/
messages_copy: string;
/**
* Navigate to first message
*/
messages_first: string;
/**
* Scroll messages down by half page
*/
messages_half_page_down: string;
/**
* Scroll messages up by half page
*/
messages_half_page_up: string;
/**
* Navigate to last message
*/
messages_last: string;
/**
* Toggle layout
*/
messages_layout_toggle: string;
/**
* Navigate to next message
*/
messages_next: string;
/**
* Scroll messages down by one page
*/
messages_page_down: string;
/**
* Scroll messages up by one page
*/
messages_page_up: string;
/**
* Navigate to previous message
*/
messages_previous: string;
/**
* Redo message
*/
messages_redo: string;
/**
* @deprecated use messages_undo. Revert message
*/
messages_revert: string;
/**
* Undo message
*/
messages_undo: string;
/**
* List available models
*/
model_list: string;
/**
* Create/update AGENTS.md
*/
project_init: string;
/**
* Compact the session
*/
session_compact: string;
/**
* Export session to editor
*/
session_export: string;
/**
* Interrupt current session
*/
session_interrupt: string;
/**
* List all sessions
*/
session_list: string;
/**
* Create a new session
*/
session_new: string;
/**
* Share current session
*/
session_share: string;
/**
* Unshare current session
*/
session_unshare: string;
/**
* Next mode
*/
switch_mode: string;
/**
* Previous Mode
*/
switch_mode_reverse: string;
/**
* List available themes
*/
theme_list: string;
/**
* Toggle tool details
*/
tool_details: string;
}
export interface McpLocalConfig {
/**
* Command and arguments to run the MCP server
*/
command: Array<string>;
/**
* Type of MCP server connection
*/
type: 'local';
/**
* Enable or disable the MCP server on startup
*/
enabled?: boolean;
/**
* Environment variables to set when running the MCP server
*/
environment?: { [key: string]: string };
}
export interface 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 };
}
export interface ModeConfig {
disable?: boolean;
model?: string;
prompt?: string;
temperature?: number;
tools?: { [key: string]: boolean };
}
export declare namespace ConfigResource {
export {
type Config as Config,
type KeybindsConfig as KeybindsConfig,
type McpLocalConfig as McpLocalConfig,
type McpRemoteConfig as McpRemoteConfig,
type ModeConfig as ModeConfig,
};
}

View File

@@ -1,267 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { APIResource } from '../core/resource';
import * as SessionAPI from './session';
import * as Shared from './shared';
import { APIPromise } from '../core/api-promise';
import { Stream } from '../core/streaming';
import { RequestOptions } from '../internal/request-options';
export class Event extends APIResource {
/**
* Get events
*/
list(options?: RequestOptions): APIPromise<Stream<EventListResponse>> {
return this._client.get('/event', { ...options, stream: true }) as APIPromise<Stream<EventListResponse>>;
}
}
export type EventListResponse =
| EventListResponse.EventInstallationUpdated
| EventListResponse.EventLspClientDiagnostics
| EventListResponse.EventMessageUpdated
| EventListResponse.EventMessageRemoved
| EventListResponse.EventMessagePartUpdated
| EventListResponse.EventMessagePartRemoved
| EventListResponse.EventStorageWrite
| EventListResponse.EventPermissionUpdated
| EventListResponse.EventFileEdited
| EventListResponse.EventSessionUpdated
| EventListResponse.EventSessionDeleted
| EventListResponse.EventSessionIdle
| EventListResponse.EventSessionError
| EventListResponse.EventServerConnected
| EventListResponse.EventFileWatcherUpdated
| EventListResponse.EventIdeInstalled;
export namespace EventListResponse {
export interface EventInstallationUpdated {
properties: EventInstallationUpdated.Properties;
type: 'installation.updated';
}
export namespace EventInstallationUpdated {
export interface Properties {
version: string;
}
}
export interface EventLspClientDiagnostics {
properties: EventLspClientDiagnostics.Properties;
type: 'lsp.client.diagnostics';
}
export namespace EventLspClientDiagnostics {
export interface Properties {
path: string;
serverID: string;
}
}
export interface EventMessageUpdated {
properties: EventMessageUpdated.Properties;
type: 'message.updated';
}
export namespace EventMessageUpdated {
export interface Properties {
info: SessionAPI.Message;
}
}
export interface EventMessageRemoved {
properties: EventMessageRemoved.Properties;
type: 'message.removed';
}
export namespace EventMessageRemoved {
export interface Properties {
messageID: string;
sessionID: string;
}
}
export interface EventMessagePartUpdated {
properties: EventMessagePartUpdated.Properties;
type: 'message.part.updated';
}
export namespace EventMessagePartUpdated {
export interface Properties {
part: SessionAPI.Part;
}
}
export interface EventMessagePartRemoved {
properties: EventMessagePartRemoved.Properties;
type: 'message.part.removed';
}
export namespace EventMessagePartRemoved {
export interface Properties {
messageID: string;
partID: string;
sessionID: string;
}
}
export interface EventStorageWrite {
properties: EventStorageWrite.Properties;
type: 'storage.write';
}
export namespace EventStorageWrite {
export interface Properties {
key: string;
content?: unknown;
}
}
export interface EventPermissionUpdated {
properties: EventPermissionUpdated.Properties;
type: 'permission.updated';
}
export namespace EventPermissionUpdated {
export interface Properties {
id: string;
metadata: { [key: string]: unknown };
sessionID: string;
time: Properties.Time;
title: string;
}
export namespace Properties {
export interface Time {
created: number;
}
}
}
export interface EventFileEdited {
properties: EventFileEdited.Properties;
type: 'file.edited';
}
export namespace EventFileEdited {
export interface Properties {
file: string;
}
}
export interface EventSessionUpdated {
properties: EventSessionUpdated.Properties;
type: 'session.updated';
}
export namespace EventSessionUpdated {
export interface Properties {
info: SessionAPI.Session;
}
}
export interface EventSessionDeleted {
properties: EventSessionDeleted.Properties;
type: 'session.deleted';
}
export namespace EventSessionDeleted {
export interface Properties {
info: SessionAPI.Session;
}
}
export interface EventSessionIdle {
properties: EventSessionIdle.Properties;
type: 'session.idle';
}
export namespace EventSessionIdle {
export interface Properties {
sessionID: string;
}
}
export interface EventSessionError {
properties: EventSessionError.Properties;
type: 'session.error';
}
export namespace EventSessionError {
export interface Properties {
error?:
| Shared.ProviderAuthError
| Shared.UnknownError
| Properties.MessageOutputLengthError
| Shared.MessageAbortedError;
sessionID?: string;
}
export namespace Properties {
export interface MessageOutputLengthError {
data: unknown;
name: 'MessageOutputLengthError';
}
}
}
export interface EventServerConnected {
properties: unknown;
type: 'server.connected';
}
export interface EventFileWatcherUpdated {
properties: EventFileWatcherUpdated.Properties;
type: 'file.watcher.updated';
}
export namespace EventFileWatcherUpdated {
export interface Properties {
event: 'rename' | 'change';
file: string;
}
}
export interface EventIdeInstalled {
properties: EventIdeInstalled.Properties;
type: 'ide.installed';
}
export namespace EventIdeInstalled {
export interface Properties {
ide: string;
}
}
}
export declare namespace Event {
export { type EventListResponse as EventListResponse };
}

View File

@@ -1,52 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { APIResource } from '../core/resource';
import { APIPromise } from '../core/api-promise';
import { RequestOptions } from '../internal/request-options';
export class FileResource extends APIResource {
/**
* Read a file
*/
read(query: FileReadParams, options?: RequestOptions): APIPromise<FileReadResponse> {
return this._client.get('/file', { query, ...options });
}
/**
* Get file status
*/
status(options?: RequestOptions): APIPromise<FileStatusResponse> {
return this._client.get('/file/status', options);
}
}
export interface File {
added: number;
path: string;
removed: number;
status: 'added' | 'deleted' | 'modified';
}
export interface FileReadResponse {
content: string;
type: 'raw' | 'patch';
}
export type FileStatusResponse = Array<File>;
export interface FileReadParams {
path: string;
}
export declare namespace FileResource {
export {
type File as File,
type FileReadResponse as FileReadResponse,
type FileStatusResponse as FileStatusResponse,
type FileReadParams as FileReadParams,
};
}

View File

@@ -1,134 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { APIResource } from '../core/resource';
import { APIPromise } from '../core/api-promise';
import { RequestOptions } from '../internal/request-options';
export class Find extends APIResource {
/**
* Find files
*/
files(query: FindFilesParams, options?: RequestOptions): APIPromise<FindFilesResponse> {
return this._client.get('/find/file', { query, ...options });
}
/**
* Find workspace symbols
*/
symbols(query: FindSymbolsParams, options?: RequestOptions): APIPromise<FindSymbolsResponse> {
return this._client.get('/find/symbol', { query, ...options });
}
/**
* Find text in files
*/
text(query: FindTextParams, options?: RequestOptions): APIPromise<FindTextResponse> {
return this._client.get('/find', { query, ...options });
}
}
export interface Symbol {
kind: number;
location: Symbol.Location;
name: string;
}
export namespace Symbol {
export interface Location {
range: Location.Range;
uri: string;
}
export namespace Location {
export interface Range {
end: Range.End;
start: Range.Start;
}
export namespace Range {
export interface End {
character: number;
line: number;
}
export interface Start {
character: number;
line: number;
}
}
}
}
export type FindFilesResponse = Array<string>;
export type FindSymbolsResponse = Array<Symbol>;
export type FindTextResponse = Array<FindTextResponse.FindTextResponseItem>;
export namespace FindTextResponse {
export interface FindTextResponseItem {
absolute_offset: number;
line_number: number;
lines: FindTextResponseItem.Lines;
path: FindTextResponseItem.Path;
submatches: Array<FindTextResponseItem.Submatch>;
}
export namespace FindTextResponseItem {
export interface Lines {
text: string;
}
export interface Path {
text: string;
}
export interface Submatch {
end: number;
match: Submatch.Match;
start: number;
}
export namespace Submatch {
export interface Match {
text: string;
}
}
}
}
export interface FindFilesParams {
query: string;
}
export interface FindSymbolsParams {
query: string;
}
export interface FindTextParams {
pattern: string;
}
export declare namespace Find {
export {
type Symbol as Symbol,
type FindFilesResponse as FindFilesResponse,
type FindSymbolsResponse as FindSymbolsResponse,
type FindTextResponse as FindTextResponse,
type FindFilesParams as FindFilesParams,
type FindSymbolsParams as FindSymbolsParams,
type FindTextParams as FindTextParams,
};
}

View File

@@ -1,81 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export * from './shared';
export {
AppResource,
type App,
type Mode,
type Model,
type Provider,
type AppInitResponse,
type AppLogResponse,
type AppModesResponse,
type AppProvidersResponse,
type AppLogParams,
} from './app';
export {
ConfigResource,
type Config,
type KeybindsConfig,
type McpLocalConfig,
type McpRemoteConfig,
type ModeConfig,
} from './config';
export { Event, type EventListResponse } from './event';
export {
FileResource,
type File,
type FileReadResponse,
type FileStatusResponse,
type FileReadParams,
} from './file';
export {
Find,
type Symbol,
type FindFilesResponse,
type FindSymbolsResponse,
type FindTextResponse,
type FindFilesParams,
type FindSymbolsParams,
type FindTextParams,
} from './find';
export {
SessionResource,
type AssistantMessage,
type FilePart,
type FilePartInput,
type FilePartSource,
type FilePartSourceText,
type FileSource,
type Message,
type Part,
type Session,
type SnapshotPart,
type StepFinishPart,
type StepStartPart,
type SymbolSource,
type TextPart,
type TextPartInput,
type ToolPart,
type ToolStateCompleted,
type ToolStateError,
type ToolStatePending,
type ToolStateRunning,
type UserMessage,
type SessionListResponse,
type SessionDeleteResponse,
type SessionAbortResponse,
type SessionInitResponse,
type SessionMessagesResponse,
type SessionSummarizeResponse,
type SessionChatParams,
type SessionInitParams,
type SessionRevertParams,
type SessionSummarizeParams,
} from './session';
export {
Tui,
type TuiAppendPromptResponse,
type TuiOpenHelpResponse,
type TuiAppendPromptParams,
} from './tui';

View File

@@ -1,605 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { APIResource } from '../core/resource';
import * as SessionAPI from './session';
import * as Shared from './shared';
import { APIPromise } from '../core/api-promise';
import { RequestOptions } from '../internal/request-options';
import { path } from '../internal/utils/path';
export class SessionResource extends APIResource {
/**
* Create a new session
*/
create(options?: RequestOptions): APIPromise<Session> {
return this._client.post('/session', options);
}
/**
* List all sessions
*/
list(options?: RequestOptions): APIPromise<SessionListResponse> {
return this._client.get('/session', options);
}
/**
* Delete a session and all its data
*/
delete(id: string, options?: RequestOptions): APIPromise<SessionDeleteResponse> {
return this._client.delete(path`/session/${id}`, options);
}
/**
* Abort a session
*/
abort(id: string, options?: RequestOptions): APIPromise<SessionAbortResponse> {
return this._client.post(path`/session/${id}/abort`, options);
}
/**
* Create and send a new message to a session
*/
chat(id: string, body: SessionChatParams, options?: RequestOptions): APIPromise<AssistantMessage> {
return this._client.post(path`/session/${id}/message`, { body, ...options });
}
/**
* Analyze the app and create an AGENTS.md file
*/
init(id: string, body: SessionInitParams, options?: RequestOptions): APIPromise<SessionInitResponse> {
return this._client.post(path`/session/${id}/init`, { body, ...options });
}
/**
* List messages for a session
*/
messages(id: string, options?: RequestOptions): APIPromise<SessionMessagesResponse> {
return this._client.get(path`/session/${id}/message`, options);
}
/**
* Revert a message
*/
revert(id: string, body: SessionRevertParams, options?: RequestOptions): APIPromise<Session> {
return this._client.post(path`/session/${id}/revert`, { body, ...options });
}
/**
* Share a session
*/
share(id: string, options?: RequestOptions): APIPromise<Session> {
return this._client.post(path`/session/${id}/share`, options);
}
/**
* Summarize the session
*/
summarize(
id: string,
body: SessionSummarizeParams,
options?: RequestOptions,
): APIPromise<SessionSummarizeResponse> {
return this._client.post(path`/session/${id}/summarize`, { body, ...options });
}
/**
* Restore all reverted messages
*/
unrevert(id: string, options?: RequestOptions): APIPromise<Session> {
return this._client.post(path`/session/${id}/unrevert`, options);
}
/**
* Unshare the session
*/
unshare(id: string, options?: RequestOptions): APIPromise<Session> {
return this._client.delete(path`/session/${id}/share`, options);
}
}
export interface AssistantMessage {
id: string;
cost: number;
mode: string;
modelID: string;
path: AssistantMessage.Path;
providerID: string;
role: 'assistant';
sessionID: string;
system: Array<string>;
time: AssistantMessage.Time;
tokens: AssistantMessage.Tokens;
error?:
| Shared.ProviderAuthError
| Shared.UnknownError
| AssistantMessage.MessageOutputLengthError
| Shared.MessageAbortedError;
summary?: boolean;
}
export namespace AssistantMessage {
export interface Path {
cwd: string;
root: string;
}
export interface Time {
created: number;
completed?: number;
}
export interface Tokens {
cache: Tokens.Cache;
input: number;
output: number;
reasoning: number;
}
export namespace Tokens {
export interface Cache {
read: number;
write: number;
}
}
export interface MessageOutputLengthError {
data: unknown;
name: 'MessageOutputLengthError';
}
}
export interface FilePart {
id: string;
messageID: string;
mime: string;
sessionID: string;
type: 'file';
url: string;
filename?: string;
source?: FilePartSource;
}
export interface FilePartInput {
mime: string;
type: 'file';
url: string;
id?: string;
filename?: string;
source?: FilePartSource;
}
export type FilePartSource = FileSource | SymbolSource;
export interface FilePartSourceText {
end: number;
start: number;
value: string;
}
export interface FileSource {
path: string;
text: FilePartSourceText;
type: 'file';
}
export type Message = UserMessage | AssistantMessage;
export type Part =
| TextPart
| FilePart
| ToolPart
| StepStartPart
| StepFinishPart
| SnapshotPart
| Part.PatchPart;
export namespace Part {
export interface PatchPart {
id: string;
files: Array<string>;
hash: string;
messageID: string;
sessionID: string;
type: 'patch';
}
}
export interface Session {
id: string;
time: Session.Time;
title: string;
version: string;
parentID?: string;
revert?: Session.Revert;
share?: Session.Share;
}
export namespace Session {
export interface Time {
created: number;
updated: number;
}
export interface Revert {
messageID: string;
diff?: string;
partID?: string;
snapshot?: string;
}
export interface Share {
url: string;
}
}
export interface SnapshotPart {
id: string;
messageID: string;
sessionID: string;
snapshot: string;
type: 'snapshot';
}
export interface StepFinishPart {
id: string;
cost: number;
messageID: string;
sessionID: string;
tokens: StepFinishPart.Tokens;
type: 'step-finish';
}
export namespace StepFinishPart {
export interface Tokens {
cache: Tokens.Cache;
input: number;
output: number;
reasoning: number;
}
export namespace Tokens {
export interface Cache {
read: number;
write: number;
}
}
}
export interface StepStartPart {
id: string;
messageID: string;
sessionID: string;
type: 'step-start';
}
export interface SymbolSource {
kind: number;
name: string;
path: string;
range: SymbolSource.Range;
text: FilePartSourceText;
type: 'symbol';
}
export namespace SymbolSource {
export interface Range {
end: Range.End;
start: Range.Start;
}
export namespace Range {
export interface End {
character: number;
line: number;
}
export interface Start {
character: number;
line: number;
}
}
}
export interface TextPart {
id: string;
messageID: string;
sessionID: string;
text: string;
type: 'text';
synthetic?: boolean;
time?: TextPart.Time;
}
export namespace TextPart {
export interface Time {
start: number;
end?: number;
}
}
export interface TextPartInput {
text: string;
type: 'text';
id?: string;
synthetic?: boolean;
time?: TextPartInput.Time;
}
export namespace TextPartInput {
export interface Time {
start: number;
end?: number;
}
}
export interface ToolPart {
id: string;
callID: string;
messageID: string;
sessionID: string;
state: ToolStatePending | ToolStateRunning | ToolStateCompleted | ToolStateError;
tool: string;
type: 'tool';
}
export interface ToolStateCompleted {
input: { [key: string]: unknown };
metadata: { [key: string]: unknown };
output: string;
status: 'completed';
time: ToolStateCompleted.Time;
title: string;
}
export namespace ToolStateCompleted {
export interface Time {
end: number;
start: number;
}
}
export interface ToolStateError {
error: string;
input: { [key: string]: unknown };
status: 'error';
time: ToolStateError.Time;
}
export namespace ToolStateError {
export interface Time {
end: number;
start: number;
}
}
export interface ToolStatePending {
status: 'pending';
}
export interface ToolStateRunning {
status: 'running';
time: ToolStateRunning.Time;
input?: unknown;
metadata?: { [key: string]: unknown };
title?: string;
}
export namespace ToolStateRunning {
export interface Time {
start: number;
}
}
export interface UserMessage {
id: string;
role: 'user';
sessionID: string;
time: UserMessage.Time;
}
export namespace UserMessage {
export interface Time {
created: number;
}
}
export type SessionListResponse = Array<Session>;
export type SessionDeleteResponse = boolean;
export type SessionAbortResponse = boolean;
export type SessionInitResponse = boolean;
export type SessionMessagesResponse = Array<SessionMessagesResponse.SessionMessagesResponseItem>;
export namespace SessionMessagesResponse {
export interface SessionMessagesResponseItem {
info: SessionAPI.Message;
parts: Array<SessionAPI.Part>;
}
}
export type SessionSummarizeResponse = boolean;
export interface SessionChatParams {
modelID: string;
parts: Array<TextPartInput | FilePartInput>;
providerID: string;
messageID?: string;
mode?: string;
system?: string;
tools?: { [key: string]: boolean };
}
export interface SessionInitParams {
messageID: string;
modelID: string;
providerID: string;
}
export interface SessionRevertParams {
messageID: string;
partID?: string;
}
export interface SessionSummarizeParams {
modelID: string;
providerID: string;
}
export declare namespace SessionResource {
export {
type AssistantMessage as AssistantMessage,
type FilePart as FilePart,
type FilePartInput as FilePartInput,
type FilePartSource as FilePartSource,
type FilePartSourceText as FilePartSourceText,
type FileSource as FileSource,
type Message as Message,
type Part as Part,
type Session as Session,
type SnapshotPart as SnapshotPart,
type StepFinishPart as StepFinishPart,
type StepStartPart as StepStartPart,
type SymbolSource as SymbolSource,
type TextPart as TextPart,
type TextPartInput as TextPartInput,
type ToolPart as ToolPart,
type ToolStateCompleted as ToolStateCompleted,
type ToolStateError as ToolStateError,
type ToolStatePending as ToolStatePending,
type ToolStateRunning as ToolStateRunning,
type UserMessage as UserMessage,
type SessionListResponse as SessionListResponse,
type SessionDeleteResponse as SessionDeleteResponse,
type SessionAbortResponse as SessionAbortResponse,
type SessionInitResponse as SessionInitResponse,
type SessionMessagesResponse as SessionMessagesResponse,
type SessionSummarizeResponse as SessionSummarizeResponse,
type SessionChatParams as SessionChatParams,
type SessionInitParams as SessionInitParams,
type SessionRevertParams as SessionRevertParams,
type SessionSummarizeParams as SessionSummarizeParams,
};
}

View File

@@ -1,33 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export interface MessageAbortedError {
data: unknown;
name: 'MessageAbortedError';
}
export interface ProviderAuthError {
data: ProviderAuthError.Data;
name: 'ProviderAuthError';
}
export namespace ProviderAuthError {
export interface Data {
message: string;
providerID: string;
}
}
export interface UnknownError {
data: UnknownError.Data;
name: 'UnknownError';
}
export namespace UnknownError {
export interface Data {
message: string;
}
}

View File

@@ -1,37 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { APIResource } from '../core/resource';
import { APIPromise } from '../core/api-promise';
import { RequestOptions } from '../internal/request-options';
export class Tui extends APIResource {
/**
* Append prompt to the TUI
*/
appendPrompt(body: TuiAppendPromptParams, options?: RequestOptions): APIPromise<TuiAppendPromptResponse> {
return this._client.post('/tui/append-prompt', { body, ...options });
}
/**
* Open the help dialog
*/
openHelp(options?: RequestOptions): APIPromise<TuiOpenHelpResponse> {
return this._client.post('/tui/open-help', options);
}
}
export type TuiAppendPromptResponse = boolean;
export type TuiOpenHelpResponse = boolean;
export interface TuiAppendPromptParams {
text: string;
}
export declare namespace Tui {
export {
type TuiAppendPromptResponse as TuiAppendPromptResponse,
type TuiOpenHelpResponse as TuiOpenHelpResponse,
type TuiAppendPromptParams as TuiAppendPromptParams,
};
}

View File

@@ -1,2 +0,0 @@
/** @deprecated Import from ./core/streaming instead */
export * from './core/streaming';

View File

@@ -1,2 +0,0 @@
/** @deprecated Import from ./core/uploads instead */
export * from './core/uploads';

View File

@@ -1 +0,0 @@
export const VERSION = '0.1.0-alpha.20'; // x-release-please-version