mirror of
https://github.com/aljazceru/opencode.git
synced 2026-01-02 07:25:00 +01:00
ci: new publish method (#1451)
This commit is contained in:
@@ -1,2 +0,0 @@
|
||||
/** @deprecated Import from ./core/api-promise instead */
|
||||
export * from './core/api-promise';
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
# `core`
|
||||
|
||||
This directory holds public modules implementing non-resource-specific SDK functionality.
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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> {}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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, '', ''];
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export { type Uploadable } from '../internal/uploads';
|
||||
export { toFile, type ToFileInput } from '../internal/to-file';
|
||||
@@ -1,2 +0,0 @@
|
||||
/** @deprecated Import from ./core/error instead */
|
||||
export * from './core/error';
|
||||
@@ -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';
|
||||
@@ -1,3 +0,0 @@
|
||||
# `internal`
|
||||
|
||||
The modules in this directory are not importable outside this package and will change between releases.
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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());
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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),
|
||||
};
|
||||
};
|
||||
@@ -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 };
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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(', ')}]`;
|
||||
}
|
||||
@@ -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>>;
|
||||
@@ -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`,
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -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';
|
||||
@@ -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');
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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);
|
||||
@@ -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));
|
||||
@@ -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),
|
||||
);
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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.
|
||||
@@ -1,2 +0,0 @@
|
||||
/** @deprecated Import from ./core/resource instead */
|
||||
export * from './core/resource';
|
||||
@@ -1 +0,0 @@
|
||||
export * from './resources/index';
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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';
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
/** @deprecated Import from ./core/streaming instead */
|
||||
export * from './core/streaming';
|
||||
@@ -1,2 +0,0 @@
|
||||
/** @deprecated Import from ./core/uploads instead */
|
||||
export * from './core/uploads';
|
||||
@@ -1 +0,0 @@
|
||||
export const VERSION = '0.1.0-alpha.20'; // x-release-please-version
|
||||
Reference in New Issue
Block a user