summaryrefslogtreecommitdiffhomepage
path: root/packages/sdk/src/internal/utils
diff options
context:
space:
mode:
authoradamdotdevin <[email protected]>2025-07-22 11:50:51 -0500
committeradamdotdevin <[email protected]>2025-07-22 11:50:51 -0500
commit10c8b495907069461f5dc464f6285321290c8b14 (patch)
tree9fed07910dd7d99688c0261a364d810a500e21d3 /packages/sdk/src/internal/utils
parent500cea5ce7fa635a924cd9abea63aaf672f7645d (diff)
downloadopencode-10c8b495907069461f5dc464f6285321290c8b14.tar.gz
opencode-10c8b495907069461f5dc464f6285321290c8b14.zip
chore: generate sdk into packages/sdk
Diffstat (limited to 'packages/sdk/src/internal/utils')
-rw-r--r--packages/sdk/src/internal/utils/base64.ts40
-rw-r--r--packages/sdk/src/internal/utils/bytes.ts32
-rw-r--r--packages/sdk/src/internal/utils/env.ts18
-rw-r--r--packages/sdk/src/internal/utils/log.ts126
-rw-r--r--packages/sdk/src/internal/utils/path.ts88
-rw-r--r--packages/sdk/src/internal/utils/sleep.ts3
-rw-r--r--packages/sdk/src/internal/utils/uuid.ts17
-rw-r--r--packages/sdk/src/internal/utils/values.ts105
8 files changed, 429 insertions, 0 deletions
diff --git a/packages/sdk/src/internal/utils/base64.ts b/packages/sdk/src/internal/utils/base64.ts
new file mode 100644
index 000000000..05d7bd9b4
--- /dev/null
+++ b/packages/sdk/src/internal/utils/base64.ts
@@ -0,0 +1,40 @@
+// 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');
+};
diff --git a/packages/sdk/src/internal/utils/bytes.ts b/packages/sdk/src/internal/utils/bytes.ts
new file mode 100644
index 000000000..8da627abe
--- /dev/null
+++ b/packages/sdk/src/internal/utils/bytes.ts
@@ -0,0 +1,32 @@
+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);
+}
diff --git a/packages/sdk/src/internal/utils/env.ts b/packages/sdk/src/internal/utils/env.ts
new file mode 100644
index 000000000..2d8480077
--- /dev/null
+++ b/packages/sdk/src/internal/utils/env.ts
@@ -0,0 +1,18 @@
+// 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;
+};
diff --git a/packages/sdk/src/internal/utils/log.ts b/packages/sdk/src/internal/utils/log.ts
new file mode 100644
index 000000000..44ac16a02
--- /dev/null
+++ b/packages/sdk/src/internal/utils/log.ts
@@ -0,0 +1,126 @@
+// 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;
+};
diff --git a/packages/sdk/src/internal/utils/path.ts b/packages/sdk/src/internal/utils/path.ts
new file mode 100644
index 000000000..6ca96e0dd
--- /dev/null
+++ b/packages/sdk/src/internal/utils/path.ts
@@ -0,0 +1,88 @@
+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);
diff --git a/packages/sdk/src/internal/utils/sleep.ts b/packages/sdk/src/internal/utils/sleep.ts
new file mode 100644
index 000000000..65e52962b
--- /dev/null
+++ b/packages/sdk/src/internal/utils/sleep.ts
@@ -0,0 +1,3 @@
+// 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));
diff --git a/packages/sdk/src/internal/utils/uuid.ts b/packages/sdk/src/internal/utils/uuid.ts
new file mode 100644
index 000000000..b0e53aaf7
--- /dev/null
+++ b/packages/sdk/src/internal/utils/uuid.ts
@@ -0,0 +1,17 @@
+// 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),
+ );
+};
diff --git a/packages/sdk/src/internal/utils/values.ts b/packages/sdk/src/internal/utils/values.ts
new file mode 100644
index 000000000..b2421fd88
--- /dev/null
+++ b/packages/sdk/src/internal/utils/values.ts
@@ -0,0 +1,105 @@
+// 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;
+ }
+};