From 1dd29f9f54e659b3f39b9f8b0e20f53eb6623216 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 11 May 2026 16:28:53 +0000 Subject: [PATCH 01/31] docs(api): clarify suffix parameter max length in fine-tuning --- .stats.yml | 4 ++-- src/resources/fine-tuning.ts | 11 ++++++++++- tests/api-resources/fine-tuning.test.ts | 1 + 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index ed55c5ab..68590a4c 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 75 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-e218fafc0c9b31bd98647d1e2de6decc55f8a7f9719b3b565f94939c2ebcf0df.yml -openapi_spec_hash: 026cc585ef61f52d4d6c4b60b969e323 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-29a7f71ffca4e4ea9bcc9e9f8836f9ac879e2d8ccc16f298c335cce4e1c641fb.yml +openapi_spec_hash: 08cbd22fc2bc9006a55be3a5cf6e497c config_hash: 6c214c91fad5ead4849be777fd9e8108 diff --git a/src/resources/fine-tuning.ts b/src/resources/fine-tuning.ts index a4815685..9ccaa767 100644 --- a/src/resources/fine-tuning.ts +++ b/src/resources/fine-tuning.ts @@ -1302,6 +1302,14 @@ export interface FineTuningCreateParams { */ from_hf_model?: string; + /** + * Number of steps to accumulate gradients before performing a weight update. + * Effectively increases the batch size without requiring more memory. For example, + * with batch_size=4 and gradient_accumulation_steps=8, the effective batch size + * is 32. + */ + gradient_accumulation_steps?: number; + /** * The API token for the Hugging Face Hub. */ @@ -1372,7 +1380,8 @@ export interface FineTuningCreateParams { random_seed?: number | null; /** - * Suffix that will be added to your fine-tuned model name + * Suffix that will be added to your fine-tuned model name. Must be at most 64 + * characters long. */ suffix?: string; diff --git a/tests/api-resources/fine-tuning.test.ts b/tests/api-resources/fine-tuning.test.ts index b4d4a6f0..db663b29 100644 --- a/tests/api-resources/fine-tuning.test.ts +++ b/tests/api-resources/fine-tuning.test.ts @@ -26,6 +26,7 @@ describe('resource fineTuning', () => { batch_size: 'max', from_checkpoint: 'from_checkpoint', from_hf_model: 'from_hf_model', + gradient_accumulation_steps: 0, hf_api_token: 'hf_api_token', hf_model_revision: 'hf_model_revision', hf_output_repo_name: 'hf_output_repo_name', From adc6bbf87b396fe14898c92ae4ddad941b78ad37 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 11 May 2026 16:55:45 +0000 Subject: [PATCH 02/31] docs(api): clarify max_seq_length parameter documentation in fine-tuning --- .stats.yml | 4 ++-- src/resources/fine-tuning.ts | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index 68590a4c..551da14b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 75 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-29a7f71ffca4e4ea9bcc9e9f8836f9ac879e2d8ccc16f298c335cce4e1c641fb.yml -openapi_spec_hash: 08cbd22fc2bc9006a55be3a5cf6e497c +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-fffea977331d642c3cde67d736d5e6fd461b2f9e5ecf9e282028a834b1a31a6e.yml +openapi_spec_hash: c541c2196a5702cb5b66eddf261359c5 config_hash: 6c214c91fad5ead4849be777fd9e8108 diff --git a/src/resources/fine-tuning.ts b/src/resources/fine-tuning.ts index 9ccaa767..ddf6ae16 100644 --- a/src/resources/fine-tuning.ts +++ b/src/resources/fine-tuning.ts @@ -1345,7 +1345,8 @@ export interface FineTuningCreateParams { max_grad_norm?: number; /** - * Maximum sequence length to use for training. + * Maximum sequence length to use for training. If not specified, the maximum + * allowed for the model and training method will be used. */ max_seq_length?: number; From 3fff4b65ebf0a6e409a1325f4b0c39ccb453526a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 11 May 2026 17:57:08 +0000 Subject: [PATCH 03/31] docs(api): improve docstring clarity and grammar across resources --- .stats.yml | 4 +- src/resources/beta/clusters/clusters.ts | 2 +- src/resources/beta/jig/jig.ts | 21 +++--- src/resources/beta/jig/secrets.ts | 10 +-- src/resources/beta/jig/volumes.ts | 16 ++--- src/resources/chat/completions.ts | 19 +++-- .../code-interpreter/code-interpreter.ts | 23 +++--- src/resources/completions.ts | 11 ++- src/resources/endpoints.ts | 15 ++-- src/resources/fine-tuning.ts | 71 +++++++++---------- 10 files changed, 91 insertions(+), 101 deletions(-) diff --git a/.stats.yml b/.stats.yml index 551da14b..29646d90 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 75 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-fffea977331d642c3cde67d736d5e6fd461b2f9e5ecf9e282028a834b1a31a6e.yml -openapi_spec_hash: c541c2196a5702cb5b66eddf261359c5 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-b584185aba41e3d597bf715d9b704f1c7d2663ae7d8f3f3c35e63d603738ee9c.yml +openapi_spec_hash: 0b26ddf285392dd9f629c1161db62376 config_hash: 6c214c91fad5ead4849be777fd9e8108 diff --git a/src/resources/beta/clusters/clusters.ts b/src/resources/beta/clusters/clusters.ts index e1e8cbf2..28870a15 100644 --- a/src/resources/beta/clusters/clusters.ts +++ b/src/resources/beta/clusters/clusters.ts @@ -313,7 +313,7 @@ export interface ClusterCreateParams { /** * Reservation start time of the cluster. This field is required for SCHEDULED * billing to specify the reservation start time for the cluster. If not provided, - * the cluster will be provisioned immediately. + * the cluster provisions immediately. */ reservation_start_time?: string; diff --git a/src/resources/beta/jig/jig.ts b/src/resources/beta/jig/jig.ts index 1e10a361..dde1b8e6 100644 --- a/src/resources/beta/jig/jig.ts +++ b/src/resources/beta/jig/jig.ts @@ -362,8 +362,7 @@ export namespace Deployment { export interface Volume { /** - * MountPath is the path in the container where the volume will be mounted (e.g., - * "/data") + * MountPath is the path in the container where the volume mounts (e.g., "/data"). */ mount_path: string; @@ -433,7 +432,7 @@ export interface JigUpdateParams { /** * EnvironmentVariables is a list of environment variables to set in the container. - * This will replace all existing environment variables + * Replaces all existing environment variables. */ environment_variables?: Array; @@ -499,8 +498,8 @@ export interface JigUpdateParams { termination_grace_period_seconds?: number; /** - * Volumes is a list of volume mounts to attach to the container. This will replace - * all existing volumes + * Volumes is a list of volume mounts to attach to the container. Replaces all + * existing volumes. */ volumes?: Array; } @@ -592,8 +591,7 @@ export namespace JigUpdateParams { export interface Volume { /** - * MountPath is the path in the container where the volume will be mounted (e.g., - * "/data") + * MountPath is the path in the container where the volume mounts (e.g., "/data"). */ mount_path: string; @@ -675,13 +673,13 @@ export interface JigDeployParams { /** * HealthCheckPath is the HTTP path for health checks (e.g., "/health"). If set, - * the platform will check this endpoint to determine container health + * the platform checks this endpoint to determine container health. */ health_check_path?: string; /** - * MaxReplicas is the maximum number of container instances that can be scaled up - * to. If not set, will be set to MinReplicas + * MaxReplicas is the maximum number of container instances. Defaults to + * MinReplicas if not set. */ max_replicas?: number; @@ -809,8 +807,7 @@ export namespace JigDeployParams { export interface Volume { /** - * MountPath is the path in the container where the volume will be mounted (e.g., - * "/data") + * MountPath is the path in the container where the volume mounts (e.g., "/data"). */ mount_path: string; diff --git a/src/resources/beta/jig/secrets.ts b/src/resources/beta/jig/secrets.ts index d533a5c7..b2db9fb6 100644 --- a/src/resources/beta/jig/secrets.ts +++ b/src/resources/beta/jig/secrets.ts @@ -54,7 +54,7 @@ export interface Secret { created_at?: string; /** - * CreatedBy is the identifier of the user who created this secret + * CreatedBy is the identifier of who created this secret. */ created_by?: string; @@ -64,7 +64,7 @@ export interface Secret { description?: string; /** - * LastUpdatedBy is the identifier of the user who last updated this secret + * LastUpdatedBy is the identifier of who last updated this secret. */ last_updated_by?: string; @@ -108,7 +108,7 @@ export interface SecretCreateParams { /** * Value is the sensitive data to store securely (e.g., API keys, passwords, - * tokens). This value will be encrypted at rest + * tokens). Encrypted at rest. */ value: string; @@ -146,8 +146,8 @@ export interface SecretUpdateParams { project_id?: string; /** - * Value is the new sensitive data to store securely. Updating this will replace - * the existing secret value + * Value is the new sensitive data to store securely. Updating this replaces the + * existing secret value. */ value?: string; } diff --git a/src/resources/beta/jig/volumes.ts b/src/resources/beta/jig/volumes.ts index e0e34a8e..4362913f 100644 --- a/src/resources/beta/jig/volumes.ts +++ b/src/resources/beta/jig/volumes.ts @@ -93,8 +93,8 @@ export interface Volume { export namespace Volume { export interface Content { /** - * Files is the list of files that will be preloaded into the volume, if the volume - * content type is "files" + * Files is the list of files to preload into the volume, if the volume content + * type is "files". */ files?: Array; @@ -132,7 +132,7 @@ export namespace Volume { export interface VersionHistory { /** - * Content specifies the new content that will be preloaded to this volume + * Content specifies the new content to preload to this volume. */ content?: VersionHistory.Content; @@ -143,7 +143,7 @@ export namespace Volume { export namespace VersionHistory { /** - * Content specifies the new content that will be preloaded to this volume + * Content specifies the new content to preload to this volume. */ export interface Content { /** @@ -177,7 +177,7 @@ export type VolumeDeleteResponse = unknown; export interface VolumeCreateParams { /** - * Content specifies the new content that will be preloaded to this volume + * Content specifies the new content to preload to this volume. */ content: VolumeCreateParams.Content; @@ -194,7 +194,7 @@ export interface VolumeCreateParams { export namespace VolumeCreateParams { /** - * Content specifies the new content that will be preloaded to this volume + * Content specifies the new content to preload to this volume. */ export interface Content { /** @@ -213,7 +213,7 @@ export namespace VolumeCreateParams { export interface VolumeUpdateParams { /** - * Content specifies the new content that will be preloaded to this volume + * Content specifies the new content to preload to this volume. */ content?: VolumeUpdateParams.Content; @@ -230,7 +230,7 @@ export interface VolumeUpdateParams { export namespace VolumeUpdateParams { /** - * Content specifies the new content that will be preloaded to this volume + * Content specifies the new content to preload to this volume. */ export interface Content { /** diff --git a/src/resources/chat/completions.ts b/src/resources/chat/completions.ts index 213c9154..a5ce7a7b 100644 --- a/src/resources/chat/completions.ts +++ b/src/resources/chat/completions.ts @@ -445,16 +445,16 @@ export interface CompletionCreateParamsBase { compliance?: 'hipaa'; /** - * Defined the behavior of the API when max_tokens exceed the maximum context - * length of the model. When set to 'error', API will return 400 with appropriate - * error message. When set to 'truncate', override the max_tokens with maximum - * context length of the model. + * Defines the behavior of the API when max_tokens exceed the maximum context + * length of the model. When set to 'error', the API returns 400 with an + * appropriate error message. When set to 'truncate', overrides max_tokens with the + * maximum context length of the model. */ context_length_exceeded_behavior?: 'truncate' | 'error'; /** - * If true, the response will contain the prompt. Can be used with `logprobs` to - * return prompt logprobs. + * If true, the response contains the prompt. Can be used with `logprobs` to return + * prompt logprobs. */ echo?: boolean; @@ -473,7 +473,7 @@ export interface CompletionCreateParamsBase { /** * An integer between 0 and 20 of the top k tokens to return log probabilities for - * at each generation step, instead of just the sampled token. Log probabilities + * at each generation step, instead of only the sampled token. Log probabilities * help assess model confidence in token predictions. */ logprobs?: number; @@ -547,9 +547,8 @@ export interface CompletionCreateParamsBase { seed?: number; /** - * A list of string sequences that will truncate (stop) inference text output. For - * example, "" will stop generation as soon as the model generates the given - * token. + * A list of string sequences that truncate (stop) inference text output. For + * example, "" stops generation as soon as the model generates the given token. */ stop?: Array; diff --git a/src/resources/code-interpreter/code-interpreter.ts b/src/resources/code-interpreter/code-interpreter.ts index f24b5a99..b91e7647 100644 --- a/src/resources/code-interpreter/code-interpreter.ts +++ b/src/resources/code-interpreter/code-interpreter.ts @@ -11,10 +11,10 @@ export class CodeInterpreter extends APIResource { /** * Executes the given code snippet and returns the output. Without a session_id, a - * new session will be created to run the code. If you do pass in a valid - * session_id, the code will be run in that session. This is useful for running - * multiple code snippets in the same environment, because dependencies and similar - * things are persisted between calls to the same session. + * new session is created to run the code. If you pass a valid session_id, the code + * runs in that session. This is useful for running multiple code snippets in the + * same environment, because dependencies and similar things are persisted between + * calls to the same session. * * @example * ```ts @@ -32,8 +32,8 @@ export class CodeInterpreter extends APIResource { /** * The result of the execution. If successful, `data` contains the result and - * `errors` will be null. If unsuccessful, `data` will be null and `errors` will - * contain the errors. + * `errors` is null. If unsuccessful, `data` is null and `errors` contains the + * errors. */ export type ExecuteResponse = ExecuteResponse.SuccessfulExecution | ExecuteResponse.FailedExecution; @@ -133,20 +133,19 @@ export interface CodeInterpreterExecuteParams { code: string; /** - * Programming language for the code to execute. Currently only supports Python, - * but more will be added. + * Programming language for the code to execute. Currently only supports Python. */ language: 'python'; /** - * Files to upload to the session. If present, files will be uploaded before - * executing the given code. + * Files to upload to the session. If present, files are uploaded before executing + * the given code. */ files?: Array; /** - * Identifier of the current session. Used to make follow-up calls. Requests will - * return an error if the session does not belong to the caller or has expired. + * Identifier of the current session. Used to make follow-up calls. Returns an + * error if the session does not belong to the caller or has expired. */ session_id?: string; } diff --git a/src/resources/completions.ts b/src/resources/completions.ts index 1d3897e7..31c3c49a 100644 --- a/src/resources/completions.ts +++ b/src/resources/completions.ts @@ -227,8 +227,8 @@ export interface CompletionCreateParamsBase { prompt: string; /** - * If true, the response will contain the prompt. Can be used with `logprobs` to - * return prompt logprobs. + * If true, the response contains the prompt. Can be used with `logprobs` to return + * prompt logprobs. */ echo?: boolean; @@ -245,7 +245,7 @@ export interface CompletionCreateParamsBase { /** * An integer between 0 and 20 of the top k tokens to return log probabilities for - * at each generation step, instead of just the sampled token. Log probabilities + * at each generation step, instead of only the sampled token. Log probabilities * help assess model confidence in token predictions. */ logprobs?: number; @@ -290,9 +290,8 @@ export interface CompletionCreateParamsBase { seed?: number; /** - * A list of string sequences that will truncate (stop) inference text output. For - * example, "" will stop generation as soon as the model generates the given - * token. + * A list of string sequences that truncate (stop) inference text output. For + * example, "" stops generation as soon as the model generates the given token. */ stop?: Array; diff --git a/src/resources/endpoints.ts b/src/resources/endpoints.ts index f4cdd54c..cae79590 100644 --- a/src/resources/endpoints.ts +++ b/src/resources/endpoints.ts @@ -8,9 +8,9 @@ import { path } from '../internal/utils/path'; export class Endpoints extends APIResource { /** - * Creates a new dedicated endpoint for serving models. The endpoint will - * automatically start after creation. You can deploy any supported model on - * hardware configurations that meet the model's requirements. + * Creates a new dedicated endpoint for serving models. The endpoint starts + * automatically after creation. You can deploy any supported model on hardware + * configurations that meet the model's requirements. * * @example * ```ts @@ -390,9 +390,8 @@ export interface EndpointCreateParams { display_name?: string; /** - * The number of minutes of inactivity after which the endpoint will be - * automatically stopped. Set to null, omit or set to 0 to disable automatic - * timeout. + * The number of minutes of inactivity after which the endpoint stops + * automatically. Set to null, omit, or set to 0 to disable automatic timeout. */ inactive_timeout?: number | null; @@ -414,8 +413,8 @@ export interface EndpointUpdateParams { display_name?: string; /** - * The number of minutes of inactivity after which the endpoint will be - * automatically stopped. Set to 0 to disable automatic timeout. + * The number of minutes of inactivity after which the endpoint stops + * automatically. Set to 0 to disable automatic timeout. */ inactive_timeout?: number | null; diff --git a/src/resources/fine-tuning.ts b/src/resources/fine-tuning.ts index ddf6ae16..6f84805d 100644 --- a/src/resources/fine-tuning.ts +++ b/src/resources/fine-tuning.ts @@ -353,8 +353,8 @@ export namespace FinetuneResponse { method: 'sft'; /** - * Whether to mask the user messages in conversational data or prompts in - * instruction data. + * Whether to mask user messages in conversational data or prompts in instruction + * data. */ train_on_inputs: boolean | 'auto'; } @@ -462,8 +462,8 @@ export interface FineTuningCreateResponse { max_grad_norm?: number; /** - * Maximum sequence length to use for training. If not specified, the maximum - * allowed for the model and training method will be used. + * Maximum sequence length to use for training. If not specified, uses the maximum + * allowed for the model and training method. */ max_seq_length?: number; @@ -546,7 +546,7 @@ export interface FineTuningCreateResponse { training_type?: FineTuningCreateResponse.FullTrainingType | FineTuningCreateResponse.LoRaTrainingType; /** - * Identifier for the user who created the job + * Identifier for who created the job. */ user_id?: string; @@ -626,8 +626,8 @@ export namespace FineTuningCreateResponse { method: 'sft'; /** - * Whether to mask the user messages in conversational data or prompts in - * instruction data. + * Whether to mask user messages in conversational data or prompts in instruction + * data. */ train_on_inputs: boolean | 'auto'; } @@ -740,8 +740,8 @@ export namespace FineTuningListResponse { max_grad_norm?: number; /** - * Maximum sequence length to use for training. If not specified, the maximum - * allowed for the model and training method will be used. + * Maximum sequence length to use for training. If not specified, uses the maximum + * allowed for the model and training method. */ max_seq_length?: number; @@ -824,7 +824,7 @@ export namespace FineTuningListResponse { training_type?: Data.FullTrainingType | Data.LoRaTrainingType; /** - * Identifier for the user who created the job + * Identifier for who created the job. */ user_id?: string; @@ -904,8 +904,8 @@ export namespace FineTuningListResponse { method: 'sft'; /** - * Whether to mask the user messages in conversational data or prompts in - * instruction data. + * Whether to mask user messages in conversational data or prompts in instruction + * data. */ train_on_inputs: boolean | 'auto'; } @@ -1021,8 +1021,8 @@ export interface FineTuningCancelResponse { max_grad_norm?: number; /** - * Maximum sequence length to use for training. If not specified, the maximum - * allowed for the model and training method will be used. + * Maximum sequence length to use for training. If not specified, uses the maximum + * allowed for the model and training method. */ max_seq_length?: number; @@ -1105,7 +1105,7 @@ export interface FineTuningCancelResponse { training_type?: FineTuningCancelResponse.FullTrainingType | FineTuningCancelResponse.LoRaTrainingType; /** - * Identifier for the user who created the job + * Identifier for who created the job. */ user_id?: string; @@ -1185,8 +1185,8 @@ export namespace FineTuningCancelResponse { method: 'sft'; /** - * Whether to mask the user messages in conversational data or prompts in - * instruction data. + * Whether to mask user messages in conversational data or prompts in instruction + * data. */ train_on_inputs: boolean | 'auto'; } @@ -1224,7 +1224,7 @@ export namespace FineTuningCancelResponse { export interface FineTuningEstimatePriceResponse { /** - * Whether the user is allowed to proceed with the fine-tuning job + * Whether you are allowed to proceed with the fine-tuning job. */ allowed_to_proceed?: boolean; @@ -1244,7 +1244,7 @@ export interface FineTuningEstimatePriceResponse { estimated_train_token_count?: number; /** - * The user's credit limit in dollars + * Your credit limit in dollars. */ user_limit?: number; } @@ -1290,8 +1290,8 @@ export interface FineTuningCreateParams { /** * The checkpoint identifier to continue training from a previous fine-tuning job. * Format is `{$JOB_ID}` or `{$OUTPUT_MODEL_NAME}` or `{$JOB_ID}:{$STEP}` or - * `{$OUTPUT_MODEL_NAME}:{$STEP}`. The step value is optional; without it, the - * final checkpoint will be used. + * `{$OUTPUT_MODEL_NAME}:{$STEP}`. The step value is optional; without it, uses the + * final checkpoint. */ from_checkpoint?: string; @@ -1381,14 +1381,13 @@ export interface FineTuningCreateParams { random_seed?: number | null; /** - * Suffix that will be added to your fine-tuned model name. Must be at most 64 - * characters long. + * Suffix to add to your fine-tuned model name. Must be at most 64 characters long. */ suffix?: string; /** - * @deprecated Whether to mask the user messages in conversational data or prompts - * in instruction data. + * @deprecated Whether to mask user messages in conversational data or prompts in + * instruction data. */ train_on_inputs?: boolean | 'auto'; @@ -1399,8 +1398,7 @@ export interface FineTuningCreateParams { training_method?: FineTuningCreateParams.TrainingMethodSft | FineTuningCreateParams.TrainingMethodDpo; /** - * The training type to use. If not provided, the job will default to LoRA training - * type. + * The training type to use. Defaults to LoRA if not provided. */ training_type?: FineTuningCreateParams.FullTrainingType | FineTuningCreateParams.LoRaTrainingType | null; @@ -1430,8 +1428,8 @@ export interface FineTuningCreateParams { wandb_name?: string; /** - * The Weights & Biases project for your run. If not specified, will use `together` - * as the project name. + * The Weights & Biases project for your run. If not specified, uses `together` as + * the project name. */ wandb_project_name?: string; @@ -1491,8 +1489,8 @@ export namespace FineTuningCreateParams { method: 'sft'; /** - * Whether to mask the user messages in conversational data or prompts in - * instruction data. + * Whether to mask user messages in conversational data or prompts in instruction + * data. */ train_on_inputs: boolean | 'auto'; } @@ -1563,8 +1561,8 @@ export interface FineTuningEstimatePriceParams { /** * The checkpoint identifier to continue training from a previous fine-tuning job. * Format is `{$JOB_ID}` or `{$OUTPUT_MODEL_NAME}` or `{$JOB_ID}:{$STEP}` or - * `{$OUTPUT_MODEL_NAME}:{$STEP}`. The step value is optional; without it, the - * final checkpoint will be used. + * `{$OUTPUT_MODEL_NAME}:{$STEP}`. The step value is optional; without it, uses the + * final checkpoint. */ from_checkpoint?: string; @@ -1593,8 +1591,7 @@ export interface FineTuningEstimatePriceParams { | FineTuningEstimatePriceParams.TrainingMethodDpo; /** - * The training type to use. If not provided, the job will default to LoRA training - * type. + * The training type to use. Defaults to LoRA if not provided. */ training_type?: | FineTuningEstimatePriceParams.FullTrainingType @@ -1612,8 +1609,8 @@ export namespace FineTuningEstimatePriceParams { method: 'sft'; /** - * Whether to mask the user messages in conversational data or prompts in - * instruction data. + * Whether to mask user messages in conversational data or prompts in instruction + * data. */ train_on_inputs: boolean | 'auto'; } From c65be590b14e2da807110160e6d87f07b7c5ff55 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 11 May 2026 21:15:10 +0000 Subject: [PATCH 04/31] fix(types): restrict endpoint to specific values in batches create --- .stats.yml | 4 ++-- src/resources/batches.ts | 10 ++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.stats.yml b/.stats.yml index 29646d90..df0d96a6 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 75 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-b584185aba41e3d597bf715d9b704f1c7d2663ae7d8f3f3c35e63d603738ee9c.yml -openapi_spec_hash: 0b26ddf285392dd9f629c1161db62376 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-72e377e4d7f3fe8bb9f0dab1a70d7d9cf0f44914ff4d9b8ab238bc7f48008621.yml +openapi_spec_hash: 798c6d992a5cb83901b5879502c22f9d config_hash: 6c214c91fad5ead4849be777fd9e8108 diff --git a/src/resources/batches.ts b/src/resources/batches.ts index 5b08e45d..ceecbedc 100644 --- a/src/resources/batches.ts +++ b/src/resources/batches.ts @@ -114,9 +114,15 @@ export type BatchListResponse = Array; export interface BatchCreateParams { /** - * The endpoint to use for batch processing + * The endpoint to use for batch processing. Each line of the uploaded input file + * is dispatched against this endpoint. + * + * - `/v1/chat/completions` — chat completion batches + * - `/v1/audio/transcriptions` — audio transcription batches (e.g. + * `openai/whisper-large-v3`) + * - `/v1/audio/translations` — audio translation batches */ - endpoint: string; + endpoint: '/v1/chat/completions' | '/v1/audio/transcriptions' | '/v1/audio/translations'; /** * ID of the uploaded input file containing batch requests From bda97f5a9c3d42bd6395e60cae0bfe21e9aeadf8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 12:43:32 +0000 Subject: [PATCH 05/31] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index df0d96a6..a6528f98 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 75 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-72e377e4d7f3fe8bb9f0dab1a70d7d9cf0f44914ff4d9b8ab238bc7f48008621.yml -openapi_spec_hash: 798c6d992a5cb83901b5879502c22f9d +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-921bbbc9118c442498e79604a394f7f1c2e2a6f743fe3c04f67ca118463337ee.yml +openapi_spec_hash: 92270ba58311172b7fcaae90e3dad1a0 config_hash: 6c214c91fad5ead4849be777fd9e8108 From 574e8babb94a913a8f5566c9363efeeac1eb65dc Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 13:07:56 +0000 Subject: [PATCH 06/31] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index a6528f98..f6336866 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 75 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-921bbbc9118c442498e79604a394f7f1c2e2a6f743fe3c04f67ca118463337ee.yml -openapi_spec_hash: 92270ba58311172b7fcaae90e3dad1a0 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-14a2d0f040bf287858da867ea3528581fb012d790d7e65b70f2b87f407efc7c7.yml +openapi_spec_hash: 84f45151f4d0eed68551b5ffda61595a config_hash: 6c214c91fad5ead4849be777fd9e8108 From 08c66e8043be75831ffa0dbbffd1c174563204f6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 16:36:12 +0000 Subject: [PATCH 07/31] feat(api): Integrate fine_tuning.list_metrics from stainless --- .stats.yml | 6 +-- MIGRATION.md | 1 + api.md | 2 + src/client.ts | 4 ++ src/resources/fine-tuning.ts | 50 +++++++++++++++++++++++++ src/resources/index.ts | 2 + tests/api-resources/fine-tuning.test.ts | 28 ++++++++++++++ 7 files changed, 90 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index f6336866..be5cec00 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 75 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-14a2d0f040bf287858da867ea3528581fb012d790d7e65b70f2b87f407efc7c7.yml +configured_endpoints: 76 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-5f05c9669c67c3f4b0ebfe2317d2768cd96317424965ebb2acf06a7757a7d0ca.yml openapi_spec_hash: 84f45151f4d0eed68551b5ffda61595a -config_hash: 6c214c91fad5ead4849be777fd9e8108 +config_hash: ec427df08d61d8888138f15cd53c6454 diff --git a/MIGRATION.md b/MIGRATION.md index a8f10e76..69fde0e8 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -69,6 +69,7 @@ This affects the following methods: - `client.beta.jig.retrieveLogs()` - `client.fineTuning.delete()` +- `client.fineTuning.listMetrics()` - `client.models.list()` - `client.endpoints.list()` - `client.endpoints.listHardware()` diff --git a/api.md b/api.md index a8dad0c5..fc990f4b 100644 --- a/api.md +++ b/api.md @@ -182,6 +182,7 @@ Types: - FineTuningEstimatePriceResponse - FineTuningListCheckpointsResponse - FineTuningListEventsResponse +- FineTuningListMetricsResponse Methods: @@ -194,6 +195,7 @@ Methods: - client.fineTuning.estimatePrice({ ...params }) -> FineTuningEstimatePriceResponse - client.fineTuning.listCheckpoints(id) -> FineTuningListCheckpointsResponse - client.fineTuning.listEvents(id) -> FineTuningListEventsResponse +- client.fineTuning.listMetrics(id, { ...params }) -> FineTuningListMetricsResponse # CodeInterpreter diff --git a/src/client.ts b/src/client.ts index ed1355ef..ada3d4bc 100644 --- a/src/client.ts +++ b/src/client.ts @@ -70,6 +70,8 @@ import { FineTuningEstimatePriceResponse, FineTuningListCheckpointsResponse, FineTuningListEventsResponse, + FineTuningListMetricsParams, + FineTuningListMetricsResponse, FineTuningListResponse, FinetuneEvent, FinetuneEventType, @@ -886,10 +888,12 @@ export declare namespace Together { type FineTuningEstimatePriceResponse as FineTuningEstimatePriceResponse, type FineTuningListCheckpointsResponse as FineTuningListCheckpointsResponse, type FineTuningListEventsResponse as FineTuningListEventsResponse, + type FineTuningListMetricsResponse as FineTuningListMetricsResponse, type FineTuningCreateParams as FineTuningCreateParams, type FineTuningDeleteParams as FineTuningDeleteParams, type FineTuningContentParams as FineTuningContentParams, type FineTuningEstimatePriceParams as FineTuningEstimatePriceParams, + type FineTuningListMetricsParams as FineTuningListMetricsParams, }; export { diff --git a/src/resources/fine-tuning.ts b/src/resources/fine-tuning.ts index 6f84805d..a42453c8 100644 --- a/src/resources/fine-tuning.ts +++ b/src/resources/fine-tuning.ts @@ -144,6 +144,23 @@ export class FineTuning extends APIResource { listEvents(id: string, options?: RequestOptions): APIPromise { return this._client.get(path`/fine-tunes/${id}/events`, options); } + + /** + * Retrieves recorded training metrics for a fine-tuning job in chronological + * order. All query parameters are optional: omit them to retrieve all metrics. + * + * @example + * ```ts + * const response = await client.fineTuning.listMetrics('id'); + * ``` + */ + listMetrics( + id: string, + query: FineTuningListMetricsParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.get(path`/fine-tunes/${id}/metrics`, { query, ...options }); + } } export interface FinetuneEvent { @@ -1269,6 +1286,10 @@ export interface FineTuningListEventsResponse { data: Array; } +export interface FineTuningListMetricsResponse { + metrics?: Array<{ [key: string]: number }>; +} + export interface FineTuningCreateParams { /** * Name of the base model to run fine-tune job on @@ -1646,6 +1667,33 @@ export namespace FineTuningEstimatePriceParams { } } +export interface FineTuningListMetricsParams { + /** + * Return only metrics with global_step >= this value. + */ + global_step_from?: number; + + /** + * Return only metrics with global_step <= this value. + */ + global_step_to?: number; + + /** + * Return only metrics logged at or after this ISO-8601 timestamp. + */ + logged_at_from?: string; + + /** + * Return only metrics logged at or before this ISO-8601 timestamp. + */ + logged_at_to?: string; + + /** + * Number of (uniformly sampled) train metrics to return. + */ + resolution?: number; +} + export declare namespace FineTuning { export { type FinetuneEvent as FinetuneEvent, @@ -1658,9 +1706,11 @@ export declare namespace FineTuning { type FineTuningEstimatePriceResponse as FineTuningEstimatePriceResponse, type FineTuningListCheckpointsResponse as FineTuningListCheckpointsResponse, type FineTuningListEventsResponse as FineTuningListEventsResponse, + type FineTuningListMetricsResponse as FineTuningListMetricsResponse, type FineTuningCreateParams as FineTuningCreateParams, type FineTuningDeleteParams as FineTuningDeleteParams, type FineTuningContentParams as FineTuningContentParams, type FineTuningEstimatePriceParams as FineTuningEstimatePriceParams, + type FineTuningListMetricsParams as FineTuningListMetricsParams, }; } diff --git a/src/resources/index.ts b/src/resources/index.ts index 9c74dd40..772138d7 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -68,10 +68,12 @@ export { type FineTuningEstimatePriceResponse, type FineTuningListCheckpointsResponse, type FineTuningListEventsResponse, + type FineTuningListMetricsResponse, type FineTuningCreateParams, type FineTuningDeleteParams, type FineTuningContentParams, type FineTuningEstimatePriceParams, + type FineTuningListMetricsParams, } from './fine-tuning'; export { Images, diff --git a/tests/api-resources/fine-tuning.test.ts b/tests/api-resources/fine-tuning.test.ts index db663b29..616b948d 100644 --- a/tests/api-resources/fine-tuning.test.ts +++ b/tests/api-resources/fine-tuning.test.ts @@ -162,4 +162,32 @@ describe('resource fineTuning', () => { expect(dataAndResponse.data).toBe(response); expect(dataAndResponse.response).toBe(rawResponse); }); + + test('listMetrics', async () => { + const responsePromise = client.fineTuning.listMetrics('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('listMetrics: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.fineTuning.listMetrics( + 'id', + { + global_step_from: 0, + global_step_to: 0, + logged_at_from: '2019-12-27T18:11:19.117Z', + logged_at_to: '2019-12-27T18:11:19.117Z', + resolution: 0, + }, + { path: '/_stainless_unknown_path' }, + ), + ).rejects.toThrow(Together.NotFoundError); + }); }); From a89bc6a4ad3973bdd5c69f72d9b2c0e54fcb9c0d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 17:46:50 +0000 Subject: [PATCH 08/31] chore: temp deletion --- .stats.yml | 6 +-- MIGRATION.md | 1 - api.md | 2 - src/client.ts | 4 -- src/resources/fine-tuning.ts | 50 ------------------------- src/resources/index.ts | 2 - tests/api-resources/fine-tuning.test.ts | 28 -------------- 7 files changed, 3 insertions(+), 90 deletions(-) diff --git a/.stats.yml b/.stats.yml index be5cec00..f6336866 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 76 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-5f05c9669c67c3f4b0ebfe2317d2768cd96317424965ebb2acf06a7757a7d0ca.yml +configured_endpoints: 75 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-14a2d0f040bf287858da867ea3528581fb012d790d7e65b70f2b87f407efc7c7.yml openapi_spec_hash: 84f45151f4d0eed68551b5ffda61595a -config_hash: ec427df08d61d8888138f15cd53c6454 +config_hash: 6c214c91fad5ead4849be777fd9e8108 diff --git a/MIGRATION.md b/MIGRATION.md index 69fde0e8..a8f10e76 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -69,7 +69,6 @@ This affects the following methods: - `client.beta.jig.retrieveLogs()` - `client.fineTuning.delete()` -- `client.fineTuning.listMetrics()` - `client.models.list()` - `client.endpoints.list()` - `client.endpoints.listHardware()` diff --git a/api.md b/api.md index fc990f4b..a8dad0c5 100644 --- a/api.md +++ b/api.md @@ -182,7 +182,6 @@ Types: - FineTuningEstimatePriceResponse - FineTuningListCheckpointsResponse - FineTuningListEventsResponse -- FineTuningListMetricsResponse Methods: @@ -195,7 +194,6 @@ Methods: - client.fineTuning.estimatePrice({ ...params }) -> FineTuningEstimatePriceResponse - client.fineTuning.listCheckpoints(id) -> FineTuningListCheckpointsResponse - client.fineTuning.listEvents(id) -> FineTuningListEventsResponse -- client.fineTuning.listMetrics(id, { ...params }) -> FineTuningListMetricsResponse # CodeInterpreter diff --git a/src/client.ts b/src/client.ts index ada3d4bc..ed1355ef 100644 --- a/src/client.ts +++ b/src/client.ts @@ -70,8 +70,6 @@ import { FineTuningEstimatePriceResponse, FineTuningListCheckpointsResponse, FineTuningListEventsResponse, - FineTuningListMetricsParams, - FineTuningListMetricsResponse, FineTuningListResponse, FinetuneEvent, FinetuneEventType, @@ -888,12 +886,10 @@ export declare namespace Together { type FineTuningEstimatePriceResponse as FineTuningEstimatePriceResponse, type FineTuningListCheckpointsResponse as FineTuningListCheckpointsResponse, type FineTuningListEventsResponse as FineTuningListEventsResponse, - type FineTuningListMetricsResponse as FineTuningListMetricsResponse, type FineTuningCreateParams as FineTuningCreateParams, type FineTuningDeleteParams as FineTuningDeleteParams, type FineTuningContentParams as FineTuningContentParams, type FineTuningEstimatePriceParams as FineTuningEstimatePriceParams, - type FineTuningListMetricsParams as FineTuningListMetricsParams, }; export { diff --git a/src/resources/fine-tuning.ts b/src/resources/fine-tuning.ts index a42453c8..6f84805d 100644 --- a/src/resources/fine-tuning.ts +++ b/src/resources/fine-tuning.ts @@ -144,23 +144,6 @@ export class FineTuning extends APIResource { listEvents(id: string, options?: RequestOptions): APIPromise { return this._client.get(path`/fine-tunes/${id}/events`, options); } - - /** - * Retrieves recorded training metrics for a fine-tuning job in chronological - * order. All query parameters are optional: omit them to retrieve all metrics. - * - * @example - * ```ts - * const response = await client.fineTuning.listMetrics('id'); - * ``` - */ - listMetrics( - id: string, - query: FineTuningListMetricsParams | null | undefined = {}, - options?: RequestOptions, - ): APIPromise { - return this._client.get(path`/fine-tunes/${id}/metrics`, { query, ...options }); - } } export interface FinetuneEvent { @@ -1286,10 +1269,6 @@ export interface FineTuningListEventsResponse { data: Array; } -export interface FineTuningListMetricsResponse { - metrics?: Array<{ [key: string]: number }>; -} - export interface FineTuningCreateParams { /** * Name of the base model to run fine-tune job on @@ -1667,33 +1646,6 @@ export namespace FineTuningEstimatePriceParams { } } -export interface FineTuningListMetricsParams { - /** - * Return only metrics with global_step >= this value. - */ - global_step_from?: number; - - /** - * Return only metrics with global_step <= this value. - */ - global_step_to?: number; - - /** - * Return only metrics logged at or after this ISO-8601 timestamp. - */ - logged_at_from?: string; - - /** - * Return only metrics logged at or before this ISO-8601 timestamp. - */ - logged_at_to?: string; - - /** - * Number of (uniformly sampled) train metrics to return. - */ - resolution?: number; -} - export declare namespace FineTuning { export { type FinetuneEvent as FinetuneEvent, @@ -1706,11 +1658,9 @@ export declare namespace FineTuning { type FineTuningEstimatePriceResponse as FineTuningEstimatePriceResponse, type FineTuningListCheckpointsResponse as FineTuningListCheckpointsResponse, type FineTuningListEventsResponse as FineTuningListEventsResponse, - type FineTuningListMetricsResponse as FineTuningListMetricsResponse, type FineTuningCreateParams as FineTuningCreateParams, type FineTuningDeleteParams as FineTuningDeleteParams, type FineTuningContentParams as FineTuningContentParams, type FineTuningEstimatePriceParams as FineTuningEstimatePriceParams, - type FineTuningListMetricsParams as FineTuningListMetricsParams, }; } diff --git a/src/resources/index.ts b/src/resources/index.ts index 772138d7..9c74dd40 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -68,12 +68,10 @@ export { type FineTuningEstimatePriceResponse, type FineTuningListCheckpointsResponse, type FineTuningListEventsResponse, - type FineTuningListMetricsResponse, type FineTuningCreateParams, type FineTuningDeleteParams, type FineTuningContentParams, type FineTuningEstimatePriceParams, - type FineTuningListMetricsParams, } from './fine-tuning'; export { Images, diff --git a/tests/api-resources/fine-tuning.test.ts b/tests/api-resources/fine-tuning.test.ts index 616b948d..db663b29 100644 --- a/tests/api-resources/fine-tuning.test.ts +++ b/tests/api-resources/fine-tuning.test.ts @@ -162,32 +162,4 @@ describe('resource fineTuning', () => { expect(dataAndResponse.data).toBe(response); expect(dataAndResponse.response).toBe(rawResponse); }); - - test('listMetrics', async () => { - const responsePromise = client.fineTuning.listMetrics('id'); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - test('listMetrics: request options and params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.fineTuning.listMetrics( - 'id', - { - global_step_from: 0, - global_step_to: 0, - logged_at_from: '2019-12-27T18:11:19.117Z', - logged_at_to: '2019-12-27T18:11:19.117Z', - resolution: 0, - }, - { path: '/_stainless_unknown_path' }, - ), - ).rejects.toThrow(Together.NotFoundError); - }); }); From ad924d976a9d1e79119db8aef2ae7667033e6ea6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 17:47:32 +0000 Subject: [PATCH 09/31] chore: temp undeletion --- .stats.yml | 6 +-- MIGRATION.md | 1 + api.md | 2 + src/client.ts | 4 ++ src/resources/fine-tuning.ts | 50 +++++++++++++++++++++++++ src/resources/index.ts | 2 + tests/api-resources/fine-tuning.test.ts | 28 ++++++++++++++ 7 files changed, 90 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index f6336866..be5cec00 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 75 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-14a2d0f040bf287858da867ea3528581fb012d790d7e65b70f2b87f407efc7c7.yml +configured_endpoints: 76 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-5f05c9669c67c3f4b0ebfe2317d2768cd96317424965ebb2acf06a7757a7d0ca.yml openapi_spec_hash: 84f45151f4d0eed68551b5ffda61595a -config_hash: 6c214c91fad5ead4849be777fd9e8108 +config_hash: ec427df08d61d8888138f15cd53c6454 diff --git a/MIGRATION.md b/MIGRATION.md index a8f10e76..69fde0e8 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -69,6 +69,7 @@ This affects the following methods: - `client.beta.jig.retrieveLogs()` - `client.fineTuning.delete()` +- `client.fineTuning.listMetrics()` - `client.models.list()` - `client.endpoints.list()` - `client.endpoints.listHardware()` diff --git a/api.md b/api.md index a8dad0c5..fc990f4b 100644 --- a/api.md +++ b/api.md @@ -182,6 +182,7 @@ Types: - FineTuningEstimatePriceResponse - FineTuningListCheckpointsResponse - FineTuningListEventsResponse +- FineTuningListMetricsResponse Methods: @@ -194,6 +195,7 @@ Methods: - client.fineTuning.estimatePrice({ ...params }) -> FineTuningEstimatePriceResponse - client.fineTuning.listCheckpoints(id) -> FineTuningListCheckpointsResponse - client.fineTuning.listEvents(id) -> FineTuningListEventsResponse +- client.fineTuning.listMetrics(id, { ...params }) -> FineTuningListMetricsResponse # CodeInterpreter diff --git a/src/client.ts b/src/client.ts index ed1355ef..ada3d4bc 100644 --- a/src/client.ts +++ b/src/client.ts @@ -70,6 +70,8 @@ import { FineTuningEstimatePriceResponse, FineTuningListCheckpointsResponse, FineTuningListEventsResponse, + FineTuningListMetricsParams, + FineTuningListMetricsResponse, FineTuningListResponse, FinetuneEvent, FinetuneEventType, @@ -886,10 +888,12 @@ export declare namespace Together { type FineTuningEstimatePriceResponse as FineTuningEstimatePriceResponse, type FineTuningListCheckpointsResponse as FineTuningListCheckpointsResponse, type FineTuningListEventsResponse as FineTuningListEventsResponse, + type FineTuningListMetricsResponse as FineTuningListMetricsResponse, type FineTuningCreateParams as FineTuningCreateParams, type FineTuningDeleteParams as FineTuningDeleteParams, type FineTuningContentParams as FineTuningContentParams, type FineTuningEstimatePriceParams as FineTuningEstimatePriceParams, + type FineTuningListMetricsParams as FineTuningListMetricsParams, }; export { diff --git a/src/resources/fine-tuning.ts b/src/resources/fine-tuning.ts index 6f84805d..a42453c8 100644 --- a/src/resources/fine-tuning.ts +++ b/src/resources/fine-tuning.ts @@ -144,6 +144,23 @@ export class FineTuning extends APIResource { listEvents(id: string, options?: RequestOptions): APIPromise { return this._client.get(path`/fine-tunes/${id}/events`, options); } + + /** + * Retrieves recorded training metrics for a fine-tuning job in chronological + * order. All query parameters are optional: omit them to retrieve all metrics. + * + * @example + * ```ts + * const response = await client.fineTuning.listMetrics('id'); + * ``` + */ + listMetrics( + id: string, + query: FineTuningListMetricsParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.get(path`/fine-tunes/${id}/metrics`, { query, ...options }); + } } export interface FinetuneEvent { @@ -1269,6 +1286,10 @@ export interface FineTuningListEventsResponse { data: Array; } +export interface FineTuningListMetricsResponse { + metrics?: Array<{ [key: string]: number }>; +} + export interface FineTuningCreateParams { /** * Name of the base model to run fine-tune job on @@ -1646,6 +1667,33 @@ export namespace FineTuningEstimatePriceParams { } } +export interface FineTuningListMetricsParams { + /** + * Return only metrics with global_step >= this value. + */ + global_step_from?: number; + + /** + * Return only metrics with global_step <= this value. + */ + global_step_to?: number; + + /** + * Return only metrics logged at or after this ISO-8601 timestamp. + */ + logged_at_from?: string; + + /** + * Return only metrics logged at or before this ISO-8601 timestamp. + */ + logged_at_to?: string; + + /** + * Number of (uniformly sampled) train metrics to return. + */ + resolution?: number; +} + export declare namespace FineTuning { export { type FinetuneEvent as FinetuneEvent, @@ -1658,9 +1706,11 @@ export declare namespace FineTuning { type FineTuningEstimatePriceResponse as FineTuningEstimatePriceResponse, type FineTuningListCheckpointsResponse as FineTuningListCheckpointsResponse, type FineTuningListEventsResponse as FineTuningListEventsResponse, + type FineTuningListMetricsResponse as FineTuningListMetricsResponse, type FineTuningCreateParams as FineTuningCreateParams, type FineTuningDeleteParams as FineTuningDeleteParams, type FineTuningContentParams as FineTuningContentParams, type FineTuningEstimatePriceParams as FineTuningEstimatePriceParams, + type FineTuningListMetricsParams as FineTuningListMetricsParams, }; } diff --git a/src/resources/index.ts b/src/resources/index.ts index 9c74dd40..772138d7 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -68,10 +68,12 @@ export { type FineTuningEstimatePriceResponse, type FineTuningListCheckpointsResponse, type FineTuningListEventsResponse, + type FineTuningListMetricsResponse, type FineTuningCreateParams, type FineTuningDeleteParams, type FineTuningContentParams, type FineTuningEstimatePriceParams, + type FineTuningListMetricsParams, } from './fine-tuning'; export { Images, diff --git a/tests/api-resources/fine-tuning.test.ts b/tests/api-resources/fine-tuning.test.ts index db663b29..616b948d 100644 --- a/tests/api-resources/fine-tuning.test.ts +++ b/tests/api-resources/fine-tuning.test.ts @@ -162,4 +162,32 @@ describe('resource fineTuning', () => { expect(dataAndResponse.data).toBe(response); expect(dataAndResponse.response).toBe(rawResponse); }); + + test('listMetrics', async () => { + const responsePromise = client.fineTuning.listMetrics('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('listMetrics: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.fineTuning.listMetrics( + 'id', + { + global_step_from: 0, + global_step_to: 0, + logged_at_from: '2019-12-27T18:11:19.117Z', + logged_at_to: '2019-12-27T18:11:19.117Z', + resolution: 0, + }, + { path: '/_stainless_unknown_path' }, + ), + ).rejects.toThrow(Together.NotFoundError); + }); }); From 67fbe61a615ddd5a2a7a58484e5bb772dd8b939b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 19:09:58 +0000 Subject: [PATCH 10/31] ci: pin GitHub Actions to commit SHAs Pin all GitHub Actions referenced in generated workflows (both first-party `actions/*` and third-party) to immutable commit SHAs. Updating pinned actions is now a deliberate codegen-side bump rather than implicit on every workflow run. --- .github/workflows/ci.yml | 14 +++++++------- .github/workflows/publish-npm.yml | 4 ++-- .github/workflows/release-doctor.yml | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9919d867..9a3c455d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,10 +21,10 @@ jobs: runs-on: ${{ github.repository == 'stainless-sdks/togetherai-typescript' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Node - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: '20' @@ -43,10 +43,10 @@ jobs: contents: read id-token: write steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Node - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: '20' @@ -61,7 +61,7 @@ jobs: github.repository == 'stainless-sdks/togetherai-typescript' && !startsWith(github.ref, 'refs/heads/stl/') id: github-oidc - uses: actions/github-script@v8 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: core.setOutput('github_token', await core.getIDToken()); @@ -80,10 +80,10 @@ jobs: runs-on: ${{ github.repository == 'stainless-sdks/togetherai-typescript' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Node - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: '20' diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index a5e9ff14..867d871d 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -14,10 +14,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Node - uses: actions/setup-node@v3 + uses: actions/setup-node@3235b876344d2a9aa001b8d1453c930bba69e610 # v3.9.1 with: node-version: '20' diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml index 2951e49d..f914e6e5 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -12,7 +12,7 @@ jobs: if: github.repository == 'togethercomputer/together-typescript' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Check release environment run: | From e2d6e640ed1c5201e4f1b679dae8da9d1ec99619 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 14 May 2026 19:14:09 +0000 Subject: [PATCH 11/31] feat(api): add params to list methods, update clusters/storage types --- .stats.yml | 4 +- MIGRATION.md | 2 + api.md | 4 +- src/resources/beta/beta.ts | 2 + src/resources/beta/clusters/clusters.ts | 822 +++++++++++++++++- src/resources/beta/clusters/index.ts | 2 + src/resources/beta/clusters/storage.ts | 46 +- src/resources/beta/index.ts | 1 + .../beta/clusters/clusters.test.ts | 61 +- .../beta/clusters/storage.test.ts | 16 +- 10 files changed, 911 insertions(+), 49 deletions(-) diff --git a/.stats.yml b/.stats.yml index be5cec00..4ae4e030 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 76 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-5f05c9669c67c3f4b0ebfe2317d2768cd96317424965ebb2acf06a7757a7d0ca.yml -openapi_spec_hash: 84f45151f4d0eed68551b5ffda61595a +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-e398785d31ad51e94f158b377be469e74748e0229a964d3602149c6bf964581e.yml +openapi_spec_hash: 3e4db6f27d100314926d87c9ae24cba2 config_hash: ec427df08d61d8888138f15cd53c6454 diff --git a/MIGRATION.md b/MIGRATION.md index 69fde0e8..ca1a79bc 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -68,6 +68,8 @@ client.example.list(undefined, { headers: { ... } }); This affects the following methods: - `client.beta.jig.retrieveLogs()` +- `client.beta.clusters.list()` +- `client.beta.clusters.storage.list()` - `client.fineTuning.delete()` - `client.fineTuning.listMetrics()` - `client.models.list()` diff --git a/api.md b/api.md index fc990f4b..80e458d6 100644 --- a/api.md +++ b/api.md @@ -80,7 +80,7 @@ Methods: - client.beta.clusters.create({ ...params }) -> Cluster - client.beta.clusters.retrieve(clusterID) -> Cluster - client.beta.clusters.update(clusterID, { ...params }) -> Cluster -- client.beta.clusters.list() -> ClusterListResponse +- client.beta.clusters.list({ ...params }) -> ClusterListResponse - client.beta.clusters.delete(clusterID) -> ClusterDeleteResponse - client.beta.clusters.listRegions() -> ClusterListRegionsResponse @@ -97,7 +97,7 @@ Methods: - client.beta.clusters.storage.create({ ...params }) -> ClusterStorage - client.beta.clusters.storage.retrieve(volumeID) -> ClusterStorage - client.beta.clusters.storage.update({ ...params }) -> ClusterStorage -- client.beta.clusters.storage.list() -> StorageListResponse +- client.beta.clusters.storage.list({ ...params }) -> StorageListResponse - client.beta.clusters.storage.delete(volumeID) -> StorageDeleteResponse # Chat diff --git a/src/resources/beta/beta.ts b/src/resources/beta/beta.ts index d196928f..35f78058 100644 --- a/src/resources/beta/beta.ts +++ b/src/resources/beta/beta.ts @@ -6,6 +6,7 @@ import { Cluster, ClusterCreateParams, ClusterDeleteResponse, + ClusterListParams, ClusterListRegionsResponse, ClusterListResponse, ClusterUpdateParams, @@ -51,5 +52,6 @@ export declare namespace Beta { type ClusterListRegionsResponse as ClusterListRegionsResponse, type ClusterCreateParams as ClusterCreateParams, type ClusterUpdateParams as ClusterUpdateParams, + type ClusterListParams as ClusterListParams, }; } diff --git a/src/resources/beta/clusters/clusters.ts b/src/resources/beta/clusters/clusters.ts index 28870a15..b52b635e 100644 --- a/src/resources/beta/clusters/clusters.ts +++ b/src/resources/beta/clusters/clusters.ts @@ -7,6 +7,7 @@ import { Storage, StorageCreateParams, StorageDeleteResponse, + StorageListParams, StorageListResponse, StorageUpdateParams, } from './storage'; @@ -45,8 +46,11 @@ export class Clusters extends APIResource { /** * List all GPU clusters. */ - list(options?: RequestOptions): APIPromise { - return this._client.get('/compute/clusters', options); + list( + query: ClusterListParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.get('/compute/clusters', { query, ...options }); } /** @@ -65,6 +69,24 @@ export class Clusters extends APIResource { } export interface Cluster { + /** + * Enabled add-ons on this cluster. Only add-ons with enabled=true in their config + * are returned. + */ + add_ons: Array; + + /** + * Actual number of preemptible GPUs currently allocated to the cluster. Updated + * asynchronously by the fulfillment and reclamation workers; may be less than + * desired_preemptible_gpus when capacity is constrained. + */ + allocated_preemptible_gpus: number; + + /** + * Billing type for the cluster (RESERVED, ON_DEMAND, or SCHEDULED_CAPACITY). + */ + billing_type: 'RESERVED' | 'ON_DEMAND' | 'SCHEDULED_CAPACITY'; + cluster_id: string; cluster_name: string; @@ -78,16 +100,34 @@ export interface Cluster { cuda_version: string; + /** + * Customer's requested number of preemptible GPUs. Set on cluster create or + * update; persists until changed. + */ + desired_preemptible_gpus: number; + gpu_type: 'H100_SXM' | 'H200_SXM' | 'RTX_6000_PCI' | 'L40_PCIE' | 'B200_SXM' | 'H100_SXM_INF'; gpu_worker_nodes: Array; kube_config: string; + /** + * Number of CPU-only worker nodes in the cluster. + */ + num_cpu_workers: number; + num_gpus: number; nvidia_driver_version: string; + /** + * Cluster-level phase transition history. + */ + phase_transitions: Array; + + project_id: string; + region: string; /** @@ -110,12 +150,16 @@ export interface Cluster { capacity_pool_id?: string; + cluster_config?: Cluster.ClusterConfig; + created_at?: string; duration_hours?: number; install_traefik?: boolean; + oidc_config?: Cluster.OidcConfig; + reservation_end_time?: string; reservation_start_time?: string; @@ -124,6 +168,49 @@ export interface Cluster { } export namespace Cluster { + /** + * AddOnInfo is returned in cluster responses and add-on CRUD operations. + */ + export interface AddOn { + add_on_type: string; + + config: AddOn.Config; + + name: string; + + state: AddOn.State; + } + + export namespace AddOn { + export interface Config { + dashboard?: Config.Dashboard; + + ingress?: Config.Ingress; + } + + export namespace Config { + export interface Dashboard { + enabled?: boolean; + } + + export interface Ingress { + enabled?: boolean; + } + } + + export interface State { + dashboard?: State.Dashboard; + + ingress?: State.Ingress; + } + + export namespace State { + export interface Dashboard {} + + export interface Ingress {} + } + } + export interface ControlPlaneNode { host_name: string; @@ -137,9 +224,36 @@ export namespace Cluster { num_cpu_cores: number; + /** + * Phase transition history for this control plane node. + */ + phase_transitions: Array; + status: string; } + export namespace ControlPlaneNode { + export interface PhaseTransition { + /** + * Node phase. + */ + phase: + | 'NODE_PHASE_PENDING' + | 'NODE_PHASE_SCHEDULING' + | 'NODE_PHASE_BOOTING' + | 'NODE_PHASE_BOOTSTRAPPING' + | 'NODE_PHASE_RUNNING' + | 'NODE_PHASE_SUCCEEDED' + | 'NODE_PHASE_FAILED' + | 'NODE_PHASE_PAUSED'; + + /** + * Timestamp when the phase transition occurred. + */ + transition_time: string; + } + } + export interface GPUWorkerNode { host_name: string; @@ -155,9 +269,192 @@ export namespace Cluster { num_gpus: number; + /** + * Phase transition history for this GPU worker node. + */ + phase_transitions: Array; + status: string; instance_id?: string; + + /** + * Remediation represents a node remediation request for an instance. An instance + * can have multiple remediations over time (e.g., failed attempts followed by + * retries). + */ + latest_remediation?: GPUWorkerNode.LatestRemediation; + + slurm_worker_hostname?: string; + } + + export namespace GPUWorkerNode { + export interface PhaseTransition { + /** + * Node phase. + */ + phase: + | 'NODE_PHASE_PENDING' + | 'NODE_PHASE_SCHEDULING' + | 'NODE_PHASE_BOOTING' + | 'NODE_PHASE_BOOTSTRAPPING' + | 'NODE_PHASE_RUNNING' + | 'NODE_PHASE_SUCCEEDED' + | 'NODE_PHASE_FAILED' + | 'NODE_PHASE_PAUSED'; + + /** + * Timestamp when the phase transition occurred. + */ + transition_time: string; + } + + /** + * Remediation represents a node remediation request for an instance. An instance + * can have multiple remediations over time (e.g., failed attempts followed by + * retries). + */ + export interface LatestRemediation { + id: string; + + cluster_id: string; + + instance_id: string; + + /** + * Remediation mode specifies how the remediation should be performed. + * + * - `REMEDIATION_MODE_VM_ONLY`: Deletes the VM and provisions a new one on any + * available host. + * - `REMEDIATION_MODE_HOST_AWARE`: Cordons the host, deletes the VM, and + * provisions a new one on a different host. + */ + mode: + | 'REMEDIATION_MODE_VM_ONLY' + | 'REMEDIATION_MODE_HOST_AWARE' + | 'REMEDIATION_MODE_EVICT_WITHOUT_REPLACEMENT' + | 'REMEDIATION_MODE_REBOOT_VM'; + + /** + * RemediationState represents the lifecycle state of a remediation. + * + * - `PENDING_APPROVAL`: Awaiting approval before processing can begin. + * - `PENDING`: Approved and queued for processing. + * - `RUNNING`: Actively being processed. + * - `SUCCEEDED`: Successfully completed. + * - `FAILED`: Failed with an error. + * - `CANCELLED`: Cancelled by user or system. + * - `AUTO_RESOLVED`: The underlying issue was automatically resolved before + * processing. + */ + state: + | 'PENDING_APPROVAL' + | 'PENDING' + | 'RUNNING' + | 'SUCCEEDED' + | 'FAILED' + | 'CANCELLED' + | 'AUTO_RESOLVED'; + + /** + * RemediationTrigger specifies how the remediation was triggered. + * + * - `REMEDIATION_TRIGGER_MANUAL`: A user-initiated remediation (either via web UI + * or API call). + * - `REMEDIATION_TRIGGER_AUTOMATED`: A system-initiated remediation that requires + * approval. + */ + trigger: 'REMEDIATION_TRIGGER_MANUAL' | 'REMEDIATION_TRIGGER_AUTOMATED'; + + /** + * Active health check run ID (UUID) that triggered this remediation. + */ + active_health_check_run_id?: string; + + /** + * When the remediation was created. + */ + create_time?: string; + + /** + * When the remediation completed. + */ + end_time?: string; + + /** + * Error message if the remediation failed. + */ + error_message?: string; + + /** + * Passive health check event ID that triggered this remediation. + */ + passive_health_check_event_id?: string; + + /** + * User-provided reason for the remediation. + */ + reason?: string; + + /** + * Who requested the remediation. + */ + requested_by?: string; + + /** + * Review comment. + */ + review_comment?: string; + + /** + * When the remediation was reviewed. + */ + review_time?: string; + + /** + * Who reviewed the remediation. + */ + reviewed_by?: string; + + /** + * When processing started. + */ + start_time?: string; + + /** + * When the remediation was last updated. + */ + update_time?: string; + } + } + + export interface PhaseTransition { + /** + * Cluster phase. + */ + phase: + | 'CLUSTER_PHASE_QUEUED' + | 'CLUSTER_PHASE_SCHEDULED' + | 'CLUSTER_PHASE_WAITING_FOR_CONTROL_PLANE_NODES' + | 'CLUSTER_PHASE_WAITING_FOR_DATA_PLANE_NODES' + | 'CLUSTER_PHASE_WAITING_FOR_SUBNET' + | 'CLUSTER_PHASE_WAITING_FOR_SHARED_VOLUME' + | 'CLUSTER_PHASE_WAITING_FOR_AUTO_SCALER' + | 'CLUSTER_PHASE_INSTALLING_DRIVERS' + | 'CLUSTER_PHASE_RUNNING_ACCEPTANCE_TESTS' + | 'CLUSTER_PHASE_ACCEPTANCE_TESTS_FAILED' + | 'CLUSTER_PHASE_RUNNING_NCCL_TESTS' + | 'CLUSTER_PHASE_NCCL_TESTS_FAILED' + | 'CLUSTER_PHASE_READY' + | 'CLUSTER_PHASE_PAUSED' + | 'CLUSTER_PHASE_ON_DEMAND_COMPUTE_PAUSED' + | 'CLUSTER_PHASE_DEGRADED' + | 'CLUSTER_PHASE_DELETING'; + + /** + * Timestamp when the phase transition occurred. + */ + transition_time: string; } export interface Volume { @@ -169,6 +466,122 @@ export namespace Cluster { volume_name: string; } + + export interface ClusterConfig { + load_balancer: 'NONE' | 'TRAEFIK' | 'NGINX' | 'ISTIO'; + + /** + * NVIDIA GPU Operator chart/version for the tenant cluster (e.g. v24.6.2). When + * omitted, a service default is applied. + */ + gpu_operator_version?: string; + + ingress?: ClusterConfig.Ingress; + + jumphost_enabled?: boolean; + + kubernetes_dashboard_enabled?: boolean; + + observability?: ClusterConfig.Observability; + + /** + * SlurmStartupScripts carries optional Slurm lifecycle scripts (prolog/epilog, + * init, extra conf). + */ + slurm_startup_scripts?: ClusterConfig.SlurmStartupScripts; + } + + export namespace ClusterConfig { + export interface Ingress { + enabled?: boolean; + } + + export interface Observability { + enabled?: boolean; + } + + /** + * SlurmStartupScripts carries optional Slurm lifecycle scripts (prolog/epilog, + * init, extra conf). + */ + export interface SlurmStartupScripts { + /** + * Slurm controller epilog script. + */ + controller_epilog?: string; + + /** + * Slurm controller prolog script. + */ + controller_prolog?: string; + + /** + * Additional slurm.conf fragments. + */ + extra_slurm_conf?: string; + + /** + * Script run on Slurm login node init. + */ + login_init_script?: string; + + /** + * Script run on Slurm nodeset init. + */ + nodeset_init_script?: string; + + /** + * Slurm worker node epilog script. + */ + worker_epilog?: string; + + /** + * Slurm worker node prolog script. + */ + worker_prolog?: string; + } + } + + export interface OidcConfig { + /** + * OIDC client ID for authentication. + */ + client_id: string; + + /** + * JWT claim to use for user groups. For example, 'groups' + */ + group_claim: string; + + /** + * Prefix to add to the group claim to form the final group name. For example, + * 'oidc:' + */ + group_prefix: string; + + /** + * OIDC issuer URL for authentication. For example, https://accounts.google.com + */ + issuer_url: string; + + /** + * JWT claim to use as the username. For example, 'sub' or 'email' + */ + username_claim: string; + + /** + * Prefix to add to the username claim to form the final username. For example, + * 'oidc:' + */ + username_prefix: string; + + /** + * CA certificate in PEM format to validate the OIDC issuer's TLS certificate. This + * field is optional but recommended if the issuer uses a private CA or self-signed + * certificate. + */ + ca_cert?: string; + } } export interface ClusterListResponse { @@ -241,6 +654,11 @@ export interface ClusterCreateParams { */ cuda_version: string; + /** + * Duration in days to keep the cluster running. + */ + duration_days: number; + /** * Type of GPU to use in the cluster */ @@ -264,6 +682,24 @@ export interface ClusterCreateParams { */ region: string; + /** + * AcceptanceTestsParams groups all GPU acceptance test options when enabled is + * true. + */ + acceptance_tests_params?: ClusterCreateParams.AcceptanceTestsParams; + + /** + * Add-ons to enable on the cluster at creation time. + */ + add_ons?: Array; + + /** + * Whether to enable auto-scaling for the cluster. If true, the cluster will + * automatically scale the number of GPU worker nodes between num_gpus and + * auto_scale_max_gpus based on the workload. + */ + auto_scale?: boolean; + /** * Maximum number of GPUs to which the cluster can be auto-scaled up. This field is * required if auto_scaled is true. @@ -271,8 +707,8 @@ export interface ClusterCreateParams { auto_scale_max_gpus?: number; /** - * Whether GPU cluster should be auto-scaled based on the workload. By default, it - * is not auto-scaled. + * @deprecated Whether GPU cluster should be auto-scaled based on the workload. By + * default, it is not auto-scaled. */ auto_scaled?: boolean; @@ -282,16 +718,13 @@ export interface ClusterCreateParams { */ capacity_pool_id?: string; + cluster_config?: ClusterCreateParams.ClusterConfig; + /** * Type of cluster to create. */ cluster_type?: 'KUBERNETES' | 'SLURM'; - /** - * Duration in days to keep the cluster running. - */ - duration_days?: number; - /** * Whether automated GPU node failover should be enabled for this cluster. By * default, it is disabled. @@ -304,6 +737,34 @@ export interface ClusterCreateParams { */ install_traefik?: boolean; + /** + * Number of GPUs to allocate from the capacity pool. Must be a multiple of 8 and + * not exceed num_gpus. + */ + num_capacity_pool_gpus?: number; + + /** + * Number of preemptible GPUs to request alongside on-demand capacity. Must be a + * multiple of 8. Preemptible nodes are cheaper but may be reclaimed when on-demand + * capacity is needed elsewhere; the system fulfills this asynchronously and + * surfaces the actual count in allocated_preemptible_gpus. + */ + num_preemptible_gpus?: number; + + /** + * Number of prepaid (PLG) reserved GPUs for this cluster. When omitted for + * RESERVED billing on create, the server defaults this to num_gpus. + */ + num_reserved_gpus?: number; + + oidc_config?: ClusterCreateParams.OidcConfig; + + /** + * Project ID for the cluster. If not set, the project from the request context is + * used. + */ + project_id?: string; + /** * Reservation end time of the cluster. This field is required for SCHEDULED * billing to specify the reservation end time for the cluster. @@ -341,12 +802,203 @@ export interface ClusterCreateParams { export namespace ClusterCreateParams { /** - * Inline configuration to create a shared volume with the cluster creation. + * AcceptanceTestsParams groups all GPU acceptance test options when enabled is + * true. */ - export interface SharedVolume { + export interface AcceptanceTestsParams { + /** + * DCGM diagnostic depth. SHORT = readiness; MEDIUM = default; LONG = system + * validation; EXTENDED = memtest. An omitted value selects MEDIUM when enabled. + */ + dcgm_diag_level?: + | 'DCGM_DIAG_LEVEL_SHORT' + | 'DCGM_DIAG_LEVEL_MEDIUM' + | 'DCGM_DIAG_LEVEL_LONG' + | 'DCGM_DIAG_LEVEL_EXTENDED'; + + /** + * Skip DCGM diagnostics acceptance test. + */ + dcgm_diag_skipped?: boolean; + + /** + * Whether to run GPU acceptance tests during cluster bring-up. + */ + enabled?: boolean; + + /** + * GPU burn duration in seconds; 0 means use the default when enabled. + */ + gpu_burn_duration?: number; + + /** + * Skip GPU burn acceptance test. + */ + gpu_burn_skipped?: boolean; + + /** + * Skip NCCL multi-node acceptance test. + */ + nccl_multi_node_skipped?: boolean; + + /** + * Skip NCCL single-node acceptance test. + */ + nccl_single_node_skipped?: boolean; + } + + export interface AddOn { + /** + * Type of add-on. Valid values: 'dashboard', 'ingress'. + */ + add_on_type: string; + + /** + * Human-readable name for this add-on instance. + */ + name: string; + + config?: AddOn.Config; + } + + export namespace AddOn { + export interface Config { + dashboard?: Config.Dashboard; + + ingress?: Config.Ingress; + } + + export namespace Config { + export interface Dashboard { + enabled?: boolean; + } + + export interface Ingress { + enabled?: boolean; + } + } + } + + export interface ClusterConfig { + load_balancer: 'NONE' | 'TRAEFIK' | 'NGINX' | 'ISTIO'; + + /** + * NVIDIA GPU Operator chart/version for the tenant cluster (e.g. v24.6.2). When + * omitted, a service default is applied. + */ + gpu_operator_version?: string; + + ingress?: ClusterConfig.Ingress; + + jumphost_enabled?: boolean; + + kubernetes_dashboard_enabled?: boolean; + + observability?: ClusterConfig.Observability; + + /** + * SlurmStartupScripts carries optional Slurm lifecycle scripts (prolog/epilog, + * init, extra conf). + */ + slurm_startup_scripts?: ClusterConfig.SlurmStartupScripts; + } + + export namespace ClusterConfig { + export interface Ingress { + enabled?: boolean; + } + + export interface Observability { + enabled?: boolean; + } + + /** + * SlurmStartupScripts carries optional Slurm lifecycle scripts (prolog/epilog, + * init, extra conf). + */ + export interface SlurmStartupScripts { + /** + * Slurm controller epilog script. + */ + controller_epilog?: string; + + /** + * Slurm controller prolog script. + */ + controller_prolog?: string; + + /** + * Additional slurm.conf fragments. + */ + extra_slurm_conf?: string; + + /** + * Script run on Slurm login node init. + */ + login_init_script?: string; + + /** + * Script run on Slurm nodeset init. + */ + nodeset_init_script?: string; + + /** + * Slurm worker node epilog script. + */ + worker_epilog?: string; + + /** + * Slurm worker node prolog script. + */ + worker_prolog?: string; + } + } + + export interface OidcConfig { /** - * Region name. Usable regions can be found from `client.clusters.list_regions()` + * OIDC client ID for authentication. */ + client_id: string; + + /** + * JWT claim to use for user groups. For example, 'groups' + */ + group_claim: string; + + /** + * Prefix to add to the group claim to form the final group name. For example, + * 'oidc:' + */ + group_prefix: string; + + /** + * OIDC issuer URL for authentication. For example, https://accounts.google.com + */ + issuer_url: string; + + /** + * JWT claim to use as the username. For example, 'sub' or 'email' + */ + username_claim: string; + + /** + * Prefix to add to the username claim to form the final username. For example, + * 'oidc:' + */ + username_prefix: string; + + /** + * CA certificate in PEM format to validate the OIDC issuer's TLS certificate. This + * field is optional but recommended if the issuer uses a private CA or self-signed + * certificate. + */ + ca_cert?: string; + } + + /** + * Inline configuration to create a shared volume with the cluster creation. + */ + export interface SharedVolume { region: string; /** @@ -354,25 +1006,48 @@ export namespace ClusterCreateParams { */ size_tib: number; + volume_name: string; + /** - * Customizable name of the volume to create. + * When true, the shared volume is not deleted when the cluster is decommissioned. */ - volume_name: string; + is_lifecycle_independent?: boolean; } } export interface ClusterUpdateParams { + /** + * Add-ons to update on the cluster. Each entry identifies an existing add-on by + * name and provides the new external config to merge. + */ + add_ons?: Array; + + cluster_config?: ClusterUpdateParams.ClusterConfig; + /** * Type of cluster to update. */ cluster_type?: 'KUBERNETES' | 'SLURM'; /** - * Number of GPUs to allocate in the cluster. This must be multiple of 8. For - * example, 8, 16 or 24 + * Target GPU count for the cluster. When omitted, the server keeps the current GPU + * count from cluster metadata (use for config-only or decommission-time-only + * updates). */ num_gpus?: number; + /** + * Updated desired number of preemptible GPUs for the cluster. When omitted, the + * current value is preserved. Must be a multiple of 8. + */ + num_preemptible_gpus?: number; + + /** + * Number of reserved GPUs to update to. This field is only applicable for clusters + * with RESERVED billing type. + */ + num_reserved_gpus?: number; + /** * Timestamp at which the cluster should be decommissioned. Only accepted for * prepaid clusters. @@ -380,6 +1055,119 @@ export interface ClusterUpdateParams { reservation_end_time?: string; } +export namespace ClusterUpdateParams { + export interface AddOn { + /** + * Name of the add-on to update. Must match an existing add-on on the cluster. + */ + name: string; + + config?: AddOn.Config; + } + + export namespace AddOn { + export interface Config { + dashboard?: Config.Dashboard; + + ingress?: Config.Ingress; + } + + export namespace Config { + export interface Dashboard { + enabled?: boolean; + } + + export interface Ingress { + enabled?: boolean; + } + } + } + + export interface ClusterConfig { + load_balancer: 'NONE' | 'TRAEFIK' | 'NGINX' | 'ISTIO'; + + /** + * NVIDIA GPU Operator chart/version for the tenant cluster (e.g. v24.6.2). When + * omitted, a service default is applied. + */ + gpu_operator_version?: string; + + ingress?: ClusterConfig.Ingress; + + jumphost_enabled?: boolean; + + kubernetes_dashboard_enabled?: boolean; + + observability?: ClusterConfig.Observability; + + /** + * SlurmStartupScripts carries optional Slurm lifecycle scripts (prolog/epilog, + * init, extra conf). + */ + slurm_startup_scripts?: ClusterConfig.SlurmStartupScripts; + } + + export namespace ClusterConfig { + export interface Ingress { + enabled?: boolean; + } + + export interface Observability { + enabled?: boolean; + } + + /** + * SlurmStartupScripts carries optional Slurm lifecycle scripts (prolog/epilog, + * init, extra conf). + */ + export interface SlurmStartupScripts { + /** + * Slurm controller epilog script. + */ + controller_epilog?: string; + + /** + * Slurm controller prolog script. + */ + controller_prolog?: string; + + /** + * Additional slurm.conf fragments. + */ + extra_slurm_conf?: string; + + /** + * Script run on Slurm login node init. + */ + login_init_script?: string; + + /** + * Script run on Slurm nodeset init. + */ + nodeset_init_script?: string; + + /** + * Slurm worker node epilog script. + */ + worker_epilog?: string; + + /** + * Slurm worker node prolog script. + */ + worker_prolog?: string; + } + } +} + +export interface ClusterListParams { + /** + * Optional UMS project ID to filter clusters by. When set, only clusters belonging + * to this project are returned. The caller must be a member of the project; + * otherwise the result set will be empty. + */ + project_id?: string; +} + Clusters.Storage = Storage; export declare namespace Clusters { @@ -390,6 +1178,7 @@ export declare namespace Clusters { type ClusterListRegionsResponse as ClusterListRegionsResponse, type ClusterCreateParams as ClusterCreateParams, type ClusterUpdateParams as ClusterUpdateParams, + type ClusterListParams as ClusterListParams, }; export { @@ -399,5 +1188,6 @@ export declare namespace Clusters { type StorageDeleteResponse as StorageDeleteResponse, type StorageCreateParams as StorageCreateParams, type StorageUpdateParams as StorageUpdateParams, + type StorageListParams as StorageListParams, }; } diff --git a/src/resources/beta/clusters/index.ts b/src/resources/beta/clusters/index.ts index b5cb85d5..876826be 100644 --- a/src/resources/beta/clusters/index.ts +++ b/src/resources/beta/clusters/index.ts @@ -8,6 +8,7 @@ export { type ClusterListRegionsResponse, type ClusterCreateParams, type ClusterUpdateParams, + type ClusterListParams, } from './clusters'; export { Storage, @@ -16,4 +17,5 @@ export { type StorageDeleteResponse, type StorageCreateParams, type StorageUpdateParams, + type StorageListParams, } from './storage'; diff --git a/src/resources/beta/clusters/storage.ts b/src/resources/beta/clusters/storage.ts index 972af5bf..72d9c473 100644 --- a/src/resources/beta/clusters/storage.ts +++ b/src/resources/beta/clusters/storage.ts @@ -34,8 +34,11 @@ export class Storage extends APIResource { /** * List all shared volumes. */ - list(options?: RequestOptions): APIPromise { - return this._client.get('/compute/clusters/storage/volumes', options); + list( + query: StorageListParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.get('/compute/clusters/storage/volumes', { query, ...options }); } /** @@ -48,24 +51,12 @@ export class Storage extends APIResource { } export interface ClusterStorage { - /** - * Size of the volume in whole tebibytes (TiB). - */ size_tib: number; - /** - * Deployment status of the volume. - */ - status: 'available' | 'bound' | 'provisioning'; + status: string; - /** - * ID of the volume. - */ volume_id: string; - /** - * Provided name of the volume. - */ volume_name: string; } @@ -78,9 +69,6 @@ export interface StorageDeleteResponse { } export interface StorageCreateParams { - /** - * Region name. Usable regions can be found from `client.clusters.list_regions()` - */ region: string; /** @@ -88,22 +76,27 @@ export interface StorageCreateParams { */ size_tib: number; + volume_name: string; + /** - * Customizable name of the volume to create. + * When true, the shared volume is not deleted when the cluster is decommissioned. */ - volume_name: string; + is_lifecycle_independent?: boolean; } export interface StorageUpdateParams { - /** - * Size of the volume in whole tebibytes (TiB). - */ - size_tib?: number; + size_tib: number; + + volume_id: string; +} +export interface StorageListParams { /** - * ID of the volume to update. + * Optional UMS project ID to filter volumes by. When set, only volumes belonging + * to this project are returned. The caller must be a member of the project; + * otherwise the result set will be empty. */ - volume_id?: string; + project_id?: string; } export declare namespace Storage { @@ -113,5 +106,6 @@ export declare namespace Storage { type StorageDeleteResponse as StorageDeleteResponse, type StorageCreateParams as StorageCreateParams, type StorageUpdateParams as StorageUpdateParams, + type StorageListParams as StorageListParams, }; } diff --git a/src/resources/beta/index.ts b/src/resources/beta/index.ts index 7a534c7a..a5ee4751 100644 --- a/src/resources/beta/index.ts +++ b/src/resources/beta/index.ts @@ -9,6 +9,7 @@ export { type ClusterListRegionsResponse, type ClusterCreateParams, type ClusterUpdateParams, + type ClusterListParams, } from './clusters/index'; export { Jig, diff --git a/tests/api-resources/beta/clusters/clusters.test.ts b/tests/api-resources/beta/clusters/clusters.test.ts index 8bd1bf1a..ee4d64ec 100644 --- a/tests/api-resources/beta/clusters/clusters.test.ts +++ b/tests/api-resources/beta/clusters/clusters.test.ts @@ -13,6 +13,7 @@ describe('resource clusters', () => { billing_type: 'RESERVED', cluster_name: 'cluster_name', cuda_version: 'cuda_version', + duration_days: 0, gpu_type: 'H100_SXM', num_gpus: 0, nvidia_driver_version: 'nvidia_driver_version', @@ -32,23 +33,74 @@ describe('resource clusters', () => { billing_type: 'RESERVED', cluster_name: 'cluster_name', cuda_version: 'cuda_version', + duration_days: 0, gpu_type: 'H100_SXM', num_gpus: 0, nvidia_driver_version: 'nvidia_driver_version', region: 'region', + acceptance_tests_params: { + dcgm_diag_level: 'DCGM_DIAG_LEVEL_SHORT', + dcgm_diag_skipped: true, + enabled: true, + gpu_burn_duration: 0, + gpu_burn_skipped: true, + nccl_multi_node_skipped: true, + nccl_single_node_skipped: true, + }, + add_ons: [ + { + add_on_type: 'add_on_type', + name: 'name', + config: { + dashboard: { enabled: true }, + ingress: { enabled: true }, + }, + }, + ], + auto_scale: true, auto_scale_max_gpus: 0, auto_scaled: true, capacity_pool_id: 'capacity_pool_id', + cluster_config: { + load_balancer: 'NONE', + gpu_operator_version: 'gpu_operator_version', + ingress: { enabled: true }, + jumphost_enabled: true, + kubernetes_dashboard_enabled: true, + observability: { enabled: true }, + slurm_startup_scripts: { + controller_epilog: 'controller_epilog', + controller_prolog: 'controller_prolog', + extra_slurm_conf: 'extra_slurm_conf', + login_init_script: 'login_init_script', + nodeset_init_script: 'nodeset_init_script', + worker_epilog: 'worker_epilog', + worker_prolog: 'worker_prolog', + }, + }, cluster_type: 'KUBERNETES', - duration_days: 0, gpu_node_failover_enabled: true, install_traefik: true, + num_capacity_pool_gpus: 0, + num_preemptible_gpus: 0, + num_reserved_gpus: 0, + oidc_config: { + client_id: 'client_id', + group_claim: 'group_claim', + group_prefix: 'group_prefix', + issuer_url: 'issuer_url', + username_claim: 'username_claim', + username_prefix: 'username_prefix', + ca_cert: 'ca_cert', + }, + project_id: 'project_id', reservation_end_time: '2019-12-27T18:11:19.117Z', reservation_start_time: '2019-12-27T18:11:19.117Z', shared_volume: { region: 'region', size_tib: 0, volume_name: 'volume_name', + is_lifecycle_independent: true, }, slurm_image: 'slurm_image', slurm_shm_size_gib: 0, @@ -89,6 +141,13 @@ describe('resource clusters', () => { expect(dataAndResponse.response).toBe(rawResponse); }); + test('list: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.beta.clusters.list({ project_id: 'project_id' }, { path: '/_stainless_unknown_path' }), + ).rejects.toThrow(Together.NotFoundError); + }); + test('delete', async () => { const responsePromise = client.beta.clusters.delete('cluster_id'); const rawResponse = await responsePromise.asResponse(); diff --git a/tests/api-resources/beta/clusters/storage.test.ts b/tests/api-resources/beta/clusters/storage.test.ts index a21ca711..6970d69b 100644 --- a/tests/api-resources/beta/clusters/storage.test.ts +++ b/tests/api-resources/beta/clusters/storage.test.ts @@ -28,6 +28,7 @@ describe('resource storage', () => { region: 'region', size_tib: 0, volume_name: 'volume_name', + is_lifecycle_independent: true, }); }); @@ -42,8 +43,8 @@ describe('resource storage', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('update', async () => { - const responsePromise = client.beta.clusters.storage.update({}); + test('update: only required params', async () => { + const responsePromise = client.beta.clusters.storage.update({ size_tib: 0, volume_id: 'volume_id' }); const rawResponse = await responsePromise.asResponse(); expect(rawResponse).toBeInstanceOf(Response); const response = await responsePromise; @@ -53,6 +54,10 @@ describe('resource storage', () => { expect(dataAndResponse.response).toBe(rawResponse); }); + test('update: required and optional params', async () => { + const response = await client.beta.clusters.storage.update({ size_tib: 0, volume_id: 'volume_id' }); + }); + test('list', async () => { const responsePromise = client.beta.clusters.storage.list(); const rawResponse = await responsePromise.asResponse(); @@ -64,6 +69,13 @@ describe('resource storage', () => { expect(dataAndResponse.response).toBe(rawResponse); }); + test('list: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.beta.clusters.storage.list({ project_id: 'project_id' }, { path: '/_stainless_unknown_path' }), + ).rejects.toThrow(Together.NotFoundError); + }); + test('delete', async () => { const responsePromise = client.beta.clusters.storage.delete('volume_id'); const rawResponse = await responsePromise.asResponse(); From cf4f87b4bdef72daf7228159cccefccbf3bfe4e8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 00:12:11 +0000 Subject: [PATCH 12/31] fix(types): change status to enum in cluster storage --- .stats.yml | 4 ++-- src/resources/beta/clusters/storage.ts | 13 ++++++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index 4ae4e030..5958b37a 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 76 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-e398785d31ad51e94f158b377be469e74748e0229a964d3602149c6bf964581e.yml -openapi_spec_hash: 3e4db6f27d100314926d87c9ae24cba2 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-5ba14fb08cb2129befa3b72c8bd20b67a863c4be6e010368e2184fbe1b18321b.yml +openapi_spec_hash: 5b1c21199bda07c852b8b48c87859784 config_hash: ec427df08d61d8888138f15cd53c6454 diff --git a/src/resources/beta/clusters/storage.ts b/src/resources/beta/clusters/storage.ts index 72d9c473..067fe539 100644 --- a/src/resources/beta/clusters/storage.ts +++ b/src/resources/beta/clusters/storage.ts @@ -53,7 +53,18 @@ export class Storage extends APIResource { export interface ClusterStorage { size_tib: number; - status: string; + /** + * Current status of the shared volume. + */ + status: + | 'scheduled' + | 'available' + | 'bound' + | 'provisioning' + | 'deleting' + | 'failed' + | 'access_revoked' + | 'unknown'; volume_id: string; From 84855a355af582d28477a3771b96d966e2ac427d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 15:25:42 +0000 Subject: [PATCH 13/31] feat(api): add h200-140gb GPU type option to jig resource --- .stats.yml | 4 ++-- src/resources/beta/jig/jig.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.stats.yml b/.stats.yml index 5958b37a..1b616aee 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 76 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-5ba14fb08cb2129befa3b72c8bd20b67a863c4be6e010368e2184fbe1b18321b.yml -openapi_spec_hash: 5b1c21199bda07c852b8b48c87859784 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-8f89a16f91cc70112c8f61ccfa8e22d08f3d898afdcbb92d3dec0ad6efba8fb2.yml +openapi_spec_hash: c4dd7519d9865e6e2fcf5b9414b06bab config_hash: ec427df08d61d8888138f15cd53c6454 diff --git a/src/resources/beta/jig/jig.ts b/src/resources/beta/jig/jig.ts index dde1b8e6..129da25b 100644 --- a/src/resources/beta/jig/jig.ts +++ b/src/resources/beta/jig/jig.ts @@ -147,7 +147,7 @@ export interface Deployment { /** * GPUType specifies the type of GPU requested (if any) for this deployment */ - gpu_type?: 'h100-80gb' | 'h100-40gb-mig' | 'b200-192gb'; + gpu_type?: 'h100-80gb' | 'h100-40gb-mig' | 'h200-140gb' | 'b200-192gb'; /** * HealthCheckPath is the HTTP path used for health checks of the application @@ -444,7 +444,7 @@ export interface JigUpdateParams { /** * GPUType specifies the GPU hardware to use (e.g., "h100-80gb") */ - gpu_type?: 'h100-80gb' | 'h100-40gb-mig' | 'b200-192gb'; + gpu_type?: 'h100-80gb' | 'h100-40gb-mig' | 'h200-140gb' | 'b200-192gb'; /** * HealthCheckPath is the HTTP path for health checks (e.g., "/health"). Set to @@ -613,7 +613,7 @@ export interface JigDeployParams { /** * GPUType specifies the GPU hardware to use (e.g., "h100-80gb"). */ - gpu_type: 'h100-80gb' | 'h100-40gb-mig' | 'b200-192gb'; + gpu_type: 'h100-80gb' | 'h100-40gb-mig' | 'h200-140gb' | 'b200-192gb'; /** * Image is the container image to deploy from registry.together.ai. From 733450c79034609b749ce7a8a9255dc63dd031c1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 16:11:30 +0000 Subject: [PATCH 14/31] fix(types): make duration_days and size_tib optional in clusters --- .stats.yml | 4 ++-- src/resources/beta/clusters/clusters.ts | 10 +++++----- src/resources/beta/clusters/storage.ts | 4 ++-- tests/api-resources/beta/clusters/clusters.test.ts | 3 +-- tests/api-resources/beta/clusters/storage.test.ts | 4 ++-- 5 files changed, 12 insertions(+), 13 deletions(-) diff --git a/.stats.yml b/.stats.yml index 1b616aee..fd8feec9 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 76 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-8f89a16f91cc70112c8f61ccfa8e22d08f3d898afdcbb92d3dec0ad6efba8fb2.yml -openapi_spec_hash: c4dd7519d9865e6e2fcf5b9414b06bab +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-e070ac8df7cf6579d5f9f5f7e2896a85de93f46f0ab4aed2f6b48aa4af092204.yml +openapi_spec_hash: 4603775ed88b25a4307e5d87299a23bb config_hash: ec427df08d61d8888138f15cd53c6454 diff --git a/src/resources/beta/clusters/clusters.ts b/src/resources/beta/clusters/clusters.ts index b52b635e..87f8c5f5 100644 --- a/src/resources/beta/clusters/clusters.ts +++ b/src/resources/beta/clusters/clusters.ts @@ -654,11 +654,6 @@ export interface ClusterCreateParams { */ cuda_version: string; - /** - * Duration in days to keep the cluster running. - */ - duration_days: number; - /** * Type of GPU to use in the cluster */ @@ -725,6 +720,11 @@ export interface ClusterCreateParams { */ cluster_type?: 'KUBERNETES' | 'SLURM'; + /** + * Duration in days to keep the cluster running. + */ + duration_days?: number; + /** * Whether automated GPU node failover should be enabled for this cluster. By * default, it is disabled. diff --git a/src/resources/beta/clusters/storage.ts b/src/resources/beta/clusters/storage.ts index 067fe539..590f49d2 100644 --- a/src/resources/beta/clusters/storage.ts +++ b/src/resources/beta/clusters/storage.ts @@ -96,9 +96,9 @@ export interface StorageCreateParams { } export interface StorageUpdateParams { - size_tib: number; - volume_id: string; + + size_tib?: number; } export interface StorageListParams { diff --git a/tests/api-resources/beta/clusters/clusters.test.ts b/tests/api-resources/beta/clusters/clusters.test.ts index ee4d64ec..f4ddb9d9 100644 --- a/tests/api-resources/beta/clusters/clusters.test.ts +++ b/tests/api-resources/beta/clusters/clusters.test.ts @@ -13,7 +13,6 @@ describe('resource clusters', () => { billing_type: 'RESERVED', cluster_name: 'cluster_name', cuda_version: 'cuda_version', - duration_days: 0, gpu_type: 'H100_SXM', num_gpus: 0, nvidia_driver_version: 'nvidia_driver_version', @@ -33,7 +32,6 @@ describe('resource clusters', () => { billing_type: 'RESERVED', cluster_name: 'cluster_name', cuda_version: 'cuda_version', - duration_days: 0, gpu_type: 'H100_SXM', num_gpus: 0, nvidia_driver_version: 'nvidia_driver_version', @@ -79,6 +77,7 @@ describe('resource clusters', () => { }, }, cluster_type: 'KUBERNETES', + duration_days: 0, gpu_node_failover_enabled: true, install_traefik: true, num_capacity_pool_gpus: 0, diff --git a/tests/api-resources/beta/clusters/storage.test.ts b/tests/api-resources/beta/clusters/storage.test.ts index 6970d69b..a0fca98a 100644 --- a/tests/api-resources/beta/clusters/storage.test.ts +++ b/tests/api-resources/beta/clusters/storage.test.ts @@ -44,7 +44,7 @@ describe('resource storage', () => { }); test('update: only required params', async () => { - const responsePromise = client.beta.clusters.storage.update({ size_tib: 0, volume_id: 'volume_id' }); + const responsePromise = client.beta.clusters.storage.update({ volume_id: 'volume_id' }); const rawResponse = await responsePromise.asResponse(); expect(rawResponse).toBeInstanceOf(Response); const response = await responsePromise; @@ -55,7 +55,7 @@ describe('resource storage', () => { }); test('update: required and optional params', async () => { - const response = await client.beta.clusters.storage.update({ size_tib: 0, volume_id: 'volume_id' }); + const response = await client.beta.clusters.storage.update({ volume_id: 'volume_id', size_tib: 0 }); }); test('list', async () => { From 09b71bc93c96f1e83d525c4f5bf222169af62044 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 17:37:23 +0000 Subject: [PATCH 15/31] feat(api): add storage file/multipart operations, logs method, rename response types --- .stats.yml | 4 ++-- MIGRATION.md | 1 + api.md | 2 +- src/resources/beta/jig/index.ts | 1 + src/resources/beta/jig/jig.ts | 21 +++++++++++++++++++- src/resources/beta/jig/volumes.ts | 16 +++++++++++++-- tests/api-resources/beta/jig/jig.test.ts | 10 +++++++++- tests/api-resources/beta/jig/volumes.test.ts | 11 ++++++++-- 8 files changed, 57 insertions(+), 9 deletions(-) diff --git a/.stats.yml b/.stats.yml index fd8feec9..ef4b0234 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 76 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-e070ac8df7cf6579d5f9f5f7e2896a85de93f46f0ab4aed2f6b48aa4af092204.yml -openapi_spec_hash: 4603775ed88b25a4307e5d87299a23bb +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-a5de4cd2b7b07333c8566d7dc805319e5b1727078b57e5236f8076a7ab4925d2.yml +openapi_spec_hash: d087f1b24b5623db2c9a8d4a2d987a2f config_hash: ec427df08d61d8888138f15cd53c6454 diff --git a/MIGRATION.md b/MIGRATION.md index ca1a79bc..be40242e 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -68,6 +68,7 @@ client.example.list(undefined, { headers: { ... } }); This affects the following methods: - `client.beta.jig.retrieveLogs()` +- `client.beta.jig.volumes.retrieve()` - `client.beta.clusters.list()` - `client.beta.clusters.storage.list()` - `client.fineTuning.delete()` diff --git a/api.md b/api.md index 80e458d6..639f1223 100644 --- a/api.md +++ b/api.md @@ -45,7 +45,7 @@ Types: Methods: - client.beta.jig.volumes.create({ ...params }) -> Volume -- client.beta.jig.volumes.retrieve(id) -> Volume +- client.beta.jig.volumes.retrieve(id, { ...params }) -> Volume - client.beta.jig.volumes.update(id, { ...params }) -> Volume - client.beta.jig.volumes.list() -> VolumeListResponse - client.beta.jig.volumes.delete(id) -> unknown diff --git a/src/resources/beta/jig/index.ts b/src/resources/beta/jig/index.ts index 89534539..8a3e70d7 100644 --- a/src/resources/beta/jig/index.ts +++ b/src/resources/beta/jig/index.ts @@ -35,5 +35,6 @@ export { type VolumeListResponse, type VolumeDeleteResponse, type VolumeCreateParams, + type VolumeRetrieveParams, type VolumeUpdateParams, } from './volumes'; diff --git a/src/resources/beta/jig/jig.ts b/src/resources/beta/jig/jig.ts index 129da25b..b0345358 100644 --- a/src/resources/beta/jig/jig.ts +++ b/src/resources/beta/jig/jig.ts @@ -28,6 +28,7 @@ import { VolumeCreateParams, VolumeDeleteResponse, VolumeListResponse, + VolumeRetrieveParams, VolumeUpdateParams, Volumes, } from './volumes'; @@ -204,7 +205,7 @@ export interface Deployment { * Status represents the overall status of the deployment (e.g., Updating, Scaling, * Ready, Failed) */ - status?: 'Updating' | 'Scaling' | 'Ready' | 'Failed'; + status?: 'Updating' | 'Scaling' | 'Ready' | 'Failed' | 'ScaledToZero'; /** * Storage is the amount of storage (in MB or units as defined by the platform) @@ -212,6 +213,12 @@ export interface Deployment { */ storage?: number; + /** + * TerminationGracePeriodSeconds is the time in seconds to wait for graceful + * shutdown before forcefully terminating the replica + */ + termination_grace_period_seconds?: number; + /** * UpdatedAt is the ISO8601 timestamp when this deployment was last updated */ @@ -830,6 +837,17 @@ export interface JigRetrieveLogsParams { * Replica ID to filter logs */ replica_id?: string; + + /** + * Deployment revision (UUID) to filter logs + */ + revision?: string; + + /** + * Deployment image version (tag or last 4 characters of image digest) to filter + * logs + */ + version?: string; } Jig.Queue = Queue; @@ -865,6 +883,7 @@ export declare namespace Jig { type VolumeListResponse as VolumeListResponse, type VolumeDeleteResponse as VolumeDeleteResponse, type VolumeCreateParams as VolumeCreateParams, + type VolumeRetrieveParams as VolumeRetrieveParams, type VolumeUpdateParams as VolumeUpdateParams, }; diff --git a/src/resources/beta/jig/volumes.ts b/src/resources/beta/jig/volumes.ts index 4362913f..f7759f84 100644 --- a/src/resources/beta/jig/volumes.ts +++ b/src/resources/beta/jig/volumes.ts @@ -16,8 +16,12 @@ export class Volumes extends APIResource { /** * Retrieve details of a specific volume by its ID or name */ - retrieve(id: string, options?: RequestOptions): APIPromise { - return this._client.get(path`/deployments/storage/volumes/${id}`, options); + retrieve( + id: string, + query: VolumeRetrieveParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.get(path`/deployments/storage/volumes/${id}`, { query, ...options }); } /** @@ -211,6 +215,13 @@ export namespace VolumeCreateParams { } } +export interface VolumeRetrieveParams { + /** + * Volume version to describe (defaults to current version) + */ + version?: number; +} + export interface VolumeUpdateParams { /** * Content specifies the new content to preload to this volume. @@ -253,6 +264,7 @@ export declare namespace Volumes { type VolumeListResponse as VolumeListResponse, type VolumeDeleteResponse as VolumeDeleteResponse, type VolumeCreateParams as VolumeCreateParams, + type VolumeRetrieveParams as VolumeRetrieveParams, type VolumeUpdateParams as VolumeUpdateParams, }; } diff --git a/tests/api-resources/beta/jig/jig.test.ts b/tests/api-resources/beta/jig/jig.test.ts index d11b57f1..3e2386cf 100644 --- a/tests/api-resources/beta/jig/jig.test.ts +++ b/tests/api-resources/beta/jig/jig.test.ts @@ -120,7 +120,15 @@ describe('resource jig', () => { test('retrieveLogs: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( - client.beta.jig.retrieveLogs('id', { replica_id: 'replica_id' }, { path: '/_stainless_unknown_path' }), + client.beta.jig.retrieveLogs( + 'id', + { + replica_id: 'replica_id', + revision: 'revision', + version: 'version', + }, + { path: '/_stainless_unknown_path' }, + ), ).rejects.toThrow(Together.NotFoundError); }); }); diff --git a/tests/api-resources/beta/jig/volumes.test.ts b/tests/api-resources/beta/jig/volumes.test.ts index c133b372..7eab3d56 100644 --- a/tests/api-resources/beta/jig/volumes.test.ts +++ b/tests/api-resources/beta/jig/volumes.test.ts @@ -11,7 +11,7 @@ describe('resource volumes', () => { test('create: only required params', async () => { const responsePromise = client.beta.jig.volumes.create({ content: {}, - name: 'name', + name: 'x', type: 'readOnly', }); const rawResponse = await responsePromise.asResponse(); @@ -26,7 +26,7 @@ describe('resource volumes', () => { test('create: required and optional params', async () => { const response = await client.beta.jig.volumes.create({ content: { source_prefix: 'models/', type: 'files' }, - name: 'name', + name: 'x', type: 'readOnly', }); }); @@ -42,6 +42,13 @@ describe('resource volumes', () => { expect(dataAndResponse.response).toBe(rawResponse); }); + test('retrieve: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.beta.jig.volumes.retrieve('id', { version: 0 }, { path: '/_stainless_unknown_path' }), + ).rejects.toThrow(Together.NotFoundError); + }); + test('update', async () => { const responsePromise = client.beta.jig.volumes.update('id', {}); const rawResponse = await responsePromise.asResponse(); From 4623a4be3ce06321b63c3240623ffe7d0f184373 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 17:39:45 +0000 Subject: [PATCH 16/31] docs(api): add field documentation to clusters and storage --- .stats.yml | 4 ++-- src/resources/beta/clusters/clusters.ts | 18 ++++++++++++++++++ src/resources/beta/clusters/storage.ts | 21 +++++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index ef4b0234..b8958640 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 76 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-a5de4cd2b7b07333c8566d7dc805319e5b1727078b57e5236f8076a7ab4925d2.yml -openapi_spec_hash: d087f1b24b5623db2c9a8d4a2d987a2f +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-ae3cfb67611b42139b6d37809a00cf0c26b4803f974a3defd3ce64394fa08e1b.yml +openapi_spec_hash: 50c3ce80ecc7d25283c96e3544567995 config_hash: ec427df08d61d8888138f15cd53c6454 diff --git a/src/resources/beta/clusters/clusters.ts b/src/resources/beta/clusters/clusters.ts index 87f8c5f5..6a4df773 100644 --- a/src/resources/beta/clusters/clusters.ts +++ b/src/resources/beta/clusters/clusters.ts @@ -458,12 +458,24 @@ export namespace Cluster { } export interface Volume { + /** + * Size of the volume in TiB. + */ size_tib: number; + /** + * Current status of the volume. + */ status: string; + /** + * ID of the volume. + */ volume_id: string; + /** + * User provided name of the volume. + */ volume_name: string; } @@ -999,6 +1011,9 @@ export namespace ClusterCreateParams { * Inline configuration to create a shared volume with the cluster creation. */ export interface SharedVolume { + /** + * Region name. Usable regions can be found from `clusters.list_regions()` + */ region: string; /** @@ -1006,6 +1021,9 @@ export namespace ClusterCreateParams { */ size_tib: number; + /** + * User provided name of the volume. + */ volume_name: string; /** diff --git a/src/resources/beta/clusters/storage.ts b/src/resources/beta/clusters/storage.ts index 590f49d2..cdfe3676 100644 --- a/src/resources/beta/clusters/storage.ts +++ b/src/resources/beta/clusters/storage.ts @@ -51,6 +51,9 @@ export class Storage extends APIResource { } export interface ClusterStorage { + /** + * Size of the volume in TiB. + */ size_tib: number; /** @@ -66,8 +69,14 @@ export interface ClusterStorage { | 'access_revoked' | 'unknown'; + /** + * ID of the volume. + */ volume_id: string; + /** + * User provided name of the volume. + */ volume_name: string; } @@ -80,6 +89,9 @@ export interface StorageDeleteResponse { } export interface StorageCreateParams { + /** + * Region name. Usable regions can be found from `clusters.list_regions()` + */ region: string; /** @@ -87,6 +99,9 @@ export interface StorageCreateParams { */ size_tib: number; + /** + * User provided name of the volume. + */ volume_name: string; /** @@ -96,8 +111,14 @@ export interface StorageCreateParams { } export interface StorageUpdateParams { + /** + * ID of the volume. + */ volume_id: string; + /** + * Size of the volume in TiB. + */ size_tib?: number; } From a70223c5f31720ab6aef7a9e0d0c9ef2229ebedb Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 17:53:49 +0000 Subject: [PATCH 17/31] feat(api): Add node remediation APIs to clusters sdks --- .stats.yml | 6 +- MIGRATION.md | 22 + api.md | 18 + bin/migration-config.json | 199 +- src/client.ts | 3 - src/internal/qs/LICENSE.md | 13 + src/internal/qs/README.md | 3 + src/internal/qs/formats.ts | 10 + src/internal/qs/index.ts | 13 + src/internal/qs/stringify.ts | 385 +++ src/internal/qs/types.ts | 71 + src/internal/qs/utils.ts | 265 ++ src/internal/utils/query.ts | 20 +- src/resources/beta/clusters/clusters.ts | 30 + src/resources/beta/clusters/index.ts | 13 + src/resources/beta/clusters/remediations.ts | 828 ++++++ .../beta/clusters/remediations.test.ts | 123 + tests/qs/empty-keys-cases.ts | 271 ++ tests/qs/stringify.test.ts | 2232 +++++++++++++++++ tests/qs/utils.test.ts | 169 ++ tests/stringifyQuery.test.ts | 6 - 21 files changed, 4669 insertions(+), 31 deletions(-) create mode 100644 src/internal/qs/LICENSE.md create mode 100644 src/internal/qs/README.md create mode 100644 src/internal/qs/formats.ts create mode 100644 src/internal/qs/index.ts create mode 100644 src/internal/qs/stringify.ts create mode 100644 src/internal/qs/types.ts create mode 100644 src/internal/qs/utils.ts create mode 100644 src/resources/beta/clusters/remediations.ts create mode 100644 tests/api-resources/beta/clusters/remediations.test.ts create mode 100644 tests/qs/empty-keys-cases.ts create mode 100644 tests/qs/stringify.test.ts create mode 100644 tests/qs/utils.test.ts diff --git a/.stats.yml b/.stats.yml index b8958640..b013946c 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 76 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-ae3cfb67611b42139b6d37809a00cf0c26b4803f974a3defd3ce64394fa08e1b.yml +configured_endpoints: 81 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-496df09bdf8bfe14dddc5ba6dc70e219336f51295d2f0671285b4056ec756f21.yml openapi_spec_hash: 50c3ce80ecc7d25283c96e3544567995 -config_hash: ec427df08d61d8888138f15cd53c6454 +config_hash: 2ab62260e2c5e9527b47bef3f36e545d diff --git a/MIGRATION.md b/MIGRATION.md index be40242e..34dbd358 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -38,6 +38,28 @@ Readable.fromWeb(res.body).pipe(process.stdout); Additionally, the `headers` property on `APIError` objects is now an instance of the Web [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers) class. It was previously defined as `Record`. +### Named path parameters + +Methods that take multiple path parameters typically now use named instead of positional arguments for better clarity and to prevent a footgun where it was easy to accidentally pass arguments in the incorrect order. + +For example, for a method that would call an endpoint at `/v1/parents/{parent_id}/children/{child_id}`, only the _last_ path parameter is positional and the rest must be passed as named arguments. + +```ts +// Before +client.parents.children.retrieve('p_123', 'c_456'); + +// After +client.parents.children.retrieve('c_456', { parent_id: 'p_123' }); +``` + +This affects the following methods: + +- `client.beta.clusters.remediations.create()` +- `client.beta.clusters.remediations.list()` +- `client.beta.clusters.remediations.approve()` +- `client.beta.clusters.remediations.cancel()` +- `client.beta.clusters.remediations.reject()` + ### URI encoded path parameters Path params are now properly encoded by default. If you were manually encoding path parameters before giving them to the SDK, you must now stop doing that and pass the diff --git a/api.md b/api.md index 639f1223..d69879a9 100644 --- a/api.md +++ b/api.md @@ -84,6 +84,24 @@ Methods: - client.beta.clusters.delete(clusterID) -> ClusterDeleteResponse - client.beta.clusters.listRegions() -> ClusterListRegionsResponse +### Remediations + +Types: + +- RemediationCreateResponse +- RemediationListResponse +- RemediationApproveResponse +- RemediationCancelResponse +- RemediationRejectResponse + +Methods: + +- client.beta.clusters.remediations.create(instanceID, { ...params }) -> RemediationCreateResponse +- client.beta.clusters.remediations.list(instanceID, { ...params }) -> RemediationListResponse +- client.beta.clusters.remediations.approve(remediationID, { ...params }) -> RemediationApproveResponse +- client.beta.clusters.remediations.cancel(remediationID, { ...params }) -> RemediationCancelResponse +- client.beta.clusters.remediations.reject(remediationID, { ...params }) -> RemediationRejectResponse + ### Storage Types: diff --git a/bin/migration-config.json b/bin/migration-config.json index 2f992570..096eef54 100644 --- a/bin/migration-config.json +++ b/bin/migration-config.json @@ -2,5 +2,202 @@ "pkg": "together-ai", "githubRepo": "https://github.com/togethercomputer/together-typescript", "clientClass": "Together", - "methods": [] + "methods": [ + { + "base": "beta.clusters.remediations", + "name": "create", + "params": [ + { + "type": "param", + "key": "instance_id", + "location": "path" + }, + { + "type": "params", + "maybeOverload": false + }, + { + "type": "options" + } + ], + "oldParams": [ + { + "type": "param", + "key": "cluster_id", + "location": "path" + }, + { + "type": "param", + "key": "instance_id", + "location": "path" + }, + { + "type": "params", + "maybeOverload": false + }, + { + "type": "options" + } + ] + }, + { + "base": "beta.clusters.remediations", + "name": "list", + "params": [ + { + "type": "param", + "key": "instance_id", + "location": "path" + }, + { + "type": "params", + "maybeOverload": false + }, + { + "type": "options" + } + ], + "oldParams": [ + { + "type": "param", + "key": "cluster_id", + "location": "path" + }, + { + "type": "param", + "key": "instance_id", + "location": "path" + }, + { + "type": "params", + "maybeOverload": true + }, + { + "type": "options" + } + ] + }, + { + "base": "beta.clusters.remediations", + "name": "approve", + "params": [ + { + "type": "param", + "key": "remediation_id", + "location": "path" + }, + { + "type": "params", + "maybeOverload": false + }, + { + "type": "options" + } + ], + "oldParams": [ + { + "type": "param", + "key": "cluster_id", + "location": "path" + }, + { + "type": "param", + "key": "instance_id", + "location": "path" + }, + { + "type": "param", + "key": "remediation_id", + "location": "path" + }, + { + "type": "params", + "maybeOverload": false + }, + { + "type": "options" + } + ] + }, + { + "base": "beta.clusters.remediations", + "name": "cancel", + "params": [ + { + "type": "param", + "key": "remediation_id", + "location": "path" + }, + { + "type": "params", + "maybeOverload": false + }, + { + "type": "options" + } + ], + "oldParams": [ + { + "type": "param", + "key": "cluster_id", + "location": "path" + }, + { + "type": "param", + "key": "instance_id", + "location": "path" + }, + { + "type": "param", + "key": "remediation_id", + "location": "path" + }, + { + "type": "options" + } + ] + }, + { + "base": "beta.clusters.remediations", + "name": "reject", + "params": [ + { + "type": "param", + "key": "remediation_id", + "location": "path" + }, + { + "type": "params", + "maybeOverload": false + }, + { + "type": "options" + } + ], + "oldParams": [ + { + "type": "param", + "key": "cluster_id", + "location": "path" + }, + { + "type": "param", + "key": "instance_id", + "location": "path" + }, + { + "type": "param", + "key": "remediation_id", + "location": "path" + }, + { + "type": "params", + "maybeOverload": false + }, + { + "type": "options" + } + ] + } + ] } diff --git a/src/client.ts b/src/client.ts index ada3d4bc..afa739c5 100644 --- a/src/client.ts +++ b/src/client.ts @@ -301,9 +301,6 @@ export class Together { return buildHeaders([{ Authorization: `Bearer ${this.apiKey}` }]); } - /** - * Basic re-implementation of `qs.stringify` for primitive types. - */ protected stringifyQuery(query: object | Record): string { return stringifyQuery(query); } diff --git a/src/internal/qs/LICENSE.md b/src/internal/qs/LICENSE.md new file mode 100644 index 00000000..3fda1573 --- /dev/null +++ b/src/internal/qs/LICENSE.md @@ -0,0 +1,13 @@ +BSD 3-Clause License + +Copyright (c) 2014, Nathan LaFreniere and other [contributors](https://github.com/puruvj/neoqs/graphs/contributors) All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/internal/qs/README.md b/src/internal/qs/README.md new file mode 100644 index 00000000..67ae04ec --- /dev/null +++ b/src/internal/qs/README.md @@ -0,0 +1,3 @@ +# qs + +This is a vendored version of [neoqs](https://github.com/PuruVJ/neoqs) which is a TypeScript rewrite of [qs](https://github.com/ljharb/qs), a query string library. diff --git a/src/internal/qs/formats.ts b/src/internal/qs/formats.ts new file mode 100644 index 00000000..e76a742f --- /dev/null +++ b/src/internal/qs/formats.ts @@ -0,0 +1,10 @@ +import type { Format } from './types'; + +export const default_format: Format = 'RFC3986'; +export const default_formatter = (v: PropertyKey) => String(v); +export const formatters: Record string> = { + RFC1738: (v: PropertyKey) => String(v).replace(/%20/g, '+'), + RFC3986: default_formatter, +}; +export const RFC1738 = 'RFC1738'; +export const RFC3986 = 'RFC3986'; diff --git a/src/internal/qs/index.ts b/src/internal/qs/index.ts new file mode 100644 index 00000000..c3a3620d --- /dev/null +++ b/src/internal/qs/index.ts @@ -0,0 +1,13 @@ +import { default_format, formatters, RFC1738, RFC3986 } from './formats'; + +const formats = { + formatters, + RFC1738, + RFC3986, + default: default_format, +}; + +export { stringify } from './stringify'; +export { formats }; + +export type { DefaultDecoder, DefaultEncoder, Format, ParseOptions, StringifyOptions } from './types'; diff --git a/src/internal/qs/stringify.ts b/src/internal/qs/stringify.ts new file mode 100644 index 00000000..7e71387f --- /dev/null +++ b/src/internal/qs/stringify.ts @@ -0,0 +1,385 @@ +import { encode, is_buffer, maybe_map, has } from './utils'; +import { default_format, default_formatter, formatters } from './formats'; +import type { NonNullableProperties, StringifyOptions } from './types'; +import { isArray } from '../utils/values'; + +const array_prefix_generators = { + brackets(prefix: PropertyKey) { + return String(prefix) + '[]'; + }, + comma: 'comma', + indices(prefix: PropertyKey, key: string) { + return String(prefix) + '[' + key + ']'; + }, + repeat(prefix: PropertyKey) { + return String(prefix); + }, +}; + +const push_to_array = function (arr: any[], value_or_array: any) { + Array.prototype.push.apply(arr, isArray(value_or_array) ? value_or_array : [value_or_array]); +}; + +let toISOString; + +const defaults = { + addQueryPrefix: false, + allowDots: false, + allowEmptyArrays: false, + arrayFormat: 'indices', + charset: 'utf-8', + charsetSentinel: false, + delimiter: '&', + encode: true, + encodeDotInKeys: false, + encoder: encode, + encodeValuesOnly: false, + format: default_format, + formatter: default_formatter, + /** @deprecated */ + indices: false, + serializeDate(date) { + return (toISOString ??= Function.prototype.call.bind(Date.prototype.toISOString))(date); + }, + skipNulls: false, + strictNullHandling: false, +} as NonNullableProperties; + +function is_non_nullish_primitive(v: unknown): v is string | number | boolean | symbol | bigint { + return ( + typeof v === 'string' || + typeof v === 'number' || + typeof v === 'boolean' || + typeof v === 'symbol' || + typeof v === 'bigint' + ); +} + +const sentinel = {}; + +function inner_stringify( + object: any, + prefix: PropertyKey, + generateArrayPrefix: StringifyOptions['arrayFormat'] | ((prefix: string, key: string) => string), + commaRoundTrip: boolean, + allowEmptyArrays: boolean, + strictNullHandling: boolean, + skipNulls: boolean, + encodeDotInKeys: boolean, + encoder: StringifyOptions['encoder'], + filter: StringifyOptions['filter'], + sort: StringifyOptions['sort'], + allowDots: StringifyOptions['allowDots'], + serializeDate: StringifyOptions['serializeDate'], + format: StringifyOptions['format'], + formatter: StringifyOptions['formatter'], + encodeValuesOnly: boolean, + charset: StringifyOptions['charset'], + sideChannel: WeakMap, +) { + let obj = object; + + let tmp_sc = sideChannel; + let step = 0; + let find_flag = false; + while ((tmp_sc = tmp_sc.get(sentinel)) !== void undefined && !find_flag) { + // Where object last appeared in the ref tree + const pos = tmp_sc.get(object); + step += 1; + if (typeof pos !== 'undefined') { + if (pos === step) { + throw new RangeError('Cyclic object value'); + } else { + find_flag = true; // Break while + } + } + if (typeof tmp_sc.get(sentinel) === 'undefined') { + step = 0; + } + } + + if (typeof filter === 'function') { + obj = filter(prefix, obj); + } else if (obj instanceof Date) { + obj = serializeDate?.(obj); + } else if (generateArrayPrefix === 'comma' && isArray(obj)) { + obj = maybe_map(obj, function (value) { + if (value instanceof Date) { + return serializeDate?.(value); + } + return value; + }); + } + + if (obj === null) { + if (strictNullHandling) { + return encoder && !encodeValuesOnly ? + // @ts-expect-error + encoder(prefix, defaults.encoder, charset, 'key', format) + : prefix; + } + + obj = ''; + } + + if (is_non_nullish_primitive(obj) || is_buffer(obj)) { + if (encoder) { + const key_value = + encodeValuesOnly ? prefix + // @ts-expect-error + : encoder(prefix, defaults.encoder, charset, 'key', format); + return [ + formatter?.(key_value) + + '=' + + // @ts-expect-error + formatter?.(encoder(obj, defaults.encoder, charset, 'value', format)), + ]; + } + return [formatter?.(prefix) + '=' + formatter?.(String(obj))]; + } + + const values: string[] = []; + + if (typeof obj === 'undefined') { + return values; + } + + let obj_keys; + if (generateArrayPrefix === 'comma' && isArray(obj)) { + // we need to join elements in + if (encodeValuesOnly && encoder) { + // @ts-expect-error values only + obj = maybe_map(obj, encoder); + } + obj_keys = [{ value: obj.length > 0 ? obj.join(',') || null : void undefined }]; + } else if (isArray(filter)) { + obj_keys = filter; + } else { + const keys = Object.keys(obj); + obj_keys = sort ? keys.sort(sort) : keys; + } + + const encoded_prefix = encodeDotInKeys ? String(prefix).replace(/\./g, '%2E') : String(prefix); + + const adjusted_prefix = + commaRoundTrip && isArray(obj) && obj.length === 1 ? encoded_prefix + '[]' : encoded_prefix; + + if (allowEmptyArrays && isArray(obj) && obj.length === 0) { + return adjusted_prefix + '[]'; + } + + for (let j = 0; j < obj_keys.length; ++j) { + const key = obj_keys[j]; + const value = + // @ts-ignore + typeof key === 'object' && typeof key.value !== 'undefined' ? key.value : obj[key as any]; + + if (skipNulls && value === null) { + continue; + } + + // @ts-ignore + const encoded_key = allowDots && encodeDotInKeys ? (key as any).replace(/\./g, '%2E') : key; + const key_prefix = + isArray(obj) ? + typeof generateArrayPrefix === 'function' ? + generateArrayPrefix(adjusted_prefix, encoded_key) + : adjusted_prefix + : adjusted_prefix + (allowDots ? '.' + encoded_key : '[' + encoded_key + ']'); + + sideChannel.set(object, step); + const valueSideChannel = new WeakMap(); + valueSideChannel.set(sentinel, sideChannel); + push_to_array( + values, + inner_stringify( + value, + key_prefix, + generateArrayPrefix, + commaRoundTrip, + allowEmptyArrays, + strictNullHandling, + skipNulls, + encodeDotInKeys, + // @ts-ignore + generateArrayPrefix === 'comma' && encodeValuesOnly && isArray(obj) ? null : encoder, + filter, + sort, + allowDots, + serializeDate, + format, + formatter, + encodeValuesOnly, + charset, + valueSideChannel, + ), + ); + } + + return values; +} + +function normalize_stringify_options( + opts: StringifyOptions = defaults, +): NonNullableProperties> & { indices?: boolean } { + if (typeof opts.allowEmptyArrays !== 'undefined' && typeof opts.allowEmptyArrays !== 'boolean') { + throw new TypeError('`allowEmptyArrays` option can only be `true` or `false`, when provided'); + } + + if (typeof opts.encodeDotInKeys !== 'undefined' && typeof opts.encodeDotInKeys !== 'boolean') { + throw new TypeError('`encodeDotInKeys` option can only be `true` or `false`, when provided'); + } + + if (opts.encoder !== null && typeof opts.encoder !== 'undefined' && typeof opts.encoder !== 'function') { + throw new TypeError('Encoder has to be a function.'); + } + + const charset = opts.charset || defaults.charset; + if (typeof opts.charset !== 'undefined' && opts.charset !== 'utf-8' && opts.charset !== 'iso-8859-1') { + throw new TypeError('The charset option must be either utf-8, iso-8859-1, or undefined'); + } + + let format = default_format; + if (typeof opts.format !== 'undefined') { + if (!has(formatters, opts.format)) { + throw new TypeError('Unknown format option provided.'); + } + format = opts.format; + } + const formatter = formatters[format]; + + let filter = defaults.filter; + if (typeof opts.filter === 'function' || isArray(opts.filter)) { + filter = opts.filter; + } + + let arrayFormat: StringifyOptions['arrayFormat']; + if (opts.arrayFormat && opts.arrayFormat in array_prefix_generators) { + arrayFormat = opts.arrayFormat; + } else if ('indices' in opts) { + arrayFormat = opts.indices ? 'indices' : 'repeat'; + } else { + arrayFormat = defaults.arrayFormat; + } + + if ('commaRoundTrip' in opts && typeof opts.commaRoundTrip !== 'boolean') { + throw new TypeError('`commaRoundTrip` must be a boolean, or absent'); + } + + const allowDots = + typeof opts.allowDots === 'undefined' ? + !!opts.encodeDotInKeys === true ? + true + : defaults.allowDots + : !!opts.allowDots; + + return { + addQueryPrefix: typeof opts.addQueryPrefix === 'boolean' ? opts.addQueryPrefix : defaults.addQueryPrefix, + // @ts-ignore + allowDots: allowDots, + allowEmptyArrays: + typeof opts.allowEmptyArrays === 'boolean' ? !!opts.allowEmptyArrays : defaults.allowEmptyArrays, + arrayFormat: arrayFormat, + charset: charset, + charsetSentinel: + typeof opts.charsetSentinel === 'boolean' ? opts.charsetSentinel : defaults.charsetSentinel, + commaRoundTrip: !!opts.commaRoundTrip, + delimiter: typeof opts.delimiter === 'undefined' ? defaults.delimiter : opts.delimiter, + encode: typeof opts.encode === 'boolean' ? opts.encode : defaults.encode, + encodeDotInKeys: + typeof opts.encodeDotInKeys === 'boolean' ? opts.encodeDotInKeys : defaults.encodeDotInKeys, + encoder: typeof opts.encoder === 'function' ? opts.encoder : defaults.encoder, + encodeValuesOnly: + typeof opts.encodeValuesOnly === 'boolean' ? opts.encodeValuesOnly : defaults.encodeValuesOnly, + filter: filter, + format: format, + formatter: formatter, + serializeDate: typeof opts.serializeDate === 'function' ? opts.serializeDate : defaults.serializeDate, + skipNulls: typeof opts.skipNulls === 'boolean' ? opts.skipNulls : defaults.skipNulls, + // @ts-ignore + sort: typeof opts.sort === 'function' ? opts.sort : null, + strictNullHandling: + typeof opts.strictNullHandling === 'boolean' ? opts.strictNullHandling : defaults.strictNullHandling, + }; +} + +export function stringify(object: any, opts: StringifyOptions = {}) { + let obj = object; + const options = normalize_stringify_options(opts); + + let obj_keys: PropertyKey[] | undefined; + let filter; + + if (typeof options.filter === 'function') { + filter = options.filter; + obj = filter('', obj); + } else if (isArray(options.filter)) { + filter = options.filter; + obj_keys = filter; + } + + const keys: string[] = []; + + if (typeof obj !== 'object' || obj === null) { + return ''; + } + + const generateArrayPrefix = array_prefix_generators[options.arrayFormat]; + const commaRoundTrip = generateArrayPrefix === 'comma' && options.commaRoundTrip; + + if (!obj_keys) { + obj_keys = Object.keys(obj); + } + + if (options.sort) { + obj_keys.sort(options.sort); + } + + const sideChannel = new WeakMap(); + for (let i = 0; i < obj_keys.length; ++i) { + const key = obj_keys[i]!; + + if (options.skipNulls && obj[key] === null) { + continue; + } + push_to_array( + keys, + inner_stringify( + obj[key], + key, + // @ts-expect-error + generateArrayPrefix, + commaRoundTrip, + options.allowEmptyArrays, + options.strictNullHandling, + options.skipNulls, + options.encodeDotInKeys, + options.encode ? options.encoder : null, + options.filter, + options.sort, + options.allowDots, + options.serializeDate, + options.format, + options.formatter, + options.encodeValuesOnly, + options.charset, + sideChannel, + ), + ); + } + + const joined = keys.join(options.delimiter); + let prefix = options.addQueryPrefix === true ? '?' : ''; + + if (options.charsetSentinel) { + if (options.charset === 'iso-8859-1') { + // encodeURIComponent('✓'), the "numeric entity" representation of a checkmark + prefix += 'utf8=%26%2310003%3B&'; + } else { + // encodeURIComponent('✓') + prefix += 'utf8=%E2%9C%93&'; + } + } + + return joined.length > 0 ? prefix + joined : ''; +} diff --git a/src/internal/qs/types.ts b/src/internal/qs/types.ts new file mode 100644 index 00000000..7c28dbb4 --- /dev/null +++ b/src/internal/qs/types.ts @@ -0,0 +1,71 @@ +export type Format = 'RFC1738' | 'RFC3986'; + +export type DefaultEncoder = (str: any, defaultEncoder?: any, charset?: string) => string; +export type DefaultDecoder = (str: string, decoder?: any, charset?: string) => string; + +export type BooleanOptional = boolean | undefined; + +export type StringifyBaseOptions = { + delimiter?: string; + allowDots?: boolean; + encodeDotInKeys?: boolean; + strictNullHandling?: boolean; + skipNulls?: boolean; + encode?: boolean; + encoder?: ( + str: any, + defaultEncoder: DefaultEncoder, + charset: string, + type: 'key' | 'value', + format?: Format, + ) => string; + filter?: Array | ((prefix: PropertyKey, value: any) => any); + arrayFormat?: 'indices' | 'brackets' | 'repeat' | 'comma'; + indices?: boolean; + sort?: ((a: PropertyKey, b: PropertyKey) => number) | null; + serializeDate?: (d: Date) => string; + format?: 'RFC1738' | 'RFC3986'; + formatter?: (str: PropertyKey) => string; + encodeValuesOnly?: boolean; + addQueryPrefix?: boolean; + charset?: 'utf-8' | 'iso-8859-1'; + charsetSentinel?: boolean; + allowEmptyArrays?: boolean; + commaRoundTrip?: boolean; +}; + +export type StringifyOptions = StringifyBaseOptions; + +export type ParseBaseOptions = { + comma?: boolean; + delimiter?: string | RegExp; + depth?: number | false; + decoder?: (str: string, defaultDecoder: DefaultDecoder, charset: string, type: 'key' | 'value') => any; + arrayLimit?: number; + parseArrays?: boolean; + plainObjects?: boolean; + allowPrototypes?: boolean; + allowSparse?: boolean; + parameterLimit?: number; + strictDepth?: boolean; + strictNullHandling?: boolean; + ignoreQueryPrefix?: boolean; + charset?: 'utf-8' | 'iso-8859-1'; + charsetSentinel?: boolean; + interpretNumericEntities?: boolean; + allowEmptyArrays?: boolean; + duplicates?: 'combine' | 'first' | 'last'; + allowDots?: boolean; + decodeDotInKeys?: boolean; +}; + +export type ParseOptions = ParseBaseOptions; + +export type ParsedQs = { + [key: string]: undefined | string | string[] | ParsedQs | ParsedQs[]; +}; + +// Type to remove null or undefined union from each property +export type NonNullableProperties = { + [K in keyof T]-?: Exclude; +}; diff --git a/src/internal/qs/utils.ts b/src/internal/qs/utils.ts new file mode 100644 index 00000000..4cd56579 --- /dev/null +++ b/src/internal/qs/utils.ts @@ -0,0 +1,265 @@ +import { RFC1738 } from './formats'; +import type { DefaultEncoder, Format } from './types'; +import { isArray } from '../utils/values'; + +export let has = (obj: object, key: PropertyKey): boolean => ( + (has = (Object as any).hasOwn ?? Function.prototype.call.bind(Object.prototype.hasOwnProperty)), + has(obj, key) +); + +const hex_table = /* @__PURE__ */ (() => { + const array = []; + for (let i = 0; i < 256; ++i) { + array.push('%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase()); + } + + return array; +})(); + +function compact_queue>(queue: Array<{ obj: T; prop: string }>) { + while (queue.length > 1) { + const item = queue.pop(); + if (!item) continue; + + const obj = item.obj[item.prop]; + + if (isArray(obj)) { + const compacted: unknown[] = []; + + for (let j = 0; j < obj.length; ++j) { + if (typeof obj[j] !== 'undefined') { + compacted.push(obj[j]); + } + } + + // @ts-ignore + item.obj[item.prop] = compacted; + } + } +} + +function array_to_object(source: any[], options: { plainObjects: boolean }) { + const obj = options && options.plainObjects ? Object.create(null) : {}; + for (let i = 0; i < source.length; ++i) { + if (typeof source[i] !== 'undefined') { + obj[i] = source[i]; + } + } + + return obj; +} + +export function merge( + target: any, + source: any, + options: { plainObjects?: boolean; allowPrototypes?: boolean } = {}, +) { + if (!source) { + return target; + } + + if (typeof source !== 'object') { + if (isArray(target)) { + target.push(source); + } else if (target && typeof target === 'object') { + if ((options && (options.plainObjects || options.allowPrototypes)) || !has(Object.prototype, source)) { + target[source] = true; + } + } else { + return [target, source]; + } + + return target; + } + + if (!target || typeof target !== 'object') { + return [target].concat(source); + } + + let mergeTarget = target; + if (isArray(target) && !isArray(source)) { + // @ts-ignore + mergeTarget = array_to_object(target, options); + } + + if (isArray(target) && isArray(source)) { + source.forEach(function (item, i) { + if (has(target, i)) { + const targetItem = target[i]; + if (targetItem && typeof targetItem === 'object' && item && typeof item === 'object') { + target[i] = merge(targetItem, item, options); + } else { + target.push(item); + } + } else { + target[i] = item; + } + }); + return target; + } + + return Object.keys(source).reduce(function (acc, key) { + const value = source[key]; + + if (has(acc, key)) { + acc[key] = merge(acc[key], value, options); + } else { + acc[key] = value; + } + return acc; + }, mergeTarget); +} + +export function assign_single_source(target: any, source: any) { + return Object.keys(source).reduce(function (acc, key) { + acc[key] = source[key]; + return acc; + }, target); +} + +export function decode(str: string, _: any, charset: string) { + const strWithoutPlus = str.replace(/\+/g, ' '); + if (charset === 'iso-8859-1') { + // unescape never throws, no try...catch needed: + return strWithoutPlus.replace(/%[0-9a-f]{2}/gi, unescape); + } + // utf-8 + try { + return decodeURIComponent(strWithoutPlus); + } catch (e) { + return strWithoutPlus; + } +} + +const limit = 1024; + +export const encode: ( + str: any, + defaultEncoder: DefaultEncoder, + charset: string, + type: 'key' | 'value', + format: Format, +) => string = (str, _defaultEncoder, charset, _kind, format: Format) => { + // This code was originally written by Brian White for the io.js core querystring library. + // It has been adapted here for stricter adherence to RFC 3986 + if (str.length === 0) { + return str; + } + + let string = str; + if (typeof str === 'symbol') { + string = Symbol.prototype.toString.call(str); + } else if (typeof str !== 'string') { + string = String(str); + } + + if (charset === 'iso-8859-1') { + return escape(string).replace(/%u[0-9a-f]{4}/gi, function ($0) { + return '%26%23' + parseInt($0.slice(2), 16) + '%3B'; + }); + } + + let out = ''; + for (let j = 0; j < string.length; j += limit) { + const segment = string.length >= limit ? string.slice(j, j + limit) : string; + const arr = []; + + for (let i = 0; i < segment.length; ++i) { + let c = segment.charCodeAt(i); + if ( + c === 0x2d || // - + c === 0x2e || // . + c === 0x5f || // _ + c === 0x7e || // ~ + (c >= 0x30 && c <= 0x39) || // 0-9 + (c >= 0x41 && c <= 0x5a) || // a-z + (c >= 0x61 && c <= 0x7a) || // A-Z + (format === RFC1738 && (c === 0x28 || c === 0x29)) // ( ) + ) { + arr[arr.length] = segment.charAt(i); + continue; + } + + if (c < 0x80) { + arr[arr.length] = hex_table[c]; + continue; + } + + if (c < 0x800) { + arr[arr.length] = hex_table[0xc0 | (c >> 6)]! + hex_table[0x80 | (c & 0x3f)]; + continue; + } + + if (c < 0xd800 || c >= 0xe000) { + arr[arr.length] = + hex_table[0xe0 | (c >> 12)]! + hex_table[0x80 | ((c >> 6) & 0x3f)] + hex_table[0x80 | (c & 0x3f)]; + continue; + } + + i += 1; + c = 0x10000 + (((c & 0x3ff) << 10) | (segment.charCodeAt(i) & 0x3ff)); + + arr[arr.length] = + hex_table[0xf0 | (c >> 18)]! + + hex_table[0x80 | ((c >> 12) & 0x3f)] + + hex_table[0x80 | ((c >> 6) & 0x3f)] + + hex_table[0x80 | (c & 0x3f)]; + } + + out += arr.join(''); + } + + return out; +}; + +export function compact(value: any) { + const queue = [{ obj: { o: value }, prop: 'o' }]; + const refs = []; + + for (let i = 0; i < queue.length; ++i) { + const item = queue[i]; + // @ts-ignore + const obj = item.obj[item.prop]; + + const keys = Object.keys(obj); + for (let j = 0; j < keys.length; ++j) { + const key = keys[j]!; + const val = obj[key]; + if (typeof val === 'object' && val !== null && refs.indexOf(val) === -1) { + queue.push({ obj: obj, prop: key }); + refs.push(val); + } + } + } + + compact_queue(queue); + + return value; +} + +export function is_regexp(obj: any) { + return Object.prototype.toString.call(obj) === '[object RegExp]'; +} + +export function is_buffer(obj: any) { + if (!obj || typeof obj !== 'object') { + return false; + } + + return !!(obj.constructor && obj.constructor.isBuffer && obj.constructor.isBuffer(obj)); +} + +export function combine(a: any, b: any) { + return [].concat(a, b); +} + +export function maybe_map(val: T[], fn: (v: T) => T) { + if (isArray(val)) { + const mapped = []; + for (let i = 0; i < val.length; i += 1) { + mapped.push(fn(val[i]!)); + } + return mapped; + } + return fn(val); +} diff --git a/src/internal/utils/query.ts b/src/internal/utils/query.ts index cbfd15e5..0139cacb 100644 --- a/src/internal/utils/query.ts +++ b/src/internal/utils/query.ts @@ -1,23 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { TogetherError } from '../../core/error'; +import * as qs from '../qs/stringify'; -/** - * Basic re-implementation of `qs.stringify` for primitive types. - */ export function stringifyQuery(query: object | Record) { - 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 TogetherError( - `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('&'); + return qs.stringify(query, { arrayFormat: 'comma' }); } diff --git a/src/resources/beta/clusters/clusters.ts b/src/resources/beta/clusters/clusters.ts index 6a4df773..ab25408a 100644 --- a/src/resources/beta/clusters/clusters.ts +++ b/src/resources/beta/clusters/clusters.ts @@ -1,6 +1,20 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { APIResource } from '../../../core/resource'; +import * as RemediationsAPI from './remediations'; +import { + RemediationApproveParams, + RemediationApproveResponse, + RemediationCancelParams, + RemediationCancelResponse, + RemediationCreateParams, + RemediationCreateResponse, + RemediationListParams, + RemediationListResponse, + RemediationRejectParams, + RemediationRejectResponse, + Remediations, +} from './remediations'; import * as StorageAPI from './storage'; import { ClusterStorage, @@ -16,6 +30,7 @@ import { RequestOptions } from '../../../internal/request-options'; import { path } from '../../../internal/utils/path'; export class Clusters extends APIResource { + remediations: RemediationsAPI.Remediations = new RemediationsAPI.Remediations(this._client); storage: StorageAPI.Storage = new StorageAPI.Storage(this._client); /** @@ -1186,6 +1201,7 @@ export interface ClusterListParams { project_id?: string; } +Clusters.Remediations = Remediations; Clusters.Storage = Storage; export declare namespace Clusters { @@ -1199,6 +1215,20 @@ export declare namespace Clusters { type ClusterListParams as ClusterListParams, }; + export { + Remediations as Remediations, + type RemediationCreateResponse as RemediationCreateResponse, + type RemediationListResponse as RemediationListResponse, + type RemediationApproveResponse as RemediationApproveResponse, + type RemediationCancelResponse as RemediationCancelResponse, + type RemediationRejectResponse as RemediationRejectResponse, + type RemediationCreateParams as RemediationCreateParams, + type RemediationListParams as RemediationListParams, + type RemediationApproveParams as RemediationApproveParams, + type RemediationCancelParams as RemediationCancelParams, + type RemediationRejectParams as RemediationRejectParams, + }; + export { Storage as Storage, type ClusterStorage as ClusterStorage, diff --git a/src/resources/beta/clusters/index.ts b/src/resources/beta/clusters/index.ts index 876826be..7fdd8798 100644 --- a/src/resources/beta/clusters/index.ts +++ b/src/resources/beta/clusters/index.ts @@ -10,6 +10,19 @@ export { type ClusterUpdateParams, type ClusterListParams, } from './clusters'; +export { + Remediations, + type RemediationCreateResponse, + type RemediationListResponse, + type RemediationApproveResponse, + type RemediationCancelResponse, + type RemediationRejectResponse, + type RemediationCreateParams, + type RemediationListParams, + type RemediationApproveParams, + type RemediationCancelParams, + type RemediationRejectParams, +} from './remediations'; export { Storage, type ClusterStorage, diff --git a/src/resources/beta/clusters/remediations.ts b/src/resources/beta/clusters/remediations.ts new file mode 100644 index 00000000..d548ed11 --- /dev/null +++ b/src/resources/beta/clusters/remediations.ts @@ -0,0 +1,828 @@ +// 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'; +import { path } from '../../../internal/utils/path'; + +export class Remediations extends APIResource { + /** + * Creates a new remediation for an instance. + * + * If mode is unspecified, it defaults to VM_ONLY. If trigger is unspecified, it + * defaults to MANUAL. + * + * For MANUAL triggers: The remediation goes directly to PENDING state. + * + * For AUTOMATED triggers: The remediation is created with PENDING_APPROVAL state. + * The caller must then use ApproveRemediation to start the remediation process. + */ + create( + instanceID: string, + params: RemediationCreateParams, + options?: RequestOptions, + ): APIPromise { + const { cluster_id, remediation_id, ...body } = params; + return this._client.post(path`/compute/clusters/${cluster_id}/instances/${instanceID}/remediations`, { + query: { remediation_id }, + body, + ...options, + }); + } + + /** + * Lists remediations for an instance or cluster. Use instances/- as wildcard to + * list all remediations in a cluster. + */ + list( + instanceID: string, + params: RemediationListParams, + options?: RequestOptions, + ): APIPromise { + const { cluster_id, ...query } = params; + return this._client.get(path`/compute/clusters/${cluster_id}/instances/${instanceID}/remediations`, { + query, + ...options, + }); + } + + /** + * Approves a pending remediation. + * + * Only remediations with state PENDING_APPROVAL can be approved. + * + * On APPROVE: state changes to PENDING and the remediation process begins. The + * reviewed_by, review_time, and review_comment fields are populated on the + * remediation after approval. + */ + approve( + remediationID: string, + params: RemediationApproveParams, + options?: RequestOptions, + ): APIPromise { + const { cluster_id, instance_id, ...body } = params; + return this._client.post( + path`/compute/clusters/${cluster_id}/instances/${instance_id}/remediations/${remediationID}/approve`, + { body, ...options }, + ); + } + + /** + * Cancels a pending remediation. + * + * Only remediations in PENDING_APPROVAL or PENDING state can be cancelled. + */ + cancel( + remediationID: string, + params: RemediationCancelParams, + options?: RequestOptions, + ): APIPromise { + const { cluster_id, instance_id } = params; + return this._client.post( + path`/compute/clusters/${cluster_id}/instances/${instance_id}/remediations/${remediationID}/cancel`, + options, + ); + } + + /** + * Rejects a pending remediation. + * + * Only remediations with state PENDING_APPROVAL can be rejected. + * + * On REJECT: state changes to CANCELLED. The reviewed_by, review_time, and + * review_comment fields are populated on the remediation after rejection. + */ + reject( + remediationID: string, + params: RemediationRejectParams, + options?: RequestOptions, + ): APIPromise { + const { cluster_id, instance_id, ...body } = params; + return this._client.post( + path`/compute/clusters/${cluster_id}/instances/${instance_id}/remediations/${remediationID}/reject`, + { body, ...options }, + ); + } +} + +/** + * Remediation represents a node remediation request for an instance. An instance + * can have multiple remediations over time (e.g., failed attempts followed by + * retries). + */ +export interface RemediationCreateResponse { + id: string; + + cluster_id: string; + + instance_id: string; + + /** + * Remediation mode specifies how the remediation should be performed. + * + * - `REMEDIATION_MODE_VM_ONLY`: Deletes the VM and provisions a new one on any + * available host. + * - `REMEDIATION_MODE_HOST_AWARE`: Cordons the host, deletes the VM, and + * provisions a new one on a different host. + */ + mode: + | 'REMEDIATION_MODE_VM_ONLY' + | 'REMEDIATION_MODE_HOST_AWARE' + | 'REMEDIATION_MODE_EVICT_WITHOUT_REPLACEMENT' + | 'REMEDIATION_MODE_REBOOT_VM'; + + /** + * RemediationState represents the lifecycle state of a remediation. + * + * - `PENDING_APPROVAL`: Awaiting approval before processing can begin. + * - `PENDING`: Approved and queued for processing. + * - `RUNNING`: Actively being processed. + * - `SUCCEEDED`: Successfully completed. + * - `FAILED`: Failed with an error. + * - `CANCELLED`: Cancelled by user or system. + * - `AUTO_RESOLVED`: The underlying issue was automatically resolved before + * processing. + */ + state: 'PENDING_APPROVAL' | 'PENDING' | 'RUNNING' | 'SUCCEEDED' | 'FAILED' | 'CANCELLED' | 'AUTO_RESOLVED'; + + /** + * RemediationTrigger specifies how the remediation was triggered. + * + * - `REMEDIATION_TRIGGER_MANUAL`: A user-initiated remediation (either via web UI + * or API call). + * - `REMEDIATION_TRIGGER_AUTOMATED`: A system-initiated remediation that requires + * approval. + */ + trigger: 'REMEDIATION_TRIGGER_MANUAL' | 'REMEDIATION_TRIGGER_AUTOMATED'; + + /** + * Active health check run ID (UUID) that triggered this remediation. + */ + active_health_check_run_id?: string; + + /** + * When the remediation was created. + */ + create_time?: string; + + /** + * When the remediation completed. + */ + end_time?: string; + + /** + * Error message if the remediation failed. + */ + error_message?: string; + + /** + * Passive health check event ID that triggered this remediation. + */ + passive_health_check_event_id?: string; + + /** + * User-provided reason for the remediation. + */ + reason?: string; + + /** + * Who requested the remediation. + */ + requested_by?: string; + + /** + * Review comment. + */ + review_comment?: string; + + /** + * When the remediation was reviewed. + */ + review_time?: string; + + /** + * Who reviewed the remediation. + */ + reviewed_by?: string; + + /** + * When processing started. + */ + start_time?: string; + + /** + * When the remediation was last updated. + */ + update_time?: string; +} + +/** + * ListRemediationsResponse is the response for ListRemediations. + */ +export interface RemediationListResponse { + /** + * Indicates if there are more results available. + */ + has_next: boolean; + + /** + * Token for the next page. + */ + next_page_token: string; + + /** + * The list of remediations. + */ + remediations: Array; +} + +export namespace RemediationListResponse { + /** + * Remediation represents a node remediation request for an instance. An instance + * can have multiple remediations over time (e.g., failed attempts followed by + * retries). + */ + export interface Remediation { + id: string; + + cluster_id: string; + + instance_id: string; + + /** + * Remediation mode specifies how the remediation should be performed. + * + * - `REMEDIATION_MODE_VM_ONLY`: Deletes the VM and provisions a new one on any + * available host. + * - `REMEDIATION_MODE_HOST_AWARE`: Cordons the host, deletes the VM, and + * provisions a new one on a different host. + */ + mode: + | 'REMEDIATION_MODE_VM_ONLY' + | 'REMEDIATION_MODE_HOST_AWARE' + | 'REMEDIATION_MODE_EVICT_WITHOUT_REPLACEMENT' + | 'REMEDIATION_MODE_REBOOT_VM'; + + /** + * RemediationState represents the lifecycle state of a remediation. + * + * - `PENDING_APPROVAL`: Awaiting approval before processing can begin. + * - `PENDING`: Approved and queued for processing. + * - `RUNNING`: Actively being processed. + * - `SUCCEEDED`: Successfully completed. + * - `FAILED`: Failed with an error. + * - `CANCELLED`: Cancelled by user or system. + * - `AUTO_RESOLVED`: The underlying issue was automatically resolved before + * processing. + */ + state: + | 'PENDING_APPROVAL' + | 'PENDING' + | 'RUNNING' + | 'SUCCEEDED' + | 'FAILED' + | 'CANCELLED' + | 'AUTO_RESOLVED'; + + /** + * RemediationTrigger specifies how the remediation was triggered. + * + * - `REMEDIATION_TRIGGER_MANUAL`: A user-initiated remediation (either via web UI + * or API call). + * - `REMEDIATION_TRIGGER_AUTOMATED`: A system-initiated remediation that requires + * approval. + */ + trigger: 'REMEDIATION_TRIGGER_MANUAL' | 'REMEDIATION_TRIGGER_AUTOMATED'; + + /** + * Active health check run ID (UUID) that triggered this remediation. + */ + active_health_check_run_id?: string; + + /** + * When the remediation was created. + */ + create_time?: string; + + /** + * When the remediation completed. + */ + end_time?: string; + + /** + * Error message if the remediation failed. + */ + error_message?: string; + + /** + * Passive health check event ID that triggered this remediation. + */ + passive_health_check_event_id?: string; + + /** + * User-provided reason for the remediation. + */ + reason?: string; + + /** + * Who requested the remediation. + */ + requested_by?: string; + + /** + * Review comment. + */ + review_comment?: string; + + /** + * When the remediation was reviewed. + */ + review_time?: string; + + /** + * Who reviewed the remediation. + */ + reviewed_by?: string; + + /** + * When processing started. + */ + start_time?: string; + + /** + * When the remediation was last updated. + */ + update_time?: string; + } +} + +/** + * Remediation represents a node remediation request for an instance. An instance + * can have multiple remediations over time (e.g., failed attempts followed by + * retries). + */ +export interface RemediationApproveResponse { + id: string; + + cluster_id: string; + + instance_id: string; + + /** + * Remediation mode specifies how the remediation should be performed. + * + * - `REMEDIATION_MODE_VM_ONLY`: Deletes the VM and provisions a new one on any + * available host. + * - `REMEDIATION_MODE_HOST_AWARE`: Cordons the host, deletes the VM, and + * provisions a new one on a different host. + */ + mode: + | 'REMEDIATION_MODE_VM_ONLY' + | 'REMEDIATION_MODE_HOST_AWARE' + | 'REMEDIATION_MODE_EVICT_WITHOUT_REPLACEMENT' + | 'REMEDIATION_MODE_REBOOT_VM'; + + /** + * RemediationState represents the lifecycle state of a remediation. + * + * - `PENDING_APPROVAL`: Awaiting approval before processing can begin. + * - `PENDING`: Approved and queued for processing. + * - `RUNNING`: Actively being processed. + * - `SUCCEEDED`: Successfully completed. + * - `FAILED`: Failed with an error. + * - `CANCELLED`: Cancelled by user or system. + * - `AUTO_RESOLVED`: The underlying issue was automatically resolved before + * processing. + */ + state: 'PENDING_APPROVAL' | 'PENDING' | 'RUNNING' | 'SUCCEEDED' | 'FAILED' | 'CANCELLED' | 'AUTO_RESOLVED'; + + /** + * RemediationTrigger specifies how the remediation was triggered. + * + * - `REMEDIATION_TRIGGER_MANUAL`: A user-initiated remediation (either via web UI + * or API call). + * - `REMEDIATION_TRIGGER_AUTOMATED`: A system-initiated remediation that requires + * approval. + */ + trigger: 'REMEDIATION_TRIGGER_MANUAL' | 'REMEDIATION_TRIGGER_AUTOMATED'; + + /** + * Active health check run ID (UUID) that triggered this remediation. + */ + active_health_check_run_id?: string; + + /** + * When the remediation was created. + */ + create_time?: string; + + /** + * When the remediation completed. + */ + end_time?: string; + + /** + * Error message if the remediation failed. + */ + error_message?: string; + + /** + * Passive health check event ID that triggered this remediation. + */ + passive_health_check_event_id?: string; + + /** + * User-provided reason for the remediation. + */ + reason?: string; + + /** + * Who requested the remediation. + */ + requested_by?: string; + + /** + * Review comment. + */ + review_comment?: string; + + /** + * When the remediation was reviewed. + */ + review_time?: string; + + /** + * Who reviewed the remediation. + */ + reviewed_by?: string; + + /** + * When processing started. + */ + start_time?: string; + + /** + * When the remediation was last updated. + */ + update_time?: string; +} + +/** + * Remediation represents a node remediation request for an instance. An instance + * can have multiple remediations over time (e.g., failed attempts followed by + * retries). + */ +export interface RemediationCancelResponse { + id: string; + + cluster_id: string; + + instance_id: string; + + /** + * Remediation mode specifies how the remediation should be performed. + * + * - `REMEDIATION_MODE_VM_ONLY`: Deletes the VM and provisions a new one on any + * available host. + * - `REMEDIATION_MODE_HOST_AWARE`: Cordons the host, deletes the VM, and + * provisions a new one on a different host. + */ + mode: + | 'REMEDIATION_MODE_VM_ONLY' + | 'REMEDIATION_MODE_HOST_AWARE' + | 'REMEDIATION_MODE_EVICT_WITHOUT_REPLACEMENT' + | 'REMEDIATION_MODE_REBOOT_VM'; + + /** + * RemediationState represents the lifecycle state of a remediation. + * + * - `PENDING_APPROVAL`: Awaiting approval before processing can begin. + * - `PENDING`: Approved and queued for processing. + * - `RUNNING`: Actively being processed. + * - `SUCCEEDED`: Successfully completed. + * - `FAILED`: Failed with an error. + * - `CANCELLED`: Cancelled by user or system. + * - `AUTO_RESOLVED`: The underlying issue was automatically resolved before + * processing. + */ + state: 'PENDING_APPROVAL' | 'PENDING' | 'RUNNING' | 'SUCCEEDED' | 'FAILED' | 'CANCELLED' | 'AUTO_RESOLVED'; + + /** + * RemediationTrigger specifies how the remediation was triggered. + * + * - `REMEDIATION_TRIGGER_MANUAL`: A user-initiated remediation (either via web UI + * or API call). + * - `REMEDIATION_TRIGGER_AUTOMATED`: A system-initiated remediation that requires + * approval. + */ + trigger: 'REMEDIATION_TRIGGER_MANUAL' | 'REMEDIATION_TRIGGER_AUTOMATED'; + + /** + * Active health check run ID (UUID) that triggered this remediation. + */ + active_health_check_run_id?: string; + + /** + * When the remediation was created. + */ + create_time?: string; + + /** + * When the remediation completed. + */ + end_time?: string; + + /** + * Error message if the remediation failed. + */ + error_message?: string; + + /** + * Passive health check event ID that triggered this remediation. + */ + passive_health_check_event_id?: string; + + /** + * User-provided reason for the remediation. + */ + reason?: string; + + /** + * Who requested the remediation. + */ + requested_by?: string; + + /** + * Review comment. + */ + review_comment?: string; + + /** + * When the remediation was reviewed. + */ + review_time?: string; + + /** + * Who reviewed the remediation. + */ + reviewed_by?: string; + + /** + * When processing started. + */ + start_time?: string; + + /** + * When the remediation was last updated. + */ + update_time?: string; +} + +/** + * Remediation represents a node remediation request for an instance. An instance + * can have multiple remediations over time (e.g., failed attempts followed by + * retries). + */ +export interface RemediationRejectResponse { + id: string; + + cluster_id: string; + + instance_id: string; + + /** + * Remediation mode specifies how the remediation should be performed. + * + * - `REMEDIATION_MODE_VM_ONLY`: Deletes the VM and provisions a new one on any + * available host. + * - `REMEDIATION_MODE_HOST_AWARE`: Cordons the host, deletes the VM, and + * provisions a new one on a different host. + */ + mode: + | 'REMEDIATION_MODE_VM_ONLY' + | 'REMEDIATION_MODE_HOST_AWARE' + | 'REMEDIATION_MODE_EVICT_WITHOUT_REPLACEMENT' + | 'REMEDIATION_MODE_REBOOT_VM'; + + /** + * RemediationState represents the lifecycle state of a remediation. + * + * - `PENDING_APPROVAL`: Awaiting approval before processing can begin. + * - `PENDING`: Approved and queued for processing. + * - `RUNNING`: Actively being processed. + * - `SUCCEEDED`: Successfully completed. + * - `FAILED`: Failed with an error. + * - `CANCELLED`: Cancelled by user or system. + * - `AUTO_RESOLVED`: The underlying issue was automatically resolved before + * processing. + */ + state: 'PENDING_APPROVAL' | 'PENDING' | 'RUNNING' | 'SUCCEEDED' | 'FAILED' | 'CANCELLED' | 'AUTO_RESOLVED'; + + /** + * RemediationTrigger specifies how the remediation was triggered. + * + * - `REMEDIATION_TRIGGER_MANUAL`: A user-initiated remediation (either via web UI + * or API call). + * - `REMEDIATION_TRIGGER_AUTOMATED`: A system-initiated remediation that requires + * approval. + */ + trigger: 'REMEDIATION_TRIGGER_MANUAL' | 'REMEDIATION_TRIGGER_AUTOMATED'; + + /** + * Active health check run ID (UUID) that triggered this remediation. + */ + active_health_check_run_id?: string; + + /** + * When the remediation was created. + */ + create_time?: string; + + /** + * When the remediation completed. + */ + end_time?: string; + + /** + * Error message if the remediation failed. + */ + error_message?: string; + + /** + * Passive health check event ID that triggered this remediation. + */ + passive_health_check_event_id?: string; + + /** + * User-provided reason for the remediation. + */ + reason?: string; + + /** + * Who requested the remediation. + */ + requested_by?: string; + + /** + * Review comment. + */ + review_comment?: string; + + /** + * When the remediation was reviewed. + */ + review_time?: string; + + /** + * Who reviewed the remediation. + */ + reviewed_by?: string; + + /** + * When processing started. + */ + start_time?: string; + + /** + * When the remediation was last updated. + */ + update_time?: string; +} + +export interface RemediationCreateParams { + /** + * Path param: The cluster ID. + */ + cluster_id: string; + + /** + * Body param: Remediation mode specifies how the remediation should be performed. + * + * - `REMEDIATION_MODE_VM_ONLY`: Deletes the VM and provisions a new one on any + * available host. + * - `REMEDIATION_MODE_HOST_AWARE`: Cordons the host, deletes the VM, and + * provisions a new one on a different host. + */ + mode: + | 'REMEDIATION_MODE_VM_ONLY' + | 'REMEDIATION_MODE_HOST_AWARE' + | 'REMEDIATION_MODE_EVICT_WITHOUT_REPLACEMENT' + | 'REMEDIATION_MODE_REBOOT_VM'; + + /** + * Query param: Optional. Client-specified ID for idempotency. + */ + remediation_id?: string; + + /** + * Body param: User-provided reason for the remediation. + */ + reason?: string; +} + +export interface RemediationListParams { + /** + * Path param: The cluster ID. + */ + cluster_id: string; + + /** + * Query param: Optional. Filter by remediation mode. Returns only remediations + * matching the specified mode. + */ + mode?: + | 'REMEDIATION_MODE_VM_ONLY' + | 'REMEDIATION_MODE_HOST_AWARE' + | 'REMEDIATION_MODE_EVICT_WITHOUT_REPLACEMENT' + | 'REMEDIATION_MODE_REBOOT_VM'; + + /** + * Query param: Optional. Order by expression. + */ + order_by?: string; + + /** + * Query param: Optional. Maximum results to return. + */ + page_size?: number; + + /** + * Query param: Optional. Pagination token from previous request. + */ + page_token?: string; + + /** + * Query param: Optional. Filter by state(s). Returns remediations matching any of + * the specified states. + */ + state?: Array< + 'PENDING_APPROVAL' | 'PENDING' | 'RUNNING' | 'SUCCEEDED' | 'FAILED' | 'CANCELLED' | 'AUTO_RESOLVED' + >; + + /** + * Query param: Optional. Filter by trigger type. Returns only remediations + * matching the specified trigger. + */ + trigger?: 'REMEDIATION_TRIGGER_MANUAL' | 'REMEDIATION_TRIGGER_AUTOMATED'; +} + +export interface RemediationApproveParams { + /** + * Path param: The cluster ID. + */ + cluster_id: string; + + /** + * Path param: The instance ID. + */ + instance_id: string; + + /** + * Body param: Comment explaining the action. + */ + comment?: string; +} + +export interface RemediationCancelParams { + /** + * The cluster ID. + */ + cluster_id: string; + + /** + * The instance ID. + */ + instance_id: string; +} + +export interface RemediationRejectParams { + /** + * Path param: The cluster ID. + */ + cluster_id: string; + + /** + * Path param: The instance ID. + */ + instance_id: string; + + /** + * Body param: Comment explaining the action. + */ + comment?: string; +} + +export declare namespace Remediations { + export { + type RemediationCreateResponse as RemediationCreateResponse, + type RemediationListResponse as RemediationListResponse, + type RemediationApproveResponse as RemediationApproveResponse, + type RemediationCancelResponse as RemediationCancelResponse, + type RemediationRejectResponse as RemediationRejectResponse, + type RemediationCreateParams as RemediationCreateParams, + type RemediationListParams as RemediationListParams, + type RemediationApproveParams as RemediationApproveParams, + type RemediationCancelParams as RemediationCancelParams, + type RemediationRejectParams as RemediationRejectParams, + }; +} diff --git a/tests/api-resources/beta/clusters/remediations.test.ts b/tests/api-resources/beta/clusters/remediations.test.ts new file mode 100644 index 00000000..afba913e --- /dev/null +++ b/tests/api-resources/beta/clusters/remediations.test.ts @@ -0,0 +1,123 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import Together from 'together-ai'; + +const client = new Together({ + apiKey: 'My API Key', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource remediations', () => { + test('create: only required params', async () => { + const responsePromise = client.beta.clusters.remediations.create('instance_id', { + cluster_id: 'cluster_id', + mode: 'REMEDIATION_MODE_VM_ONLY', + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('create: required and optional params', async () => { + const response = await client.beta.clusters.remediations.create('instance_id', { + cluster_id: 'cluster_id', + mode: 'REMEDIATION_MODE_VM_ONLY', + remediation_id: 'remediation_id', + reason: 'reason', + }); + }); + + test('list: only required params', async () => { + const responsePromise = client.beta.clusters.remediations.list('instance_id', { + cluster_id: 'cluster_id', + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('list: required and optional params', async () => { + const response = await client.beta.clusters.remediations.list('instance_id', { + cluster_id: 'cluster_id', + mode: 'REMEDIATION_MODE_VM_ONLY', + order_by: 'order_by', + page_size: 0, + page_token: 'page_token', + state: ['PENDING_APPROVAL'], + trigger: 'REMEDIATION_TRIGGER_MANUAL', + }); + }); + + test('approve: only required params', async () => { + const responsePromise = client.beta.clusters.remediations.approve('remediation_id', { + cluster_id: 'cluster_id', + instance_id: 'instance_id', + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('approve: required and optional params', async () => { + const response = await client.beta.clusters.remediations.approve('remediation_id', { + cluster_id: 'cluster_id', + instance_id: 'instance_id', + comment: 'comment', + }); + }); + + test('cancel: only required params', async () => { + const responsePromise = client.beta.clusters.remediations.cancel('remediation_id', { + cluster_id: 'cluster_id', + instance_id: 'instance_id', + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('cancel: required and optional params', async () => { + const response = await client.beta.clusters.remediations.cancel('remediation_id', { + cluster_id: 'cluster_id', + instance_id: 'instance_id', + }); + }); + + test('reject: only required params', async () => { + const responsePromise = client.beta.clusters.remediations.reject('remediation_id', { + cluster_id: 'cluster_id', + instance_id: 'instance_id', + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('reject: required and optional params', async () => { + const response = await client.beta.clusters.remediations.reject('remediation_id', { + cluster_id: 'cluster_id', + instance_id: 'instance_id', + comment: 'comment', + }); + }); +}); diff --git a/tests/qs/empty-keys-cases.ts b/tests/qs/empty-keys-cases.ts new file mode 100644 index 00000000..ea2c1b0a --- /dev/null +++ b/tests/qs/empty-keys-cases.ts @@ -0,0 +1,271 @@ +export const empty_test_cases = [ + { + input: '&', + with_empty_keys: {}, + stringify_output: { + brackets: '', + indices: '', + repeat: '', + }, + no_empty_keys: {}, + }, + { + input: '&&', + with_empty_keys: {}, + stringify_output: { + brackets: '', + indices: '', + repeat: '', + }, + no_empty_keys: {}, + }, + { + input: '&=', + with_empty_keys: { '': '' }, + stringify_output: { + brackets: '=', + indices: '=', + repeat: '=', + }, + no_empty_keys: {}, + }, + { + input: '&=&', + with_empty_keys: { '': '' }, + stringify_output: { + brackets: '=', + indices: '=', + repeat: '=', + }, + no_empty_keys: {}, + }, + { + input: '&=&=', + with_empty_keys: { '': ['', ''] }, + stringify_output: { + brackets: '[]=&[]=', + indices: '[0]=&[1]=', + repeat: '=&=', + }, + no_empty_keys: {}, + }, + { + input: '&=&=&', + with_empty_keys: { '': ['', ''] }, + stringify_output: { + brackets: '[]=&[]=', + indices: '[0]=&[1]=', + repeat: '=&=', + }, + no_empty_keys: {}, + }, + { + input: '=', + with_empty_keys: { '': '' }, + no_empty_keys: {}, + stringify_output: { + brackets: '=', + indices: '=', + repeat: '=', + }, + }, + { + input: '=&', + with_empty_keys: { '': '' }, + stringify_output: { + brackets: '=', + indices: '=', + repeat: '=', + }, + no_empty_keys: {}, + }, + { + input: '=&&&', + with_empty_keys: { '': '' }, + stringify_output: { + brackets: '=', + indices: '=', + repeat: '=', + }, + no_empty_keys: {}, + }, + { + input: '=&=&=&', + with_empty_keys: { '': ['', '', ''] }, + stringify_output: { + brackets: '[]=&[]=&[]=', + indices: '[0]=&[1]=&[2]=', + repeat: '=&=&=', + }, + no_empty_keys: {}, + }, + { + input: '=&a[]=b&a[1]=c', + with_empty_keys: { '': '', a: ['b', 'c'] }, + stringify_output: { + brackets: '=&a[]=b&a[]=c', + indices: '=&a[0]=b&a[1]=c', + repeat: '=&a=b&a=c', + }, + no_empty_keys: { a: ['b', 'c'] }, + }, + { + input: '=a', + with_empty_keys: { '': 'a' }, + no_empty_keys: {}, + stringify_output: { + brackets: '=a', + indices: '=a', + repeat: '=a', + }, + }, + { + input: 'a==a', + with_empty_keys: { a: '=a' }, + no_empty_keys: { a: '=a' }, + stringify_output: { + brackets: 'a==a', + indices: 'a==a', + repeat: 'a==a', + }, + }, + { + input: '=&a[]=b', + with_empty_keys: { '': '', a: ['b'] }, + stringify_output: { + brackets: '=&a[]=b', + indices: '=&a[0]=b', + repeat: '=&a=b', + }, + no_empty_keys: { a: ['b'] }, + }, + { + input: '=&a[]=b&a[]=c&a[2]=d', + with_empty_keys: { '': '', a: ['b', 'c', 'd'] }, + stringify_output: { + brackets: '=&a[]=b&a[]=c&a[]=d', + indices: '=&a[0]=b&a[1]=c&a[2]=d', + repeat: '=&a=b&a=c&a=d', + }, + no_empty_keys: { a: ['b', 'c', 'd'] }, + }, + { + input: '=a&=b', + with_empty_keys: { '': ['a', 'b'] }, + stringify_output: { + brackets: '[]=a&[]=b', + indices: '[0]=a&[1]=b', + repeat: '=a&=b', + }, + no_empty_keys: {}, + }, + { + input: '=a&foo=b', + with_empty_keys: { '': 'a', foo: 'b' }, + no_empty_keys: { foo: 'b' }, + stringify_output: { + brackets: '=a&foo=b', + indices: '=a&foo=b', + repeat: '=a&foo=b', + }, + }, + { + input: 'a[]=b&a=c&=', + with_empty_keys: { '': '', a: ['b', 'c'] }, + stringify_output: { + brackets: '=&a[]=b&a[]=c', + indices: '=&a[0]=b&a[1]=c', + repeat: '=&a=b&a=c', + }, + no_empty_keys: { a: ['b', 'c'] }, + }, + { + input: 'a[]=b&a=c&=', + with_empty_keys: { '': '', a: ['b', 'c'] }, + stringify_output: { + brackets: '=&a[]=b&a[]=c', + indices: '=&a[0]=b&a[1]=c', + repeat: '=&a=b&a=c', + }, + no_empty_keys: { a: ['b', 'c'] }, + }, + { + input: 'a[0]=b&a=c&=', + with_empty_keys: { '': '', a: ['b', 'c'] }, + stringify_output: { + brackets: '=&a[]=b&a[]=c', + indices: '=&a[0]=b&a[1]=c', + repeat: '=&a=b&a=c', + }, + no_empty_keys: { a: ['b', 'c'] }, + }, + { + input: 'a=b&a[]=c&=', + with_empty_keys: { '': '', a: ['b', 'c'] }, + stringify_output: { + brackets: '=&a[]=b&a[]=c', + indices: '=&a[0]=b&a[1]=c', + repeat: '=&a=b&a=c', + }, + no_empty_keys: { a: ['b', 'c'] }, + }, + { + input: 'a=b&a[0]=c&=', + with_empty_keys: { '': '', a: ['b', 'c'] }, + stringify_output: { + brackets: '=&a[]=b&a[]=c', + indices: '=&a[0]=b&a[1]=c', + repeat: '=&a=b&a=c', + }, + no_empty_keys: { a: ['b', 'c'] }, + }, + { + input: '[]=a&[]=b& []=1', + with_empty_keys: { '': ['a', 'b'], ' ': ['1'] }, + stringify_output: { + brackets: '[]=a&[]=b& []=1', + indices: '[0]=a&[1]=b& [0]=1', + repeat: '=a&=b& =1', + }, + no_empty_keys: { 0: 'a', 1: 'b', ' ': ['1'] }, + }, + { + input: '[0]=a&[1]=b&a[0]=1&a[1]=2', + with_empty_keys: { '': ['a', 'b'], a: ['1', '2'] }, + no_empty_keys: { 0: 'a', 1: 'b', a: ['1', '2'] }, + stringify_output: { + brackets: '[]=a&[]=b&a[]=1&a[]=2', + indices: '[0]=a&[1]=b&a[0]=1&a[1]=2', + repeat: '=a&=b&a=1&a=2', + }, + }, + { + input: '[deep]=a&[deep]=2', + with_empty_keys: { '': { deep: ['a', '2'] } }, + stringify_output: { + brackets: '[deep][]=a&[deep][]=2', + indices: '[deep][0]=a&[deep][1]=2', + repeat: '[deep]=a&[deep]=2', + }, + no_empty_keys: { deep: ['a', '2'] }, + }, + { + input: '%5B0%5D=a&%5B1%5D=b', + with_empty_keys: { '': ['a', 'b'] }, + stringify_output: { + brackets: '[]=a&[]=b', + indices: '[0]=a&[1]=b', + repeat: '=a&=b', + }, + no_empty_keys: { 0: 'a', 1: 'b' }, + }, +] satisfies { + input: string; + with_empty_keys: Record; + stringify_output: { + brackets: string; + indices: string; + repeat: string; + }; + no_empty_keys: Record; +}[]; diff --git a/tests/qs/stringify.test.ts b/tests/qs/stringify.test.ts new file mode 100644 index 00000000..b2f937e3 --- /dev/null +++ b/tests/qs/stringify.test.ts @@ -0,0 +1,2232 @@ +import iconv from 'iconv-lite'; +import { stringify } from 'together-ai/internal/qs'; +import { encode } from 'together-ai/internal/qs/utils'; +import { StringifyOptions } from 'together-ai/internal/qs/types'; +import { empty_test_cases } from './empty-keys-cases'; +import assert from 'assert'; + +describe('stringify()', function () { + test('stringifies a querystring object', function () { + expect(stringify({ a: 'b' })).toBe('a=b'); + expect(stringify({ a: 1 })).toBe('a=1'); + expect(stringify({ a: 1, b: 2 })).toBe('a=1&b=2'); + expect(stringify({ a: 'A_Z' })).toBe('a=A_Z'); + expect(stringify({ a: '€' })).toBe('a=%E2%82%AC'); + expect(stringify({ a: '' })).toBe('a=%EE%80%80'); + expect(stringify({ a: 'א' })).toBe('a=%D7%90'); + expect(stringify({ a: '𐐷' })).toBe('a=%F0%90%90%B7'); + }); + + test('stringifies falsy values', function () { + expect(stringify(undefined)).toBe(''); + expect(stringify(null)).toBe(''); + expect(stringify(null, { strictNullHandling: true })).toBe(''); + expect(stringify(false)).toBe(''); + expect(stringify(0)).toBe(''); + }); + + test('stringifies symbols', function () { + expect(stringify(Symbol.iterator)).toBe(''); + expect(stringify([Symbol.iterator])).toBe('0=Symbol%28Symbol.iterator%29'); + expect(stringify({ a: Symbol.iterator })).toBe('a=Symbol%28Symbol.iterator%29'); + expect(stringify({ a: [Symbol.iterator] }, { encodeValuesOnly: true, arrayFormat: 'brackets' })).toBe( + 'a[]=Symbol%28Symbol.iterator%29', + ); + }); + + test('stringifies bigints', function () { + var three = BigInt(3); + // @ts-expect-error + var encodeWithN = function (value, defaultEncoder, charset) { + var result = defaultEncoder(value, defaultEncoder, charset); + return typeof value === 'bigint' ? result + 'n' : result; + }; + + expect(stringify(three)).toBe(''); + expect(stringify([three])).toBe('0=3'); + expect(stringify([three], { encoder: encodeWithN })).toBe('0=3n'); + expect(stringify({ a: three })).toBe('a=3'); + expect(stringify({ a: three }, { encoder: encodeWithN })).toBe('a=3n'); + expect(stringify({ a: [three] }, { encodeValuesOnly: true, arrayFormat: 'brackets' })).toBe('a[]=3'); + expect( + stringify({ a: [three] }, { encodeValuesOnly: true, encoder: encodeWithN, arrayFormat: 'brackets' }), + ).toBe('a[]=3n'); + }); + + test('encodes dot in key of object when encodeDotInKeys and allowDots is provided', function () { + expect( + stringify({ 'name.obj': { first: 'John', last: 'Doe' } }, { allowDots: false, encodeDotInKeys: false }), + ).toBe('name.obj%5Bfirst%5D=John&name.obj%5Blast%5D=Doe'); + expect( + stringify({ 'name.obj': { first: 'John', last: 'Doe' } }, { allowDots: true, encodeDotInKeys: false }), + ).toBe('name.obj.first=John&name.obj.last=Doe'); + expect( + stringify({ 'name.obj': { first: 'John', last: 'Doe' } }, { allowDots: false, encodeDotInKeys: true }), + ).toBe('name%252Eobj%5Bfirst%5D=John&name%252Eobj%5Blast%5D=Doe'); + expect( + stringify({ 'name.obj': { first: 'John', last: 'Doe' } }, { allowDots: true, encodeDotInKeys: true }), + ).toBe('name%252Eobj.first=John&name%252Eobj.last=Doe'); + + // st.equal( + // stringify( + // { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, + // { allowDots: false, encodeDotInKeys: false }, + // ), + // 'name.obj.subobject%5Bfirst.godly.name%5D=John&name.obj.subobject%5Blast%5D=Doe', + // 'with allowDots false and encodeDotInKeys false', + // ); + // st.equal( + // stringify( + // { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, + // { allowDots: true, encodeDotInKeys: false }, + // ), + // 'name.obj.subobject.first.godly.name=John&name.obj.subobject.last=Doe', + // 'with allowDots false and encodeDotInKeys false', + // ); + // st.equal( + // stringify( + // { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, + // { allowDots: false, encodeDotInKeys: true }, + // ), + // 'name%252Eobj%252Esubobject%5Bfirst.godly.name%5D=John&name%252Eobj%252Esubobject%5Blast%5D=Doe', + // 'with allowDots false and encodeDotInKeys true', + // ); + // st.equal( + // stringify( + // { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, + // { allowDots: true, encodeDotInKeys: true }, + // ), + // 'name%252Eobj%252Esubobject.first%252Egodly%252Ename=John&name%252Eobj%252Esubobject.last=Doe', + // 'with allowDots true and encodeDotInKeys true', + // ); + expect( + stringify( + { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, + { allowDots: false, encodeDotInKeys: false }, + ), + ).toBe('name.obj.subobject%5Bfirst.godly.name%5D=John&name.obj.subobject%5Blast%5D=Doe'); + expect( + stringify( + { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, + { allowDots: true, encodeDotInKeys: false }, + ), + ).toBe('name.obj.subobject.first.godly.name=John&name.obj.subobject.last=Doe'); + expect( + stringify( + { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, + { allowDots: false, encodeDotInKeys: true }, + ), + ).toBe('name%252Eobj%252Esubobject%5Bfirst.godly.name%5D=John&name%252Eobj%252Esubobject%5Blast%5D=Doe'); + expect( + stringify( + { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, + { allowDots: true, encodeDotInKeys: true }, + ), + ).toBe('name%252Eobj%252Esubobject.first%252Egodly%252Ename=John&name%252Eobj%252Esubobject.last=Doe'); + }); + + test('should encode dot in key of object, and automatically set allowDots to `true` when encodeDotInKeys is true and allowDots in undefined', function () { + // st.equal( + // stringify( + // { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, + // { encodeDotInKeys: true }, + // ), + // 'name%252Eobj%252Esubobject.first%252Egodly%252Ename=John&name%252Eobj%252Esubobject.last=Doe', + // 'with allowDots undefined and encodeDotInKeys true', + // ); + expect( + stringify( + { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, + { encodeDotInKeys: true }, + ), + ).toBe('name%252Eobj%252Esubobject.first%252Egodly%252Ename=John&name%252Eobj%252Esubobject.last=Doe'); + }); + + test('should encode dot in key of object when encodeDotInKeys and allowDots is provided, and nothing else when encodeValuesOnly is provided', function () { + // st.equal( + // stringify( + // { 'name.obj': { first: 'John', last: 'Doe' } }, + // { + // encodeDotInKeys: true, + // allowDots: true, + // encodeValuesOnly: true, + // }, + // ), + // 'name%2Eobj.first=John&name%2Eobj.last=Doe', + // ); + expect( + stringify( + { 'name.obj': { first: 'John', last: 'Doe' } }, + { + encodeDotInKeys: true, + allowDots: true, + encodeValuesOnly: true, + }, + ), + ).toBe('name%2Eobj.first=John&name%2Eobj.last=Doe'); + + // st.equal( + // stringify( + // { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, + // { allowDots: true, encodeDotInKeys: true, encodeValuesOnly: true }, + // ), + // 'name%2Eobj%2Esubobject.first%2Egodly%2Ename=John&name%2Eobj%2Esubobject.last=Doe', + // ); + expect( + stringify( + { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, + { allowDots: true, encodeDotInKeys: true, encodeValuesOnly: true }, + ), + ).toBe('name%2Eobj%2Esubobject.first%2Egodly%2Ename=John&name%2Eobj%2Esubobject.last=Doe'); + }); + + test('throws when `commaRoundTrip` is not a boolean', function () { + // st['throws']( + // function () { + // stringify({}, { commaRoundTrip: 'not a boolean' }); + // }, + // TypeError, + // 'throws when `commaRoundTrip` is not a boolean', + // ); + expect(() => { + // @ts-expect-error + stringify({}, { commaRoundTrip: 'not a boolean' }); + }).toThrow(TypeError); + }); + + test('throws when `encodeDotInKeys` is not a boolean', function () { + // st['throws'](function () { + // stringify({ a: [], b: 'zz' }, { encodeDotInKeys: 'foobar' }); + // }, TypeError); + expect(() => { + // @ts-expect-error + stringify({ a: [], b: 'zz' }, { encodeDotInKeys: 'foobar' }); + }).toThrow(TypeError); + + // st['throws'](function () { + // stringify({ a: [], b: 'zz' }, { encodeDotInKeys: 0 }); + // }, TypeError); + expect(() => { + // @ts-expect-error + stringify({ a: [], b: 'zz' }, { encodeDotInKeys: 0 }); + }).toThrow(TypeError); + + // st['throws'](function () { + // stringify({ a: [], b: 'zz' }, { encodeDotInKeys: NaN }); + // }, TypeError); + expect(() => { + // @ts-expect-error + stringify({ a: [], b: 'zz' }, { encodeDotInKeys: NaN }); + }).toThrow(TypeError); + + // st['throws'](function () { + // stringify({ a: [], b: 'zz' }, { encodeDotInKeys: null }); + // }, TypeError); + expect(() => { + // @ts-expect-error + stringify({ a: [], b: 'zz' }, { encodeDotInKeys: null }); + }).toThrow(TypeError); + }); + + test('adds query prefix', function () { + // st.equal(stringify({ a: 'b' }, { addQueryPrefix: true }), '?a=b'); + expect(stringify({ a: 'b' }, { addQueryPrefix: true })).toBe('?a=b'); + }); + + test('with query prefix, outputs blank string given an empty object', function () { + // st.equal(stringify({}, { addQueryPrefix: true }), ''); + expect(stringify({}, { addQueryPrefix: true })).toBe(''); + }); + + test('stringifies nested falsy values', function () { + // st.equal(stringify({ a: { b: { c: null } } }), 'a%5Bb%5D%5Bc%5D='); + // st.equal( + // stringify({ a: { b: { c: null } } }, { strictNullHandling: true }), + // 'a%5Bb%5D%5Bc%5D', + // ); + // st.equal(stringify({ a: { b: { c: false } } }), 'a%5Bb%5D%5Bc%5D=false'); + expect(stringify({ a: { b: { c: null } } })).toBe('a%5Bb%5D%5Bc%5D='); + expect(stringify({ a: { b: { c: null } } }, { strictNullHandling: true })).toBe('a%5Bb%5D%5Bc%5D'); + expect(stringify({ a: { b: { c: false } } })).toBe('a%5Bb%5D%5Bc%5D=false'); + }); + + test('stringifies a nested object', function () { + // st.equal(stringify({ a: { b: 'c' } }), 'a%5Bb%5D=c'); + // st.equal(stringify({ a: { b: { c: { d: 'e' } } } }), 'a%5Bb%5D%5Bc%5D%5Bd%5D=e'); + expect(stringify({ a: { b: 'c' } })).toBe('a%5Bb%5D=c'); + expect(stringify({ a: { b: { c: { d: 'e' } } } })).toBe('a%5Bb%5D%5Bc%5D%5Bd%5D=e'); + }); + + test('`allowDots` option: stringifies a nested object with dots notation', function () { + // st.equal(stringify({ a: { b: 'c' } }, { allowDots: true }), 'a.b=c'); + // st.equal(stringify({ a: { b: { c: { d: 'e' } } } }, { allowDots: true }), 'a.b.c.d=e'); + expect(stringify({ a: { b: 'c' } }, { allowDots: true })).toBe('a.b=c'); + expect(stringify({ a: { b: { c: { d: 'e' } } } }, { allowDots: true })).toBe('a.b.c.d=e'); + }); + + test('stringifies an array value', function () { + // st.equal( + // stringify({ a: ['b', 'c', 'd'] }, { arrayFormat: 'indices' }), + // 'a%5B0%5D=b&a%5B1%5D=c&a%5B2%5D=d', + // 'indices => indices', + // ); + // st.equal( + // stringify({ a: ['b', 'c', 'd'] }, { arrayFormat: 'brackets' }), + // 'a%5B%5D=b&a%5B%5D=c&a%5B%5D=d', + // 'brackets => brackets', + // ); + // st.equal( + // stringify({ a: ['b', 'c', 'd'] }, { arrayFormat: 'comma' }), + // 'a=b%2Cc%2Cd', + // 'comma => comma', + // ); + // st.equal( + // stringify({ a: ['b', 'c', 'd'] }, { arrayFormat: 'comma', commaRoundTrip: true }), + // 'a=b%2Cc%2Cd', + // 'comma round trip => comma', + // ); + // st.equal( + // stringify({ a: ['b', 'c', 'd'] }), + // 'a%5B0%5D=b&a%5B1%5D=c&a%5B2%5D=d', + // 'default => indices', + // ); + expect(stringify({ a: ['b', 'c', 'd'] }, { arrayFormat: 'indices' })).toBe( + 'a%5B0%5D=b&a%5B1%5D=c&a%5B2%5D=d', + ); + expect(stringify({ a: ['b', 'c', 'd'] }, { arrayFormat: 'brackets' })).toBe( + 'a%5B%5D=b&a%5B%5D=c&a%5B%5D=d', + ); + expect(stringify({ a: ['b', 'c', 'd'] }, { arrayFormat: 'comma' })).toBe('a=b%2Cc%2Cd'); + expect(stringify({ a: ['b', 'c', 'd'] }, { arrayFormat: 'comma', commaRoundTrip: true })).toBe( + 'a=b%2Cc%2Cd', + ); + expect(stringify({ a: ['b', 'c', 'd'] })).toBe('a%5B0%5D=b&a%5B1%5D=c&a%5B2%5D=d'); + }); + + test('`skipNulls` option', function () { + // st.equal( + // stringify({ a: 'b', c: null }, { skipNulls: true }), + // 'a=b', + // 'omits nulls when asked', + // ); + expect(stringify({ a: 'b', c: null }, { skipNulls: true })).toBe('a=b'); + + // st.equal( + // stringify({ a: { b: 'c', d: null } }, { skipNulls: true }), + // 'a%5Bb%5D=c', + // 'omits nested nulls when asked', + // ); + expect(stringify({ a: { b: 'c', d: null } }, { skipNulls: true })).toBe('a%5Bb%5D=c'); + }); + + test('omits array indices when asked', function () { + // st.equal(stringify({ a: ['b', 'c', 'd'] }, { indices: false }), 'a=b&a=c&a=d'); + expect(stringify({ a: ['b', 'c', 'd'] }, { indices: false })).toBe('a=b&a=c&a=d'); + }); + + test('omits object key/value pair when value is empty array', function () { + // st.equal(stringify({ a: [], b: 'zz' }), 'b=zz'); + expect(stringify({ a: [], b: 'zz' })).toBe('b=zz'); + }); + + test('should not omit object key/value pair when value is empty array and when asked', function () { + // st.equal(stringify({ a: [], b: 'zz' }), 'b=zz'); + // st.equal(stringify({ a: [], b: 'zz' }, { allowEmptyArrays: false }), 'b=zz'); + // st.equal(stringify({ a: [], b: 'zz' }, { allowEmptyArrays: true }), 'a[]&b=zz'); + expect(stringify({ a: [], b: 'zz' })).toBe('b=zz'); + expect(stringify({ a: [], b: 'zz' }, { allowEmptyArrays: false })).toBe('b=zz'); + expect(stringify({ a: [], b: 'zz' }, { allowEmptyArrays: true })).toBe('a[]&b=zz'); + }); + + test('should throw when allowEmptyArrays is not of type boolean', function () { + // st['throws'](function () { + // stringify({ a: [], b: 'zz' }, { allowEmptyArrays: 'foobar' }); + // }, TypeError); + expect(() => { + // @ts-expect-error + stringify({ a: [], b: 'zz' }, { allowEmptyArrays: 'foobar' }); + }).toThrow(TypeError); + + // st['throws'](function () { + // stringify({ a: [], b: 'zz' }, { allowEmptyArrays: 0 }); + // }, TypeError); + expect(() => { + // @ts-expect-error + stringify({ a: [], b: 'zz' }, { allowEmptyArrays: 0 }); + }).toThrow(TypeError); + + // st['throws'](function () { + // stringify({ a: [], b: 'zz' }, { allowEmptyArrays: NaN }); + // }, TypeError); + expect(() => { + // @ts-expect-error + stringify({ a: [], b: 'zz' }, { allowEmptyArrays: NaN }); + }).toThrow(TypeError); + + // st['throws'](function () { + // stringify({ a: [], b: 'zz' }, { allowEmptyArrays: null }); + // }, TypeError); + expect(() => { + // @ts-expect-error + stringify({ a: [], b: 'zz' }, { allowEmptyArrays: null }); + }).toThrow(TypeError); + }); + + test('allowEmptyArrays + strictNullHandling', function () { + // st.equal( + // stringify({ testEmptyArray: [] }, { strictNullHandling: true, allowEmptyArrays: true }), + // 'testEmptyArray[]', + // ); + expect(stringify({ testEmptyArray: [] }, { strictNullHandling: true, allowEmptyArrays: true })).toBe( + 'testEmptyArray[]', + ); + }); + + describe('stringifies an array value with one item vs multiple items', function () { + test('non-array item', function () { + // s2t.equal( + // stringify({ a: 'c' }, { encodeValuesOnly: true, arrayFormat: 'indices' }), + // 'a=c', + // ); + // s2t.equal( + // stringify({ a: 'c' }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), + // 'a=c', + // ); + // s2t.equal(stringify({ a: 'c' }, { encodeValuesOnly: true, arrayFormat: 'comma' }), 'a=c'); + // s2t.equal(stringify({ a: 'c' }, { encodeValuesOnly: true }), 'a=c'); + expect(stringify({ a: 'c' }, { encodeValuesOnly: true, arrayFormat: 'indices' })).toBe('a=c'); + expect(stringify({ a: 'c' }, { encodeValuesOnly: true, arrayFormat: 'brackets' })).toBe('a=c'); + expect(stringify({ a: 'c' }, { encodeValuesOnly: true, arrayFormat: 'comma' })).toBe('a=c'); + expect(stringify({ a: 'c' }, { encodeValuesOnly: true })).toBe('a=c'); + }); + + test('array with a single item', function () { + // s2t.equal( + // stringify({ a: ['c'] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), + // 'a[0]=c', + // ); + // s2t.equal( + // stringify({ a: ['c'] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), + // 'a[]=c', + // ); + // s2t.equal( + // stringify({ a: ['c'] }, { encodeValuesOnly: true, arrayFormat: 'comma' }), + // 'a=c', + // ); + // s2t.equal( + // stringify( + // { a: ['c'] }, + // { encodeValuesOnly: true, arrayFormat: 'comma', commaRoundTrip: true }, + // ), + // 'a[]=c', + // ); // so it parses back as an array + // s2t.equal(stringify({ a: ['c'] }, { encodeValuesOnly: true }), 'a[0]=c'); + expect(stringify({ a: ['c'] }, { encodeValuesOnly: true, arrayFormat: 'indices' })).toBe('a[0]=c'); + expect(stringify({ a: ['c'] }, { encodeValuesOnly: true, arrayFormat: 'brackets' })).toBe('a[]=c'); + expect(stringify({ a: ['c'] }, { encodeValuesOnly: true, arrayFormat: 'comma' })).toBe('a=c'); + expect( + stringify({ a: ['c'] }, { encodeValuesOnly: true, arrayFormat: 'comma', commaRoundTrip: true }), + ).toBe('a[]=c'); + expect(stringify({ a: ['c'] }, { encodeValuesOnly: true })).toBe('a[0]=c'); + }); + + test('array with multiple items', function () { + // s2t.equal( + // stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), + // 'a[0]=c&a[1]=d', + // ); + // s2t.equal( + // stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), + // 'a[]=c&a[]=d', + // ); + // s2t.equal( + // stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true, arrayFormat: 'comma' }), + // 'a=c,d', + // ); + // s2t.equal( + // stringify( + // { a: ['c', 'd'] }, + // { encodeValuesOnly: true, arrayFormat: 'comma', commaRoundTrip: true }, + // ), + // 'a=c,d', + // ); + // s2t.equal(stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true }), 'a[0]=c&a[1]=d'); + expect(stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true, arrayFormat: 'indices' })).toBe( + 'a[0]=c&a[1]=d', + ); + expect(stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true, arrayFormat: 'brackets' })).toBe( + 'a[]=c&a[]=d', + ); + expect(stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true, arrayFormat: 'comma' })).toBe('a=c,d'); + expect( + stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true, arrayFormat: 'comma', commaRoundTrip: true }), + ).toBe('a=c,d'); + expect(stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true })).toBe('a[0]=c&a[1]=d'); + }); + + test('array with multiple items with a comma inside', function () { + // s2t.equal( + // stringify({ a: ['c,d', 'e'] }, { encodeValuesOnly: true, arrayFormat: 'comma' }), + // 'a=c%2Cd,e', + // ); + // s2t.equal(stringify({ a: ['c,d', 'e'] }, { arrayFormat: 'comma' }), 'a=c%2Cd%2Ce'); + expect(stringify({ a: ['c,d', 'e'] }, { encodeValuesOnly: true, arrayFormat: 'comma' })).toBe( + 'a=c%2Cd,e', + ); + expect(stringify({ a: ['c,d', 'e'] }, { arrayFormat: 'comma' })).toBe('a=c%2Cd%2Ce'); + + // s2t.equal( + // stringify( + // { a: ['c,d', 'e'] }, + // { encodeValuesOnly: true, arrayFormat: 'comma', commaRoundTrip: true }, + // ), + // 'a=c%2Cd,e', + // ); + // s2t.equal( + // stringify({ a: ['c,d', 'e'] }, { arrayFormat: 'comma', commaRoundTrip: true }), + // 'a=c%2Cd%2Ce', + // ); + expect( + stringify( + { a: ['c,d', 'e'] }, + { encodeValuesOnly: true, arrayFormat: 'comma', commaRoundTrip: true }, + ), + ).toBe('a=c%2Cd,e'); + expect(stringify({ a: ['c,d', 'e'] }, { arrayFormat: 'comma', commaRoundTrip: true })).toBe( + 'a=c%2Cd%2Ce', + ); + }); + }); + + test('stringifies a nested array value', function () { + expect(stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true, arrayFormat: 'indices' })).toBe( + 'a[b][0]=c&a[b][1]=d', + ); + expect(stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true, arrayFormat: 'brackets' })).toBe( + 'a[b][]=c&a[b][]=d', + ); + expect(stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true, arrayFormat: 'comma' })).toBe( + 'a[b]=c,d', + ); + expect(stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true })).toBe('a[b][0]=c&a[b][1]=d'); + }); + + test('stringifies comma and empty array values', function () { + // st.equal( + // stringify({ a: [',', '', 'c,d%'] }, { encode: false, arrayFormat: 'indices' }), + // 'a[0]=,&a[1]=&a[2]=c,d%', + // ); + // st.equal( + // stringify({ a: [',', '', 'c,d%'] }, { encode: false, arrayFormat: 'brackets' }), + // 'a[]=,&a[]=&a[]=c,d%', + // ); + // st.equal( + // stringify({ a: [',', '', 'c,d%'] }, { encode: false, arrayFormat: 'comma' }), + // 'a=,,,c,d%', + // ); + // st.equal( + // stringify({ a: [',', '', 'c,d%'] }, { encode: false, arrayFormat: 'repeat' }), + // 'a=,&a=&a=c,d%', + // ); + expect(stringify({ a: [',', '', 'c,d%'] }, { encode: false, arrayFormat: 'indices' })).toBe( + 'a[0]=,&a[1]=&a[2]=c,d%', + ); + expect(stringify({ a: [',', '', 'c,d%'] }, { encode: false, arrayFormat: 'brackets' })).toBe( + 'a[]=,&a[]=&a[]=c,d%', + ); + expect(stringify({ a: [',', '', 'c,d%'] }, { encode: false, arrayFormat: 'comma' })).toBe('a=,,,c,d%'); + expect(stringify({ a: [',', '', 'c,d%'] }, { encode: false, arrayFormat: 'repeat' })).toBe( + 'a=,&a=&a=c,d%', + ); + + // st.equal( + // stringify( + // { a: [',', '', 'c,d%'] }, + // { encode: true, encodeValuesOnly: true, arrayFormat: 'indices' }, + // ), + // 'a[0]=%2C&a[1]=&a[2]=c%2Cd%25', + // ); + // st.equal( + // stringify( + // { a: [',', '', 'c,d%'] }, + // { encode: true, encodeValuesOnly: true, arrayFormat: 'brackets' }, + // ), + // 'a[]=%2C&a[]=&a[]=c%2Cd%25', + // ); + // st.equal( + // stringify( + // { a: [',', '', 'c,d%'] }, + // { encode: true, encodeValuesOnly: true, arrayFormat: 'comma' }, + // ), + // 'a=%2C,,c%2Cd%25', + // ); + // st.equal( + // stringify( + // { a: [',', '', 'c,d%'] }, + // { encode: true, encodeValuesOnly: true, arrayFormat: 'repeat' }, + // ), + // 'a=%2C&a=&a=c%2Cd%25', + // ); + expect( + stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: false, arrayFormat: 'indices' }), + ).toBe('a%5B0%5D=%2C&a%5B1%5D=&a%5B2%5D=c%2Cd%25'); + expect( + stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: true, arrayFormat: 'brackets' }), + ).toBe('a[]=%2C&a[]=&a[]=c%2Cd%25'); + expect( + stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: false, arrayFormat: 'comma' }), + ).toBe('a=%2C%2C%2Cc%2Cd%25'); + expect( + stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: false, arrayFormat: 'repeat' }), + ).toBe('a=%2C&a=&a=c%2Cd%25'); + + // st.equal( + // stringify( + // { a: [',', '', 'c,d%'] }, + // { encode: true, encodeValuesOnly: false, arrayFormat: 'indices' }, + // ), + // 'a%5B0%5D=%2C&a%5B1%5D=&a%5B2%5D=c%2Cd%25', + // ); + // st.equal( + // stringify( + // { a: [',', '', 'c,d%'] }, + // { encode: true, encodeValuesOnly: false, arrayFormat: 'brackets' }, + // ), + // 'a%5B%5D=%2C&a%5B%5D=&a%5B%5D=c%2Cd%25', + // ); + // st.equal( + // stringify( + // { a: [',', '', 'c,d%'] }, + // { encode: true, encodeValuesOnly: false, arrayFormat: 'comma' }, + // ), + // 'a=%2C%2C%2Cc%2Cd%25', + // ); + // st.equal( + // stringify( + // { a: [',', '', 'c,d%'] }, + // { encode: true, encodeValuesOnly: false, arrayFormat: 'repeat' }, + // ), + // 'a=%2C&a=&a=c%2Cd%25', + // ); + expect( + stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: false, arrayFormat: 'repeat' }), + ).toBe('a=%2C&a=&a=c%2Cd%25'); + expect( + stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: false, arrayFormat: 'indices' }), + ).toBe('a%5B0%5D=%2C&a%5B1%5D=&a%5B2%5D=c%2Cd%25'); + expect( + stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: true, arrayFormat: 'brackets' }), + ).toBe('a[]=%2C&a[]=&a[]=c%2Cd%25'); + expect( + stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: false, arrayFormat: 'comma' }), + ).toBe('a=%2C%2C%2Cc%2Cd%25'); + expect( + stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: false, arrayFormat: 'repeat' }), + ).toBe('a=%2C&a=&a=c%2Cd%25'); + }); + + test('stringifies comma and empty non-array values', function () { + // st.equal( + // stringify({ a: ',', b: '', c: 'c,d%' }, { encode: false, arrayFormat: 'indices' }), + // 'a=,&b=&c=c,d%', + // ); + // st.equal( + // stringify({ a: ',', b: '', c: 'c,d%' }, { encode: false, arrayFormat: 'brackets' }), + // 'a=,&b=&c=c,d%', + // ); + // st.equal( + // stringify({ a: ',', b: '', c: 'c,d%' }, { encode: false, arrayFormat: 'comma' }), + // 'a=,&b=&c=c,d%', + // ); + // st.equal( + // stringify({ a: ',', b: '', c: 'c,d%' }, { encode: false, arrayFormat: 'repeat' }), + // 'a=,&b=&c=c,d%', + // ); + expect(stringify({ a: ',', b: '', c: 'c,d%' }, { encode: false, arrayFormat: 'indices' })).toBe( + 'a=,&b=&c=c,d%', + ); + expect(stringify({ a: ',', b: '', c: 'c,d%' }, { encode: false, arrayFormat: 'brackets' })).toBe( + 'a=,&b=&c=c,d%', + ); + + // st.equal( + // stringify( + // { a: ',', b: '', c: 'c,d%' }, + // { encode: true, encodeValuesOnly: true, arrayFormat: 'indices' }, + // ), + // 'a=%2C&b=&c=c%2Cd%25', + // ); + // st.equal( + // stringify( + // { a: ',', b: '', c: 'c,d%' }, + // { encode: true, encodeValuesOnly: true, arrayFormat: 'brackets' }, + // ), + // 'a=%2C&b=&c=c%2Cd%25', + // ); + // st.equal( + // stringify( + // { a: ',', b: '', c: 'c,d%' }, + // { encode: true, encodeValuesOnly: true, arrayFormat: 'comma' }, + // ), + // 'a=%2C&b=&c=c%2Cd%25', + // ); + // st.equal( + // stringify( + // { a: ',', b: '', c: 'c,d%' }, + // { encode: true, encodeValuesOnly: true, arrayFormat: 'repeat' }, + // ), + // 'a=%2C&b=&c=c%2Cd%25', + // ); + expect( + stringify( + { a: ',', b: '', c: 'c,d%' }, + { encode: true, encodeValuesOnly: true, arrayFormat: 'indices' }, + ), + ).toBe('a=%2C&b=&c=c%2Cd%25'); + expect( + stringify( + { a: ',', b: '', c: 'c,d%' }, + { encode: true, encodeValuesOnly: true, arrayFormat: 'brackets' }, + ), + ).toBe('a=%2C&b=&c=c%2Cd%25'); + expect( + stringify({ a: ',', b: '', c: 'c,d%' }, { encode: true, encodeValuesOnly: true, arrayFormat: 'comma' }), + ).toBe('a=%2C&b=&c=c%2Cd%25'); + expect( + stringify( + { a: ',', b: '', c: 'c,d%' }, + { encode: true, encodeValuesOnly: true, arrayFormat: 'repeat' }, + ), + ).toBe('a=%2C&b=&c=c%2Cd%25'); + + // st.equal( + // stringify( + // { a: ',', b: '', c: 'c,d%' }, + // { encode: true, encodeValuesOnly: false, arrayFormat: 'indices' }, + // ), + // 'a=%2C&b=&c=c%2Cd%25', + // ); + // st.equal( + // stringify( + // { a: ',', b: '', c: 'c,d%' }, + // { encode: true, encodeValuesOnly: false, arrayFormat: 'brackets' }, + // ), + // 'a=%2C&b=&c=c%2Cd%25', + // ); + // st.equal( + // stringify( + // { a: ',', b: '', c: 'c,d%' }, + // { encode: true, encodeValuesOnly: false, arrayFormat: 'comma' }, + // ), + // 'a=%2C&b=&c=c%2Cd%25', + // ); + // st.equal( + // stringify( + // { a: ',', b: '', c: 'c,d%' }, + // { encode: true, encodeValuesOnly: false, arrayFormat: 'repeat' }, + // ), + // 'a=%2C&b=&c=c%2Cd%25', + // ); + expect( + stringify( + { a: ',', b: '', c: 'c,d%' }, + { encode: true, encodeValuesOnly: false, arrayFormat: 'indices' }, + ), + ).toBe('a=%2C&b=&c=c%2Cd%25'); + expect( + stringify( + { a: ',', b: '', c: 'c,d%' }, + { encode: true, encodeValuesOnly: false, arrayFormat: 'brackets' }, + ), + ).toBe('a=%2C&b=&c=c%2Cd%25'); + expect( + stringify( + { a: ',', b: '', c: 'c,d%' }, + { encode: true, encodeValuesOnly: false, arrayFormat: 'comma' }, + ), + ).toBe('a=%2C&b=&c=c%2Cd%25'); + expect( + stringify( + { a: ',', b: '', c: 'c,d%' }, + { encode: true, encodeValuesOnly: false, arrayFormat: 'repeat' }, + ), + ).toBe('a=%2C&b=&c=c%2Cd%25'); + }); + + test('stringifies a nested array value with dots notation', function () { + // st.equal( + // stringify( + // { a: { b: ['c', 'd'] } }, + // { allowDots: true, encodeValuesOnly: true, arrayFormat: 'indices' }, + // ), + // 'a.b[0]=c&a.b[1]=d', + // 'indices: stringifies with dots + indices', + // ); + // st.equal( + // stringify( + // { a: { b: ['c', 'd'] } }, + // { allowDots: true, encodeValuesOnly: true, arrayFormat: 'brackets' }, + // ), + // 'a.b[]=c&a.b[]=d', + // 'brackets: stringifies with dots + brackets', + // ); + // st.equal( + // stringify( + // { a: { b: ['c', 'd'] } }, + // { allowDots: true, encodeValuesOnly: true, arrayFormat: 'comma' }, + // ), + // 'a.b=c,d', + // 'comma: stringifies with dots + comma', + // ); + // st.equal( + // stringify({ a: { b: ['c', 'd'] } }, { allowDots: true, encodeValuesOnly: true }), + // 'a.b[0]=c&a.b[1]=d', + // 'default: stringifies with dots + indices', + // ); + expect( + stringify( + { a: { b: ['c', 'd'] } }, + { allowDots: true, encodeValuesOnly: true, arrayFormat: 'indices' }, + ), + ).toBe('a.b[0]=c&a.b[1]=d'); + expect( + stringify( + { a: { b: ['c', 'd'] } }, + { allowDots: true, encodeValuesOnly: true, arrayFormat: 'brackets' }, + ), + ).toBe('a.b[]=c&a.b[]=d'); + expect( + stringify({ a: { b: ['c', 'd'] } }, { allowDots: true, encodeValuesOnly: true, arrayFormat: 'comma' }), + ).toBe('a.b=c,d'); + expect(stringify({ a: { b: ['c', 'd'] } }, { allowDots: true, encodeValuesOnly: true })).toBe( + 'a.b[0]=c&a.b[1]=d', + ); + }); + + test('stringifies an object inside an array', function () { + // st.equal( + // stringify({ a: [{ b: 'c' }] }, { arrayFormat: 'indices', encodeValuesOnly: true }), + // 'a[0][b]=c', + // 'indices => indices', + // ); + // st.equal( + // stringify({ a: [{ b: 'c' }] }, { arrayFormat: 'repeat', encodeValuesOnly: true }), + // 'a[b]=c', + // 'repeat => repeat', + // ); + // st.equal( + // stringify({ a: [{ b: 'c' }] }, { arrayFormat: 'brackets', encodeValuesOnly: true }), + // 'a[][b]=c', + // 'brackets => brackets', + // ); + // st.equal( + // stringify({ a: [{ b: 'c' }] }, { encodeValuesOnly: true }), + // 'a[0][b]=c', + // 'default => indices', + // ); + expect(stringify({ a: [{ b: 'c' }] }, { arrayFormat: 'indices', encodeValuesOnly: true })).toBe( + 'a[0][b]=c', + ); + expect(stringify({ a: [{ b: 'c' }] }, { arrayFormat: 'repeat', encodeValuesOnly: true })).toBe('a[b]=c'); + expect(stringify({ a: [{ b: 'c' }] }, { arrayFormat: 'brackets', encodeValuesOnly: true })).toBe( + 'a[][b]=c', + ); + expect(stringify({ a: [{ b: 'c' }] }, { encodeValuesOnly: true })).toBe('a[0][b]=c'); + + // st.equal( + // stringify({ a: [{ b: { c: [1] } }] }, { arrayFormat: 'indices', encodeValuesOnly: true }), + // 'a[0][b][c][0]=1', + // 'indices => indices', + // ); + // st.equal( + // stringify({ a: [{ b: { c: [1] } }] }, { arrayFormat: 'repeat', encodeValuesOnly: true }), + // 'a[b][c]=1', + // 'repeat => repeat', + // ); + // st.equal( + // stringify({ a: [{ b: { c: [1] } }] }, { arrayFormat: 'brackets', encodeValuesOnly: true }), + // 'a[][b][c][]=1', + // 'brackets => brackets', + // ); + // st.equal( + // stringify({ a: [{ b: { c: [1] } }] }, { encodeValuesOnly: true }), + // 'a[0][b][c][0]=1', + // 'default => indices', + // ); + expect(stringify({ a: [{ b: { c: [1] } }] }, { arrayFormat: 'indices', encodeValuesOnly: true })).toBe( + 'a[0][b][c][0]=1', + ); + expect(stringify({ a: [{ b: { c: [1] } }] }, { arrayFormat: 'repeat', encodeValuesOnly: true })).toBe( + 'a[b][c]=1', + ); + expect(stringify({ a: [{ b: { c: [1] } }] }, { arrayFormat: 'brackets', encodeValuesOnly: true })).toBe( + 'a[][b][c][]=1', + ); + expect(stringify({ a: [{ b: { c: [1] } }] }, { encodeValuesOnly: true })).toBe('a[0][b][c][0]=1'); + }); + + test('stringifies an array with mixed objects and primitives', function () { + // st.equal( + // stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), + // 'a[0][b]=1&a[1]=2&a[2]=3', + // 'indices => indices', + // ); + // st.equal( + // stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), + // 'a[][b]=1&a[]=2&a[]=3', + // 'brackets => brackets', + // ); + // st.equal( + // stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true, arrayFormat: 'comma' }), + // '???', + // 'brackets => brackets', + // { skip: 'TODO: figure out what this should do' }, + // ); + // st.equal( + // stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true }), + // 'a[0][b]=1&a[1]=2&a[2]=3', + // 'default => indices', + // ); + expect(stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true, arrayFormat: 'indices' })).toBe( + 'a[0][b]=1&a[1]=2&a[2]=3', + ); + expect(stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true, arrayFormat: 'brackets' })).toBe( + 'a[][b]=1&a[]=2&a[]=3', + ); + // !Skipped: Figure out what this should do + // expect( + // stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true, arrayFormat: 'comma' }), + // ).toBe('???'); + expect(stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true })).toBe('a[0][b]=1&a[1]=2&a[2]=3'); + }); + + test('stringifies an object inside an array with dots notation', function () { + // st.equal( + // stringify({ a: [{ b: 'c' }] }, { allowDots: true, encode: false, arrayFormat: 'indices' }), + // 'a[0].b=c', + // 'indices => indices', + // ); + // st.equal( + // stringify( + // { a: [{ b: 'c' }] }, + // { allowDots: true, encode: false, arrayFormat: 'brackets' }, + // ), + // 'a[].b=c', + // 'brackets => brackets', + // ); + // st.equal( + // stringify({ a: [{ b: 'c' }] }, { allowDots: true, encode: false }), + // 'a[0].b=c', + // 'default => indices', + // ); + expect(stringify({ a: [{ b: 'c' }] }, { allowDots: true, encode: false, arrayFormat: 'indices' })).toBe( + 'a[0].b=c', + ); + expect(stringify({ a: [{ b: 'c' }] }, { allowDots: true, encode: false, arrayFormat: 'brackets' })).toBe( + 'a[].b=c', + ); + expect(stringify({ a: [{ b: 'c' }] }, { allowDots: true, encode: false })).toBe('a[0].b=c'); + + // st.equal( + // stringify( + // { a: [{ b: { c: [1] } }] }, + // { allowDots: true, encode: false, arrayFormat: 'indices' }, + // ), + // 'a[0].b.c[0]=1', + // 'indices => indices', + // ); + // st.equal( + // stringify( + // { a: [{ b: { c: [1] } }] }, + // { allowDots: true, encode: false, arrayFormat: 'brackets' }, + // ), + // 'a[].b.c[]=1', + // 'brackets => brackets', + // ); + // st.equal( + // stringify({ a: [{ b: { c: [1] } }] }, { allowDots: true, encode: false }), + // 'a[0].b.c[0]=1', + // 'default => indices', + // ); + expect( + stringify({ a: [{ b: { c: [1] } }] }, { allowDots: true, encode: false, arrayFormat: 'indices' }), + ).toBe('a[0].b.c[0]=1'); + expect( + stringify({ a: [{ b: { c: [1] } }] }, { allowDots: true, encode: false, arrayFormat: 'brackets' }), + ).toBe('a[].b.c[]=1'); + expect(stringify({ a: [{ b: { c: [1] } }] }, { allowDots: true, encode: false })).toBe('a[0].b.c[0]=1'); + }); + + test('does not omit object keys when indices = false', function () { + // st.equal(stringify({ a: [{ b: 'c' }] }, { indices: false }), 'a%5Bb%5D=c'); + expect(stringify({ a: [{ b: 'c' }] }, { indices: false })).toBe('a%5Bb%5D=c'); + }); + + test('uses indices notation for arrays when indices=true', function () { + // st.equal(stringify({ a: ['b', 'c'] }, { indices: true }), 'a%5B0%5D=b&a%5B1%5D=c'); + expect(stringify({ a: ['b', 'c'] }, { indices: true })).toBe('a%5B0%5D=b&a%5B1%5D=c'); + }); + + test('uses indices notation for arrays when no arrayFormat is specified', function () { + // st.equal(stringify({ a: ['b', 'c'] }), 'a%5B0%5D=b&a%5B1%5D=c'); + expect(stringify({ a: ['b', 'c'] })).toBe('a%5B0%5D=b&a%5B1%5D=c'); + }); + + test('uses indices notation for arrays when arrayFormat=indices', function () { + // st.equal(stringify({ a: ['b', 'c'] }, { arrayFormat: 'indices' }), 'a%5B0%5D=b&a%5B1%5D=c'); + expect(stringify({ a: ['b', 'c'] }, { arrayFormat: 'indices' })).toBe('a%5B0%5D=b&a%5B1%5D=c'); + }); + + test('uses repeat notation for arrays when arrayFormat=repeat', function () { + // st.equal(stringify({ a: ['b', 'c'] }, { arrayFormat: 'repeat' }), 'a=b&a=c'); + expect(stringify({ a: ['b', 'c'] }, { arrayFormat: 'repeat' })).toBe('a=b&a=c'); + }); + + test('uses brackets notation for arrays when arrayFormat=brackets', function () { + // st.equal(stringify({ a: ['b', 'c'] }, { arrayFormat: 'brackets' }), 'a%5B%5D=b&a%5B%5D=c'); + expect(stringify({ a: ['b', 'c'] }, { arrayFormat: 'brackets' })).toBe('a%5B%5D=b&a%5B%5D=c'); + }); + + test('stringifies a complicated object', function () { + // st.equal(stringify({ a: { b: 'c', d: 'e' } }), 'a%5Bb%5D=c&a%5Bd%5D=e'); + expect(stringify({ a: { b: 'c', d: 'e' } })).toBe('a%5Bb%5D=c&a%5Bd%5D=e'); + }); + + test('stringifies an empty value', function () { + // st.equal(stringify({ a: '' }), 'a='); + // st.equal(stringify({ a: null }, { strictNullHandling: true }), 'a'); + expect(stringify({ a: '' })).toBe('a='); + expect(stringify({ a: null }, { strictNullHandling: true })).toBe('a'); + + // st.equal(stringify({ a: '', b: '' }), 'a=&b='); + // st.equal(stringify({ a: null, b: '' }, { strictNullHandling: true }), 'a&b='); + expect(stringify({ a: '', b: '' })).toBe('a=&b='); + expect(stringify({ a: null, b: '' }, { strictNullHandling: true })).toBe('a&b='); + + // st.equal(stringify({ a: { b: '' } }), 'a%5Bb%5D='); + // st.equal(stringify({ a: { b: null } }, { strictNullHandling: true }), 'a%5Bb%5D'); + // st.equal(stringify({ a: { b: null } }, { strictNullHandling: false }), 'a%5Bb%5D='); + expect(stringify({ a: { b: '' } })).toBe('a%5Bb%5D='); + expect(stringify({ a: { b: null } }, { strictNullHandling: true })).toBe('a%5Bb%5D'); + expect(stringify({ a: { b: null } }, { strictNullHandling: false })).toBe('a%5Bb%5D='); + }); + + test('stringifies an empty array in different arrayFormat', function () { + // st.equal(stringify({ a: [], b: [null], c: 'c' }, { encode: false }), 'b[0]=&c=c'); + expect(stringify({ a: [], b: [null], c: 'c' }, { encode: false })).toBe('b[0]=&c=c'); + // arrayFormat default + // st.equal( + // stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'indices' }), + // 'b[0]=&c=c', + // ); + // st.equal( + // stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'brackets' }), + // 'b[]=&c=c', + // ); + // st.equal( + // stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'repeat' }), + // 'b=&c=c', + // ); + // st.equal( + // stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'comma' }), + // 'b=&c=c', + // ); + // st.equal( + // stringify( + // { a: [], b: [null], c: 'c' }, + // { encode: false, arrayFormat: 'comma', commaRoundTrip: true }, + // ), + // 'b[]=&c=c', + // ); + expect(stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'indices' })).toBe( + 'b[0]=&c=c', + ); + expect(stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'brackets' })).toBe( + 'b[]=&c=c', + ); + expect(stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'repeat' })).toBe('b=&c=c'); + expect(stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'comma' })).toBe('b=&c=c'); + expect( + stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'comma', commaRoundTrip: true }), + ).toBe('b[]=&c=c'); + + // with strictNullHandling + // st.equal( + // stringify( + // { a: [], b: [null], c: 'c' }, + // { encode: false, arrayFormat: 'indices', strictNullHandling: true }, + // ), + // 'b[0]&c=c', + // ); + // st.equal( + // stringify( + // { a: [], b: [null], c: 'c' }, + // { encode: false, arrayFormat: 'brackets', strictNullHandling: true }, + // ), + // 'b[]&c=c', + // ); + // st.equal( + // stringify( + // { a: [], b: [null], c: 'c' }, + // { encode: false, arrayFormat: 'repeat', strictNullHandling: true }, + // ), + // 'b&c=c', + // ); + // st.equal( + // stringify( + // { a: [], b: [null], c: 'c' }, + // { encode: false, arrayFormat: 'comma', strictNullHandling: true }, + // ), + // 'b&c=c', + // ); + // st.equal( + // stringify( + // { a: [], b: [null], c: 'c' }, + // { encode: false, arrayFormat: 'comma', strictNullHandling: true, commaRoundTrip: true }, + // ), + // 'b[]&c=c', + // ); + + expect( + stringify( + { a: [], b: [null], c: 'c' }, + { encode: false, arrayFormat: 'indices', strictNullHandling: true }, + ), + ).toBe('b[0]&c=c'); + expect( + stringify( + { a: [], b: [null], c: 'c' }, + { encode: false, arrayFormat: 'brackets', strictNullHandling: true }, + ), + ).toBe('b[]&c=c'); + expect( + stringify( + { a: [], b: [null], c: 'c' }, + { encode: false, arrayFormat: 'repeat', strictNullHandling: true }, + ), + ).toBe('b&c=c'); + expect( + stringify( + { a: [], b: [null], c: 'c' }, + { encode: false, arrayFormat: 'comma', strictNullHandling: true }, + ), + ).toBe('b&c=c'); + expect( + stringify( + { a: [], b: [null], c: 'c' }, + { encode: false, arrayFormat: 'comma', strictNullHandling: true, commaRoundTrip: true }, + ), + ).toBe('b[]&c=c'); + + // with skipNulls + // st.equal( + // stringify( + // { a: [], b: [null], c: 'c' }, + // { encode: false, arrayFormat: 'indices', skipNulls: true }, + // ), + // 'c=c', + // ); + // st.equal( + // stringify( + // { a: [], b: [null], c: 'c' }, + // { encode: false, arrayFormat: 'brackets', skipNulls: true }, + // ), + // 'c=c', + // ); + // st.equal( + // stringify( + // { a: [], b: [null], c: 'c' }, + // { encode: false, arrayFormat: 'repeat', skipNulls: true }, + // ), + // 'c=c', + // ); + // st.equal( + // stringify( + // { a: [], b: [null], c: 'c' }, + // { encode: false, arrayFormat: 'comma', skipNulls: true }, + // ), + // 'c=c', + // ); + expect( + stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'indices', skipNulls: true }), + ).toBe('c=c'); + expect( + stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'brackets', skipNulls: true }), + ).toBe('c=c'); + expect( + stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'repeat', skipNulls: true }), + ).toBe('c=c'); + expect( + stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'comma', skipNulls: true }), + ).toBe('c=c'); + }); + + test('stringifies a null object', function () { + var obj = Object.create(null); + obj.a = 'b'; + // st.equal(stringify(obj), 'a=b'); + expect(stringify(obj)).toBe('a=b'); + }); + + test('returns an empty string for invalid input', function () { + // st.equal(stringify(undefined), ''); + // st.equal(stringify(false), ''); + // st.equal(stringify(null), ''); + // st.equal(stringify(''), ''); + expect(stringify(undefined)).toBe(''); + expect(stringify(false)).toBe(''); + expect(stringify(null)).toBe(''); + expect(stringify('')).toBe(''); + }); + + test('stringifies an object with a null object as a child', function () { + var obj = { a: Object.create(null) }; + + obj.a.b = 'c'; + // st.equal(stringify(obj), 'a%5Bb%5D=c'); + expect(stringify(obj)).toBe('a%5Bb%5D=c'); + }); + + test('drops keys with a value of undefined', function () { + // st.equal(stringify({ a: undefined }), ''); + expect(stringify({ a: undefined })).toBe(''); + + // st.equal( + // stringify({ a: { b: undefined, c: null } }, { strictNullHandling: true }), + // 'a%5Bc%5D', + // ); + // st.equal( + // stringify({ a: { b: undefined, c: null } }, { strictNullHandling: false }), + // 'a%5Bc%5D=', + // ); + // st.equal(stringify({ a: { b: undefined, c: '' } }), 'a%5Bc%5D='); + expect(stringify({ a: { b: undefined, c: null } }, { strictNullHandling: true })).toBe('a%5Bc%5D'); + expect(stringify({ a: { b: undefined, c: null } }, { strictNullHandling: false })).toBe('a%5Bc%5D='); + expect(stringify({ a: { b: undefined, c: '' } })).toBe('a%5Bc%5D='); + }); + + test('url encodes values', function () { + // st.equal(stringify({ a: 'b c' }), 'a=b%20c'); + expect(stringify({ a: 'b c' })).toBe('a=b%20c'); + }); + + test('stringifies a date', function () { + var now = new Date(); + var str = 'a=' + encodeURIComponent(now.toISOString()); + // st.equal(stringify({ a: now }), str); + expect(stringify({ a: now })).toBe(str); + }); + + test('stringifies the weird object from qs', function () { + // st.equal( + // stringify({ 'my weird field': '~q1!2"\'w$5&7/z8)?' }), + // 'my%20weird%20field=~q1%212%22%27w%245%267%2Fz8%29%3F', + // ); + expect(stringify({ 'my weird field': '~q1!2"\'w$5&7/z8)?' })).toBe( + 'my%20weird%20field=~q1%212%22%27w%245%267%2Fz8%29%3F', + ); + }); + + // TODO: Investigate how to to intercept in vitest + // TODO(rob) + test('skips properties that are part of the object prototype', function () { + // st.intercept(Object.prototype, 'crash', { value: 'test' }); + // @ts-expect-error + Object.prototype.crash = 'test'; + + // st.equal(stringify({ a: 'b' }), 'a=b'); + // st.equal(stringify({ a: { b: 'c' } }), 'a%5Bb%5D=c'); + expect(stringify({ a: 'b' })).toBe('a=b'); + expect(stringify({ a: { b: 'c' } })).toBe('a%5Bb%5D=c'); + }); + + test('stringifies boolean values', function () { + // st.equal(stringify({ a: true }), 'a=true'); + // st.equal(stringify({ a: { b: true } }), 'a%5Bb%5D=true'); + // st.equal(stringify({ b: false }), 'b=false'); + // st.equal(stringify({ b: { c: false } }), 'b%5Bc%5D=false'); + expect(stringify({ a: true })).toBe('a=true'); + expect(stringify({ a: { b: true } })).toBe('a%5Bb%5D=true'); + expect(stringify({ b: false })).toBe('b=false'); + expect(stringify({ b: { c: false } })).toBe('b%5Bc%5D=false'); + }); + + test('stringifies buffer values', function () { + // st.equal(stringify({ a: Buffer.from('test') }), 'a=test'); + // st.equal(stringify({ a: { b: Buffer.from('test') } }), 'a%5Bb%5D=test'); + }); + + test('stringifies an object using an alternative delimiter', function () { + // st.equal(stringify({ a: 'b', c: 'd' }, { delimiter: ';' }), 'a=b;c=d'); + expect(stringify({ a: 'b', c: 'd' }, { delimiter: ';' })).toBe('a=b;c=d'); + }); + + // We dont target environments which dont even have Buffer + // test('does not blow up when Buffer global is missing', function () { + // var restore = mockProperty(global, 'Buffer', { delete: true }); + + // var result = stringify({ a: 'b', c: 'd' }); + + // restore(); + + // st.equal(result, 'a=b&c=d'); + // st.end(); + // }); + + test('does not crash when parsing circular references', function () { + var a: any = {}; + a.b = a; + + // st['throws']( + // function () { + // stringify({ 'foo[bar]': 'baz', 'foo[baz]': a }); + // }, + // /RangeError: Cyclic object value/, + // 'cyclic values throw', + // ); + expect(() => { + stringify({ 'foo[bar]': 'baz', 'foo[baz]': a }); + }).toThrow('Cyclic object value'); + + var circular: any = { + a: 'value', + }; + circular.a = circular; + // st['throws']( + // function () { + // stringify(circular); + // }, + // /RangeError: Cyclic object value/, + // 'cyclic values throw', + // ); + expect(() => { + stringify(circular); + }).toThrow('Cyclic object value'); + + var arr = ['a']; + // st.doesNotThrow(function () { + // stringify({ x: arr, y: arr }); + // }, 'non-cyclic values do not throw'); + expect(() => { + stringify({ x: arr, y: arr }); + }).not.toThrow(); + }); + + test('non-circular duplicated references can still work', function () { + var hourOfDay = { + function: 'hour_of_day', + }; + + var p1 = { + function: 'gte', + arguments: [hourOfDay, 0], + }; + var p2 = { + function: 'lte', + arguments: [hourOfDay, 23], + }; + + // st.equal( + // stringify( + // { filters: { $and: [p1, p2] } }, + // { encodeValuesOnly: true, arrayFormat: 'indices' }, + // ), + // 'filters[$and][0][function]=gte&filters[$and][0][arguments][0][function]=hour_of_day&filters[$and][0][arguments][1]=0&filters[$and][1][function]=lte&filters[$and][1][arguments][0][function]=hour_of_day&filters[$and][1][arguments][1]=23', + // ); + // st.equal( + // stringify( + // { filters: { $and: [p1, p2] } }, + // { encodeValuesOnly: true, arrayFormat: 'brackets' }, + // ), + // 'filters[$and][][function]=gte&filters[$and][][arguments][][function]=hour_of_day&filters[$and][][arguments][]=0&filters[$and][][function]=lte&filters[$and][][arguments][][function]=hour_of_day&filters[$and][][arguments][]=23', + // ); + // st.equal( + // stringify( + // { filters: { $and: [p1, p2] } }, + // { encodeValuesOnly: true, arrayFormat: 'repeat' }, + // ), + // 'filters[$and][function]=gte&filters[$and][arguments][function]=hour_of_day&filters[$and][arguments]=0&filters[$and][function]=lte&filters[$and][arguments][function]=hour_of_day&filters[$and][arguments]=23', + // ); + expect( + stringify({ filters: { $and: [p1, p2] } }, { encodeValuesOnly: true, arrayFormat: 'indices' }), + ).toBe( + 'filters[$and][0][function]=gte&filters[$and][0][arguments][0][function]=hour_of_day&filters[$and][0][arguments][1]=0&filters[$and][1][function]=lte&filters[$and][1][arguments][0][function]=hour_of_day&filters[$and][1][arguments][1]=23', + ); + expect( + stringify({ filters: { $and: [p1, p2] } }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), + ).toBe( + 'filters[$and][][function]=gte&filters[$and][][arguments][][function]=hour_of_day&filters[$and][][arguments][]=0&filters[$and][][function]=lte&filters[$and][][arguments][][function]=hour_of_day&filters[$and][][arguments][]=23', + ); + expect( + stringify({ filters: { $and: [p1, p2] } }, { encodeValuesOnly: true, arrayFormat: 'repeat' }), + ).toBe( + 'filters[$and][function]=gte&filters[$and][arguments][function]=hour_of_day&filters[$and][arguments]=0&filters[$and][function]=lte&filters[$and][arguments][function]=hour_of_day&filters[$and][arguments]=23', + ); + }); + + test('selects properties when filter=array', function () { + // st.equal(stringify({ a: 'b' }, { filter: ['a'] }), 'a=b'); + // st.equal(stringify({ a: 1 }, { filter: [] }), ''); + expect(stringify({ a: 'b' }, { filter: ['a'] })).toBe('a=b'); + expect(stringify({ a: 1 }, { filter: [] })).toBe(''); + + // st.equal( + // stringify( + // { a: { b: [1, 2, 3, 4], c: 'd' }, c: 'f' }, + // { filter: ['a', 'b', 0, 2], arrayFormat: 'indices' }, + // ), + // 'a%5Bb%5D%5B0%5D=1&a%5Bb%5D%5B2%5D=3', + // 'indices => indices', + // ); + // st.equal( + // stringify( + // { a: { b: [1, 2, 3, 4], c: 'd' }, c: 'f' }, + // { filter: ['a', 'b', 0, 2], arrayFormat: 'brackets' }, + // ), + // 'a%5Bb%5D%5B%5D=1&a%5Bb%5D%5B%5D=3', + // 'brackets => brackets', + // ); + // st.equal( + // stringify({ a: { b: [1, 2, 3, 4], c: 'd' }, c: 'f' }, { filter: ['a', 'b', 0, 2] }), + // 'a%5Bb%5D%5B0%5D=1&a%5Bb%5D%5B2%5D=3', + // 'default => indices', + // ); + expect(stringify({ a: { b: [1, 2, 3, 4], c: 'd' }, c: 'f' }, { filter: ['a', 'b', 0, 2] })).toBe( + 'a%5Bb%5D%5B0%5D=1&a%5Bb%5D%5B2%5D=3', + ); + expect( + stringify( + { a: { b: [1, 2, 3, 4], c: 'd' }, c: 'f' }, + { filter: ['a', 'b', 0, 2], arrayFormat: 'indices' }, + ), + ).toBe('a%5Bb%5D%5B0%5D=1&a%5Bb%5D%5B2%5D=3'); + expect( + stringify( + { a: { b: [1, 2, 3, 4], c: 'd' }, c: 'f' }, + { filter: ['a', 'b', 0, 2], arrayFormat: 'brackets' }, + ), + ).toBe('a%5Bb%5D%5B%5D=1&a%5Bb%5D%5B%5D=3'); + }); + + test('supports custom representations when filter=function', function () { + var calls = 0; + var obj = { a: 'b', c: 'd', e: { f: new Date(1257894000000) } }; + var filterFunc: StringifyOptions['filter'] = function (prefix, value) { + calls += 1; + if (calls === 1) { + // st.equal(prefix, '', 'prefix is empty'); + // st.equal(value, obj); + expect(prefix).toBe(''); + expect(value).toBe(obj); + } else if (prefix === 'c') { + return void 0; + } else if (value instanceof Date) { + // st.equal(prefix, 'e[f]'); + expect(prefix).toBe('e[f]'); + return value.getTime(); + } + return value; + }; + + // st.equal(stringify(obj, { filter: filterFunc }), 'a=b&e%5Bf%5D=1257894000000'); + // st.equal(calls, 5); + expect(stringify(obj, { filter: filterFunc })).toBe('a=b&e%5Bf%5D=1257894000000'); + expect(calls).toBe(5); + }); + + test('can disable uri encoding', function () { + // st.equal(stringify({ a: 'b' }, { encode: false }), 'a=b'); + // st.equal(stringify({ a: { b: 'c' } }, { encode: false }), 'a[b]=c'); + // st.equal( + // stringify({ a: 'b', c: null }, { strictNullHandling: true, encode: false }), + // 'a=b&c', + // ); + expect(stringify({ a: 'b' }, { encode: false })).toBe('a=b'); + expect(stringify({ a: { b: 'c' } }, { encode: false })).toBe('a[b]=c'); + expect(stringify({ a: 'b', c: null }, { strictNullHandling: true, encode: false })).toBe('a=b&c'); + }); + + test('can sort the keys', function () { + // @ts-expect-error + var sort: NonNullable = function (a: string, b: string) { + return a.localeCompare(b); + }; + // st.equal(stringify({ a: 'c', z: 'y', b: 'f' }, { sort: sort }), 'a=c&b=f&z=y'); + // st.equal( + // stringify({ a: 'c', z: { j: 'a', i: 'b' }, b: 'f' }, { sort: sort }), + // 'a=c&b=f&z%5Bi%5D=b&z%5Bj%5D=a', + // ); + expect(stringify({ a: 'c', z: 'y', b: 'f' }, { sort: sort })).toBe('a=c&b=f&z=y'); + expect(stringify({ a: 'c', z: { j: 'a', i: 'b' }, b: 'f' }, { sort: sort })).toBe( + 'a=c&b=f&z%5Bi%5D=b&z%5Bj%5D=a', + ); + }); + + test('can sort the keys at depth 3 or more too', function () { + // @ts-expect-error + var sort: NonNullable = function (a: string, b: string) { + return a.localeCompare(b); + }; + // st.equal( + // stringify( + // { a: 'a', z: { zj: { zjb: 'zjb', zja: 'zja' }, zi: { zib: 'zib', zia: 'zia' } }, b: 'b' }, + // { sort: sort, encode: false }, + // ), + // 'a=a&b=b&z[zi][zia]=zia&z[zi][zib]=zib&z[zj][zja]=zja&z[zj][zjb]=zjb', + // ); + // st.equal( + // stringify( + // { a: 'a', z: { zj: { zjb: 'zjb', zja: 'zja' }, zi: { zib: 'zib', zia: 'zia' } }, b: 'b' }, + // { sort: null, encode: false }, + // ), + // 'a=a&z[zj][zjb]=zjb&z[zj][zja]=zja&z[zi][zib]=zib&z[zi][zia]=zia&b=b', + // ); + expect( + stringify( + { a: 'a', z: { zj: { zjb: 'zjb', zja: 'zja' }, zi: { zib: 'zib', zia: 'zia' } }, b: 'b' }, + { sort: sort, encode: false }, + ), + ).toBe('a=a&b=b&z[zi][zia]=zia&z[zi][zib]=zib&z[zj][zja]=zja&z[zj][zjb]=zjb'); + expect( + stringify( + { a: 'a', z: { zj: { zjb: 'zjb', zja: 'zja' }, zi: { zib: 'zib', zia: 'zia' } }, b: 'b' }, + { sort: null, encode: false }, + ), + ).toBe('a=a&z[zj][zjb]=zjb&z[zj][zja]=zja&z[zi][zib]=zib&z[zi][zia]=zia&b=b'); + }); + + test('can stringify with custom encoding', function () { + // st.equal( + // stringify( + // { 県: '大阪府', '': '' }, + // { + // encoder: function (str) { + // if (str.length === 0) { + // return ''; + // } + // var buf = iconv.encode(str, 'shiftjis'); + // var result = []; + // for (var i = 0; i < buf.length; ++i) { + // result.push(buf.readUInt8(i).toString(16)); + // } + // return '%' + result.join('%'); + // }, + // }, + // ), + // '%8c%a7=%91%e5%8d%e3%95%7b&=', + // ); + expect( + stringify( + { 県: '大阪府', '': '' }, + { + encoder: function (str) { + if (str.length === 0) { + return ''; + } + var buf = iconv.encode(str, 'shiftjis'); + var result = []; + for (var i = 0; i < buf.length; ++i) { + result.push(buf.readUInt8(i).toString(16)); + } + return '%' + result.join('%'); + }, + }, + ), + ).toBe('%8c%a7=%91%e5%8d%e3%95%7b&='); + }); + + test('receives the default encoder as a second argument', function () { + // stringify( + // { a: 1, b: new Date(), c: true, d: [1] }, + // { + // encoder: function (str) { + // st.match(typeof str, /^(?:string|number|boolean)$/); + // return ''; + // }, + // }, + // ); + + stringify( + { a: 1, b: new Date(), c: true, d: [1] }, + { + encoder: function (str) { + // st.match(typeof str, /^(?:string|number|boolean)$/); + assert.match(typeof str, /^(?:string|number|boolean)$/); + return ''; + }, + }, + ); + }); + + test('receives the default encoder as a second argument', function () { + // stringify( + // { a: 1 }, + // { + // encoder: function (str, defaultEncoder) { + // st.equal(defaultEncoder, utils.encode); + // }, + // }, + // ); + + stringify( + { a: 1 }, + { + // @ts-ignore + encoder: function (_str, defaultEncoder) { + expect(defaultEncoder).toBe(encode); + }, + }, + ); + }); + + test('throws error with wrong encoder', function () { + // st['throws'](function () { + // stringify({}, { encoder: 'string' }); + // }, new TypeError('Encoder has to be a function.')); + // st.end(); + expect(() => { + // @ts-expect-error + stringify({}, { encoder: 'string' }); + }).toThrow(TypeError); + }); + + (typeof Buffer === 'undefined' ? test.skip : test)( + 'can use custom encoder for a buffer object', + function () { + // st.equal( + // stringify( + // { a: Buffer.from([1]) }, + // { + // encoder: function (buffer) { + // if (typeof buffer === 'string') { + // return buffer; + // } + // return String.fromCharCode(buffer.readUInt8(0) + 97); + // }, + // }, + // ), + // 'a=b', + // ); + expect( + stringify( + { a: Buffer.from([1]) }, + { + encoder: function (buffer) { + if (typeof buffer === 'string') { + return buffer; + } + return String.fromCharCode(buffer.readUInt8(0) + 97); + }, + }, + ), + ).toBe('a=b'); + + // st.equal( + // stringify( + // { a: Buffer.from('a b') }, + // { + // encoder: function (buffer) { + // return buffer; + // }, + // }, + // ), + // 'a=a b', + // ); + expect( + stringify( + { a: Buffer.from('a b') }, + { + encoder: function (buffer) { + return buffer; + }, + }, + ), + ).toBe('a=a b'); + }, + ); + + test('serializeDate option', function () { + var date = new Date(); + // st.equal( + // stringify({ a: date }), + // 'a=' + date.toISOString().replace(/:/g, '%3A'), + // 'default is toISOString', + // ); + expect(stringify({ a: date })).toBe('a=' + date.toISOString().replace(/:/g, '%3A')); + + var mutatedDate = new Date(); + mutatedDate.toISOString = function () { + throw new SyntaxError(); + }; + // st['throws'](function () { + // mutatedDate.toISOString(); + // }, SyntaxError); + expect(() => { + mutatedDate.toISOString(); + }).toThrow(SyntaxError); + // st.equal( + // stringify({ a: mutatedDate }), + // 'a=' + Date.prototype.toISOString.call(mutatedDate).replace(/:/g, '%3A'), + // 'toISOString works even when method is not locally present', + // ); + expect(stringify({ a: mutatedDate })).toBe( + 'a=' + Date.prototype.toISOString.call(mutatedDate).replace(/:/g, '%3A'), + ); + + var specificDate = new Date(6); + // st.equal( + // stringify( + // { a: specificDate }, + // { + // serializeDate: function (d) { + // return d.getTime() * 7; + // }, + // }, + // ), + // 'a=42', + // 'custom serializeDate function called', + // ); + expect( + stringify( + { a: specificDate }, + { + // @ts-ignore + serializeDate: function (d) { + return d.getTime() * 7; + }, + }, + ), + ).toBe('a=42'); + + // st.equal( + // stringify( + // { a: [date] }, + // { + // serializeDate: function (d) { + // return d.getTime(); + // }, + // arrayFormat: 'comma', + // }, + // ), + // 'a=' + date.getTime(), + // 'works with arrayFormat comma', + // ); + // st.equal( + // stringify( + // { a: [date] }, + // { + // serializeDate: function (d) { + // return d.getTime(); + // }, + // arrayFormat: 'comma', + // commaRoundTrip: true, + // }, + // ), + // 'a%5B%5D=' + date.getTime(), + // 'works with arrayFormat comma', + // ); + expect( + stringify( + { a: [date] }, + { + // @ts-expect-error + serializeDate: function (d) { + return d.getTime(); + }, + arrayFormat: 'comma', + }, + ), + ).toBe('a=' + date.getTime()); + expect( + stringify( + { a: [date] }, + { + // @ts-expect-error + serializeDate: function (d) { + return d.getTime(); + }, + arrayFormat: 'comma', + commaRoundTrip: true, + }, + ), + ).toBe('a%5B%5D=' + date.getTime()); + }); + + test('RFC 1738 serialization', function () { + // st.equal(stringify({ a: 'b c' }, { format: formats.RFC1738 }), 'a=b+c'); + // st.equal(stringify({ 'a b': 'c d' }, { format: formats.RFC1738 }), 'a+b=c+d'); + // st.equal( + // stringify({ 'a b': Buffer.from('a b') }, { format: formats.RFC1738 }), + // 'a+b=a+b', + // ); + expect(stringify({ a: 'b c' }, { format: 'RFC1738' })).toBe('a=b+c'); + expect(stringify({ 'a b': 'c d' }, { format: 'RFC1738' })).toBe('a+b=c+d'); + expect(stringify({ 'a b': Buffer.from('a b') }, { format: 'RFC1738' })).toBe('a+b=a+b'); + + // st.equal(stringify({ 'foo(ref)': 'bar' }, { format: formats.RFC1738 }), 'foo(ref)=bar'); + expect(stringify({ 'foo(ref)': 'bar' }, { format: 'RFC1738' })).toBe('foo(ref)=bar'); + }); + + test('RFC 3986 spaces serialization', function () { + // st.equal(stringify({ a: 'b c' }, { format: formats.RFC3986 }), 'a=b%20c'); + // st.equal(stringify({ 'a b': 'c d' }, { format: formats.RFC3986 }), 'a%20b=c%20d'); + // st.equal( + // stringify({ 'a b': Buffer.from('a b') }, { format: formats.RFC3986 }), + // 'a%20b=a%20b', + // ); + expect(stringify({ a: 'b c' }, { format: 'RFC3986' })).toBe('a=b%20c'); + expect(stringify({ 'a b': 'c d' }, { format: 'RFC3986' })).toBe('a%20b=c%20d'); + expect(stringify({ 'a b': Buffer.from('a b') }, { format: 'RFC3986' })).toBe('a%20b=a%20b'); + }); + + test('Backward compatibility to RFC 3986', function () { + // st.equal(stringify({ a: 'b c' }), 'a=b%20c'); + // st.equal(stringify({ 'a b': Buffer.from('a b') }), 'a%20b=a%20b'); + expect(stringify({ a: 'b c' })).toBe('a=b%20c'); + expect(stringify({ 'a b': Buffer.from('a b') })).toBe('a%20b=a%20b'); + }); + + test('Edge cases and unknown formats', function () { + ['UFO1234', false, 1234, null, {}, []].forEach(function (format) { + // st['throws'](function () { + // stringify({ a: 'b c' }, { format: format }); + // }, new TypeError('Unknown format option provided.')); + expect(() => { + // @ts-expect-error + stringify({ a: 'b c' }, { format: format }); + }).toThrow(TypeError); + }); + }); + + test('encodeValuesOnly', function () { + // st.equal( + // stringify( + // { a: 'b', c: ['d', 'e=f'], f: [['g'], ['h']] }, + // { encodeValuesOnly: true, arrayFormat: 'indices' }, + // ), + // 'a=b&c[0]=d&c[1]=e%3Df&f[0][0]=g&f[1][0]=h', + // 'encodeValuesOnly + indices', + // ); + // st.equal( + // stringify( + // { a: 'b', c: ['d', 'e=f'], f: [['g'], ['h']] }, + // { encodeValuesOnly: true, arrayFormat: 'brackets' }, + // ), + // 'a=b&c[]=d&c[]=e%3Df&f[][]=g&f[][]=h', + // 'encodeValuesOnly + brackets', + // ); + // st.equal( + // stringify( + // { a: 'b', c: ['d', 'e=f'], f: [['g'], ['h']] }, + // { encodeValuesOnly: true, arrayFormat: 'repeat' }, + // ), + // 'a=b&c=d&c=e%3Df&f=g&f=h', + // 'encodeValuesOnly + repeat', + // ); + expect( + stringify( + { a: 'b', c: ['d', 'e=f'], f: [['g'], ['h']] }, + { encodeValuesOnly: true, arrayFormat: 'indices' }, + ), + ).toBe('a=b&c[0]=d&c[1]=e%3Df&f[0][0]=g&f[1][0]=h'); + expect( + stringify( + { a: 'b', c: ['d', 'e=f'], f: [['g'], ['h']] }, + { encodeValuesOnly: true, arrayFormat: 'brackets' }, + ), + ).toBe('a=b&c[]=d&c[]=e%3Df&f[][]=g&f[][]=h'); + expect( + stringify( + { a: 'b', c: ['d', 'e=f'], f: [['g'], ['h']] }, + { encodeValuesOnly: true, arrayFormat: 'repeat' }, + ), + ).toBe('a=b&c=d&c=e%3Df&f=g&f=h'); + + // st.equal( + // stringify({ a: 'b', c: ['d', 'e'], f: [['g'], ['h']] }, { arrayFormat: 'indices' }), + // 'a=b&c%5B0%5D=d&c%5B1%5D=e&f%5B0%5D%5B0%5D=g&f%5B1%5D%5B0%5D=h', + // 'no encodeValuesOnly + indices', + // ); + // st.equal( + // stringify({ a: 'b', c: ['d', 'e'], f: [['g'], ['h']] }, { arrayFormat: 'brackets' }), + // 'a=b&c%5B%5D=d&c%5B%5D=e&f%5B%5D%5B%5D=g&f%5B%5D%5B%5D=h', + // 'no encodeValuesOnly + brackets', + // ); + // st.equal( + // stringify({ a: 'b', c: ['d', 'e'], f: [['g'], ['h']] }, { arrayFormat: 'repeat' }), + // 'a=b&c=d&c=e&f=g&f=h', + // 'no encodeValuesOnly + repeat', + // ); + expect(stringify({ a: 'b', c: ['d', 'e'], f: [['g'], ['h']] }, { arrayFormat: 'indices' })).toBe( + 'a=b&c%5B0%5D=d&c%5B1%5D=e&f%5B0%5D%5B0%5D=g&f%5B1%5D%5B0%5D=h', + ); + expect(stringify({ a: 'b', c: ['d', 'e'], f: [['g'], ['h']] }, { arrayFormat: 'brackets' })).toBe( + 'a=b&c%5B%5D=d&c%5B%5D=e&f%5B%5D%5B%5D=g&f%5B%5D%5B%5D=h', + ); + expect(stringify({ a: 'b', c: ['d', 'e'], f: [['g'], ['h']] }, { arrayFormat: 'repeat' })).toBe( + 'a=b&c=d&c=e&f=g&f=h', + ); + }); + + test('encodeValuesOnly - strictNullHandling', function () { + // st.equal( + // stringify({ a: { b: null } }, { encodeValuesOnly: true, strictNullHandling: true }), + // 'a[b]', + // ); + expect(stringify({ a: { b: null } }, { encodeValuesOnly: true, strictNullHandling: true })).toBe('a[b]'); + }); + + test('throws if an invalid charset is specified', function () { + // st['throws'](function () { + // stringify({ a: 'b' }, { charset: 'foobar' }); + // }, new TypeError('The charset option must be either utf-8, iso-8859-1, or undefined')); + expect(() => { + // @ts-expect-error + stringify({ a: 'b' }, { charset: 'foobar' }); + }).toThrow(TypeError); + }); + + test('respects a charset of iso-8859-1', function () { + // st.equal(stringify({ æ: 'æ' }, { charset: 'iso-8859-1' }), '%E6=%E6'); + expect(stringify({ æ: 'æ' }, { charset: 'iso-8859-1' })).toBe('%E6=%E6'); + }); + + test('encodes unrepresentable chars as numeric entities in iso-8859-1 mode', function () { + // st.equal(stringify({ a: '☺' }, { charset: 'iso-8859-1' }), 'a=%26%239786%3B'); + expect(stringify({ a: '☺' }, { charset: 'iso-8859-1' })).toBe('a=%26%239786%3B'); + }); + + test('respects an explicit charset of utf-8 (the default)', function () { + // st.equal(stringify({ a: 'æ' }, { charset: 'utf-8' }), 'a=%C3%A6'); + expect(stringify({ a: 'æ' }, { charset: 'utf-8' })).toBe('a=%C3%A6'); + }); + + test('`charsetSentinel` option', function () { + // st.equal( + // stringify({ a: 'æ' }, { charsetSentinel: true, charset: 'utf-8' }), + // 'utf8=%E2%9C%93&a=%C3%A6', + // 'adds the right sentinel when instructed to and the charset is utf-8', + // ); + expect(stringify({ a: 'æ' }, { charsetSentinel: true, charset: 'utf-8' })).toBe( + 'utf8=%E2%9C%93&a=%C3%A6', + ); + + // st.equal( + // stringify({ a: 'æ' }, { charsetSentinel: true, charset: 'iso-8859-1' }), + // 'utf8=%26%2310003%3B&a=%E6', + // 'adds the right sentinel when instructed to and the charset is iso-8859-1', + // ); + expect(stringify({ a: 'æ' }, { charsetSentinel: true, charset: 'iso-8859-1' })).toBe( + 'utf8=%26%2310003%3B&a=%E6', + ); + }); + + test('does not mutate the options argument', function () { + var options = {}; + stringify({}, options); + // st.deepEqual(options, {}); + expect(options).toEqual({}); + }); + + test('strictNullHandling works with custom filter', function () { + // @ts-expect-error + var filter = function (_prefix, value) { + return value; + }; + + var options = { strictNullHandling: true, filter: filter }; + // st.equal(stringify({ key: null }, options), 'key'); + expect(stringify({ key: null }, options)).toBe('key'); + }); + + test('strictNullHandling works with null serializeDate', function () { + var serializeDate = function () { + return null; + }; + var options = { strictNullHandling: true, serializeDate: serializeDate }; + var date = new Date(); + // st.equal(stringify({ key: date }, options), 'key'); + // @ts-expect-error + expect(stringify({ key: date }, options)).toBe('key'); + }); + + test('allows for encoding keys and values differently', function () { + // @ts-expect-error + var encoder = function (str, defaultEncoder, charset, type) { + if (type === 'key') { + return defaultEncoder(str, defaultEncoder, charset, type).toLowerCase(); + } + if (type === 'value') { + return defaultEncoder(str, defaultEncoder, charset, type).toUpperCase(); + } + throw 'this should never happen! type: ' + type; + }; + + // st.deepEqual(stringify({ KeY: 'vAlUe' }, { encoder: encoder }), 'key=VALUE'); + expect(stringify({ KeY: 'vAlUe' }, { encoder: encoder })).toBe('key=VALUE'); + }); + + test('objects inside arrays', function () { + var obj = { a: { b: { c: 'd', e: 'f' } } }; + var withArray = { a: { b: [{ c: 'd', e: 'f' }] } }; + + // st.equal( + // stringify(obj, { encode: false }), + // 'a[b][c]=d&a[b][e]=f', + // 'no array, no arrayFormat', + // ); + // st.equal( + // stringify(obj, { encode: false, arrayFormat: 'brackets' }), + // 'a[b][c]=d&a[b][e]=f', + // 'no array, bracket', + // ); + // st.equal( + // stringify(obj, { encode: false, arrayFormat: 'indices' }), + // 'a[b][c]=d&a[b][e]=f', + // 'no array, indices', + // ); + // st.equal( + // stringify(obj, { encode: false, arrayFormat: 'repeat' }), + // 'a[b][c]=d&a[b][e]=f', + // 'no array, repeat', + // ); + // st.equal( + // stringify(obj, { encode: false, arrayFormat: 'comma' }), + // 'a[b][c]=d&a[b][e]=f', + // 'no array, comma', + // ); + expect(stringify(obj, { encode: false })).toBe('a[b][c]=d&a[b][e]=f'); + expect(stringify(obj, { encode: false, arrayFormat: 'brackets' })).toBe('a[b][c]=d&a[b][e]=f'); + expect(stringify(obj, { encode: false, arrayFormat: 'indices' })).toBe('a[b][c]=d&a[b][e]=f'); + expect(stringify(obj, { encode: false, arrayFormat: 'repeat' })).toBe('a[b][c]=d&a[b][e]=f'); + expect(stringify(obj, { encode: false, arrayFormat: 'comma' })).toBe('a[b][c]=d&a[b][e]=f'); + + // st.equal( + // stringify(withArray, { encode: false }), + // 'a[b][0][c]=d&a[b][0][e]=f', + // 'array, no arrayFormat', + // ); + // st.equal( + // stringify(withArray, { encode: false, arrayFormat: 'brackets' }), + // 'a[b][][c]=d&a[b][][e]=f', + // 'array, bracket', + // ); + // st.equal( + // stringify(withArray, { encode: false, arrayFormat: 'indices' }), + // 'a[b][0][c]=d&a[b][0][e]=f', + // 'array, indices', + // ); + // st.equal( + // stringify(withArray, { encode: false, arrayFormat: 'repeat' }), + // 'a[b][c]=d&a[b][e]=f', + // 'array, repeat', + // ); + // st.equal( + // stringify(withArray, { encode: false, arrayFormat: 'comma' }), + // '???', + // 'array, comma', + // { skip: 'TODO: figure out what this should do' }, + // ); + expect(stringify(withArray, { encode: false })).toBe('a[b][0][c]=d&a[b][0][e]=f'); + expect(stringify(withArray, { encode: false, arrayFormat: 'brackets' })).toBe('a[b][][c]=d&a[b][][e]=f'); + expect(stringify(withArray, { encode: false, arrayFormat: 'indices' })).toBe('a[b][0][c]=d&a[b][0][e]=f'); + expect(stringify(withArray, { encode: false, arrayFormat: 'repeat' })).toBe('a[b][c]=d&a[b][e]=f'); + // !TODo: Figure out what this should do + // expect(stringify(withArray, { encode: false, arrayFormat: 'comma' })).toBe( + // 'a[b][c]=d&a[b][e]=f', + // ); + }); + + test('stringifies sparse arrays', function () { + // st.equal( + // stringify({ a: [, '2', , , '1'] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), + // 'a[1]=2&a[4]=1', + // ); + // st.equal( + // stringify({ a: [, '2', , , '1'] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), + // 'a[]=2&a[]=1', + // ); + // st.equal( + // stringify({ a: [, '2', , , '1'] }, { encodeValuesOnly: true, arrayFormat: 'repeat' }), + // 'a=2&a=1', + // ); + expect(stringify({ a: [, '2', , , '1'] }, { encodeValuesOnly: true, arrayFormat: 'indices' })).toBe( + 'a[1]=2&a[4]=1', + ); + expect(stringify({ a: [, '2', , , '1'] }, { encodeValuesOnly: true, arrayFormat: 'brackets' })).toBe( + 'a[]=2&a[]=1', + ); + expect(stringify({ a: [, '2', , , '1'] }, { encodeValuesOnly: true, arrayFormat: 'repeat' })).toBe( + 'a=2&a=1', + ); + + // st.equal( + // stringify( + // { a: [, { b: [, , { c: '1' }] }] }, + // { encodeValuesOnly: true, arrayFormat: 'indices' }, + // ), + // 'a[1][b][2][c]=1', + // ); + // st.equal( + // stringify( + // { a: [, { b: [, , { c: '1' }] }] }, + // { encodeValuesOnly: true, arrayFormat: 'brackets' }, + // ), + // 'a[][b][][c]=1', + // ); + // st.equal( + // stringify( + // { a: [, { b: [, , { c: '1' }] }] }, + // { encodeValuesOnly: true, arrayFormat: 'repeat' }, + // ), + // 'a[b][c]=1', + // ); + expect( + stringify({ a: [, { b: [, , { c: '1' }] }] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), + ).toBe('a[1][b][2][c]=1'); + expect( + stringify({ a: [, { b: [, , { c: '1' }] }] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), + ).toBe('a[][b][][c]=1'); + expect( + stringify({ a: [, { b: [, , { c: '1' }] }] }, { encodeValuesOnly: true, arrayFormat: 'repeat' }), + ).toBe('a[b][c]=1'); + + // st.equal( + // stringify( + // { a: [, [, , [, , , { c: '1' }]]] }, + // { encodeValuesOnly: true, arrayFormat: 'indices' }, + // ), + // 'a[1][2][3][c]=1', + // ); + // st.equal( + // stringify( + // { a: [, [, , [, , , { c: '1' }]]] }, + // { encodeValuesOnly: true, arrayFormat: 'brackets' }, + // ), + // 'a[][][][c]=1', + // ); + // st.equal( + // stringify( + // { a: [, [, , [, , , { c: '1' }]]] }, + // { encodeValuesOnly: true, arrayFormat: 'repeat' }, + // ), + // 'a[c]=1', + // ); + expect( + stringify({ a: [, [, , [, , , { c: '1' }]]] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), + ).toBe('a[1][2][3][c]=1'); + expect( + stringify({ a: [, [, , [, , , { c: '1' }]]] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), + ).toBe('a[][][][c]=1'); + expect( + stringify({ a: [, [, , [, , , { c: '1' }]]] }, { encodeValuesOnly: true, arrayFormat: 'repeat' }), + ).toBe('a[c]=1'); + + // st.equal( + // stringify( + // { a: [, [, , [, , , { c: [, '1'] }]]] }, + // { encodeValuesOnly: true, arrayFormat: 'indices' }, + // ), + // 'a[1][2][3][c][1]=1', + // ); + // st.equal( + // stringify( + // { a: [, [, , [, , , { c: [, '1'] }]]] }, + // { encodeValuesOnly: true, arrayFormat: 'brackets' }, + // ), + // 'a[][][][c][]=1', + // ); + // st.equal( + // stringify( + // { a: [, [, , [, , , { c: [, '1'] }]]] }, + // { encodeValuesOnly: true, arrayFormat: 'repeat' }, + // ), + // 'a[c]=1', + // ); + expect( + stringify({ a: [, [, , [, , , { c: [, '1'] }]]] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), + ).toBe('a[1][2][3][c][1]=1'); + expect( + stringify({ a: [, [, , [, , , { c: [, '1'] }]]] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), + ).toBe('a[][][][c][]=1'); + expect( + stringify({ a: [, [, , [, , , { c: [, '1'] }]]] }, { encodeValuesOnly: true, arrayFormat: 'repeat' }), + ).toBe('a[c]=1'); + }); + + test('encodes a very long string', function () { + var chars = []; + var expected = []; + for (var i = 0; i < 5e3; i++) { + chars.push(' ' + i); + + expected.push('%20' + i); + } + + var obj = { + foo: chars.join(''), + }; + + // st.equal( + // stringify(obj, { arrayFormat: 'bracket', charset: 'utf-8' }), + // 'foo=' + expected.join(''), + // ); + // @ts-expect-error + expect(stringify(obj, { arrayFormat: 'bracket', charset: 'utf-8' })).toBe('foo=' + expected.join('')); + }); +}); + +describe('stringifies empty keys', function () { + empty_test_cases.forEach(function (testCase) { + test('stringifies an object with empty string key with ' + testCase.input, function () { + // st.deepEqual( + // stringify(testCase.withEmptyKeys, { encode: false, arrayFormat: 'indices' }), + // testCase.stringifyOutput.indices, + // 'test case: ' + testCase.input + ', indices', + // ); + // st.deepEqual( + // stringify(testCase.withEmptyKeys, { encode: false, arrayFormat: 'brackets' }), + // testCase.stringifyOutput.brackets, + // 'test case: ' + testCase.input + ', brackets', + // ); + // st.deepEqual( + // stringify(testCase.withEmptyKeys, { encode: false, arrayFormat: 'repeat' }), + // testCase.stringifyOutput.repeat, + // 'test case: ' + testCase.input + ', repeat', + // ); + expect(stringify(testCase.with_empty_keys, { encode: false, arrayFormat: 'indices' })).toBe( + testCase.stringify_output.indices, + ); + expect(stringify(testCase.with_empty_keys, { encode: false, arrayFormat: 'brackets' })).toBe( + testCase.stringify_output.brackets, + ); + expect(stringify(testCase.with_empty_keys, { encode: false, arrayFormat: 'repeat' })).toBe( + testCase.stringify_output.repeat, + ); + }); + }); + + test('edge case with object/arrays', function () { + // st.deepEqual(stringify({ '': { '': [2, 3] } }, { encode: false }), '[][0]=2&[][1]=3'); + // st.deepEqual( + // stringify({ '': { '': [2, 3], a: 2 } }, { encode: false }), + // '[][0]=2&[][1]=3&[a]=2', + // ); + // st.deepEqual( + // stringify({ '': { '': [2, 3] } }, { encode: false, arrayFormat: 'indices' }), + // '[][0]=2&[][1]=3', + // ); + // st.deepEqual( + // stringify({ '': { '': [2, 3], a: 2 } }, { encode: false, arrayFormat: 'indices' }), + // '[][0]=2&[][1]=3&[a]=2', + // ); + expect(stringify({ '': { '': [2, 3] } }, { encode: false })).toBe('[][0]=2&[][1]=3'); + expect(stringify({ '': { '': [2, 3], a: 2 } }, { encode: false })).toBe('[][0]=2&[][1]=3&[a]=2'); + expect(stringify({ '': { '': [2, 3] } }, { encode: false, arrayFormat: 'indices' })).toBe( + '[][0]=2&[][1]=3', + ); + expect(stringify({ '': { '': [2, 3], a: 2 } }, { encode: false, arrayFormat: 'indices' })).toBe( + '[][0]=2&[][1]=3&[a]=2', + ); + }); +}); diff --git a/tests/qs/utils.test.ts b/tests/qs/utils.test.ts new file mode 100644 index 00000000..99994813 --- /dev/null +++ b/tests/qs/utils.test.ts @@ -0,0 +1,169 @@ +import { combine, merge, is_buffer, assign_single_source } from 'together-ai/internal/qs/utils'; + +describe('merge()', function () { + // t.deepEqual(merge(null, true), [null, true], 'merges true into null'); + expect(merge(null, true)).toEqual([null, true]); + + // t.deepEqual(merge(null, [42]), [null, 42], 'merges null into an array'); + expect(merge(null, [42])).toEqual([null, 42]); + + // t.deepEqual( + // merge({ a: 'b' }, { a: 'c' }), + // { a: ['b', 'c'] }, + // 'merges two objects with the same key', + // ); + expect(merge({ a: 'b' }, { a: 'c' })).toEqual({ a: ['b', 'c'] }); + + var oneMerged = merge({ foo: 'bar' }, { foo: { first: '123' } }); + // t.deepEqual( + // oneMerged, + // { foo: ['bar', { first: '123' }] }, + // 'merges a standalone and an object into an array', + // ); + expect(oneMerged).toEqual({ foo: ['bar', { first: '123' }] }); + + var twoMerged = merge({ foo: ['bar', { first: '123' }] }, { foo: { second: '456' } }); + // t.deepEqual( + // twoMerged, + // { foo: { 0: 'bar', 1: { first: '123' }, second: '456' } }, + // 'merges a standalone and two objects into an array', + // ); + expect(twoMerged).toEqual({ foo: { 0: 'bar', 1: { first: '123' }, second: '456' } }); + + var sandwiched = merge({ foo: ['bar', { first: '123', second: '456' }] }, { foo: 'baz' }); + // t.deepEqual( + // sandwiched, + // { foo: ['bar', { first: '123', second: '456' }, 'baz'] }, + // 'merges an object sandwiched by two standalones into an array', + // ); + expect(sandwiched).toEqual({ foo: ['bar', { first: '123', second: '456' }, 'baz'] }); + + var nestedArrays = merge({ foo: ['baz'] }, { foo: ['bar', 'xyzzy'] }); + // t.deepEqual(nestedArrays, { foo: ['baz', 'bar', 'xyzzy'] }); + expect(nestedArrays).toEqual({ foo: ['baz', 'bar', 'xyzzy'] }); + + var noOptionsNonObjectSource = merge({ foo: 'baz' }, 'bar'); + // t.deepEqual(noOptionsNonObjectSource, { foo: 'baz', bar: true }); + expect(noOptionsNonObjectSource).toEqual({ foo: 'baz', bar: true }); + + (typeof Object.defineProperty !== 'function' ? test.skip : test)( + 'avoids invoking array setters unnecessarily', + function () { + var setCount = 0; + var getCount = 0; + var observed: any[] = []; + Object.defineProperty(observed, 0, { + get: function () { + getCount += 1; + return { bar: 'baz' }; + }, + set: function () { + setCount += 1; + }, + }); + merge(observed, [null]); + // st.equal(setCount, 0); + // st.equal(getCount, 1); + expect(setCount).toEqual(0); + expect(getCount).toEqual(1); + observed[0] = observed[0]; + // st.equal(setCount, 1); + // st.equal(getCount, 2); + expect(setCount).toEqual(1); + expect(getCount).toEqual(2); + }, + ); +}); + +test('assign()', function () { + var target = { a: 1, b: 2 }; + var source = { b: 3, c: 4 }; + var result = assign_single_source(target, source); + + expect(result).toEqual(target); + expect(target).toEqual({ a: 1, b: 3, c: 4 }); + expect(source).toEqual({ b: 3, c: 4 }); +}); + +describe('combine()', function () { + test('both arrays', function () { + var a = [1]; + var b = [2]; + var combined = combine(a, b); + + // st.deepEqual(a, [1], 'a is not mutated'); + // st.deepEqual(b, [2], 'b is not mutated'); + // st.notEqual(a, combined, 'a !== combined'); + // st.notEqual(b, combined, 'b !== combined'); + // st.deepEqual(combined, [1, 2], 'combined is a + b'); + expect(a).toEqual([1]); + expect(b).toEqual([2]); + expect(combined).toEqual([1, 2]); + expect(a).not.toEqual(combined); + expect(b).not.toEqual(combined); + }); + + test('one array, one non-array', function () { + var aN = 1; + var a = [aN]; + var bN = 2; + var b = [bN]; + + var combinedAnB = combine(aN, b); + // st.deepEqual(b, [bN], 'b is not mutated'); + // st.notEqual(aN, combinedAnB, 'aN + b !== aN'); + // st.notEqual(a, combinedAnB, 'aN + b !== a'); + // st.notEqual(bN, combinedAnB, 'aN + b !== bN'); + // st.notEqual(b, combinedAnB, 'aN + b !== b'); + // st.deepEqual([1, 2], combinedAnB, 'first argument is array-wrapped when not an array'); + expect(b).toEqual([bN]); + expect(combinedAnB).not.toEqual(aN); + expect(combinedAnB).not.toEqual(a); + expect(combinedAnB).not.toEqual(bN); + expect(combinedAnB).not.toEqual(b); + expect(combinedAnB).toEqual([1, 2]); + + var combinedABn = combine(a, bN); + // st.deepEqual(a, [aN], 'a is not mutated'); + // st.notEqual(aN, combinedABn, 'a + bN !== aN'); + // st.notEqual(a, combinedABn, 'a + bN !== a'); + // st.notEqual(bN, combinedABn, 'a + bN !== bN'); + // st.notEqual(b, combinedABn, 'a + bN !== b'); + // st.deepEqual([1, 2], combinedABn, 'second argument is array-wrapped when not an array'); + expect(a).toEqual([aN]); + expect(combinedABn).not.toEqual(aN); + expect(combinedABn).not.toEqual(a); + expect(combinedABn).not.toEqual(bN); + expect(combinedABn).not.toEqual(b); + expect(combinedABn).toEqual([1, 2]); + }); + + test('neither is an array', function () { + var combined = combine(1, 2); + // st.notEqual(1, combined, '1 + 2 !== 1'); + // st.notEqual(2, combined, '1 + 2 !== 2'); + // st.deepEqual([1, 2], combined, 'both arguments are array-wrapped when not an array'); + expect(combined).not.toEqual(1); + expect(combined).not.toEqual(2); + expect(combined).toEqual([1, 2]); + }); +}); + +test('is_buffer()', function () { + for (const x of [null, undefined, true, false, '', 'abc', 42, 0, NaN, {}, [], function () {}, /a/g]) { + // t.equal(is_buffer(x), false, inspect(x) + ' is not a buffer'); + expect(is_buffer(x)).toEqual(false); + } + + var fakeBuffer = { constructor: Buffer }; + // t.equal(is_buffer(fakeBuffer), false, 'fake buffer is not a buffer'); + expect(is_buffer(fakeBuffer)).toEqual(false); + + var saferBuffer = Buffer.from('abc'); + // t.equal(is_buffer(saferBuffer), true, 'SaferBuffer instance is a buffer'); + expect(is_buffer(saferBuffer)).toEqual(true); + + var buffer = Buffer.from('abc'); + // t.equal(is_buffer(buffer), true, 'real Buffer instance is a buffer'); + expect(is_buffer(buffer)).toEqual(true); +}); diff --git a/tests/stringifyQuery.test.ts b/tests/stringifyQuery.test.ts index 7d71bf2d..7fb211f2 100644 --- a/tests/stringifyQuery.test.ts +++ b/tests/stringifyQuery.test.ts @@ -18,10 +18,4 @@ describe(stringifyQuery, () => { expect(stringifyQuery(input)).toEqual(expected); }); } - - for (const value of [[], {}, new Date()]) { - it(`${JSON.stringify(value)} -> `, () => { - expect(() => stringifyQuery({ value })).toThrow(`Cannot stringify type ${typeof value}`); - }); - } }); From 009034659e0e986fb5355df4f67336efd586b3d7 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 18:09:29 +0000 Subject: [PATCH 18/31] feat(api): manual updates --- .stats.yml | 6 +- MIGRATION.md | 1 + api.md | 2 + bin/migration-config.json | 38 +++++ src/resources/beta/clusters/clusters.ts | 4 + src/resources/beta/clusters/index.ts | 2 + src/resources/beta/clusters/remediations.ts | 141 ++++++++++++++++++ .../beta/clusters/remediations.test.ts | 21 +++ 8 files changed, 212 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index b013946c..1a514860 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 81 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-496df09bdf8bfe14dddc5ba6dc70e219336f51295d2f0671285b4056ec756f21.yml +configured_endpoints: 82 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-b17511a0596b10a96c1af8ea8ab68f43b5a1215f9c8a3ba57ee043215a382b14.yml openapi_spec_hash: 50c3ce80ecc7d25283c96e3544567995 -config_hash: 2ab62260e2c5e9527b47bef3f36e545d +config_hash: 6d7d1c4ec367638d41671e1a4f84808c diff --git a/MIGRATION.md b/MIGRATION.md index 34dbd358..b81c0478 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -55,6 +55,7 @@ client.parents.children.retrieve('c_456', { parent_id: 'p_123' }); This affects the following methods: - `client.beta.clusters.remediations.create()` +- `client.beta.clusters.remediations.retrieve()` - `client.beta.clusters.remediations.list()` - `client.beta.clusters.remediations.approve()` - `client.beta.clusters.remediations.cancel()` diff --git a/api.md b/api.md index d69879a9..d0d617c8 100644 --- a/api.md +++ b/api.md @@ -89,6 +89,7 @@ Methods: Types: - RemediationCreateResponse +- RemediationRetrieveResponse - RemediationListResponse - RemediationApproveResponse - RemediationCancelResponse @@ -97,6 +98,7 @@ Types: Methods: - client.beta.clusters.remediations.create(instanceID, { ...params }) -> RemediationCreateResponse +- client.beta.clusters.remediations.retrieve(remediationID, { ...params }) -> RemediationRetrieveResponse - client.beta.clusters.remediations.list(instanceID, { ...params }) -> RemediationListResponse - client.beta.clusters.remediations.approve(remediationID, { ...params }) -> RemediationApproveResponse - client.beta.clusters.remediations.cancel(remediationID, { ...params }) -> RemediationCancelResponse diff --git a/bin/migration-config.json b/bin/migration-config.json index 096eef54..281ff1dc 100644 --- a/bin/migration-config.json +++ b/bin/migration-config.json @@ -40,6 +40,44 @@ } ] }, + { + "base": "beta.clusters.remediations", + "name": "retrieve", + "params": [ + { + "type": "param", + "key": "remediation_id", + "location": "path" + }, + { + "type": "params", + "maybeOverload": false + }, + { + "type": "options" + } + ], + "oldParams": [ + { + "type": "param", + "key": "cluster_id", + "location": "path" + }, + { + "type": "param", + "key": "instance_id", + "location": "path" + }, + { + "type": "param", + "key": "remediation_id", + "location": "path" + }, + { + "type": "options" + } + ] + }, { "base": "beta.clusters.remediations", "name": "list", diff --git a/src/resources/beta/clusters/clusters.ts b/src/resources/beta/clusters/clusters.ts index ab25408a..2b90cafb 100644 --- a/src/resources/beta/clusters/clusters.ts +++ b/src/resources/beta/clusters/clusters.ts @@ -13,6 +13,8 @@ import { RemediationListResponse, RemediationRejectParams, RemediationRejectResponse, + RemediationRetrieveParams, + RemediationRetrieveResponse, Remediations, } from './remediations'; import * as StorageAPI from './storage'; @@ -1218,11 +1220,13 @@ export declare namespace Clusters { export { Remediations as Remediations, type RemediationCreateResponse as RemediationCreateResponse, + type RemediationRetrieveResponse as RemediationRetrieveResponse, type RemediationListResponse as RemediationListResponse, type RemediationApproveResponse as RemediationApproveResponse, type RemediationCancelResponse as RemediationCancelResponse, type RemediationRejectResponse as RemediationRejectResponse, type RemediationCreateParams as RemediationCreateParams, + type RemediationRetrieveParams as RemediationRetrieveParams, type RemediationListParams as RemediationListParams, type RemediationApproveParams as RemediationApproveParams, type RemediationCancelParams as RemediationCancelParams, diff --git a/src/resources/beta/clusters/index.ts b/src/resources/beta/clusters/index.ts index 7fdd8798..01b79c70 100644 --- a/src/resources/beta/clusters/index.ts +++ b/src/resources/beta/clusters/index.ts @@ -13,11 +13,13 @@ export { export { Remediations, type RemediationCreateResponse, + type RemediationRetrieveResponse, type RemediationListResponse, type RemediationApproveResponse, type RemediationCancelResponse, type RemediationRejectResponse, type RemediationCreateParams, + type RemediationRetrieveParams, type RemediationListParams, type RemediationApproveParams, type RemediationCancelParams, diff --git a/src/resources/beta/clusters/remediations.ts b/src/resources/beta/clusters/remediations.ts index d548ed11..6dc6907d 100644 --- a/src/resources/beta/clusters/remediations.ts +++ b/src/resources/beta/clusters/remediations.ts @@ -30,6 +30,22 @@ export class Remediations extends APIResource { }); } + /** + * Retrieve the status of a specific remdiation on a specific instance in a + * specific cluster. + */ + retrieve( + remediationID: string, + params: RemediationRetrieveParams, + options?: RequestOptions, + ): APIPromise { + const { cluster_id, instance_id } = params; + return this._client.get( + path`/compute/clusters/${cluster_id}/instances/${instance_id}/remediations/${remediationID}`, + options, + ); + } + /** * Lists remediations for an instance or cluster. Use instances/- as wildcard to * list all remediations in a cluster. @@ -216,6 +232,117 @@ export interface RemediationCreateResponse { update_time?: string; } +/** + * Remediation represents a node remediation request for an instance. An instance + * can have multiple remediations over time (e.g., failed attempts followed by + * retries). + */ +export interface RemediationRetrieveResponse { + id: string; + + cluster_id: string; + + instance_id: string; + + /** + * Remediation mode specifies how the remediation should be performed. + * + * - `REMEDIATION_MODE_VM_ONLY`: Deletes the VM and provisions a new one on any + * available host. + * - `REMEDIATION_MODE_HOST_AWARE`: Cordons the host, deletes the VM, and + * provisions a new one on a different host. + */ + mode: + | 'REMEDIATION_MODE_VM_ONLY' + | 'REMEDIATION_MODE_HOST_AWARE' + | 'REMEDIATION_MODE_EVICT_WITHOUT_REPLACEMENT' + | 'REMEDIATION_MODE_REBOOT_VM'; + + /** + * RemediationState represents the lifecycle state of a remediation. + * + * - `PENDING_APPROVAL`: Awaiting approval before processing can begin. + * - `PENDING`: Approved and queued for processing. + * - `RUNNING`: Actively being processed. + * - `SUCCEEDED`: Successfully completed. + * - `FAILED`: Failed with an error. + * - `CANCELLED`: Cancelled by user or system. + * - `AUTO_RESOLVED`: The underlying issue was automatically resolved before + * processing. + */ + state: 'PENDING_APPROVAL' | 'PENDING' | 'RUNNING' | 'SUCCEEDED' | 'FAILED' | 'CANCELLED' | 'AUTO_RESOLVED'; + + /** + * RemediationTrigger specifies how the remediation was triggered. + * + * - `REMEDIATION_TRIGGER_MANUAL`: A user-initiated remediation (either via web UI + * or API call). + * - `REMEDIATION_TRIGGER_AUTOMATED`: A system-initiated remediation that requires + * approval. + */ + trigger: 'REMEDIATION_TRIGGER_MANUAL' | 'REMEDIATION_TRIGGER_AUTOMATED'; + + /** + * Active health check run ID (UUID) that triggered this remediation. + */ + active_health_check_run_id?: string; + + /** + * When the remediation was created. + */ + create_time?: string; + + /** + * When the remediation completed. + */ + end_time?: string; + + /** + * Error message if the remediation failed. + */ + error_message?: string; + + /** + * Passive health check event ID that triggered this remediation. + */ + passive_health_check_event_id?: string; + + /** + * User-provided reason for the remediation. + */ + reason?: string; + + /** + * Who requested the remediation. + */ + requested_by?: string; + + /** + * Review comment. + */ + review_comment?: string; + + /** + * When the remediation was reviewed. + */ + review_time?: string; + + /** + * Who reviewed the remediation. + */ + reviewed_by?: string; + + /** + * When processing started. + */ + start_time?: string; + + /** + * When the remediation was last updated. + */ + update_time?: string; +} + /** * ListRemediationsResponse is the response for ListRemediations. */ @@ -720,6 +847,18 @@ export interface RemediationCreateParams { reason?: string; } +export interface RemediationRetrieveParams { + /** + * The cluster ID. + */ + cluster_id: string; + + /** + * The instance ID. + */ + instance_id: string; +} + export interface RemediationListParams { /** * Path param: The cluster ID. @@ -815,11 +954,13 @@ export interface RemediationRejectParams { export declare namespace Remediations { export { type RemediationCreateResponse as RemediationCreateResponse, + type RemediationRetrieveResponse as RemediationRetrieveResponse, type RemediationListResponse as RemediationListResponse, type RemediationApproveResponse as RemediationApproveResponse, type RemediationCancelResponse as RemediationCancelResponse, type RemediationRejectResponse as RemediationRejectResponse, type RemediationCreateParams as RemediationCreateParams, + type RemediationRetrieveParams as RemediationRetrieveParams, type RemediationListParams as RemediationListParams, type RemediationApproveParams as RemediationApproveParams, type RemediationCancelParams as RemediationCancelParams, diff --git a/tests/api-resources/beta/clusters/remediations.test.ts b/tests/api-resources/beta/clusters/remediations.test.ts index afba913e..7e1b931f 100644 --- a/tests/api-resources/beta/clusters/remediations.test.ts +++ b/tests/api-resources/beta/clusters/remediations.test.ts @@ -31,6 +31,27 @@ describe('resource remediations', () => { }); }); + test('retrieve: only required params', async () => { + const responsePromise = client.beta.clusters.remediations.retrieve('remediation_id', { + cluster_id: 'cluster_id', + instance_id: 'instance_id', + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('retrieve: required and optional params', async () => { + const response = await client.beta.clusters.remediations.retrieve('remediation_id', { + cluster_id: 'cluster_id', + instance_id: 'instance_id', + }); + }); + test('list: only required params', async () => { const responsePromise = client.beta.clusters.remediations.list('instance_id', { cluster_id: 'cluster_id', From 72b7cb899ef411e8ec19e2a8d38852b057184f4d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 18:18:51 +0000 Subject: [PATCH 19/31] fix(api): remove trigger parameter from remediations list method --- .stats.yml | 4 +- src/resources/beta/clusters/remediations.ts | 63 +++++++++---------- src/resources/models/models.ts | 3 +- .../beta/clusters/remediations.test.ts | 1 - 4 files changed, 33 insertions(+), 38 deletions(-) diff --git a/.stats.yml b/.stats.yml index 1a514860..2ddc3b42 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 82 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-b17511a0596b10a96c1af8ea8ab68f43b5a1215f9c8a3ba57ee043215a382b14.yml -openapi_spec_hash: 50c3ce80ecc7d25283c96e3544567995 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-c8200db997f998a51b2b469b69fcc3a24032e50b45aaaf340c2c176473393adc.yml +openapi_spec_hash: 2544f6840cf7ad23eae3fe8bffed4c4b config_hash: 6d7d1c4ec367638d41671e1a4f84808c diff --git a/src/resources/beta/clusters/remediations.ts b/src/resources/beta/clusters/remediations.ts index 6dc6907d..6fa66fdb 100644 --- a/src/resources/beta/clusters/remediations.ts +++ b/src/resources/beta/clusters/remediations.ts @@ -9,13 +9,12 @@ export class Remediations extends APIResource { /** * Creates a new remediation for an instance. * - * If mode is unspecified, it defaults to VM_ONLY. If trigger is unspecified, it - * defaults to MANUAL. + * Remediations created via the API goes directly to PENDING state. * - * For MANUAL triggers: The remediation goes directly to PENDING state. - * - * For AUTOMATED triggers: The remediation is created with PENDING_APPROVAL state. - * The caller must then use ApproveRemediation to start the remediation process. + * Our system may trigger automated remediations that require approval. These + * remediations are created with PENDING_APPROVAL state. The user must call + * /approve to start the actual remediation process. These operations can also be + * rejected by calling /reject. */ create( instanceID: string, @@ -47,8 +46,7 @@ export class Remediations extends APIResource { } /** - * Lists remediations for an instance or cluster. Use instances/- as wildcard to - * list all remediations in a cluster. + * Lists remediations for an instance or cluster. */ list( instanceID: string, @@ -818,7 +816,7 @@ export interface RemediationRejectResponse { export interface RemediationCreateParams { /** - * Path param: The cluster ID. + * Path param */ cluster_id: string; @@ -837,7 +835,7 @@ export interface RemediationCreateParams { | 'REMEDIATION_MODE_REBOOT_VM'; /** - * Query param: Optional. Client-specified ID for idempotency. + * Query param: Client-specified ID for idempotency. */ remediation_id?: string; @@ -848,26 +846,20 @@ export interface RemediationCreateParams { } export interface RemediationRetrieveParams { - /** - * The cluster ID. - */ cluster_id: string; - /** - * The instance ID. - */ instance_id: string; } export interface RemediationListParams { /** - * Path param: The cluster ID. + * Path param */ cluster_id: string; /** - * Query param: Optional. Filter by remediation mode. Returns only remediations - * matching the specified mode. + * Query param: Filter by remediation mode. Returns only remediations matching the + * specified mode. */ mode?: | 'REMEDIATION_MODE_VM_ONLY' @@ -876,43 +868,46 @@ export interface RemediationListParams { | 'REMEDIATION_MODE_REBOOT_VM'; /** - * Query param: Optional. Order by expression. + * Query param: Order by expression. */ order_by?: string; /** - * Query param: Optional. Maximum results to return. + * Query param: Maximum results to return. */ page_size?: number; /** - * Query param: Optional. Pagination token from previous request. + * Query param: Pagination token from previous request. */ page_token?: string; /** - * Query param: Optional. Filter by state(s). Returns remediations matching any of - * the specified states. + * Query param: Filter by state(s). Returns remediations matching any of the + * specified states. + * + * - `PENDING_APPROVAL`: Awaiting approval before processing can begin. + * - `PENDING`: Approved and queued for processing. + * - `RUNNING`: Actively being processed. + * - `SUCCEEDED`: Successfully completed. + * - `FAILED`: Failed with an error. + * - `CANCELLED`: Cancelled by user or system. + * - `AUTO_RESOLVED`: The underlying issue was automatically resolved before + * processing. */ state?: Array< 'PENDING_APPROVAL' | 'PENDING' | 'RUNNING' | 'SUCCEEDED' | 'FAILED' | 'CANCELLED' | 'AUTO_RESOLVED' >; - - /** - * Query param: Optional. Filter by trigger type. Returns only remediations - * matching the specified trigger. - */ - trigger?: 'REMEDIATION_TRIGGER_MANUAL' | 'REMEDIATION_TRIGGER_AUTOMATED'; } export interface RemediationApproveParams { /** - * Path param: The cluster ID. + * Path param */ cluster_id: string; /** - * Path param: The instance ID. + * Path param */ instance_id: string; @@ -936,12 +931,12 @@ export interface RemediationCancelParams { export interface RemediationRejectParams { /** - * Path param: The cluster ID. + * Path param */ cluster_id: string; /** - * Path param: The instance ID. + * Path param */ instance_id: string; diff --git a/src/resources/models/models.ts b/src/resources/models/models.ts index 276ae66b..eb774720 100644 --- a/src/resources/models/models.ts +++ b/src/resources/models/models.ts @@ -10,7 +10,8 @@ export class Models extends APIResource { uploads: UploadsAPI.Uploads = new UploadsAPI.Uploads(this._client); /** - * Lists all of Together's open-source models + * Lists all of Together's open-source models and metadata including pricing, chat + * template, and context. * * @example * ```ts diff --git a/tests/api-resources/beta/clusters/remediations.test.ts b/tests/api-resources/beta/clusters/remediations.test.ts index 7e1b931f..8b2af0d6 100644 --- a/tests/api-resources/beta/clusters/remediations.test.ts +++ b/tests/api-resources/beta/clusters/remediations.test.ts @@ -73,7 +73,6 @@ describe('resource remediations', () => { page_size: 0, page_token: 'page_token', state: ['PENDING_APPROVAL'], - trigger: 'REMEDIATION_TRIGGER_MANUAL', }); }); From d1782a2791b23ca07f1b1e5db8ae5de4f8c8f294 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 18:27:22 +0000 Subject: [PATCH 20/31] feat(api): manual updates --- .stats.yml | 2 +- api.md | 16 +- src/resources/beta/clusters/clusters.ts | 132 +---- src/resources/beta/clusters/index.ts | 6 +- src/resources/beta/clusters/remediations.ts | 584 +------------------- 5 files changed, 19 insertions(+), 721 deletions(-) diff --git a/.stats.yml b/.stats.yml index 2ddc3b42..8113129d 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 82 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-c8200db997f998a51b2b469b69fcc3a24032e50b45aaaf340c2c176473393adc.yml openapi_spec_hash: 2544f6840cf7ad23eae3fe8bffed4c4b -config_hash: 6d7d1c4ec367638d41671e1a4f84808c +config_hash: b35d5968fb07cce1c1be735f874898b1 diff --git a/api.md b/api.md index d0d617c8..ca06cda9 100644 --- a/api.md +++ b/api.md @@ -88,21 +88,17 @@ Methods: Types: -- RemediationCreateResponse -- RemediationRetrieveResponse +- Remediation - RemediationListResponse -- RemediationApproveResponse -- RemediationCancelResponse -- RemediationRejectResponse Methods: -- client.beta.clusters.remediations.create(instanceID, { ...params }) -> RemediationCreateResponse -- client.beta.clusters.remediations.retrieve(remediationID, { ...params }) -> RemediationRetrieveResponse +- client.beta.clusters.remediations.create(instanceID, { ...params }) -> Remediation +- client.beta.clusters.remediations.retrieve(remediationID, { ...params }) -> Remediation - client.beta.clusters.remediations.list(instanceID, { ...params }) -> RemediationListResponse -- client.beta.clusters.remediations.approve(remediationID, { ...params }) -> RemediationApproveResponse -- client.beta.clusters.remediations.cancel(remediationID, { ...params }) -> RemediationCancelResponse -- client.beta.clusters.remediations.reject(remediationID, { ...params }) -> RemediationRejectResponse +- client.beta.clusters.remediations.approve(remediationID, { ...params }) -> Remediation +- client.beta.clusters.remediations.cancel(remediationID, { ...params }) -> Remediation +- client.beta.clusters.remediations.reject(remediationID, { ...params }) -> Remediation ### Storage diff --git a/src/resources/beta/clusters/clusters.ts b/src/resources/beta/clusters/clusters.ts index 2b90cafb..77243917 100644 --- a/src/resources/beta/clusters/clusters.ts +++ b/src/resources/beta/clusters/clusters.ts @@ -3,18 +3,14 @@ import { APIResource } from '../../../core/resource'; import * as RemediationsAPI from './remediations'; import { + Remediation, RemediationApproveParams, - RemediationApproveResponse, RemediationCancelParams, - RemediationCancelResponse, RemediationCreateParams, - RemediationCreateResponse, RemediationListParams, RemediationListResponse, RemediationRejectParams, - RemediationRejectResponse, RemediationRetrieveParams, - RemediationRetrieveResponse, Remediations, } from './remediations'; import * as StorageAPI from './storage'; @@ -300,7 +296,7 @@ export namespace Cluster { * can have multiple remediations over time (e.g., failed attempts followed by * retries). */ - latest_remediation?: GPUWorkerNode.LatestRemediation; + latest_remediation?: RemediationsAPI.Remediation; slurm_worker_hostname?: string; } @@ -325,124 +321,6 @@ export namespace Cluster { */ transition_time: string; } - - /** - * Remediation represents a node remediation request for an instance. An instance - * can have multiple remediations over time (e.g., failed attempts followed by - * retries). - */ - export interface LatestRemediation { - id: string; - - cluster_id: string; - - instance_id: string; - - /** - * Remediation mode specifies how the remediation should be performed. - * - * - `REMEDIATION_MODE_VM_ONLY`: Deletes the VM and provisions a new one on any - * available host. - * - `REMEDIATION_MODE_HOST_AWARE`: Cordons the host, deletes the VM, and - * provisions a new one on a different host. - */ - mode: - | 'REMEDIATION_MODE_VM_ONLY' - | 'REMEDIATION_MODE_HOST_AWARE' - | 'REMEDIATION_MODE_EVICT_WITHOUT_REPLACEMENT' - | 'REMEDIATION_MODE_REBOOT_VM'; - - /** - * RemediationState represents the lifecycle state of a remediation. - * - * - `PENDING_APPROVAL`: Awaiting approval before processing can begin. - * - `PENDING`: Approved and queued for processing. - * - `RUNNING`: Actively being processed. - * - `SUCCEEDED`: Successfully completed. - * - `FAILED`: Failed with an error. - * - `CANCELLED`: Cancelled by user or system. - * - `AUTO_RESOLVED`: The underlying issue was automatically resolved before - * processing. - */ - state: - | 'PENDING_APPROVAL' - | 'PENDING' - | 'RUNNING' - | 'SUCCEEDED' - | 'FAILED' - | 'CANCELLED' - | 'AUTO_RESOLVED'; - - /** - * RemediationTrigger specifies how the remediation was triggered. - * - * - `REMEDIATION_TRIGGER_MANUAL`: A user-initiated remediation (either via web UI - * or API call). - * - `REMEDIATION_TRIGGER_AUTOMATED`: A system-initiated remediation that requires - * approval. - */ - trigger: 'REMEDIATION_TRIGGER_MANUAL' | 'REMEDIATION_TRIGGER_AUTOMATED'; - - /** - * Active health check run ID (UUID) that triggered this remediation. - */ - active_health_check_run_id?: string; - - /** - * When the remediation was created. - */ - create_time?: string; - - /** - * When the remediation completed. - */ - end_time?: string; - - /** - * Error message if the remediation failed. - */ - error_message?: string; - - /** - * Passive health check event ID that triggered this remediation. - */ - passive_health_check_event_id?: string; - - /** - * User-provided reason for the remediation. - */ - reason?: string; - - /** - * Who requested the remediation. - */ - requested_by?: string; - - /** - * Review comment. - */ - review_comment?: string; - - /** - * When the remediation was reviewed. - */ - review_time?: string; - - /** - * Who reviewed the remediation. - */ - reviewed_by?: string; - - /** - * When processing started. - */ - start_time?: string; - - /** - * When the remediation was last updated. - */ - update_time?: string; - } } export interface PhaseTransition { @@ -1219,12 +1097,8 @@ export declare namespace Clusters { export { Remediations as Remediations, - type RemediationCreateResponse as RemediationCreateResponse, - type RemediationRetrieveResponse as RemediationRetrieveResponse, + type Remediation as Remediation, type RemediationListResponse as RemediationListResponse, - type RemediationApproveResponse as RemediationApproveResponse, - type RemediationCancelResponse as RemediationCancelResponse, - type RemediationRejectResponse as RemediationRejectResponse, type RemediationCreateParams as RemediationCreateParams, type RemediationRetrieveParams as RemediationRetrieveParams, type RemediationListParams as RemediationListParams, diff --git a/src/resources/beta/clusters/index.ts b/src/resources/beta/clusters/index.ts index 01b79c70..815c8bd5 100644 --- a/src/resources/beta/clusters/index.ts +++ b/src/resources/beta/clusters/index.ts @@ -12,12 +12,8 @@ export { } from './clusters'; export { Remediations, - type RemediationCreateResponse, - type RemediationRetrieveResponse, + type Remediation, type RemediationListResponse, - type RemediationApproveResponse, - type RemediationCancelResponse, - type RemediationRejectResponse, type RemediationCreateParams, type RemediationRetrieveParams, type RemediationListParams, diff --git a/src/resources/beta/clusters/remediations.ts b/src/resources/beta/clusters/remediations.ts index 6fa66fdb..25d1f4a7 100644 --- a/src/resources/beta/clusters/remediations.ts +++ b/src/resources/beta/clusters/remediations.ts @@ -20,7 +20,7 @@ export class Remediations extends APIResource { instanceID: string, params: RemediationCreateParams, options?: RequestOptions, - ): APIPromise { + ): APIPromise { const { cluster_id, remediation_id, ...body } = params; return this._client.post(path`/compute/clusters/${cluster_id}/instances/${instanceID}/remediations`, { query: { remediation_id }, @@ -37,7 +37,7 @@ export class Remediations extends APIResource { remediationID: string, params: RemediationRetrieveParams, options?: RequestOptions, - ): APIPromise { + ): APIPromise { const { cluster_id, instance_id } = params; return this._client.get( path`/compute/clusters/${cluster_id}/instances/${instance_id}/remediations/${remediationID}`, @@ -73,7 +73,7 @@ export class Remediations extends APIResource { remediationID: string, params: RemediationApproveParams, options?: RequestOptions, - ): APIPromise { + ): APIPromise { const { cluster_id, instance_id, ...body } = params; return this._client.post( path`/compute/clusters/${cluster_id}/instances/${instance_id}/remediations/${remediationID}/approve`, @@ -90,7 +90,7 @@ export class Remediations extends APIResource { remediationID: string, params: RemediationCancelParams, options?: RequestOptions, - ): APIPromise { + ): APIPromise { const { cluster_id, instance_id } = params; return this._client.post( path`/compute/clusters/${cluster_id}/instances/${instance_id}/remediations/${remediationID}/cancel`, @@ -110,7 +110,7 @@ export class Remediations extends APIResource { remediationID: string, params: RemediationRejectParams, options?: RequestOptions, - ): APIPromise { + ): APIPromise { const { cluster_id, instance_id, ...body } = params; return this._client.post( path`/compute/clusters/${cluster_id}/instances/${instance_id}/remediations/${remediationID}/reject`, @@ -124,118 +124,7 @@ export class Remediations extends APIResource { * can have multiple remediations over time (e.g., failed attempts followed by * retries). */ -export interface RemediationCreateResponse { - id: string; - - cluster_id: string; - - instance_id: string; - - /** - * Remediation mode specifies how the remediation should be performed. - * - * - `REMEDIATION_MODE_VM_ONLY`: Deletes the VM and provisions a new one on any - * available host. - * - `REMEDIATION_MODE_HOST_AWARE`: Cordons the host, deletes the VM, and - * provisions a new one on a different host. - */ - mode: - | 'REMEDIATION_MODE_VM_ONLY' - | 'REMEDIATION_MODE_HOST_AWARE' - | 'REMEDIATION_MODE_EVICT_WITHOUT_REPLACEMENT' - | 'REMEDIATION_MODE_REBOOT_VM'; - - /** - * RemediationState represents the lifecycle state of a remediation. - * - * - `PENDING_APPROVAL`: Awaiting approval before processing can begin. - * - `PENDING`: Approved and queued for processing. - * - `RUNNING`: Actively being processed. - * - `SUCCEEDED`: Successfully completed. - * - `FAILED`: Failed with an error. - * - `CANCELLED`: Cancelled by user or system. - * - `AUTO_RESOLVED`: The underlying issue was automatically resolved before - * processing. - */ - state: 'PENDING_APPROVAL' | 'PENDING' | 'RUNNING' | 'SUCCEEDED' | 'FAILED' | 'CANCELLED' | 'AUTO_RESOLVED'; - - /** - * RemediationTrigger specifies how the remediation was triggered. - * - * - `REMEDIATION_TRIGGER_MANUAL`: A user-initiated remediation (either via web UI - * or API call). - * - `REMEDIATION_TRIGGER_AUTOMATED`: A system-initiated remediation that requires - * approval. - */ - trigger: 'REMEDIATION_TRIGGER_MANUAL' | 'REMEDIATION_TRIGGER_AUTOMATED'; - - /** - * Active health check run ID (UUID) that triggered this remediation. - */ - active_health_check_run_id?: string; - - /** - * When the remediation was created. - */ - create_time?: string; - - /** - * When the remediation completed. - */ - end_time?: string; - - /** - * Error message if the remediation failed. - */ - error_message?: string; - - /** - * Passive health check event ID that triggered this remediation. - */ - passive_health_check_event_id?: string; - - /** - * User-provided reason for the remediation. - */ - reason?: string; - - /** - * Who requested the remediation. - */ - requested_by?: string; - - /** - * Review comment. - */ - review_comment?: string; - - /** - * When the remediation was reviewed. - */ - review_time?: string; - - /** - * Who reviewed the remediation. - */ - reviewed_by?: string; - - /** - * When processing started. - */ - start_time?: string; - - /** - * When the remediation was last updated. - */ - update_time?: string; -} - -/** - * Remediation represents a node remediation request for an instance. An instance - * can have multiple remediations over time (e.g., failed attempts followed by - * retries). - */ -export interface RemediationRetrieveResponse { +export interface Remediation { id: string; cluster_id: string; @@ -358,460 +247,7 @@ export interface RemediationListResponse { /** * The list of remediations. */ - remediations: Array; -} - -export namespace RemediationListResponse { - /** - * Remediation represents a node remediation request for an instance. An instance - * can have multiple remediations over time (e.g., failed attempts followed by - * retries). - */ - export interface Remediation { - id: string; - - cluster_id: string; - - instance_id: string; - - /** - * Remediation mode specifies how the remediation should be performed. - * - * - `REMEDIATION_MODE_VM_ONLY`: Deletes the VM and provisions a new one on any - * available host. - * - `REMEDIATION_MODE_HOST_AWARE`: Cordons the host, deletes the VM, and - * provisions a new one on a different host. - */ - mode: - | 'REMEDIATION_MODE_VM_ONLY' - | 'REMEDIATION_MODE_HOST_AWARE' - | 'REMEDIATION_MODE_EVICT_WITHOUT_REPLACEMENT' - | 'REMEDIATION_MODE_REBOOT_VM'; - - /** - * RemediationState represents the lifecycle state of a remediation. - * - * - `PENDING_APPROVAL`: Awaiting approval before processing can begin. - * - `PENDING`: Approved and queued for processing. - * - `RUNNING`: Actively being processed. - * - `SUCCEEDED`: Successfully completed. - * - `FAILED`: Failed with an error. - * - `CANCELLED`: Cancelled by user or system. - * - `AUTO_RESOLVED`: The underlying issue was automatically resolved before - * processing. - */ - state: - | 'PENDING_APPROVAL' - | 'PENDING' - | 'RUNNING' - | 'SUCCEEDED' - | 'FAILED' - | 'CANCELLED' - | 'AUTO_RESOLVED'; - - /** - * RemediationTrigger specifies how the remediation was triggered. - * - * - `REMEDIATION_TRIGGER_MANUAL`: A user-initiated remediation (either via web UI - * or API call). - * - `REMEDIATION_TRIGGER_AUTOMATED`: A system-initiated remediation that requires - * approval. - */ - trigger: 'REMEDIATION_TRIGGER_MANUAL' | 'REMEDIATION_TRIGGER_AUTOMATED'; - - /** - * Active health check run ID (UUID) that triggered this remediation. - */ - active_health_check_run_id?: string; - - /** - * When the remediation was created. - */ - create_time?: string; - - /** - * When the remediation completed. - */ - end_time?: string; - - /** - * Error message if the remediation failed. - */ - error_message?: string; - - /** - * Passive health check event ID that triggered this remediation. - */ - passive_health_check_event_id?: string; - - /** - * User-provided reason for the remediation. - */ - reason?: string; - - /** - * Who requested the remediation. - */ - requested_by?: string; - - /** - * Review comment. - */ - review_comment?: string; - - /** - * When the remediation was reviewed. - */ - review_time?: string; - - /** - * Who reviewed the remediation. - */ - reviewed_by?: string; - - /** - * When processing started. - */ - start_time?: string; - - /** - * When the remediation was last updated. - */ - update_time?: string; - } -} - -/** - * Remediation represents a node remediation request for an instance. An instance - * can have multiple remediations over time (e.g., failed attempts followed by - * retries). - */ -export interface RemediationApproveResponse { - id: string; - - cluster_id: string; - - instance_id: string; - - /** - * Remediation mode specifies how the remediation should be performed. - * - * - `REMEDIATION_MODE_VM_ONLY`: Deletes the VM and provisions a new one on any - * available host. - * - `REMEDIATION_MODE_HOST_AWARE`: Cordons the host, deletes the VM, and - * provisions a new one on a different host. - */ - mode: - | 'REMEDIATION_MODE_VM_ONLY' - | 'REMEDIATION_MODE_HOST_AWARE' - | 'REMEDIATION_MODE_EVICT_WITHOUT_REPLACEMENT' - | 'REMEDIATION_MODE_REBOOT_VM'; - - /** - * RemediationState represents the lifecycle state of a remediation. - * - * - `PENDING_APPROVAL`: Awaiting approval before processing can begin. - * - `PENDING`: Approved and queued for processing. - * - `RUNNING`: Actively being processed. - * - `SUCCEEDED`: Successfully completed. - * - `FAILED`: Failed with an error. - * - `CANCELLED`: Cancelled by user or system. - * - `AUTO_RESOLVED`: The underlying issue was automatically resolved before - * processing. - */ - state: 'PENDING_APPROVAL' | 'PENDING' | 'RUNNING' | 'SUCCEEDED' | 'FAILED' | 'CANCELLED' | 'AUTO_RESOLVED'; - - /** - * RemediationTrigger specifies how the remediation was triggered. - * - * - `REMEDIATION_TRIGGER_MANUAL`: A user-initiated remediation (either via web UI - * or API call). - * - `REMEDIATION_TRIGGER_AUTOMATED`: A system-initiated remediation that requires - * approval. - */ - trigger: 'REMEDIATION_TRIGGER_MANUAL' | 'REMEDIATION_TRIGGER_AUTOMATED'; - - /** - * Active health check run ID (UUID) that triggered this remediation. - */ - active_health_check_run_id?: string; - - /** - * When the remediation was created. - */ - create_time?: string; - - /** - * When the remediation completed. - */ - end_time?: string; - - /** - * Error message if the remediation failed. - */ - error_message?: string; - - /** - * Passive health check event ID that triggered this remediation. - */ - passive_health_check_event_id?: string; - - /** - * User-provided reason for the remediation. - */ - reason?: string; - - /** - * Who requested the remediation. - */ - requested_by?: string; - - /** - * Review comment. - */ - review_comment?: string; - - /** - * When the remediation was reviewed. - */ - review_time?: string; - - /** - * Who reviewed the remediation. - */ - reviewed_by?: string; - - /** - * When processing started. - */ - start_time?: string; - - /** - * When the remediation was last updated. - */ - update_time?: string; -} - -/** - * Remediation represents a node remediation request for an instance. An instance - * can have multiple remediations over time (e.g., failed attempts followed by - * retries). - */ -export interface RemediationCancelResponse { - id: string; - - cluster_id: string; - - instance_id: string; - - /** - * Remediation mode specifies how the remediation should be performed. - * - * - `REMEDIATION_MODE_VM_ONLY`: Deletes the VM and provisions a new one on any - * available host. - * - `REMEDIATION_MODE_HOST_AWARE`: Cordons the host, deletes the VM, and - * provisions a new one on a different host. - */ - mode: - | 'REMEDIATION_MODE_VM_ONLY' - | 'REMEDIATION_MODE_HOST_AWARE' - | 'REMEDIATION_MODE_EVICT_WITHOUT_REPLACEMENT' - | 'REMEDIATION_MODE_REBOOT_VM'; - - /** - * RemediationState represents the lifecycle state of a remediation. - * - * - `PENDING_APPROVAL`: Awaiting approval before processing can begin. - * - `PENDING`: Approved and queued for processing. - * - `RUNNING`: Actively being processed. - * - `SUCCEEDED`: Successfully completed. - * - `FAILED`: Failed with an error. - * - `CANCELLED`: Cancelled by user or system. - * - `AUTO_RESOLVED`: The underlying issue was automatically resolved before - * processing. - */ - state: 'PENDING_APPROVAL' | 'PENDING' | 'RUNNING' | 'SUCCEEDED' | 'FAILED' | 'CANCELLED' | 'AUTO_RESOLVED'; - - /** - * RemediationTrigger specifies how the remediation was triggered. - * - * - `REMEDIATION_TRIGGER_MANUAL`: A user-initiated remediation (either via web UI - * or API call). - * - `REMEDIATION_TRIGGER_AUTOMATED`: A system-initiated remediation that requires - * approval. - */ - trigger: 'REMEDIATION_TRIGGER_MANUAL' | 'REMEDIATION_TRIGGER_AUTOMATED'; - - /** - * Active health check run ID (UUID) that triggered this remediation. - */ - active_health_check_run_id?: string; - - /** - * When the remediation was created. - */ - create_time?: string; - - /** - * When the remediation completed. - */ - end_time?: string; - - /** - * Error message if the remediation failed. - */ - error_message?: string; - - /** - * Passive health check event ID that triggered this remediation. - */ - passive_health_check_event_id?: string; - - /** - * User-provided reason for the remediation. - */ - reason?: string; - - /** - * Who requested the remediation. - */ - requested_by?: string; - - /** - * Review comment. - */ - review_comment?: string; - - /** - * When the remediation was reviewed. - */ - review_time?: string; - - /** - * Who reviewed the remediation. - */ - reviewed_by?: string; - - /** - * When processing started. - */ - start_time?: string; - - /** - * When the remediation was last updated. - */ - update_time?: string; -} - -/** - * Remediation represents a node remediation request for an instance. An instance - * can have multiple remediations over time (e.g., failed attempts followed by - * retries). - */ -export interface RemediationRejectResponse { - id: string; - - cluster_id: string; - - instance_id: string; - - /** - * Remediation mode specifies how the remediation should be performed. - * - * - `REMEDIATION_MODE_VM_ONLY`: Deletes the VM and provisions a new one on any - * available host. - * - `REMEDIATION_MODE_HOST_AWARE`: Cordons the host, deletes the VM, and - * provisions a new one on a different host. - */ - mode: - | 'REMEDIATION_MODE_VM_ONLY' - | 'REMEDIATION_MODE_HOST_AWARE' - | 'REMEDIATION_MODE_EVICT_WITHOUT_REPLACEMENT' - | 'REMEDIATION_MODE_REBOOT_VM'; - - /** - * RemediationState represents the lifecycle state of a remediation. - * - * - `PENDING_APPROVAL`: Awaiting approval before processing can begin. - * - `PENDING`: Approved and queued for processing. - * - `RUNNING`: Actively being processed. - * - `SUCCEEDED`: Successfully completed. - * - `FAILED`: Failed with an error. - * - `CANCELLED`: Cancelled by user or system. - * - `AUTO_RESOLVED`: The underlying issue was automatically resolved before - * processing. - */ - state: 'PENDING_APPROVAL' | 'PENDING' | 'RUNNING' | 'SUCCEEDED' | 'FAILED' | 'CANCELLED' | 'AUTO_RESOLVED'; - - /** - * RemediationTrigger specifies how the remediation was triggered. - * - * - `REMEDIATION_TRIGGER_MANUAL`: A user-initiated remediation (either via web UI - * or API call). - * - `REMEDIATION_TRIGGER_AUTOMATED`: A system-initiated remediation that requires - * approval. - */ - trigger: 'REMEDIATION_TRIGGER_MANUAL' | 'REMEDIATION_TRIGGER_AUTOMATED'; - - /** - * Active health check run ID (UUID) that triggered this remediation. - */ - active_health_check_run_id?: string; - - /** - * When the remediation was created. - */ - create_time?: string; - - /** - * When the remediation completed. - */ - end_time?: string; - - /** - * Error message if the remediation failed. - */ - error_message?: string; - - /** - * Passive health check event ID that triggered this remediation. - */ - passive_health_check_event_id?: string; - - /** - * User-provided reason for the remediation. - */ - reason?: string; - - /** - * Who requested the remediation. - */ - requested_by?: string; - - /** - * Review comment. - */ - review_comment?: string; - - /** - * When the remediation was reviewed. - */ - review_time?: string; - - /** - * Who reviewed the remediation. - */ - reviewed_by?: string; - - /** - * When processing started. - */ - start_time?: string; - - /** - * When the remediation was last updated. - */ - update_time?: string; + remediations: Array; } export interface RemediationCreateParams { @@ -948,12 +384,8 @@ export interface RemediationRejectParams { export declare namespace Remediations { export { - type RemediationCreateResponse as RemediationCreateResponse, - type RemediationRetrieveResponse as RemediationRetrieveResponse, + type Remediation as Remediation, type RemediationListResponse as RemediationListResponse, - type RemediationApproveResponse as RemediationApproveResponse, - type RemediationCancelResponse as RemediationCancelResponse, - type RemediationRejectResponse as RemediationRejectResponse, type RemediationCreateParams as RemediationCreateParams, type RemediationRetrieveParams as RemediationRetrieveParams, type RemediationListParams as RemediationListParams, From 056ac2730ed710f0facb6fe73ebe005008449ae6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 18:29:36 +0000 Subject: [PATCH 21/31] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 8113129d..19314335 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 82 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-c8200db997f998a51b2b469b69fcc3a24032e50b45aaaf340c2c176473393adc.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-3ae47a93fc03893f34b2e442ab8194c1f4224d9d7fe2d02b6342dbe03862f1e7.yml openapi_spec_hash: 2544f6840cf7ad23eae3fe8bffed4c4b config_hash: b35d5968fb07cce1c1be735f874898b1 From fac1032d9ae4720162e16df09dbed9865b8b4494 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 20:23:51 +0000 Subject: [PATCH 22/31] chore(tests): remove redundant File import --- tests/uploads.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/uploads.test.ts b/tests/uploads.test.ts index 1f6b02c8..20a7257e 100644 --- a/tests/uploads.test.ts +++ b/tests/uploads.test.ts @@ -1,7 +1,6 @@ import fs from 'fs'; import type { ResponseLike } from 'together-ai/internal/to-file'; import { toFile } from 'together-ai/core/uploads'; -import { File } from 'node:buffer'; class MyClass { name: string = 'foo'; From 88adf97945ffb851fb7248c586dc40e46f9c7ff1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 21:10:38 +0000 Subject: [PATCH 23/31] fix(types): remove error field, make requestId required in queue QueueSubmitResponse --- .stats.yml | 4 +- src/resources/beta/clusters/clusters.ts | 44 ++++++++++++ src/resources/beta/clusters/remediations.ts | 69 +++++++++++++++++++ src/resources/beta/clusters/storage.ts | 36 ++++++++++ src/resources/beta/jig/jig.ts | 36 ++++++++++ src/resources/beta/jig/queue.ts | 75 +++++++++++++-------- src/resources/beta/jig/secrets.ts | 28 ++++++++ src/resources/beta/jig/volumes.ts | 29 ++++++++ tests/api-resources/beta/jig/queue.test.ts | 4 +- 9 files changed, 292 insertions(+), 33 deletions(-) diff --git a/.stats.yml b/.stats.yml index 19314335..93e753b4 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 82 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-3ae47a93fc03893f34b2e442ab8194c1f4224d9d7fe2d02b6342dbe03862f1e7.yml -openapi_spec_hash: 2544f6840cf7ad23eae3fe8bffed4c4b +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-ecf580edec990fe441cc295edaa94c3ee9f29229cf86770ae3a98cd2c62c1c91.yml +openapi_spec_hash: 28960794b18db3cde13c972a7240eef2 config_hash: b35d5968fb07cce1c1be735f874898b1 diff --git a/src/resources/beta/clusters/clusters.ts b/src/resources/beta/clusters/clusters.ts index 77243917..bc7a5613 100644 --- a/src/resources/beta/clusters/clusters.ts +++ b/src/resources/beta/clusters/clusters.ts @@ -37,6 +37,19 @@ export class Clusters extends APIResource { * DC-local storage, Kubernetes and Slurm cluster flavors, a REST API, and * Terraform support, you can run workloads flexibly without complex infrastructure * management. + * + * @example + * ```ts + * const cluster = await client.beta.clusters.create({ + * billing_type: 'RESERVED', + * cluster_name: 'cluster_name', + * cuda_version: 'cuda_version', + * gpu_type: 'H100_SXM', + * num_gpus: 0, + * nvidia_driver_version: 'nvidia_driver_version', + * region: 'region', + * }); + * ``` */ create(body: ClusterCreateParams, options?: RequestOptions): APIPromise { return this._client.post('/compute/clusters', { body, ...options }); @@ -44,6 +57,13 @@ export class Clusters extends APIResource { /** * Retrieve information about a specific GPU cluster. + * + * @example + * ```ts + * const cluster = await client.beta.clusters.retrieve( + * 'cluster_id', + * ); + * ``` */ retrieve(clusterID: string, options?: RequestOptions): APIPromise { return this._client.get(path`/compute/clusters/${clusterID}`, options); @@ -51,6 +71,13 @@ export class Clusters extends APIResource { /** * Update the configuration of an existing GPU cluster. + * + * @example + * ```ts + * const cluster = await client.beta.clusters.update( + * 'cluster_id', + * ); + * ``` */ update(clusterID: string, body: ClusterUpdateParams, options?: RequestOptions): APIPromise { return this._client.put(path`/compute/clusters/${clusterID}`, { body, ...options }); @@ -58,6 +85,11 @@ export class Clusters extends APIResource { /** * List all GPU clusters. + * + * @example + * ```ts + * const clusters = await client.beta.clusters.list(); + * ``` */ list( query: ClusterListParams | null | undefined = {}, @@ -68,6 +100,13 @@ export class Clusters extends APIResource { /** * Delete a GPU cluster by cluster ID. + * + * @example + * ```ts + * const cluster = await client.beta.clusters.delete( + * 'cluster_id', + * ); + * ``` */ delete(clusterID: string, options?: RequestOptions): APIPromise { return this._client.delete(path`/compute/clusters/${clusterID}`, options); @@ -75,6 +114,11 @@ export class Clusters extends APIResource { /** * List regions and corresponding supported driver versions + * + * @example + * ```ts + * const response = await client.beta.clusters.listRegions(); + * ``` */ listRegions(options?: RequestOptions): APIPromise { return this._client.get('/compute/regions', options); diff --git a/src/resources/beta/clusters/remediations.ts b/src/resources/beta/clusters/remediations.ts index 25d1f4a7..8294382b 100644 --- a/src/resources/beta/clusters/remediations.ts +++ b/src/resources/beta/clusters/remediations.ts @@ -15,6 +15,18 @@ export class Remediations extends APIResource { * remediations are created with PENDING_APPROVAL state. The user must call * /approve to start the actual remediation process. These operations can also be * rejected by calling /reject. + * + * @example + * ```ts + * const remediation = + * await client.beta.clusters.remediations.create( + * 'instance_id', + * { + * cluster_id: 'cluster_id', + * mode: 'REMEDIATION_MODE_VM_ONLY', + * }, + * ); + * ``` */ create( instanceID: string, @@ -32,6 +44,18 @@ export class Remediations extends APIResource { /** * Retrieve the status of a specific remdiation on a specific instance in a * specific cluster. + * + * @example + * ```ts + * const remediation = + * await client.beta.clusters.remediations.retrieve( + * 'remediation_id', + * { + * cluster_id: 'cluster_id', + * instance_id: 'instance_id', + * }, + * ); + * ``` */ retrieve( remediationID: string, @@ -47,6 +71,15 @@ export class Remediations extends APIResource { /** * Lists remediations for an instance or cluster. + * + * @example + * ```ts + * const remediations = + * await client.beta.clusters.remediations.list( + * 'instance_id', + * { cluster_id: 'cluster_id' }, + * ); + * ``` */ list( instanceID: string, @@ -68,6 +101,18 @@ export class Remediations extends APIResource { * On APPROVE: state changes to PENDING and the remediation process begins. The * reviewed_by, review_time, and review_comment fields are populated on the * remediation after approval. + * + * @example + * ```ts + * const remediation = + * await client.beta.clusters.remediations.approve( + * 'remediation_id', + * { + * cluster_id: 'cluster_id', + * instance_id: 'instance_id', + * }, + * ); + * ``` */ approve( remediationID: string, @@ -85,6 +130,18 @@ export class Remediations extends APIResource { * Cancels a pending remediation. * * Only remediations in PENDING_APPROVAL or PENDING state can be cancelled. + * + * @example + * ```ts + * const remediation = + * await client.beta.clusters.remediations.cancel( + * 'remediation_id', + * { + * cluster_id: 'cluster_id', + * instance_id: 'instance_id', + * }, + * ); + * ``` */ cancel( remediationID: string, @@ -105,6 +162,18 @@ export class Remediations extends APIResource { * * On REJECT: state changes to CANCELLED. The reviewed_by, review_time, and * review_comment fields are populated on the remediation after rejection. + * + * @example + * ```ts + * const remediation = + * await client.beta.clusters.remediations.reject( + * 'remediation_id', + * { + * cluster_id: 'cluster_id', + * instance_id: 'instance_id', + * }, + * ); + * ``` */ reject( remediationID: string, diff --git a/src/resources/beta/clusters/storage.ts b/src/resources/beta/clusters/storage.ts index cdfe3676..b22b2023 100644 --- a/src/resources/beta/clusters/storage.ts +++ b/src/resources/beta/clusters/storage.ts @@ -12,6 +12,16 @@ export class Storage extends APIResource { * at cluster creation time, and resize as your data grows. All shared storage is * backed by multi-NIC bare metal paths, ensuring high-throughput and low-latency * performance for shared storage. + * + * @example + * ```ts + * const clusterStorage = + * await client.beta.clusters.storage.create({ + * region: 'region', + * size_tib: 0, + * volume_name: 'volume_name', + * }); + * ``` */ create(body: StorageCreateParams, options?: RequestOptions): APIPromise { return this._client.post('/compute/clusters/storage/volumes', { body, ...options }); @@ -19,6 +29,12 @@ export class Storage extends APIResource { /** * Retrieve information about a specific shared volume. + * + * @example + * ```ts + * const clusterStorage = + * await client.beta.clusters.storage.retrieve('volume_id'); + * ``` */ retrieve(volumeID: string, options?: RequestOptions): APIPromise { return this._client.get(path`/compute/clusters/storage/volumes/${volumeID}`, options); @@ -26,6 +42,14 @@ export class Storage extends APIResource { /** * Update the configuration of an existing shared volume. + * + * @example + * ```ts + * const clusterStorage = + * await client.beta.clusters.storage.update({ + * volume_id: 'volume_id', + * }); + * ``` */ update(body: StorageUpdateParams, options?: RequestOptions): APIPromise { return this._client.put('/compute/clusters/storage/volumes', { body, ...options }); @@ -33,6 +57,11 @@ export class Storage extends APIResource { /** * List all shared volumes. + * + * @example + * ```ts + * const storages = await client.beta.clusters.storage.list(); + * ``` */ list( query: StorageListParams | null | undefined = {}, @@ -44,6 +73,13 @@ export class Storage extends APIResource { /** * Delete a shared volume. Note that if this volume is attached to a cluster, * deleting will fail. + * + * @example + * ```ts + * const storage = await client.beta.clusters.storage.delete( + * 'volume_id', + * ); + * ``` */ delete(volumeID: string, options?: RequestOptions): APIPromise { return this._client.delete(path`/compute/clusters/storage/volumes/${volumeID}`, options); diff --git a/src/resources/beta/jig/jig.ts b/src/resources/beta/jig/jig.ts index b0345358..8efae5b3 100644 --- a/src/resources/beta/jig/jig.ts +++ b/src/resources/beta/jig/jig.ts @@ -43,6 +43,11 @@ export class Jig extends APIResource { /** * Retrieve details of a specific deployment by its ID or name + * + * @example + * ```ts + * const deployment = await client.beta.jig.retrieve('id'); + * ``` */ retrieve(id: string, options?: RequestOptions): APIPromise { return this._client.get(path`/deployments/${id}`, options); @@ -50,6 +55,11 @@ export class Jig extends APIResource { /** * Update an existing deployment configuration + * + * @example + * ```ts + * const deployment = await client.beta.jig.update('id'); + * ``` */ update(id: string, body: JigUpdateParams, options?: RequestOptions): APIPromise { return this._client.patch(path`/deployments/${id}`, { body, ...options }); @@ -57,6 +67,11 @@ export class Jig extends APIResource { /** * Get a list of all deployments in your project + * + * @example + * ```ts + * const jigs = await client.beta.jig.list(); + * ``` */ list(options?: RequestOptions): APIPromise { return this._client.get('/deployments', options); @@ -64,6 +79,15 @@ export class Jig extends APIResource { /** * Create a new deployment with specified configuration + * + * @example + * ```ts + * const deployment = await client.beta.jig.deploy({ + * gpu_type: 'h100-80gb', + * image: 'image', + * name: 'x', + * }); + * ``` */ deploy(body: JigDeployParams, options?: RequestOptions): APIPromise { return this._client.post('/deployments', { body, ...options }); @@ -71,6 +95,11 @@ export class Jig extends APIResource { /** * Delete an existing deployment + * + * @example + * ```ts + * const response = await client.beta.jig.destroy('id'); + * ``` */ destroy(id: string, options?: RequestOptions): APIPromise { return this._client.delete(path`/deployments/${id}`, options); @@ -78,6 +107,13 @@ export class Jig extends APIResource { /** * Retrieve logs from a deployment, optionally filtered by replica ID. + * + * @example + * ```ts + * const deploymentLogs = await client.beta.jig.retrieveLogs( + * 'id', + * ); + * ``` */ retrieveLogs( id: string, diff --git a/src/resources/beta/jig/queue.ts b/src/resources/beta/jig/queue.ts index 129d62c7..f6bcf2f8 100644 --- a/src/resources/beta/jig/queue.ts +++ b/src/resources/beta/jig/queue.ts @@ -8,6 +8,14 @@ export class Queue extends APIResource { /** * Poll the current status of a previously submitted job. Provide the request_id * and model as query parameters. + * + * @example + * ```ts + * const queue = await client.beta.jig.queue.retrieve({ + * model: 'model', + * request_id: 'request_id', + * }); + * ``` */ retrieve(query: QueueRetrieveParams, options?: RequestOptions): APIPromise { return this._client.get('/queue/status', { query, ...options }); @@ -17,6 +25,14 @@ export class Queue extends APIResource { * Cancel a pending job. Only jobs in pending status can be canceled. Running jobs * cannot be stopped. Returns the job status after the attempt. If the job is not * pending, returns 409 with the current status unchanged. + * + * @example + * ```ts + * const response = await client.beta.jig.queue.cancel({ + * model: 'model', + * request_id: 'request_id', + * }); + * ``` */ cancel(body: QueueCancelParams, options?: RequestOptions): APIPromise { return this._client.post('/queue/cancel', { body, ...options }); @@ -25,6 +41,13 @@ export class Queue extends APIResource { /** * Get the current queue statistics for a model, including pending and running job * counts. + * + * @example + * ```ts + * const response = await client.beta.jig.queue.metrics({ + * model: 'model', + * }); + * ``` */ metrics(query: QueueMetricsParams, options?: RequestOptions): APIPromise { return this._client.get('/queue/metrics', { query, ...options }); @@ -34,12 +57,23 @@ export class Queue extends APIResource { * Submit a new job to the queue for asynchronous processing. Jobs are processed in * strict priority order (higher priority first, FIFO within the same priority). * Returns a request ID that can be used to poll status or cancel the job. + * + * @example + * ```ts + * const response = await client.beta.jig.queue.submit({ + * model: 'my-queue-model', + * payload: { foo: 'bar' }, + * }); + * ``` */ submit(body: QueueSubmitParams, options?: RequestOptions): APIPromise { return this._client.post('/queue/submit', { body, ...options }); } } +/** + * Current status and metadata for a queued job. + */ export interface QueueRetrieveResponse { /** * Model identifier the job was submitted to @@ -109,6 +143,9 @@ export interface QueueRetrieveResponse { warnings?: Array; } +/** + * Status returned after a cancel attempt. + */ export interface QueueCancelResponse { /** * Job status after the cancel attempt. Only pending jobs can be canceled. If the @@ -117,6 +154,9 @@ export interface QueueCancelResponse { status: 'canceled' | 'running' | 'done' | 'failed'; } +/** + * Queue job counts for a model. + */ export interface QueueMetricsResponse { /** * Number of jobs currently being processed @@ -134,37 +174,14 @@ export interface QueueMetricsResponse { total_jobs: number; } +/** + * Response returned after queueing a job. + */ export interface QueueSubmitResponse { - error?: QueueSubmitResponse.Error; - /** * Unique identifier for the submitted job. Use this to poll status or cancel. */ - requestId?: string; -} - -export namespace QueueSubmitResponse { - export interface Error { - /** - * Machine-readable error code - */ - code?: string; - - /** - * Human-readable error message - */ - message?: string; - - /** - * The parameter that caused the error, if applicable - */ - param?: string; - - /** - * Error category (e.g. "invalid_request_error", "not_found_error") - */ - type?: string; - } + requestId: string; } export interface QueueRetrieveParams { @@ -211,8 +228,8 @@ export interface QueueSubmitParams { payload: { [key: string]: unknown }; /** - * Arbitrary JSON metadata stored with the job and returned in status responses. - * The model and system may add or update keys during processing. + * Arbitrary JSON metadata stored with the job. Returned in status responses, where + * the model and system may have added or modified keys (e.g. progress). */ info?: { [key: string]: unknown }; diff --git a/src/resources/beta/jig/secrets.ts b/src/resources/beta/jig/secrets.ts index b2db9fb6..0ebeaf41 100644 --- a/src/resources/beta/jig/secrets.ts +++ b/src/resources/beta/jig/secrets.ts @@ -8,6 +8,14 @@ import { path } from '../../../internal/utils/path'; export class Secrets extends APIResource { /** * Create a new secret to store sensitive configuration values + * + * @example + * ```ts + * const secret = await client.beta.jig.secrets.create({ + * name: 'x', + * value: 'x', + * }); + * ``` */ create(body: SecretCreateParams, options?: RequestOptions): APIPromise { return this._client.post('/deployments/secrets', { body, ...options }); @@ -15,6 +23,11 @@ export class Secrets extends APIResource { /** * Retrieve details of a specific secret by its ID or name + * + * @example + * ```ts + * const secret = await client.beta.jig.secrets.retrieve('id'); + * ``` */ retrieve(id: string, options?: RequestOptions): APIPromise { return this._client.get(path`/deployments/secrets/${id}`, options); @@ -22,6 +35,11 @@ export class Secrets extends APIResource { /** * Update an existing secret's value or metadata + * + * @example + * ```ts + * const secret = await client.beta.jig.secrets.update('id'); + * ``` */ update(id: string, body: SecretUpdateParams, options?: RequestOptions): APIPromise { return this._client.patch(path`/deployments/secrets/${id}`, { body, ...options }); @@ -29,6 +47,11 @@ export class Secrets extends APIResource { /** * Retrieve all secrets in your project + * + * @example + * ```ts + * const secrets = await client.beta.jig.secrets.list(); + * ``` */ list(options?: RequestOptions): APIPromise { return this._client.get('/deployments/secrets', options); @@ -36,6 +59,11 @@ export class Secrets extends APIResource { /** * Delete an existing secret + * + * @example + * ```ts + * const secret = await client.beta.jig.secrets.delete('id'); + * ``` */ delete(id: string, options?: RequestOptions): APIPromise { return this._client.delete(path`/deployments/secrets/${id}`, options); diff --git a/src/resources/beta/jig/volumes.ts b/src/resources/beta/jig/volumes.ts index f7759f84..0d042f7a 100644 --- a/src/resources/beta/jig/volumes.ts +++ b/src/resources/beta/jig/volumes.ts @@ -8,6 +8,15 @@ import { path } from '../../../internal/utils/path'; export class Volumes extends APIResource { /** * Create a new volume to preload files in deployments + * + * @example + * ```ts + * const volume = await client.beta.jig.volumes.create({ + * content: {}, + * name: 'x', + * type: 'readOnly', + * }); + * ``` */ create(body: VolumeCreateParams, options?: RequestOptions): APIPromise { return this._client.post('/deployments/storage/volumes', { body, ...options }); @@ -15,6 +24,11 @@ export class Volumes extends APIResource { /** * Retrieve details of a specific volume by its ID or name + * + * @example + * ```ts + * const volume = await client.beta.jig.volumes.retrieve('id'); + * ``` */ retrieve( id: string, @@ -26,6 +40,11 @@ export class Volumes extends APIResource { /** * Update an existing volume's configuration or contents + * + * @example + * ```ts + * const volume = await client.beta.jig.volumes.update('id'); + * ``` */ update(id: string, body: VolumeUpdateParams, options?: RequestOptions): APIPromise { return this._client.patch(path`/deployments/storage/volumes/${id}`, { body, ...options }); @@ -33,6 +52,11 @@ export class Volumes extends APIResource { /** * Retrieve all volumes in your project + * + * @example + * ```ts + * const volumes = await client.beta.jig.volumes.list(); + * ``` */ list(options?: RequestOptions): APIPromise { return this._client.get('/deployments/storage/volumes', options); @@ -40,6 +64,11 @@ export class Volumes extends APIResource { /** * Delete an existing volume + * + * @example + * ```ts + * const volume = await client.beta.jig.volumes.delete('id'); + * ``` */ delete(id: string, options?: RequestOptions): APIPromise { return this._client.delete(path`/deployments/storage/volumes/${id}`, options); diff --git a/tests/api-resources/beta/jig/queue.test.ts b/tests/api-resources/beta/jig/queue.test.ts index c1e0f601..4e45a7fd 100644 --- a/tests/api-resources/beta/jig/queue.test.ts +++ b/tests/api-resources/beta/jig/queue.test.ts @@ -55,7 +55,7 @@ describe('resource queue', () => { test('submit: only required params', async () => { const responsePromise = client.beta.jig.queue.submit({ - model: 'model', + model: 'my-queue-model', payload: { foo: 'bar' }, }); const rawResponse = await responsePromise.asResponse(); @@ -69,7 +69,7 @@ describe('resource queue', () => { test('submit: required and optional params', async () => { const response = await client.beta.jig.queue.submit({ - model: 'model', + model: 'my-queue-model', payload: { foo: 'bar' }, info: { foo: 'bar' }, priority: 0, From 06d9a67b24273d69622381d7ea9b11660a4c350d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 21:19:32 +0000 Subject: [PATCH 24/31] fix(typescript): upgrade tsc-multi so that it works with Node 26 --- package.json | 2 +- yarn.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index cd334f9a..2c7b7c3a 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "publint": "^0.2.12", "ts-jest": "^29.1.0", "ts-node": "^10.5.0", - "tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz", + "tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.11/tsc-multi.tgz", "tsconfig-paths": "^4.0.0", "tslib": "^2.8.1", "typescript": "5.8.3", diff --git a/yarn.lock b/yarn.lock index 2f4f2bb7..48ed9b2c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3279,9 +3279,9 @@ ts-node@^10.5.0: v8-compile-cache-lib "^3.0.0" yn "3.1.1" -"tsc-multi@https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz": - version "1.1.9" - resolved "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz#777f6f5d9e26bf0e94e5170990dd3a841d6707cd" +"tsc-multi@https://github.com/stainless-api/tsc-multi/releases/download/v1.1.11/tsc-multi.tgz": + version "1.1.11" + resolved "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.11/tsc-multi.tgz#010247051be13b55abdc98f787c017285149f4f2" dependencies: debug "^4.3.7" fast-glob "^3.3.2" From 6c56e574e5289f07e8cad895f19ce0b8e87a7ecc Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 19 May 2026 19:05:30 +0000 Subject: [PATCH 25/31] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 93e753b4..9a2b16fb 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 82 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-ecf580edec990fe441cc295edaa94c3ee9f29229cf86770ae3a98cd2c62c1c91.yml -openapi_spec_hash: 28960794b18db3cde13c972a7240eef2 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-4ac2f3d9b5d799b305a422c2914a36228953a7975c5279e5e3591ae953edb5b1.yml +openapi_spec_hash: 23512750c02dc91c9598c94a5e1e2062 config_hash: b35d5968fb07cce1c1be735f874898b1 From 5975b8530ab0719fcf896c491d8c1f3efc883a82 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 16:16:11 +0000 Subject: [PATCH 26/31] feat(api): add trigger param, update mode to array in remediations list --- .stats.yml | 4 ++-- src/resources/beta/clusters/remediations.ts | 15 +++++++++++---- .../beta/clusters/remediations.test.ts | 3 ++- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/.stats.yml b/.stats.yml index 9a2b16fb..43ce6a8a 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 82 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-4ac2f3d9b5d799b305a422c2914a36228953a7975c5279e5e3591ae953edb5b1.yml -openapi_spec_hash: 23512750c02dc91c9598c94a5e1e2062 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-8671c2d4a49490bb7814a9cf067d8c3494c0aadd7b048986d920e0f9936766cc.yml +openapi_spec_hash: e1bd8079218c2a05ad2075d8e146ef60 config_hash: b35d5968fb07cce1c1be735f874898b1 diff --git a/src/resources/beta/clusters/remediations.ts b/src/resources/beta/clusters/remediations.ts index 8294382b..31c0dc8e 100644 --- a/src/resources/beta/clusters/remediations.ts +++ b/src/resources/beta/clusters/remediations.ts @@ -363,14 +363,15 @@ export interface RemediationListParams { cluster_id: string; /** - * Query param: Filter by remediation mode. Returns only remediations matching the - * specified mode. + * Query param: Filter by remediation mode(s). Returns remediations matching any of + * the specified modes. */ - mode?: + mode?: Array< | 'REMEDIATION_MODE_VM_ONLY' | 'REMEDIATION_MODE_HOST_AWARE' | 'REMEDIATION_MODE_EVICT_WITHOUT_REPLACEMENT' - | 'REMEDIATION_MODE_REBOOT_VM'; + | 'REMEDIATION_MODE_REBOOT_VM' + >; /** * Query param: Order by expression. @@ -403,6 +404,12 @@ export interface RemediationListParams { state?: Array< 'PENDING_APPROVAL' | 'PENDING' | 'RUNNING' | 'SUCCEEDED' | 'FAILED' | 'CANCELLED' | 'AUTO_RESOLVED' >; + + /** + * Query param: Filter by trigger type(s). Returns remediations matching any of the + * specified triggers. + */ + trigger?: Array<'REMEDIATION_TRIGGER_MANUAL' | 'REMEDIATION_TRIGGER_AUTOMATED'>; } export interface RemediationApproveParams { diff --git a/tests/api-resources/beta/clusters/remediations.test.ts b/tests/api-resources/beta/clusters/remediations.test.ts index 8b2af0d6..c4407f2b 100644 --- a/tests/api-resources/beta/clusters/remediations.test.ts +++ b/tests/api-resources/beta/clusters/remediations.test.ts @@ -68,11 +68,12 @@ describe('resource remediations', () => { test('list: required and optional params', async () => { const response = await client.beta.clusters.remediations.list('instance_id', { cluster_id: 'cluster_id', - mode: 'REMEDIATION_MODE_VM_ONLY', + mode: ['REMEDIATION_MODE_VM_ONLY'], order_by: 'order_by', page_size: 0, page_token: 'page_token', state: ['PENDING_APPROVAL'], + trigger: ['REMEDIATION_TRIGGER_MANUAL'], }); }); From dcbecdf7dee2080d9df6b1bc5dfba1635835b69e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 16:43:44 +0000 Subject: [PATCH 27/31] feat(api): add instance_name field to Remediation model --- .stats.yml | 4 ++-- src/resources/beta/clusters/remediations.ts | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 43ce6a8a..d828b7b1 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 82 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-8671c2d4a49490bb7814a9cf067d8c3494c0aadd7b048986d920e0f9936766cc.yml -openapi_spec_hash: e1bd8079218c2a05ad2075d8e146ef60 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-b7b48bf22d5bb751c9ccc7f9960dfa823f92c83889469918c13074f6d6eacd41.yml +openapi_spec_hash: e26bbd1583736995e71ee020e6f76e58 config_hash: b35d5968fb07cce1c1be735f874898b1 diff --git a/src/resources/beta/clusters/remediations.ts b/src/resources/beta/clusters/remediations.ts index 31c0dc8e..d84e8a19 100644 --- a/src/resources/beta/clusters/remediations.ts +++ b/src/resources/beta/clusters/remediations.ts @@ -258,6 +258,11 @@ export interface Remediation { */ error_message?: string; + /** + * Display name of the targeted instance. + */ + instance_name?: string; + /** * Passive health check event ID that triggered this remediation. */ From 9b846104ae9cb157c424b0cca7cf490e38bf4a5d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 16:44:44 +0000 Subject: [PATCH 28/31] feat(api): add disable_position_bias_correction, remove num_samples from evals --- .stats.yml | 4 +- src/resources/evals.ts | 291 +++++++++++++++++++++++++++-------------- 2 files changed, 194 insertions(+), 101 deletions(-) diff --git a/.stats.yml b/.stats.yml index d828b7b1..d671988a 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 82 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-b7b48bf22d5bb751c9ccc7f9960dfa823f92c83889469918c13074f6d6eacd41.yml -openapi_spec_hash: e26bbd1583736995e71ee020e6f76e58 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-835a3888d379fab044fd90910453b084ca9befdca7ce367f358dfd9cabcfcb14.yml +openapi_spec_hash: 36dae2e8eaa8dbf627d639e08e3fe24a config_hash: b35d5968fb07cce1c1be735f874898b1 diff --git a/src/resources/evals.ts b/src/resources/evals.ts index 848038bf..6f1e4e52 100644 --- a/src/resources/evals.ts +++ b/src/resources/evals.ts @@ -197,37 +197,37 @@ export namespace EvaluationJob { export interface EvaluationCompareResults { /** - * Number of times model A won + * Number of samples where model A was judged the winner */ A_wins?: number; /** - * Number of times model B won + * Number of samples where model B was judged the winner */ B_wins?: number; /** - * Number of failed generations. + * Number of generation failures across model A and model B. */ generation_fail_count?: number | null; /** - * Number of failed judge generations + * Number of judge inference failures. In the default two-pass mode + * (disable_position_bias_correction=false) this is the combined failure count from + * both the original-order and flipped-order judge passes. */ judge_fail_count?: number | null; /** - * Total number of samples compared - */ - num_samples?: number; - - /** - * Data File ID + * File ID of the detailed output file. Each row contains the original input fields + * plus judge outputs. In two-pass mode the file includes both original-order and + * flipped-order judge fields; in single-pass mode + * (disable_position_bias_correction=true) only original-order fields are present. */ result_file_id?: string; /** - * Number of ties + * Number of samples that resulted in a tie */ Ties?: number; } @@ -357,37 +357,37 @@ export namespace EvalStatusResponse { export interface EvaluationCompareResults { /** - * Number of times model A won + * Number of samples where model A was judged the winner */ A_wins?: number; /** - * Number of times model B won + * Number of samples where model B was judged the winner */ B_wins?: number; /** - * Number of failed generations. + * Number of generation failures across model A and model B. */ generation_fail_count?: number | null; /** - * Number of failed judge generations + * Number of judge inference failures. In the default two-pass mode + * (disable_position_bias_correction=false) this is the combined failure count from + * both the original-order and flipped-order judge passes. */ judge_fail_count?: number | null; /** - * Total number of samples compared - */ - num_samples?: number; - - /** - * Data File ID + * File ID of the detailed output file. Each row contains the original input fields + * plus judge outputs. In two-pass mode the file includes both original-order and + * flipped-order judge fields; in single-pass mode + * (disable_position_bias_correction=true) only original-order fields are present. */ result_file_id?: string; /** - * Number of ties + * Number of samples that resulted in a tie */ Ties?: number; } @@ -428,7 +428,7 @@ export namespace EvalCreateParams { pass_labels: Array; /** - * Field name in the input data + * Column name in the input dataset containing pre-generated responses */ model_to_evaluate?: string | EvaluationClassifyParameters.EvaluationModelRequest; } @@ -441,7 +441,15 @@ export namespace EvalCreateParams { model: string; /** - * Source of the judge model. + * Source of the judge model inference: - `serverless`: Together's shared + * serverless inference API. Default concurrency: 25 workers. - `dedicated`: A + * Together dedicated deployment endpoint. Default concurrency: 5 workers (minimum + * enforced even if num_workers is set lower). + * + * - `external`: An external inference API (e.g. OpenAI, Anthropic, Google, + * OpenRouter). Requires `external_api_token` and `external_base_url`. Default + * concurrency: 2 workers for first-party APIs, 20 for proxy/aggregator + * endpoints. */ model_source: 'serverless' | 'dedicated' | 'external'; @@ -451,43 +459,46 @@ export namespace EvalCreateParams { system_template: string; /** - * Bearer/API token for external judge models. + * Bearer/API token for the external judge model provider. Required when + * model_source is 'external'. */ external_api_token?: string; /** - * Base URL for external judge models. Must be OpenAI-compatible base URL. + * Base URL of the external inference API for the judge. Must be OpenAI-compatible. + * Required when model_source is 'external'. */ external_base_url?: string; /** - * Maximum number of tokens the judge model can generate. Defaults to 32768. - * Increase for reasoning models (e.g. Gemini, o-series) that consume output token - * budget for chain-of-thought. + * Maximum number of tokens the judge model may generate. Defaults to 32768 if + * omitted. Set higher for reasoning judges (e.g. o-series, Gemini) that spend + * tokens on internal chain-of-thought before emitting the verdict JSON. */ max_tokens?: number; /** - * Number of concurrent workers for inference requests. Overrides the default - * concurrency for this model. Useful for tuning throughput when using proxy - * endpoints (e.g. OpenRouter) or rate-limited external APIs. + * Number of concurrent inference workers for the judge. Overrides the + * source-specific default (serverless: 25, dedicated: 5, external: 2–20). For + * dedicated endpoints the value is clamped to a minimum of 5 regardless of what is + * set here. */ num_workers?: number; /** - * Sampling temperature for the judge model. Defaults to 0.05. + * Sampling temperature for the judge model. Defaults to 0.05 if omitted. */ temperature?: number; } export interface EvaluationModelRequest { /** - * Input prompt template + * User message template. Supports Jinja2 variables referencing dataset columns. */ input_template: string; /** - * Maximum number of tokens to generate + * Maximum number of tokens to generate. */ max_tokens: number; @@ -497,34 +508,45 @@ export namespace EvalCreateParams { model: string; /** - * Source of the model. + * Source of the model inference: - `serverless`: Together's shared serverless + * inference API. Default concurrency: 25 workers. - `dedicated`: A Together + * dedicated deployment endpoint. Default concurrency: 5 workers (minimum enforced + * even if num_workers is set lower). Authentication uses the requesting user's + * Together API token automatically. + * + * - `external`: An external inference API (e.g. OpenAI, Anthropic, Google, + * OpenRouter). Requires `external_api_token` and `external_base_url`. Default + * concurrency: 2 workers for first-party APIs (OpenAI, Anthropic, Google), 20 + * for proxy/aggregator endpoints. */ model_source: 'serverless' | 'dedicated' | 'external'; /** - * System prompt template + * System prompt template. Supports Jinja2 variables referencing dataset columns. */ system_template: string; /** - * Sampling temperature + * Sampling temperature for generation. */ temperature: number; /** - * Bearer/API token for external models. + * Bearer/API token for the external model provider. Required when model_source is + * 'external'. */ external_api_token?: string; /** - * Base URL for external models. Must be OpenAI-compatible base URL + * Base URL of the external inference API. Must be OpenAI-compatible. Required when + * model_source is 'external'. */ external_base_url?: string; /** - * Number of concurrent workers for inference requests. Overrides the default - * concurrency for this model. Useful for tuning throughput when using proxy - * endpoints (e.g. OpenRouter) or rate-limited external APIs. + * Number of concurrent inference workers. Overrides the source-specific default + * (serverless: 25, dedicated: 5, external: 2–20). For dedicated endpoints the + * value is clamped to a minimum of 5 regardless of what is set here. */ num_workers?: number; } @@ -554,7 +576,7 @@ export namespace EvalCreateParams { pass_threshold: number; /** - * Field name in the input data + * Column name in the input dataset containing pre-generated responses */ model_to_evaluate?: string | EvaluationScoreParameters.EvaluationModelRequest; } @@ -567,7 +589,15 @@ export namespace EvalCreateParams { model: string; /** - * Source of the judge model. + * Source of the judge model inference: - `serverless`: Together's shared + * serverless inference API. Default concurrency: 25 workers. - `dedicated`: A + * Together dedicated deployment endpoint. Default concurrency: 5 workers (minimum + * enforced even if num_workers is set lower). + * + * - `external`: An external inference API (e.g. OpenAI, Anthropic, Google, + * OpenRouter). Requires `external_api_token` and `external_base_url`. Default + * concurrency: 2 workers for first-party APIs, 20 for proxy/aggregator + * endpoints. */ model_source: 'serverless' | 'dedicated' | 'external'; @@ -577,43 +607,46 @@ export namespace EvalCreateParams { system_template: string; /** - * Bearer/API token for external judge models. + * Bearer/API token for the external judge model provider. Required when + * model_source is 'external'. */ external_api_token?: string; /** - * Base URL for external judge models. Must be OpenAI-compatible base URL. + * Base URL of the external inference API for the judge. Must be OpenAI-compatible. + * Required when model_source is 'external'. */ external_base_url?: string; /** - * Maximum number of tokens the judge model can generate. Defaults to 32768. - * Increase for reasoning models (e.g. Gemini, o-series) that consume output token - * budget for chain-of-thought. + * Maximum number of tokens the judge model may generate. Defaults to 32768 if + * omitted. Set higher for reasoning judges (e.g. o-series, Gemini) that spend + * tokens on internal chain-of-thought before emitting the verdict JSON. */ max_tokens?: number; /** - * Number of concurrent workers for inference requests. Overrides the default - * concurrency for this model. Useful for tuning throughput when using proxy - * endpoints (e.g. OpenRouter) or rate-limited external APIs. + * Number of concurrent inference workers for the judge. Overrides the + * source-specific default (serverless: 25, dedicated: 5, external: 2–20). For + * dedicated endpoints the value is clamped to a minimum of 5 regardless of what is + * set here. */ num_workers?: number; /** - * Sampling temperature for the judge model. Defaults to 0.05. + * Sampling temperature for the judge model. Defaults to 0.05 if omitted. */ temperature?: number; } export interface EvaluationModelRequest { /** - * Input prompt template + * User message template. Supports Jinja2 variables referencing dataset columns. */ input_template: string; /** - * Maximum number of tokens to generate + * Maximum number of tokens to generate. */ max_tokens: number; @@ -623,34 +656,45 @@ export namespace EvalCreateParams { model: string; /** - * Source of the model. + * Source of the model inference: - `serverless`: Together's shared serverless + * inference API. Default concurrency: 25 workers. - `dedicated`: A Together + * dedicated deployment endpoint. Default concurrency: 5 workers (minimum enforced + * even if num_workers is set lower). Authentication uses the requesting user's + * Together API token automatically. + * + * - `external`: An external inference API (e.g. OpenAI, Anthropic, Google, + * OpenRouter). Requires `external_api_token` and `external_base_url`. Default + * concurrency: 2 workers for first-party APIs (OpenAI, Anthropic, Google), 20 + * for proxy/aggregator endpoints. */ model_source: 'serverless' | 'dedicated' | 'external'; /** - * System prompt template + * System prompt template. Supports Jinja2 variables referencing dataset columns. */ system_template: string; /** - * Sampling temperature + * Sampling temperature for generation. */ temperature: number; /** - * Bearer/API token for external models. + * Bearer/API token for the external model provider. Required when model_source is + * 'external'. */ external_api_token?: string; /** - * Base URL for external models. Must be OpenAI-compatible base URL + * Base URL of the external inference API. Must be OpenAI-compatible. Required when + * model_source is 'external'. */ external_base_url?: string; /** - * Number of concurrent workers for inference requests. Overrides the default - * concurrency for this model. Useful for tuning throughput when using proxy - * endpoints (e.g. OpenRouter) or rate-limited external APIs. + * Number of concurrent inference workers. Overrides the source-specific default + * (serverless: 25, dedicated: 5, external: 2–20). For dedicated endpoints the + * value is clamped to a minimum of 5 regardless of what is set here. */ num_workers?: number; } @@ -658,21 +702,37 @@ export namespace EvalCreateParams { export interface EvaluationCompareParameters { /** - * Data file name + * Data file ID */ input_data_file_path: string; judge: EvaluationCompareParameters.Judge; /** - * Field name in the input data + * When false (default), the judge runs twice per sample: once with model A's + * response first (original order) and once with model B's response first (flipped + * order). The two verdicts are reconciled to cancel out position bias. When true, + * only the original-order pass is run, halving judge cost and latency at the + * expense of position-bias correction. The result file will not contain + * flipped-order judge fields when this is true. + */ + disable_position_bias_correction?: boolean; + + /** + * Either an EvaluationModelRequest for generation or a string column name from the + * dataset (when responses are pre-generated). When both model_a and model_b are + * EvaluationModelRequest objects, their inference runs execute in parallel to + * reduce total wall-clock time. */ - model_a?: string | EvaluationCompareParameters.EvaluationModelRequest; + model_a?: EvaluationCompareParameters.EvaluationModelRequest | string; /** - * Field name in the input data + * Either an EvaluationModelRequest for generation or a string column name from the + * dataset (when responses are pre-generated). When both model_a and model_b are + * EvaluationModelRequest objects, their inference runs execute in parallel to + * reduce total wall-clock time. */ - model_b?: string | EvaluationCompareParameters.EvaluationModelRequest; + model_b?: EvaluationCompareParameters.EvaluationModelRequest | string; } export namespace EvaluationCompareParameters { @@ -683,7 +743,15 @@ export namespace EvalCreateParams { model: string; /** - * Source of the judge model. + * Source of the judge model inference: - `serverless`: Together's shared + * serverless inference API. Default concurrency: 25 workers. - `dedicated`: A + * Together dedicated deployment endpoint. Default concurrency: 5 workers (minimum + * enforced even if num_workers is set lower). + * + * - `external`: An external inference API (e.g. OpenAI, Anthropic, Google, + * OpenRouter). Requires `external_api_token` and `external_base_url`. Default + * concurrency: 2 workers for first-party APIs, 20 for proxy/aggregator + * endpoints. */ model_source: 'serverless' | 'dedicated' | 'external'; @@ -693,43 +761,46 @@ export namespace EvalCreateParams { system_template: string; /** - * Bearer/API token for external judge models. + * Bearer/API token for the external judge model provider. Required when + * model_source is 'external'. */ external_api_token?: string; /** - * Base URL for external judge models. Must be OpenAI-compatible base URL. + * Base URL of the external inference API for the judge. Must be OpenAI-compatible. + * Required when model_source is 'external'. */ external_base_url?: string; /** - * Maximum number of tokens the judge model can generate. Defaults to 32768. - * Increase for reasoning models (e.g. Gemini, o-series) that consume output token - * budget for chain-of-thought. + * Maximum number of tokens the judge model may generate. Defaults to 32768 if + * omitted. Set higher for reasoning judges (e.g. o-series, Gemini) that spend + * tokens on internal chain-of-thought before emitting the verdict JSON. */ max_tokens?: number; /** - * Number of concurrent workers for inference requests. Overrides the default - * concurrency for this model. Useful for tuning throughput when using proxy - * endpoints (e.g. OpenRouter) or rate-limited external APIs. + * Number of concurrent inference workers for the judge. Overrides the + * source-specific default (serverless: 25, dedicated: 5, external: 2–20). For + * dedicated endpoints the value is clamped to a minimum of 5 regardless of what is + * set here. */ num_workers?: number; /** - * Sampling temperature for the judge model. Defaults to 0.05. + * Sampling temperature for the judge model. Defaults to 0.05 if omitted. */ temperature?: number; } export interface EvaluationModelRequest { /** - * Input prompt template + * User message template. Supports Jinja2 variables referencing dataset columns. */ input_template: string; /** - * Maximum number of tokens to generate + * Maximum number of tokens to generate. */ max_tokens: number; @@ -739,46 +810,57 @@ export namespace EvalCreateParams { model: string; /** - * Source of the model. + * Source of the model inference: - `serverless`: Together's shared serverless + * inference API. Default concurrency: 25 workers. - `dedicated`: A Together + * dedicated deployment endpoint. Default concurrency: 5 workers (minimum enforced + * even if num_workers is set lower). Authentication uses the requesting user's + * Together API token automatically. + * + * - `external`: An external inference API (e.g. OpenAI, Anthropic, Google, + * OpenRouter). Requires `external_api_token` and `external_base_url`. Default + * concurrency: 2 workers for first-party APIs (OpenAI, Anthropic, Google), 20 + * for proxy/aggregator endpoints. */ model_source: 'serverless' | 'dedicated' | 'external'; /** - * System prompt template + * System prompt template. Supports Jinja2 variables referencing dataset columns. */ system_template: string; /** - * Sampling temperature + * Sampling temperature for generation. */ temperature: number; /** - * Bearer/API token for external models. + * Bearer/API token for the external model provider. Required when model_source is + * 'external'. */ external_api_token?: string; /** - * Base URL for external models. Must be OpenAI-compatible base URL + * Base URL of the external inference API. Must be OpenAI-compatible. Required when + * model_source is 'external'. */ external_base_url?: string; /** - * Number of concurrent workers for inference requests. Overrides the default - * concurrency for this model. Useful for tuning throughput when using proxy - * endpoints (e.g. OpenRouter) or rate-limited external APIs. + * Number of concurrent inference workers. Overrides the source-specific default + * (serverless: 25, dedicated: 5, external: 2–20). For dedicated endpoints the + * value is clamped to a minimum of 5 regardless of what is set here. */ num_workers?: number; } export interface EvaluationModelRequest { /** - * Input prompt template + * User message template. Supports Jinja2 variables referencing dataset columns. */ input_template: string; /** - * Maximum number of tokens to generate + * Maximum number of tokens to generate. */ max_tokens: number; @@ -788,34 +870,45 @@ export namespace EvalCreateParams { model: string; /** - * Source of the model. + * Source of the model inference: - `serverless`: Together's shared serverless + * inference API. Default concurrency: 25 workers. - `dedicated`: A Together + * dedicated deployment endpoint. Default concurrency: 5 workers (minimum enforced + * even if num_workers is set lower). Authentication uses the requesting user's + * Together API token automatically. + * + * - `external`: An external inference API (e.g. OpenAI, Anthropic, Google, + * OpenRouter). Requires `external_api_token` and `external_base_url`. Default + * concurrency: 2 workers for first-party APIs (OpenAI, Anthropic, Google), 20 + * for proxy/aggregator endpoints. */ model_source: 'serverless' | 'dedicated' | 'external'; /** - * System prompt template + * System prompt template. Supports Jinja2 variables referencing dataset columns. */ system_template: string; /** - * Sampling temperature + * Sampling temperature for generation. */ temperature: number; /** - * Bearer/API token for external models. + * Bearer/API token for the external model provider. Required when model_source is + * 'external'. */ external_api_token?: string; /** - * Base URL for external models. Must be OpenAI-compatible base URL + * Base URL of the external inference API. Must be OpenAI-compatible. Required when + * model_source is 'external'. */ external_base_url?: string; /** - * Number of concurrent workers for inference requests. Overrides the default - * concurrency for this model. Useful for tuning throughput when using proxy - * endpoints (e.g. OpenRouter) or rate-limited external APIs. + * Number of concurrent inference workers. Overrides the source-specific default + * (serverless: 25, dedicated: 5, external: 2–20). For dedicated endpoints the + * value is clamped to a minimum of 5 regardless of what is set here. */ num_workers?: number; } From 43a6806fc6eb76c952bcc3b225e0c54f33674f54 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 17:00:15 +0000 Subject: [PATCH 29/31] feat(api): remove node_name field from ClusterNode --- .stats.yml | 4 ++-- src/resources/beta/clusters/clusters.ts | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/.stats.yml b/.stats.yml index d671988a..4162d464 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 82 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-835a3888d379fab044fd90910453b084ca9befdca7ce367f358dfd9cabcfcb14.yml -openapi_spec_hash: 36dae2e8eaa8dbf627d639e08e3fe24a +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-bab5a37892a3ada78d4cf218b2ae742fffdca6b39a1671624b47d5bf61bcb7f0.yml +openapi_spec_hash: 36032066f6699857a4e6ed02d682a21c config_hash: b35d5968fb07cce1c1be735f874898b1 diff --git a/src/resources/beta/clusters/clusters.ts b/src/resources/beta/clusters/clusters.ts index bc7a5613..4c83ba00 100644 --- a/src/resources/beta/clusters/clusters.ts +++ b/src/resources/beta/clusters/clusters.ts @@ -277,8 +277,6 @@ export namespace Cluster { node_id: string; - node_name: string; - num_cpu_cores: number; /** @@ -320,8 +318,6 @@ export namespace Cluster { node_id: string; - node_name: string; - num_cpu_cores: number; num_gpus: number; From 5dfdd3fec16bbdeabbcf774d1e2866a7f7fd7fd1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 17:40:29 +0000 Subject: [PATCH 30/31] docs(api): update gradient_accumulation_steps description in fine-tuning --- .stats.yml | 4 ++-- src/resources/fine-tuning.ts | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.stats.yml b/.stats.yml index 4162d464..4ca876d7 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 82 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-bab5a37892a3ada78d4cf218b2ae742fffdca6b39a1671624b47d5bf61bcb7f0.yml -openapi_spec_hash: 36032066f6699857a4e6ed02d682a21c +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai/togetherai-ce108a2095d36552bb556506de04475674f512a13bc5aa099e9750993405be14.yml +openapi_spec_hash: 4763dd426dd805306bbb38a314158cd3 config_hash: b35d5968fb07cce1c1be735f874898b1 diff --git a/src/resources/fine-tuning.ts b/src/resources/fine-tuning.ts index a42453c8..8bbd727c 100644 --- a/src/resources/fine-tuning.ts +++ b/src/resources/fine-tuning.ts @@ -1324,10 +1324,8 @@ export interface FineTuningCreateParams { from_hf_model?: string; /** - * Number of steps to accumulate gradients before performing a weight update. - * Effectively increases the batch size without requiring more memory. For example, - * with batch_size=4 and gradient_accumulation_steps=8, the effective batch size - * is 32. + * Number of steps to accumulate gradients before performing a weight update. If + * omitted or set to 0, the model default is used. */ gradient_accumulation_steps?: number; From 2cb5b36fb2b2f7f9eace8acd725efb3414702314 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 17:41:57 +0000 Subject: [PATCH 31/31] release: 0.41.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 44 +++++++++++++++++++++++++++++++++++ package.json | 2 +- src/version.ts | 2 +- 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 65078153..dbe1b2bf 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.40.0" + ".": "0.41.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 798b3bd7..3e8f3a68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,49 @@ # Changelog +## 0.41.0 (2026-05-20) + +Full Changelog: [v0.40.0...v0.41.0](https://github.com/togethercomputer/together-typescript/compare/v0.40.0...v0.41.0) + +### Features + +* **api:** add disable_position_bias_correction, remove num_samples from evals ([9b84610](https://github.com/togethercomputer/together-typescript/commit/9b846104ae9cb157c424b0cca7cf490e38bf4a5d)) +* **api:** add h200-140gb GPU type option to jig resource ([84855a3](https://github.com/togethercomputer/together-typescript/commit/84855a355af582d28477a3771b96d966e2ac427d)) +* **api:** add instance_name field to Remediation model ([dcbecdf](https://github.com/togethercomputer/together-typescript/commit/dcbecdf7dee2080d9df6b1bc5dfba1635835b69e)) +* **api:** Add node remediation APIs to clusters sdks ([a70223c](https://github.com/togethercomputer/together-typescript/commit/a70223c5f31720ab6aef7a9e0d0c9ef2229ebedb)) +* **api:** add params to list methods, update clusters/storage types ([e2d6e64](https://github.com/togethercomputer/together-typescript/commit/e2d6e640ed1c5201e4f1b679dae8da9d1ec99619)) +* **api:** add storage file/multipart operations, logs method, rename response types ([09b71bc](https://github.com/togethercomputer/together-typescript/commit/09b71bc93c96f1e83d525c4f5bf222169af62044)) +* **api:** add trigger param, update mode to array in remediations list ([5975b85](https://github.com/togethercomputer/together-typescript/commit/5975b8530ab0719fcf896c491d8c1f3efc883a82)) +* **api:** Integrate fine_tuning.list_metrics from stainless ([08c66e8](https://github.com/togethercomputer/together-typescript/commit/08c66e8043be75831ffa0dbbffd1c174563204f6)) +* **api:** manual updates ([d1782a2](https://github.com/togethercomputer/together-typescript/commit/d1782a2791b23ca07f1b1e5db8ae5de4f8c8f294)) +* **api:** manual updates ([0090346](https://github.com/togethercomputer/together-typescript/commit/009034659e0e986fb5355df4f67336efd586b3d7)) +* **api:** remove node_name field from ClusterNode ([43a6806](https://github.com/togethercomputer/together-typescript/commit/43a6806fc6eb76c952bcc3b225e0c54f33674f54)) + + +### Bug Fixes + +* **api:** remove trigger parameter from remediations list method ([72b7cb8](https://github.com/togethercomputer/together-typescript/commit/72b7cb899ef411e8ec19e2a8d38852b057184f4d)) +* **types:** change status to enum in cluster storage ([cf4f87b](https://github.com/togethercomputer/together-typescript/commit/cf4f87b4bdef72daf7228159cccefccbf3bfe4e8)) +* **typescript:** upgrade tsc-multi so that it works with Node 26 ([06d9a67](https://github.com/togethercomputer/together-typescript/commit/06d9a67b24273d69622381d7ea9b11660a4c350d)) +* **types:** make duration_days and size_tib optional in clusters ([733450c](https://github.com/togethercomputer/together-typescript/commit/733450c79034609b749ce7a8a9255dc63dd031c1)) +* **types:** remove error field, make requestId required in queue QueueSubmitResponse ([88adf97](https://github.com/togethercomputer/together-typescript/commit/88adf97945ffb851fb7248c586dc40e46f9c7ff1)) +* **types:** restrict endpoint to specific values in batches create ([c65be59](https://github.com/togethercomputer/together-typescript/commit/c65be590b14e2da807110160e6d87f07b7c5ff55)) + + +### Chores + +* temp deletion ([a89bc6a](https://github.com/togethercomputer/together-typescript/commit/a89bc6a4ad3973bdd5c69f72d9b2c0e54fcb9c0d)) +* temp undeletion ([ad924d9](https://github.com/togethercomputer/together-typescript/commit/ad924d976a9d1e79119db8aef2ae7667033e6ea6)) +* **tests:** remove redundant File import ([fac1032](https://github.com/togethercomputer/together-typescript/commit/fac1032d9ae4720162e16df09dbed9865b8b4494)) + + +### Documentation + +* **api:** add field documentation to clusters and storage ([4623a4b](https://github.com/togethercomputer/together-typescript/commit/4623a4be3ce06321b63c3240623ffe7d0f184373)) +* **api:** clarify max_seq_length parameter documentation in fine-tuning ([adc6bbf](https://github.com/togethercomputer/together-typescript/commit/adc6bbf87b396fe14898c92ae4ddad941b78ad37)) +* **api:** clarify suffix parameter max length in fine-tuning ([1dd29f9](https://github.com/togethercomputer/together-typescript/commit/1dd29f9f54e659b3f39b9f8b0e20f53eb6623216)) +* **api:** improve docstring clarity and grammar across resources ([3fff4b6](https://github.com/togethercomputer/together-typescript/commit/3fff4b65ebf0a6e409a1325f4b0c39ccb453526a)) +* **api:** update gradient_accumulation_steps description in fine-tuning ([5dfdd3f](https://github.com/togethercomputer/together-typescript/commit/5dfdd3fec16bbdeabbcf774d1e2866a7f7fd7fd1)) + ## 0.40.0 (2026-05-11) Full Changelog: [v0.39.0...v0.40.0](https://github.com/togethercomputer/together-typescript/compare/v0.39.0...v0.40.0) diff --git a/package.json b/package.json index 2c7b7c3a..0af99cb5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "together-ai", - "version": "0.40.0", + "version": "0.41.0", "description": "The official TypeScript library for the Together API", "author": "Together ", "types": "dist/index.d.ts", diff --git a/src/version.ts b/src/version.ts index c17cceec..2f1576d5 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '0.40.0'; // x-release-please-version +export const VERSION = '0.41.0'; // x-release-please-version