langsmith 0.5.26 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.js CHANGED
@@ -12,8 +12,15 @@ import { raiseForStatus, isLangSmithNotFoundError, isLangSmithConflictError, } f
12
12
  import { promptCacheSingleton, } from "./utils/prompt_cache/index.js";
13
13
  import * as fsUtils from "./utils/fs.js";
14
14
  import { _shouldStreamForGlobalFetchImplementation, _getFetchImplementation, } from "./singletons/fetch.js";
15
+ import { DEFAULT_API_URL, hasValue, loadProfileClientConfig, } from "./utils/profiles.js";
15
16
  import { serialize as serializePayloadForTracing, estimateSerializedSize, } from "./utils/fast-safe-stringify/index.js";
16
17
  import { getSharedSerializeWorker, hasLargeString, } from "./utils/serialize_worker.js";
18
+ function assertPullPublicPromptAllowed(promptIdentifier, dangerouslyPullPublicPrompt) {
19
+ const [owner] = parseHubIdentifier(promptIdentifier);
20
+ if (owner !== "-" && !dangerouslyPullPublicPrompt) {
21
+ throw new Error("Pulling a public prompt by owner/name is disabled by default because prompts may contain untrusted serialized LangChain objects. If you trust this prompt, set `dangerouslyPullPublicPrompt: true` to acknowledge the risk.");
22
+ }
23
+ }
17
24
  /**
18
25
  * Catches timestamps without a timezone suffix.
19
26
  */
@@ -120,7 +127,6 @@ export const DEFAULT_MAX_SIZE_BYTES = 1024 * 1024 * 1024; // 1GB
120
127
  const SERVER_INFO_REQUEST_TIMEOUT_MS = 10000;
121
128
  /** Maximum number of operations to batch in a single request. */
122
129
  const DEFAULT_BATCH_SIZE_LIMIT = 100;
123
- const DEFAULT_API_URL = "https://api.smith.langchain.com";
124
130
  export class AutoBatchQueue {
125
131
  constructor(maxSizeBytes) {
126
132
  Object.defineProperty(this, "items", {
@@ -153,19 +159,11 @@ export class AutoBatchQueue {
153
159
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise
154
160
  itemPromiseResolve = resolve;
155
161
  });
156
- // By default we compute the exact serialized size here by stringifying
157
- // the payload. This is expensive: JSON.stringify on large payloads
158
- // blocks the event loop on the user's hot path.
159
- //
160
- // Opting into LANGSMITH_PERF_OPTIMIZATION=true switches to a cheap
161
- // structural estimate instead. The estimate is only used for soft
162
- // memory accounting (queue size limit and downstream async caller
163
- // memory tracking), never for anything correctness-critical -- the
164
- // real serialization still happens later, off the hot path, when the
165
- // batch is assembled for sending.
166
- const size = getLangSmithEnvironmentVariable("PERF_OPTIMIZATION") === "true"
167
- ? estimateSerializedSize(item.item).size
168
- : serializePayloadForTracing(item.item, `Serializing run with id: ${item.item.id}`).length;
162
+ // Use a cheap structural estimate for soft memory accounting (queue size
163
+ // limit and downstream async caller memory tracking). The exact
164
+ // serialization still happens later, off the hot path, when the batch is
165
+ // assembled for sending.
166
+ const size = estimateSerializedSize(item.item).size;
169
167
  // Check if adding this item would exceed the size limit
170
168
  // Allow the run if the queue is empty (to support large single traces)
171
169
  if (this.sizeBytes + size > this.maxSizeBytes && this.items.length > 0) {
@@ -232,15 +230,145 @@ export class Client {
232
230
  return this._tracingMode;
233
231
  }
234
232
  get _fetch() {
235
- return this.fetchImplementation || _getFetchImplementation(this.debug);
233
+ const fetchImplementation = this.fetchImplementation || _getFetchImplementation(this.debug);
234
+ return (async (input, init) => {
235
+ let authHeader;
236
+ const profileManagedAuthorization = this.getProfileManagedAuthorizationHeader(init);
237
+ if (this.apiKey !== undefined) {
238
+ authHeader = { name: "x-api-key", value: `${this.apiKey}` };
239
+ }
240
+ else if (!this.hasExplicitAuthHeader(init, profileManagedAuthorization)) {
241
+ authHeader = await this.profileAuth?.getAuthHeader(fetchImplementation, init?.signal);
242
+ }
243
+ return fetchImplementation(input, this.applyCurrentAuthHeaders(init, authHeader, profileManagedAuthorization));
244
+ });
245
+ }
246
+ getProfileManagedAuthorizationHeader(init) {
247
+ if (!init?.headers || !this.profileAuth) {
248
+ return undefined;
249
+ }
250
+ const authorization = new Headers(init.headers).get("Authorization");
251
+ if (!hasValue(authorization)) {
252
+ return undefined;
253
+ }
254
+ return this.profileAuth.isProfileAuthorizationHeader(authorization ?? "")
255
+ ? (authorization ?? undefined)
256
+ : undefined;
257
+ }
258
+ isProfileManagedAuthorizationHeader(value, profileManagedAuthorization) {
259
+ return (value === profileManagedAuthorization ||
260
+ this.profileAuth?.isProfileAuthorizationHeader(value) === true);
261
+ }
262
+ hasExplicitAuthHeader(init, profileManagedAuthorization) {
263
+ if (!init?.headers) {
264
+ return false;
265
+ }
266
+ const headers = new Headers(init.headers);
267
+ if (hasValue(headers.get("x-api-key"))) {
268
+ return true;
269
+ }
270
+ const authorization = headers.get("Authorization");
271
+ if (!hasValue(authorization)) {
272
+ return false;
273
+ }
274
+ return !this.isProfileManagedAuthorizationHeader(authorization ?? "", profileManagedAuthorization);
275
+ }
276
+ applyCurrentAuthHeaders(init, authHeader, profileManagedAuthorization) {
277
+ if (!authHeader) {
278
+ return init;
279
+ }
280
+ const applyAuth = (headers) => {
281
+ if (this.apiKey !== undefined && authHeader.name === "x-api-key") {
282
+ headers.delete("Authorization");
283
+ if (!headers.has("x-api-key")) {
284
+ headers.set("x-api-key", authHeader.value);
285
+ }
286
+ return headers;
287
+ }
288
+ if (authHeader.name === "Authorization") {
289
+ if (hasValue(headers.get("x-api-key"))) {
290
+ return headers;
291
+ }
292
+ const authorization = headers.get("Authorization");
293
+ if (hasValue(authorization) &&
294
+ !this.isProfileManagedAuthorizationHeader(authorization ?? "", profileManagedAuthorization)) {
295
+ return headers;
296
+ }
297
+ headers.set("Authorization", authHeader.value);
298
+ return headers;
299
+ }
300
+ const authorization = headers.get("Authorization");
301
+ if (hasValue(authorization) &&
302
+ !this.isProfileManagedAuthorizationHeader(authorization ?? "", profileManagedAuthorization)) {
303
+ return headers;
304
+ }
305
+ if (hasValue(authorization)) {
306
+ headers.delete("Authorization");
307
+ }
308
+ if (!headers.has("x-api-key")) {
309
+ headers.set("x-api-key", authHeader.value);
310
+ }
311
+ return headers;
312
+ };
313
+ if (!init) {
314
+ return {
315
+ headers: { [authHeader.name]: authHeader.value },
316
+ };
317
+ }
318
+ if (init.headers instanceof Headers) {
319
+ return { ...init, headers: applyAuth(new Headers(init.headers)) };
320
+ }
321
+ if (Array.isArray(init.headers)) {
322
+ return { ...init, headers: applyAuth(new Headers(init.headers)) };
323
+ }
324
+ const headers = {
325
+ ...(init.headers ?? {}),
326
+ };
327
+ const getHeaderKey = (name) => Object.keys(headers).find((key) => key.toLowerCase() === name);
328
+ const getHeader = (name) => {
329
+ const key = getHeaderKey(name);
330
+ return key ? headers[key] : undefined;
331
+ };
332
+ const hasApiKey = hasValue(getHeader("x-api-key"));
333
+ const authorization = getHeader("authorization");
334
+ const hasExplicitAuthorization = hasValue(authorization) &&
335
+ !this.isProfileManagedAuthorizationHeader(authorization ?? "", profileManagedAuthorization);
336
+ if (this.apiKey !== undefined && authHeader.name === "x-api-key") {
337
+ const authorizationKey = getHeaderKey("authorization");
338
+ if (authorizationKey) {
339
+ delete headers[authorizationKey];
340
+ }
341
+ if (!hasApiKey) {
342
+ headers["x-api-key"] = authHeader.value;
343
+ }
344
+ return { ...init, headers };
345
+ }
346
+ if (authHeader.name === "Authorization") {
347
+ if (!hasApiKey && !hasExplicitAuthorization) {
348
+ const authorizationKey = getHeaderKey("authorization");
349
+ if (authorizationKey && authorizationKey !== "Authorization") {
350
+ delete headers[authorizationKey];
351
+ }
352
+ headers.Authorization = authHeader.value;
353
+ }
354
+ return { ...init, headers };
355
+ }
356
+ if (!hasExplicitAuthorization) {
357
+ const authorizationKey = getHeaderKey("authorization");
358
+ if (authorizationKey) {
359
+ delete headers[authorizationKey];
360
+ }
361
+ if (!hasApiKey) {
362
+ headers["x-api-key"] = authHeader.value;
363
+ }
364
+ }
365
+ return { ...init, headers };
236
366
  }
237
367
  /**
238
368
  * Serialize a payload for tracing, optionally offloading the work to a
239
- * Node worker thread when LANGSMITH_PERF_OPTIMIZATION=true and the runtime
240
- * supports worker_threads.
369
+ * Node worker thread when the runtime supports worker_threads.
241
370
  *
242
371
  * Falls back to synchronous serialization when:
243
- * - the perf flag is off
244
372
  * - manualFlushMode is enabled (serverless: worker boot cost > benefit)
245
373
  * - worker_threads is unavailable (non-Node runtimes)
246
374
  * - the payload contains values that can't be structured-cloned across
@@ -256,8 +384,7 @@ export class Client {
256
384
  });
257
385
  }
258
386
  async _serializeBody(payload, errorContext) {
259
- const perfOptIn = getLangSmithEnvironmentVariable("PERF_OPTIMIZATION") === "true";
260
- if (!perfOptIn || this.manualFlushMode) {
387
+ if (this.manualFlushMode) {
261
388
  return serializePayloadForTracing(payload, errorContext);
262
389
  }
263
390
  // Shape-aware gate: worker offload pays for itself only when the
@@ -500,6 +627,12 @@ export class Client {
500
627
  writable: true,
501
628
  value: void 0
502
629
  });
630
+ Object.defineProperty(this, "profileAuth", {
631
+ enumerable: true,
632
+ configurable: true,
633
+ writable: true,
634
+ value: void 0
635
+ });
503
636
  Object.defineProperty(this, "multipartStreamingDisabled", {
504
637
  enumerable: true,
505
638
  configurable: true,
@@ -548,12 +681,15 @@ export class Client {
548
681
  if (this.apiUrl.endsWith("/")) {
549
682
  this.apiUrl = this.apiUrl.slice(0, -1);
550
683
  }
551
- this.apiKey = trimQuotes(config.apiKey ?? defaultConfig.apiKey);
684
+ const configuredApiKey = trimQuotes(config.apiKey ?? defaultConfig.apiKey);
685
+ this.apiKey = hasValue(configuredApiKey) ? configuredApiKey : undefined;
686
+ this.profileAuth =
687
+ this.apiKey !== undefined ? undefined : defaultConfig.profileAuth;
552
688
  this.webUrl = trimQuotes(config.webUrl ?? defaultConfig.webUrl);
553
689
  if (this.webUrl?.endsWith("/")) {
554
690
  this.webUrl = this.webUrl.slice(0, -1);
555
691
  }
556
- this.workspaceId = trimQuotes(config.workspaceId ?? getLangSmithEnvironmentVariable("WORKSPACE_ID"));
692
+ this.workspaceId = trimQuotes(config.workspaceId ?? defaultConfig.workspaceId);
557
693
  this.timeout_ms = config.timeout_ms ?? 90_000;
558
694
  this.caller = new AsyncCaller({
559
695
  ...(config.callerOptions ?? {}),
@@ -638,18 +774,31 @@ export class Client {
638
774
  this._customHeaders = config.headers ?? {};
639
775
  }
640
776
  static getDefaultClientConfig() {
641
- const apiKey = getLangSmithEnvironmentVariable("API_KEY");
642
- const apiUrl = getLangSmithEnvironmentVariable("ENDPOINT") ?? DEFAULT_API_URL;
777
+ const profileConfig = loadProfileClientConfig();
778
+ const envApiKey = getLangSmithEnvironmentVariable("API_KEY");
779
+ const envApiUrl = getLangSmithEnvironmentVariable("ENDPOINT");
780
+ const envWorkspaceId = getLangSmithEnvironmentVariable("WORKSPACE_ID");
781
+ const envAuthSet = hasValue(envApiKey);
782
+ const apiUrl = envApiUrl ?? profileConfig.apiUrl ?? DEFAULT_API_URL;
783
+ const workspaceId = envWorkspaceId ?? profileConfig.workspaceId;
643
784
  const hideInputs = getLangSmithEnvironmentVariable("HIDE_INPUTS") === "true";
644
785
  const hideOutputs = getLangSmithEnvironmentVariable("HIDE_OUTPUTS") === "true";
645
786
  const hideMetadata = getLangSmithEnvironmentVariable("HIDE_METADATA") === "true";
646
787
  return {
647
788
  apiUrl: apiUrl,
648
- apiKey: apiKey,
789
+ apiKey: envApiKey,
649
790
  webUrl: undefined,
650
791
  hideInputs: hideInputs,
651
792
  hideOutputs: hideOutputs,
652
793
  hideMetadata: hideMetadata,
794
+ workspaceId: workspaceId,
795
+ oauthAccessToken: !envAuthSet
796
+ ? profileConfig.oauthAccessToken
797
+ : undefined,
798
+ oauthRefreshToken: !envAuthSet
799
+ ? profileConfig.oauthRefreshToken
800
+ : undefined,
801
+ profileAuth: !envAuthSet ? profileConfig.profileAuth : undefined,
653
802
  };
654
803
  }
655
804
  getHostUrl() {
@@ -701,9 +850,15 @@ export class Client {
701
850
  ...this._customHeaders,
702
851
  };
703
852
  // Required headers that should not be overridden
704
- if (this.apiKey) {
853
+ if (this.apiKey !== undefined) {
705
854
  headers["x-api-key"] = `${this.apiKey}`;
706
855
  }
856
+ else {
857
+ const profileAuthHeader = this.profileAuth?.currentAuthHeader();
858
+ if (profileAuthHeader) {
859
+ headers[profileAuthHeader.name] = profileAuthHeader.value;
860
+ }
861
+ }
707
862
  if (this.workspaceId) {
708
863
  headers["x-tenant-id"] = this.workspaceId;
709
864
  }
@@ -4460,7 +4615,24 @@ export class Client {
4460
4615
  hub_model_provider: result.model_provider,
4461
4616
  };
4462
4617
  }
4618
+ /**
4619
+ * Pull a prompt commit from the LangSmith API.
4620
+ *
4621
+ * Public prompts referenced by owner/name cross a trust boundary because the
4622
+ * prompt manifest may contain serialized LangChain objects and configuration
4623
+ * that affect runtime behavior. For example, a prompt can intentionally
4624
+ * configure a model with a custom base URL, headers, model name, or other
4625
+ * constructor arguments. These are supported features, but they also mean the
4626
+ * prompt contents should be treated as executable configuration rather than
4627
+ * plain text.
4628
+ *
4629
+ * Set `dangerouslyPullPublicPrompt: true` only after reviewing and trusting
4630
+ * the prompt contents, not merely the publishing account. Prompts from your
4631
+ * own or your organization's account can still be unsafe if that account or
4632
+ * prompt was compromised.
4633
+ */
4463
4634
  async pullPromptCommit(promptIdentifier, options) {
4635
+ assertPullPublicPromptAllowed(promptIdentifier, options?.dangerouslyPullPublicPrompt);
4464
4636
  // Check cache first if not skipped
4465
4637
  const refreshFunc = this._fetchPromptFromApi.bind(this, promptIdentifier, options);
4466
4638
  if (!options?.skipCache && this._promptCache) {
@@ -4480,12 +4652,26 @@ export class Client {
4480
4652
  /**
4481
4653
  * This method should not be used directly, use `import { pull } from "langchain/hub"` instead.
4482
4654
  * Using this method directly returns the JSON string of the prompt rather than a LangChain object.
4655
+ *
4656
+ * Public prompts referenced by owner/name cross a trust boundary because the
4657
+ * prompt manifest may contain serialized LangChain objects and configuration
4658
+ * that affect runtime behavior. For example, a prompt can intentionally
4659
+ * configure a model with a custom base URL, headers, model name, or other
4660
+ * constructor arguments. These are supported features, but they also mean the
4661
+ * prompt contents should be treated as executable configuration rather than
4662
+ * plain text.
4663
+ *
4664
+ * Set `dangerouslyPullPublicPrompt: true` only after reviewing and trusting
4665
+ * the prompt contents, not merely the publishing account. Prompts from your
4666
+ * own or your organization's account can still be unsafe if that account or
4667
+ * prompt was compromised.
4483
4668
  * @private
4484
4669
  */
4485
4670
  async _pullPrompt(promptIdentifier, options) {
4486
4671
  const promptObject = await this.pullPromptCommit(promptIdentifier, {
4487
4672
  includeModel: options?.includeModel,
4488
4673
  skipCache: options?.skipCache,
4674
+ dangerouslyPullPublicPrompt: options?.dangerouslyPullPublicPrompt,
4489
4675
  });
4490
4676
  const prompt = JSON.stringify(promptObject.manifest);
4491
4677
  return prompt;
package/dist/index.cjs CHANGED
@@ -18,4 +18,4 @@ Object.defineProperty(exports, "PromptCache", { enumerable: true, get: function
18
18
  Object.defineProperty(exports, "configureGlobalPromptCache", { enumerable: true, get: function () { return index_js_1.configureGlobalPromptCache; } });
19
19
  Object.defineProperty(exports, "promptCacheSingleton", { enumerable: true, get: function () { return index_js_1.promptCacheSingleton; } });
20
20
  // Update using pnpm bump-version
21
- exports.__version__ = "0.5.26";
21
+ exports.__version__ = "0.6.1";
package/dist/index.d.ts CHANGED
@@ -5,4 +5,4 @@ export { overrideFetchImplementation } from "./singletons/fetch.js";
5
5
  export { getDefaultProjectName } from "./utils/project.js";
6
6
  export { uuid7, uuid7FromTime } from "./uuid.js";
7
7
  export { Cache, PromptCache, type CacheConfig, type CacheMetrics, configureGlobalPromptCache, promptCacheSingleton, } from "./utils/prompt_cache/index.js";
8
- export declare const __version__ = "0.5.26";
8
+ export declare const __version__ = "0.6.1";
package/dist/index.js CHANGED
@@ -5,4 +5,4 @@ export { getDefaultProjectName } from "./utils/project.js";
5
5
  export { uuid7, uuid7FromTime } from "./uuid.js";
6
6
  export { Cache, PromptCache, configureGlobalPromptCache, promptCacheSingleton, } from "./utils/prompt_cache/index.js";
7
7
  // Update using pnpm bump-version
8
- export const __version__ = "0.5.26";
8
+ export const __version__ = "0.6.1";
@@ -94,6 +94,7 @@ function getLangSmithEnvVarsMetadata() {
94
94
  "LANGSMITH_API_KEY",
95
95
  "LANGSMITH_ENDPOINT",
96
96
  "LANGSMITH_TRACING_V2",
97
+ "LANGSMITH_CONFIG_FILE",
97
98
  "LANGSMITH_PROJECT",
98
99
  "LANGSMITH_SESSION",
99
100
  ];
package/dist/utils/env.js CHANGED
@@ -76,6 +76,7 @@ export function getLangSmithEnvVarsMetadata() {
76
76
  "LANGSMITH_API_KEY",
77
77
  "LANGSMITH_ENDPOINT",
78
78
  "LANGSMITH_TRACING_V2",
79
+ "LANGSMITH_CONFIG_FILE",
79
80
  "LANGSMITH_PROJECT",
80
81
  "LANGSMITH_SESSION",
81
82
  ];
@@ -0,0 +1,292 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.ProfileAuth = exports.DEFAULT_API_URL = void 0;
37
+ exports.hasValue = hasValue;
38
+ exports.loadProfileClientConfig = loadProfileClientConfig;
39
+ const env_js_1 = require("./env.cjs");
40
+ const fsUtils = __importStar(require("./fs.cjs"));
41
+ exports.DEFAULT_API_URL = "https://api.smith.langchain.com";
42
+ const OAUTH_CLIENT_ID = "langsmith-cli";
43
+ const TOKEN_REFRESH_LEEWAY_MS = 60_000;
44
+ const TOKEN_REFRESH_TIMEOUT_MS = 10_000;
45
+ function isBrowserLikeRuntime() {
46
+ const env = (0, env_js_1.getEnv)();
47
+ return env === "browser" || env === "webworker";
48
+ }
49
+ function getProfileConfigPath() {
50
+ const explicitPath = (0, env_js_1.getEnvironmentVariable)("LANGSMITH_CONFIG_FILE");
51
+ if (explicitPath) {
52
+ return explicitPath;
53
+ }
54
+ const home = (0, env_js_1.getEnvironmentVariable)("HOME") ?? (0, env_js_1.getEnvironmentVariable)("USERPROFILE");
55
+ if (!home) {
56
+ return undefined;
57
+ }
58
+ return fsUtils.path.join(home, ".langsmith", "config.json");
59
+ }
60
+ function resolveProfileName(config) {
61
+ const envProfile = (0, env_js_1.getEnvironmentVariable)("LANGSMITH_PROFILE");
62
+ if (envProfile) {
63
+ return envProfile;
64
+ }
65
+ if (config.current_profile) {
66
+ return config.current_profile;
67
+ }
68
+ if (config.profiles?.default) {
69
+ return "default";
70
+ }
71
+ return undefined;
72
+ }
73
+ function loadProfileState() {
74
+ if (isBrowserLikeRuntime()) {
75
+ return undefined;
76
+ }
77
+ const configPath = getProfileConfigPath();
78
+ if (!configPath || !fsUtils.existsSync(configPath)) {
79
+ return undefined;
80
+ }
81
+ try {
82
+ const config = JSON.parse(fsUtils.readFileSync(configPath));
83
+ const profileName = resolveProfileName(config);
84
+ const profile = profileName ? config.profiles?.[profileName] : undefined;
85
+ if (!profileName || !profile) {
86
+ return undefined;
87
+ }
88
+ return { configPath, config, profileName, profile };
89
+ }
90
+ catch {
91
+ return undefined;
92
+ }
93
+ }
94
+ function hasValue(value) {
95
+ return value !== undefined && value !== null && value.trim() !== "";
96
+ }
97
+ function trimConfigValue(value) {
98
+ return value?.trim().replace(/^["']|["']$/g, "");
99
+ }
100
+ function shouldRefreshProfileToken(profile) {
101
+ const oauth = profile.oauth;
102
+ if (!oauth?.refresh_token) {
103
+ return false;
104
+ }
105
+ if (!oauth.access_token) {
106
+ return true;
107
+ }
108
+ if (!oauth.expires_at) {
109
+ return false;
110
+ }
111
+ const expiresAt = Date.parse(oauth.expires_at);
112
+ if (Number.isNaN(expiresAt)) {
113
+ return false;
114
+ }
115
+ return expiresAt <= Date.now() + TOKEN_REFRESH_LEEWAY_MS;
116
+ }
117
+ function normalizeConfigUrl(apiUrl) {
118
+ let normalized = apiUrl;
119
+ while (normalized.endsWith("/")) {
120
+ normalized = normalized.slice(0, -1);
121
+ }
122
+ const apiV1Suffix = "/api/v1";
123
+ return normalized.endsWith(apiV1Suffix)
124
+ ? normalized.slice(0, -apiV1Suffix.length)
125
+ : normalized;
126
+ }
127
+ function applyTokenResponse(profile, token) {
128
+ profile.oauth ??= {};
129
+ if (token.access_token) {
130
+ profile.oauth.access_token = token.access_token;
131
+ }
132
+ if (token.refresh_token) {
133
+ profile.oauth.refresh_token = token.refresh_token;
134
+ }
135
+ if (typeof token.expires_in === "number" && token.expires_in > 0) {
136
+ profile.oauth.expires_at = new Date(Date.now() + token.expires_in * 1000).toISOString();
137
+ }
138
+ }
139
+ function getAbortReason(signal) {
140
+ return (signal.reason ??
141
+ new Error("The operation was aborted."));
142
+ }
143
+ async function waitForAbortSignal(promise, signal) {
144
+ if (!signal) {
145
+ return promise;
146
+ }
147
+ if (signal.aborted) {
148
+ throw getAbortReason(signal);
149
+ }
150
+ let cleanup;
151
+ const abortPromise = new Promise((_, reject) => {
152
+ const onAbort = () => {
153
+ reject(getAbortReason(signal));
154
+ };
155
+ signal.addEventListener("abort", onAbort, { once: true });
156
+ cleanup = () => {
157
+ signal.removeEventListener("abort", onAbort);
158
+ };
159
+ });
160
+ try {
161
+ return await Promise.race([promise, abortPromise]);
162
+ }
163
+ finally {
164
+ cleanup?.();
165
+ }
166
+ }
167
+ function loadProfileClientConfig() {
168
+ const state = loadProfileState();
169
+ const profile = state?.profile;
170
+ if (!state || !profile) {
171
+ return {};
172
+ }
173
+ const apiKey = trimConfigValue(profile.api_key);
174
+ const oauthAccessToken = trimConfigValue(profile.oauth?.access_token);
175
+ const oauthRefreshToken = trimConfigValue(profile.oauth?.refresh_token);
176
+ return {
177
+ apiUrl: profile.api_url,
178
+ apiKey,
179
+ workspaceId: profile.workspace_id,
180
+ oauthAccessToken,
181
+ oauthRefreshToken,
182
+ profileAuth: apiKey || oauthAccessToken || oauthRefreshToken
183
+ ? new ProfileAuth(state)
184
+ : undefined,
185
+ };
186
+ }
187
+ class ProfileAuth {
188
+ constructor(state) {
189
+ Object.defineProperty(this, "state", {
190
+ enumerable: true,
191
+ configurable: true,
192
+ writable: true,
193
+ value: state
194
+ });
195
+ Object.defineProperty(this, "refreshPromise", {
196
+ enumerable: true,
197
+ configurable: true,
198
+ writable: true,
199
+ value: void 0
200
+ });
201
+ Object.defineProperty(this, "managedAuthorizationValue", {
202
+ enumerable: true,
203
+ configurable: true,
204
+ writable: true,
205
+ value: void 0
206
+ });
207
+ this.rememberProfileAuthHeader(this.currentAuthHeader());
208
+ }
209
+ currentAuthHeader() {
210
+ const header = currentAuthHeaderFromProfile(this.state.profile);
211
+ this.rememberProfileAuthHeader(header);
212
+ return header;
213
+ }
214
+ async getAuthHeader(fetchImplementation, signal) {
215
+ if (shouldRefreshProfileToken(this.state.profile)) {
216
+ if (!this.refreshPromise) {
217
+ this.refreshPromise = this.refreshOAuthToken(fetchImplementation).finally(() => {
218
+ this.refreshPromise = undefined;
219
+ });
220
+ }
221
+ await waitForAbortSignal(this.refreshPromise, signal);
222
+ }
223
+ const header = authHeaderFromProfile(this.state.profile);
224
+ this.rememberProfileAuthHeader(header);
225
+ return header;
226
+ }
227
+ isProfileAuthorizationHeader(value) {
228
+ return value === this.managedAuthorizationValue;
229
+ }
230
+ async refreshOAuthToken(fetchImplementation) {
231
+ const refreshToken = this.state.profile.oauth?.refresh_token;
232
+ if (!refreshToken) {
233
+ return;
234
+ }
235
+ const refreshApiUrl = trimConfigValue(this.state.profile.api_url) ?? exports.DEFAULT_API_URL;
236
+ try {
237
+ const body = new URLSearchParams({
238
+ grant_type: "refresh_token",
239
+ client_id: OAUTH_CLIENT_ID,
240
+ refresh_token: refreshToken,
241
+ });
242
+ const response = await fetchImplementation(`${normalizeConfigUrl(refreshApiUrl)}/oauth/token`, {
243
+ method: "POST",
244
+ headers: {
245
+ "Content-Type": "application/x-www-form-urlencoded",
246
+ },
247
+ body: body.toString(),
248
+ signal: AbortSignal.timeout(TOKEN_REFRESH_TIMEOUT_MS),
249
+ });
250
+ if (!response.ok) {
251
+ return;
252
+ }
253
+ const token = (await response.json());
254
+ if (!token.access_token) {
255
+ return;
256
+ }
257
+ applyTokenResponse(this.state.profile, token);
258
+ this.state.config.profiles ??= {};
259
+ this.state.config.profiles[this.state.profileName] = this.state.profile;
260
+ await fsUtils.writeFileAtomic(this.state.configPath, `${JSON.stringify(this.state.config, null, 2)}\n`);
261
+ }
262
+ catch {
263
+ return;
264
+ }
265
+ }
266
+ rememberProfileAuthHeader(header) {
267
+ this.managedAuthorizationValue =
268
+ header?.name === "Authorization" ? header.value : undefined;
269
+ }
270
+ }
271
+ exports.ProfileAuth = ProfileAuth;
272
+ function currentAuthHeaderFromProfile(profile) {
273
+ const oauthAccessToken = trimConfigValue(profile.oauth?.access_token);
274
+ if (oauthAccessToken) {
275
+ return { name: "Authorization", value: `Bearer ${oauthAccessToken}` };
276
+ }
277
+ if (trimConfigValue(profile.oauth?.refresh_token)) {
278
+ return undefined;
279
+ }
280
+ return authHeaderFromProfile(profile);
281
+ }
282
+ function authHeaderFromProfile(profile) {
283
+ const oauthAccessToken = trimConfigValue(profile.oauth?.access_token);
284
+ if (oauthAccessToken) {
285
+ return { name: "Authorization", value: `Bearer ${oauthAccessToken}` };
286
+ }
287
+ const apiKey = trimConfigValue(profile.api_key);
288
+ if (apiKey) {
289
+ return { name: "x-api-key", value: apiKey };
290
+ }
291
+ return undefined;
292
+ }