From 7407c592a4ba65b6f52b778d3978c60a09d6edcf Mon Sep 17 00:00:00 2001 From: Alex Z Date: Tue, 30 Dec 2025 17:59:02 -0800 Subject: [PATCH 1/3] initial --- js/src/exports.ts | 15 ++ js/src/prompt-environments.ts | 287 ++++++++++++++++++++++++++++++++++ 2 files changed, 302 insertions(+) create mode 100644 js/src/prompt-environments.ts diff --git a/js/src/exports.ts b/js/src/exports.ts index fc097c339..a74a1b21f 100644 --- a/js/src/exports.ts +++ b/js/src/exports.ts @@ -101,6 +101,21 @@ export { wrapTraced, } from "./logger"; +export { + deletePromptEnvironment, + getPromptEnvironment, + listPromptEnvironments, + setPromptEnvironment, +} from "./prompt-environments"; + +export type { + DeletePromptEnvironmentOptions, + GetPromptEnvironmentOptions, + ListPromptEnvironmentsOptions, + PromptEnvironmentAssociation, + SetPromptEnvironmentOptions, +} from "./prompt-environments"; + export { isTemplateFormat, parseTemplateFormat, diff --git a/js/src/prompt-environments.ts b/js/src/prompt-environments.ts new file mode 100644 index 000000000..df8cde3dc --- /dev/null +++ b/js/src/prompt-environments.ts @@ -0,0 +1,287 @@ +import { + BraintrustState, + FullLoginOptions, + _internalGetGlobalState, +} from "./logger"; + +/** + * Represents an association between a prompt version and an environment. + */ +export interface PromptEnvironmentAssociation { + /** Unique identifier for the association */ + id: string; + /** Type of object (always "prompt" for now) */ + object_type: string; + /** The prompt ID */ + object_id: string; + /** The version (xact_id) of the prompt */ + object_version: string; + /** The environment slug */ + environment_slug: string; + /** When the association was created */ + created: string; +} + +export type ListPromptEnvironmentsOptions = FullLoginOptions & { + /** The ID of the prompt to list environment associations for */ + promptId: string; + /** Optional state for testing */ + state?: BraintrustState; +}; + +export type GetPromptEnvironmentOptions = FullLoginOptions & { + /** The ID of the prompt */ + promptId: string; + /** The environment slug to get */ + environmentSlug: string; + /** Optional state for testing */ + state?: BraintrustState; +}; + +export type SetPromptEnvironmentOptions = FullLoginOptions & { + /** The ID of the prompt */ + promptId: string; + /** The environment slug to set */ + environmentSlug: string; + /** The version (xact_id) of the prompt to associate with this environment */ + version: string; + /** Optional state for testing */ + state?: BraintrustState; +}; + +export type DeletePromptEnvironmentOptions = FullLoginOptions & { + /** The ID of the prompt */ + promptId: string; + /** The environment slug to delete */ + environmentSlug: string; + /** Optional state for testing */ + state?: BraintrustState; +}; + +/** + * List all environment associations for a prompt. + * + * @param options Options for the request + * @param options.promptId The ID of the prompt to list environment associations for + * @param options.apiKey The API key to use. If not specified, will use the `BRAINTRUST_API_KEY` environment variable. + * @param options.appUrl The URL of the Braintrust App. Defaults to https://www.braintrust.dev. + * @param options.orgName (Optional) The name of a specific organization to connect to. + * @returns A list of environment associations for the prompt + * + * @example + * ```javascript + * const associations = await listPromptEnvironments({ + * promptId: "prompt-uuid", + * }); + * console.log(associations); + * // [{ environment_slug: "production", object_version: "123", ... }] + * ``` + */ +export async function listPromptEnvironments({ + promptId, + appUrl, + apiKey, + orgName, + fetch, + forceLogin, + state: stateArg, +}: ListPromptEnvironmentsOptions): Promise { + const state = stateArg ?? _internalGetGlobalState(); + + await state.login({ + orgName, + apiKey, + appUrl, + fetch, + forceLogin, + }); + + const response = await state + .apiConn() + .get_json( + `environment-object/prompt/${promptId}`, + orgName ? { org_name: orgName } : {}, + ); + + return (response.objects ?? []) as PromptEnvironmentAssociation[]; +} + +/** + * Get a specific environment association for a prompt. + * + * @param options Options for the request + * @param options.promptId The ID of the prompt + * @param options.environmentSlug The environment slug to get (e.g., "production", "staging") + * @param options.apiKey The API key to use. If not specified, will use the `BRAINTRUST_API_KEY` environment variable. + * @param options.appUrl The URL of the Braintrust App. Defaults to https://www.braintrust.dev. + * @param options.orgName (Optional) The name of a specific organization to connect to. + * @returns The environment association + * @throws If no association exists for the prompt and environment + * + * @example + * ```javascript + * const association = await getPromptEnvironment({ + * promptId: "prompt-uuid", + * environmentSlug: "production", + * }); + * console.log(association.object_version); // "123" + * ``` + */ +export async function getPromptEnvironment({ + promptId, + environmentSlug, + appUrl, + apiKey, + orgName, + fetch, + forceLogin, + state: stateArg, +}: GetPromptEnvironmentOptions): Promise { + const state = stateArg ?? _internalGetGlobalState(); + + await state.login({ + orgName, + apiKey, + appUrl, + fetch, + forceLogin, + }); + + const response = await state + .apiConn() + .get_json( + `environment-object/prompt/${promptId}/${environmentSlug}`, + orgName ? { org_name: orgName } : {}, + ); + + return response as PromptEnvironmentAssociation; +} + +/** + * Set (or update) the prompt version associated with an environment. + * If an association already exists, it will be updated. Otherwise, a new one will be created. + * + * @param options Options for the request + * @param options.promptId The ID of the prompt + * @param options.environmentSlug The environment slug to set (e.g., "production", "staging") + * @param options.version The version (xact_id) of the prompt to associate with this environment + * @param options.apiKey The API key to use. If not specified, will use the `BRAINTRUST_API_KEY` environment variable. + * @param options.appUrl The URL of the Braintrust App. Defaults to https://www.braintrust.dev. + * @param options.orgName (Optional) The name of a specific organization to connect to. + * @returns The created or updated environment association + * + * @example + * ```javascript + * // Associate prompt version "456" with the "production" environment + * const association = await setPromptEnvironment({ + * promptId: "prompt-uuid", + * environmentSlug: "production", + * version: "456", + * }); + * ``` + */ +export async function setPromptEnvironment({ + promptId, + environmentSlug, + version, + appUrl, + apiKey, + orgName, + fetch, + forceLogin, + state: stateArg, +}: SetPromptEnvironmentOptions): Promise { + const state = stateArg ?? _internalGetGlobalState(); + + await state.login({ + orgName, + apiKey, + appUrl, + fetch, + forceLogin, + }); + + // Use PUT for upsert behavior - need to use state.fetch since HTTPConnection doesn't have put method + const apiConn = state.apiConn(); + const url = `${apiConn.base_url}/environment-object/prompt/${promptId}/${environmentSlug}`; + const resp = await state.fetch(url, { + method: "PUT", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + ...(apiConn.token ? { Authorization: `Bearer ${apiConn.token}` } : {}), + }, + body: JSON.stringify({ + object_version: version, + ...(orgName && { org_name: orgName }), + }), + }); + + if (!resp.ok) { + const text = await resp.text(); + throw new Error(`${resp.status}: ${resp.statusText} (${text})`); + } + + return (await resp.json()) as PromptEnvironmentAssociation; +} + +/** + * Delete an environment association for a prompt. + * + * @param options Options for the request + * @param options.promptId The ID of the prompt + * @param options.environmentSlug The environment slug to delete (e.g., "production", "staging") + * @param options.apiKey The API key to use. If not specified, will use the `BRAINTRUST_API_KEY` environment variable. + * @param options.appUrl The URL of the Braintrust App. Defaults to https://www.braintrust.dev. + * @param options.orgName (Optional) The name of a specific organization to connect to. + * @returns The deleted environment association + * @throws If no association exists for the prompt and environment + * + * @example + * ```javascript + * // Remove the "staging" environment association + * await deletePromptEnvironment({ + * promptId: "prompt-uuid", + * environmentSlug: "staging", + * }); + * ``` + */ +export async function deletePromptEnvironment({ + promptId, + environmentSlug, + appUrl, + apiKey, + orgName, + fetch, + forceLogin, + state: stateArg, +}: DeletePromptEnvironmentOptions): Promise { + const state = stateArg ?? _internalGetGlobalState(); + + await state.login({ + orgName, + apiKey, + appUrl, + fetch, + forceLogin, + }); + + // Need to use state.fetch since HTTPConnection doesn't have delete method + const apiConn = state.apiConn(); + const queryString = orgName ? `?org_name=${encodeURIComponent(orgName)}` : ""; + const url = `${apiConn.base_url}/environment-object/prompt/${promptId}/${environmentSlug}${queryString}`; + const resp = await state.fetch(url, { + method: "DELETE", + headers: { + Accept: "application/json", + ...(apiConn.token ? { Authorization: `Bearer ${apiConn.token}` } : {}), + }, + }); + + if (!resp.ok) { + const text = await resp.text(); + throw new Error(`${resp.status}: ${resp.statusText} (${text})`); + } + + return (await resp.json()) as PromptEnvironmentAssociation; +} From fd36c0426ae56e50ac37de8e76f797fbf465699e Mon Sep 17 00:00:00 2001 From: Alex Z Date: Tue, 6 Jan 2026 11:15:56 -0800 Subject: [PATCH 2/3] cleanup --- js/src/prompt-environments.ts | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/js/src/prompt-environments.ts b/js/src/prompt-environments.ts index df8cde3dc..f7271d04b 100644 --- a/js/src/prompt-environments.ts +++ b/js/src/prompt-environments.ts @@ -4,57 +4,36 @@ import { _internalGetGlobalState, } from "./logger"; -/** - * Represents an association between a prompt version and an environment. - */ export interface PromptEnvironmentAssociation { - /** Unique identifier for the association */ id: string; - /** Type of object (always "prompt" for now) */ object_type: string; - /** The prompt ID */ object_id: string; - /** The version (xact_id) of the prompt */ object_version: string; - /** The environment slug */ environment_slug: string; - /** When the association was created */ created: string; } export type ListPromptEnvironmentsOptions = FullLoginOptions & { - /** The ID of the prompt to list environment associations for */ promptId: string; - /** Optional state for testing */ state?: BraintrustState; }; export type GetPromptEnvironmentOptions = FullLoginOptions & { - /** The ID of the prompt */ promptId: string; - /** The environment slug to get */ environmentSlug: string; - /** Optional state for testing */ state?: BraintrustState; }; export type SetPromptEnvironmentOptions = FullLoginOptions & { - /** The ID of the prompt */ promptId: string; - /** The environment slug to set */ environmentSlug: string; - /** The version (xact_id) of the prompt to associate with this environment */ version: string; - /** Optional state for testing */ state?: BraintrustState; }; export type DeletePromptEnvironmentOptions = FullLoginOptions & { - /** The ID of the prompt */ promptId: string; - /** The environment slug to delete */ environmentSlug: string; - /** Optional state for testing */ state?: BraintrustState; }; From a6ad9400784ba8cc686a0a7a31753c51b0879c37 Mon Sep 17 00:00:00 2001 From: Alex Z Date: Wed, 7 Jan 2026 13:39:33 -0800 Subject: [PATCH 3/3] better api --- js/src/exports.ts | 15 -------- js/src/framework2.ts | 27 ++++++++++++++ js/src/logger.ts | 49 +++++++++++++++++++++++++ js/src/prompt-environments.ts | 69 +++-------------------------------- 4 files changed, 82 insertions(+), 78 deletions(-) diff --git a/js/src/exports.ts b/js/src/exports.ts index a74a1b21f..fc097c339 100644 --- a/js/src/exports.ts +++ b/js/src/exports.ts @@ -101,21 +101,6 @@ export { wrapTraced, } from "./logger"; -export { - deletePromptEnvironment, - getPromptEnvironment, - listPromptEnvironments, - setPromptEnvironment, -} from "./prompt-environments"; - -export type { - DeletePromptEnvironmentOptions, - GetPromptEnvironmentOptions, - ListPromptEnvironmentsOptions, - PromptEnvironmentAssociation, - SetPromptEnvironmentOptions, -} from "./prompt-environments"; - export { isTemplateFormat, parseTemplateFormat, diff --git a/js/src/framework2.ts b/js/src/framework2.ts index 096f5284c..f47a86f83 100644 --- a/js/src/framework2.ts +++ b/js/src/framework2.ts @@ -28,6 +28,15 @@ import { Prompt, PromptRowWithId, } from "./logger"; +import { + clearPromptEnvironment, + listPromptEnvironments, + setPromptEnvironment, + type ClearPromptEnvironmentOptions, + type ListPromptEnvironmentsOptions, + type PromptEnvironmentAssociation, + type SetPromptEnvironmentOptions, +} from "./prompt-environments"; import { GenericFunction } from "./framework-types"; interface BaseFnOpts { @@ -581,6 +590,24 @@ export class PromptBuilder { return prompt; } + + public listPromptEnvironments( + options: ListPromptEnvironmentsOptions, + ): Promise { + return listPromptEnvironments(options); + } + + public setPromptEnvironment( + options: SetPromptEnvironmentOptions, + ): Promise { + return setPromptEnvironment(options); + } + + public clearPromptEnvironment( + options: ClearPromptEnvironmentOptions, + ): Promise { + return clearPromptEnvironment(options); + } } export function promptDefinitionToPromptData( diff --git a/js/src/logger.ts b/js/src/logger.ts index ac4b7f499..9ab5e2b8a 100644 --- a/js/src/logger.ts +++ b/js/src/logger.ts @@ -89,6 +89,15 @@ import iso, { IsoAsyncLocalStorage } from "./isomorph"; import { canUseDiskCache, DiskCache } from "./prompt-cache/disk-cache"; import { LRUCache } from "./prompt-cache/lru-cache"; import { PromptCache } from "./prompt-cache/prompt-cache"; +import { + clearPromptEnvironment, + listPromptEnvironments, + setPromptEnvironment, + type ClearPromptEnvironmentOptions, + type ListPromptEnvironmentsOptions, + type PromptEnvironmentAssociation, + type SetPromptEnvironmentOptions, +} from "./prompt-environments"; import { addAzureBlobHeaders, getCurrentUnixTimestamp, @@ -6482,6 +6491,46 @@ export class Prompt< return this.getParsedPromptData()!; } + private requireId(): string { + const id = this.id as string | undefined; + if (!id) { + throw new Error( + "Prompt id is required to manage environments. Load the prompt from the API first.", + ); + } + return id; + } + + public listPromptEnvironments( + this: Prompt, + options: Omit = {}, + ): Promise { + return listPromptEnvironments({ + ...options, + promptId: this.requireId(), + }); + } + + public setPromptEnvironment( + this: Prompt, + options: Omit, + ): Promise { + return setPromptEnvironment({ + ...options, + promptId: this.requireId(), + }); + } + + public clearPromptEnvironment( + this: Prompt, + options: Omit, + ): Promise { + return clearPromptEnvironment({ + ...options, + promptId: this.requireId(), + }); + } + /** * Build the prompt with the given formatting options. The args you pass in will * be forwarded to the mustache template that defines the prompt and rendered with diff --git a/js/src/prompt-environments.ts b/js/src/prompt-environments.ts index f7271d04b..0fcc5136d 100644 --- a/js/src/prompt-environments.ts +++ b/js/src/prompt-environments.ts @@ -18,12 +18,6 @@ export type ListPromptEnvironmentsOptions = FullLoginOptions & { state?: BraintrustState; }; -export type GetPromptEnvironmentOptions = FullLoginOptions & { - promptId: string; - environmentSlug: string; - state?: BraintrustState; -}; - export type SetPromptEnvironmentOptions = FullLoginOptions & { promptId: string; environmentSlug: string; @@ -31,7 +25,7 @@ export type SetPromptEnvironmentOptions = FullLoginOptions & { state?: BraintrustState; }; -export type DeletePromptEnvironmentOptions = FullLoginOptions & { +export type ClearPromptEnvironmentOptions = FullLoginOptions & { promptId: string; environmentSlug: string; state?: BraintrustState; @@ -85,57 +79,6 @@ export async function listPromptEnvironments({ return (response.objects ?? []) as PromptEnvironmentAssociation[]; } -/** - * Get a specific environment association for a prompt. - * - * @param options Options for the request - * @param options.promptId The ID of the prompt - * @param options.environmentSlug The environment slug to get (e.g., "production", "staging") - * @param options.apiKey The API key to use. If not specified, will use the `BRAINTRUST_API_KEY` environment variable. - * @param options.appUrl The URL of the Braintrust App. Defaults to https://www.braintrust.dev. - * @param options.orgName (Optional) The name of a specific organization to connect to. - * @returns The environment association - * @throws If no association exists for the prompt and environment - * - * @example - * ```javascript - * const association = await getPromptEnvironment({ - * promptId: "prompt-uuid", - * environmentSlug: "production", - * }); - * console.log(association.object_version); // "123" - * ``` - */ -export async function getPromptEnvironment({ - promptId, - environmentSlug, - appUrl, - apiKey, - orgName, - fetch, - forceLogin, - state: stateArg, -}: GetPromptEnvironmentOptions): Promise { - const state = stateArg ?? _internalGetGlobalState(); - - await state.login({ - orgName, - apiKey, - appUrl, - fetch, - forceLogin, - }); - - const response = await state - .apiConn() - .get_json( - `environment-object/prompt/${promptId}/${environmentSlug}`, - orgName ? { org_name: orgName } : {}, - ); - - return response as PromptEnvironmentAssociation; -} - /** * Set (or update) the prompt version associated with an environment. * If an association already exists, it will be updated. Otherwise, a new one will be created. @@ -205,7 +148,7 @@ export async function setPromptEnvironment({ } /** - * Delete an environment association for a prompt. + * Clear an environment association for a prompt. * * @param options Options for the request * @param options.promptId The ID of the prompt @@ -213,19 +156,19 @@ export async function setPromptEnvironment({ * @param options.apiKey The API key to use. If not specified, will use the `BRAINTRUST_API_KEY` environment variable. * @param options.appUrl The URL of the Braintrust App. Defaults to https://www.braintrust.dev. * @param options.orgName (Optional) The name of a specific organization to connect to. - * @returns The deleted environment association + * @returns The cleared environment association * @throws If no association exists for the prompt and environment * * @example * ```javascript * // Remove the "staging" environment association - * await deletePromptEnvironment({ + * await clearPromptEnvironment({ * promptId: "prompt-uuid", * environmentSlug: "staging", * }); * ``` */ -export async function deletePromptEnvironment({ +export async function clearPromptEnvironment({ promptId, environmentSlug, appUrl, @@ -234,7 +177,7 @@ export async function deletePromptEnvironment({ fetch, forceLogin, state: stateArg, -}: DeletePromptEnvironmentOptions): Promise { +}: ClearPromptEnvironmentOptions): Promise { const state = stateArg ?? _internalGetGlobalState(); await state.login({