langsmith 0.10.5 → 0.10.7

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.
@@ -0,0 +1,296 @@
1
+ import { getLangSmithEnvironmentVariable } from "../utils/env.js";
2
+ import { LangSmithSandboxTokenVerificationError } from "./errors.js";
3
+ /** Header carrying the signed identity of a LangSmith-login service URL request. */
4
+ export const USER_TOKEN_HEADER = "X-Langsmith-User-Token";
5
+ /** Header carrying the signature of a proxy callback request. */
6
+ export const CALLBACK_SIGNATURE_HEADER = "X-LangSmith-Signature-JWT";
7
+ const CALLBACK_SUBJECT = "langsmith-sandbox-callback";
8
+ const JWKS_PATH = "/.well-known/jwks.json";
9
+ const JWKS_TTL_MS = 300_000;
10
+ // Bounds refetches when a token names a kid the cached set lacks.
11
+ const JWKS_MIN_REFRESH_MS = 30_000;
12
+ const LEEWAY_SECONDS = 30;
13
+ function fail(message) {
14
+ throw new LangSmithSandboxTokenVerificationError(message);
15
+ }
16
+ function subtle() {
17
+ const s = globalThis.crypto?.subtle;
18
+ if (!s)
19
+ fail("Web Crypto is not available in this runtime");
20
+ return s;
21
+ }
22
+ function base64UrlDecode(value) {
23
+ const b64 = value.replace(/-/g, "+").replace(/_/g, "/");
24
+ const bin = atob(b64 + "=".repeat((4 - (b64.length % 4)) % 4));
25
+ const out = new Uint8Array(bin.length);
26
+ for (let i = 0; i < bin.length; i++)
27
+ out[i] = bin.charCodeAt(i);
28
+ return out;
29
+ }
30
+ function decodeJson(segment) {
31
+ const value = JSON.parse(new TextDecoder().decode(base64UrlDecode(segment)));
32
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
33
+ throw new Error("not a JSON object");
34
+ }
35
+ return value;
36
+ }
37
+ function isLoopback(hostname) {
38
+ return (hostname === "localhost" ||
39
+ hostname === "[::1]" ||
40
+ /^127(\.\d{1,3}){3}$/.test(hostname));
41
+ }
42
+ function checkJwksUrl(url, allowInsecure) {
43
+ const { protocol, hostname } = new URL(url);
44
+ if (protocol === "https:" || allowInsecure)
45
+ return;
46
+ if (protocol === "http:" && isLoopback(hostname))
47
+ return;
48
+ throw new Error(`JWKS URL must use https, got ${JSON.stringify(url)}; pass ` +
49
+ "allowInsecureJwks: true only if the network path to LangSmith is trusted");
50
+ }
51
+ function jwksUrlFrom(apiUrl) {
52
+ return new URL(JWKS_PATH, new URL(apiUrl).origin).toString();
53
+ }
54
+ function serviceHost(audience) {
55
+ return audience.includes("://") ? new URL(audience).host : audience;
56
+ }
57
+ function toBytes(body) {
58
+ if (typeof body === "string")
59
+ return new TextEncoder().encode(body);
60
+ if (body instanceof ArrayBuffer)
61
+ return new Uint8Array(body);
62
+ return new Uint8Array(body);
63
+ }
64
+ function hex(buf) {
65
+ return Array.from(new Uint8Array(buf), (b) => b.toString(16).padStart(2, "0")).join("");
66
+ }
67
+ function timingSafeEqual(a, b) {
68
+ if (a.length !== b.length)
69
+ return false;
70
+ let diff = 0;
71
+ for (let i = 0; i < a.length; i++)
72
+ diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
73
+ return diff === 0;
74
+ }
75
+ /**
76
+ * Verifies tokens LangSmith signs for code running in or behind a sandbox.
77
+ *
78
+ * Keys are fetched from LangSmith's JWKS endpoint and cached. Requires Web
79
+ * Crypto Ed25519 support (Node.js 20+, Deno, Bun, Cloudflare Workers).
80
+ *
81
+ * @example
82
+ * ```typescript
83
+ * const verifier = new SandboxTokenVerifier();
84
+ *
85
+ * // In an app served from a LangSmith-login service URL:
86
+ * const user = await verifier.verifyUserToken(
87
+ * req.headers.get(USER_TOKEN_HEADER)!,
88
+ * { audience: req.headers.get("host")! }
89
+ * );
90
+ *
91
+ * // In a proxy callback endpoint:
92
+ * const callback = await verifier.verifyCallback({
93
+ * body: await req.text(),
94
+ * signature: req.headers.get(CALLBACK_SIGNATURE_HEADER)!,
95
+ * aud: "https://example.com/sandbox-callback",
96
+ * });
97
+ * ```
98
+ */
99
+ export class SandboxTokenVerifier {
100
+ constructor(config = {}) {
101
+ Object.defineProperty(this, "jwksUrl", {
102
+ enumerable: true,
103
+ configurable: true,
104
+ writable: true,
105
+ value: void 0
106
+ });
107
+ Object.defineProperty(this, "timeoutMs", {
108
+ enumerable: true,
109
+ configurable: true,
110
+ writable: true,
111
+ value: void 0
112
+ });
113
+ Object.defineProperty(this, "keys", {
114
+ enumerable: true,
115
+ configurable: true,
116
+ writable: true,
117
+ value: new Map()
118
+ });
119
+ Object.defineProperty(this, "fetchedAt", {
120
+ enumerable: true,
121
+ configurable: true,
122
+ writable: true,
123
+ value: 0
124
+ });
125
+ Object.defineProperty(this, "inflight", {
126
+ enumerable: true,
127
+ configurable: true,
128
+ writable: true,
129
+ value: void 0
130
+ });
131
+ this.jwksUrl =
132
+ config.jwksUrl ??
133
+ jwksUrlFrom(config.apiUrl ??
134
+ getLangSmithEnvironmentVariable("ENDPOINT") ??
135
+ "https://api.smith.langchain.com");
136
+ checkJwksUrl(this.jwksUrl, config.allowInsecureJwks ?? false);
137
+ this.timeoutMs = config.timeoutMs ?? 10_000;
138
+ }
139
+ /**
140
+ * Verify the `X-Langsmith-User-Token` header of a service URL request.
141
+ *
142
+ * LangSmith sets this header only for service URLs that use LangSmith login.
143
+ * Code that knows it is running in a sandbox can instead trust the unsigned
144
+ * `X-Langsmith-User-Id` and `X-Langsmith-User-Email` headers, which the
145
+ * sandbox runtime strips from inbound requests and sets itself; verify this
146
+ * token when that is not guaranteed.
147
+ */
148
+ async verifyUserToken(token, options) {
149
+ const claims = await this.verify(token, serviceHost(options.audience), options.issuer);
150
+ const sub = claims.sub;
151
+ if (typeof sub !== "string" || !sub || sub === CALLBACK_SUBJECT) {
152
+ fail("token is not a user token");
153
+ }
154
+ return {
155
+ subject: sub,
156
+ email: typeof claims.email === "string" && claims.email
157
+ ? claims.email
158
+ : undefined,
159
+ name: typeof claims.name === "string" && claims.name
160
+ ? claims.name
161
+ : undefined,
162
+ expiresAt: new Date(claims.exp * 1000),
163
+ };
164
+ }
165
+ /** Verify a proxy callback request and return its parsed payload. */
166
+ async verifyCallback(options) {
167
+ const claims = await this.verify(options.signature, options.aud, options.issuer);
168
+ if (claims.sub !== CALLBACK_SUBJECT) {
169
+ fail("signature is not a callback signature");
170
+ }
171
+ const expected = claims.body_sha256;
172
+ if (typeof expected !== "string")
173
+ fail("signature has no body hash");
174
+ const raw = toBytes(options.body);
175
+ const digest = hex(await subtle().digest("SHA-256", raw));
176
+ if (!timingSafeEqual(expected, digest))
177
+ fail("body does not match signature");
178
+ let payload;
179
+ try {
180
+ payload = JSON.parse(new TextDecoder().decode(raw));
181
+ }
182
+ catch (e) {
183
+ fail(`malformed callback body: ${e.message}`);
184
+ }
185
+ const id = payload?.identity;
186
+ if (typeof payload?.host !== "string" ||
187
+ typeof payload.port !== "number" ||
188
+ typeof id?.tenant_id !== "string" ||
189
+ typeof id.sandbox_id !== "string") {
190
+ fail("malformed callback body");
191
+ }
192
+ return payload;
193
+ }
194
+ async verify(token, audience, issuer) {
195
+ const parts = token ? token.split(".") : [];
196
+ if (parts.length !== 3)
197
+ fail("malformed token");
198
+ let header;
199
+ let claims;
200
+ let signature;
201
+ try {
202
+ header = decodeJson(parts[0]);
203
+ claims = decodeJson(parts[1]);
204
+ signature = base64UrlDecode(parts[2]);
205
+ }
206
+ catch (e) {
207
+ fail(`malformed token: ${e.message}`);
208
+ }
209
+ if (header.alg !== "EdDSA") {
210
+ fail(`unexpected signing algorithm: ${JSON.stringify(header.alg)}`);
211
+ }
212
+ if (typeof header.kid !== "string" || !header.kid)
213
+ fail("token has no kid");
214
+ const key = await this.key(header.kid);
215
+ const signed = new TextEncoder().encode(`${parts[0]}.${parts[1]}`);
216
+ if (!(await subtle().verify({ name: "Ed25519" }, key, signature, signed))) {
217
+ fail("invalid signature");
218
+ }
219
+ const now = Date.now() / 1000;
220
+ const { exp, iat, nbf, iss, aud } = claims;
221
+ if (typeof exp !== "number")
222
+ fail("token has no exp");
223
+ if (now > exp + LEEWAY_SECONDS)
224
+ fail("token is expired");
225
+ if (typeof iat !== "number")
226
+ fail("token has no iat");
227
+ if (iat > now + LEEWAY_SECONDS)
228
+ fail("token is not yet valid");
229
+ if (nbf !== undefined &&
230
+ (typeof nbf !== "number" || nbf > now + LEEWAY_SECONDS)) {
231
+ fail("token is not yet valid");
232
+ }
233
+ if (typeof iss !== "string")
234
+ fail("token has no iss");
235
+ if (issuer !== undefined && iss !== issuer.replace(/\/+$/, "")) {
236
+ fail("token has the wrong issuer");
237
+ }
238
+ const audiences = typeof aud === "string" ? [aud] : Array.isArray(aud) ? aud : [];
239
+ const matches = typeof audience === "function"
240
+ ? audiences.some((a) => typeof a === "string" && audience(a))
241
+ : audiences.includes(audience);
242
+ if (audience !== undefined && !matches) {
243
+ fail("token has the wrong audience");
244
+ }
245
+ return claims;
246
+ }
247
+ async key(kid) {
248
+ const age = Date.now() - this.fetchedAt;
249
+ const fresh = this.keys.has(kid) && age < JWKS_TTL_MS;
250
+ if (!fresh && (this.fetchedAt === 0 || age >= JWKS_MIN_REFRESH_MS)) {
251
+ this.inflight ??= this.refresh().finally(() => {
252
+ this.inflight = undefined;
253
+ });
254
+ await this.inflight;
255
+ }
256
+ const key = this.keys.get(kid);
257
+ if (!key)
258
+ fail(`unknown signing key: ${kid}`);
259
+ try {
260
+ return await key;
261
+ }
262
+ catch (e) {
263
+ fail(`signing key ${kid} is not a usable Ed25519 key: ${e.message}`);
264
+ }
265
+ }
266
+ async refresh() {
267
+ let body;
268
+ try {
269
+ const resp = await fetch(this.jwksUrl, {
270
+ signal: AbortSignal.timeout(this.timeoutMs),
271
+ });
272
+ if (!resp.ok)
273
+ throw new Error(`HTTP ${resp.status}`);
274
+ body = await resp.json();
275
+ }
276
+ catch (e) {
277
+ fail(`failed to fetch JWKS from ${this.jwksUrl}: ${e.message}`);
278
+ }
279
+ const entries = body?.keys;
280
+ if (!Array.isArray(entries)) {
281
+ fail("invalid JWKS: expected an object with a keys array");
282
+ }
283
+ const keys = new Map();
284
+ for (const jwk of entries) {
285
+ if (typeof jwk !== "object" || jwk === null)
286
+ continue;
287
+ if (jwk.kty !== "OKP" || jwk.crv !== "Ed25519" || !jwk.kid || !jwk.x)
288
+ continue;
289
+ const imported = subtle().importKey("jwk", { kty: "OKP", crv: "Ed25519", x: jwk.x }, { name: "Ed25519" }, false, ["verify"]);
290
+ imported.catch(() => undefined);
291
+ keys.set(jwk.kid, imported);
292
+ }
293
+ this.keys = keys;
294
+ this.fetchedAt = Date.now();
295
+ }
296
+ }
package/dist/schemas.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { Address } from "./address.js";
1
2
  export interface TracerSession {
2
3
  tenant_id: string;
3
4
  id: string;
@@ -149,6 +150,7 @@ export interface RunCreate extends BaseRun {
149
150
  revision_id?: string;
150
151
  child_runs?: this[];
151
152
  session_name?: string;
153
+ address?: Address;
152
154
  }
153
155
  export interface RunUpdate {
154
156
  id?: string;
@@ -167,6 +169,7 @@ export interface RunUpdate {
167
169
  events?: KVMap[];
168
170
  session_id?: string;
169
171
  session_name?: string;
172
+ address?: Address;
170
173
  /** Unique ID assigned to every run within this nested trace. **/
171
174
  trace_id?: string;
172
175
  /**
@@ -9,6 +9,7 @@ const traceable_js_1 = require("./singletons/traceable.cjs");
9
9
  const constants_js_1 = require("./singletons/constants.cjs");
10
10
  const asserts_js_1 = require("./utils/asserts.cjs");
11
11
  const index_js_1 = require("./index.cjs");
12
+ const agent_addressing_js_1 = require("./utils/agent_addressing.cjs");
12
13
  const otel_js_1 = require("./singletons/otel.cjs");
13
14
  const utils_js_1 = require("./experimental/otel/utils.cjs");
14
15
  const constants_js_2 = require("./experimental/otel/constants.cjs");
@@ -472,13 +473,15 @@ const convertSerializableArg = (arg, options) => {
472
473
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
473
474
  function traceable(wrappedFunc, config) {
474
475
  const { aggregator, argsConfigPath, __finalTracedIteratorKey, processInputs, processOutputs, extractAttachments, on_start, ...runTreeConfig } = config ?? {};
476
+ runTreeConfig.address = (0, agent_addressing_js_1.checkAddress)(runTreeConfig.address);
477
+ (0, agent_addressing_js_1.rejectConflicting)(runTreeConfig.project_name, runTreeConfig.address);
475
478
  const processInputsFn = processInputs ?? ((x) => x);
476
479
  const processOutputsFn = processOutputs ?? ((x) => x);
477
480
  const extractAttachmentsFn = extractAttachments ?? ((...x) => [undefined, runInputsToMap(x)]);
478
481
  const traceableFunc = (...args) => {
479
482
  let ensuredConfig;
483
+ let runtimeConfig;
480
484
  try {
481
- let runtimeConfig;
482
485
  if (argsConfigPath) {
483
486
  const [index, path] = argsConfigPath;
484
487
  if (index === args.length - 1 && !path) {
@@ -520,7 +523,10 @@ function traceable(wrappedFunc, config) {
520
523
  name: wrappedFunc.name || "<lambda>",
521
524
  ...runTreeConfig,
522
525
  };
526
+ runtimeConfig = undefined;
523
527
  }
528
+ // Runtime config outranks decorator config, whichever mode each names.
529
+ [ensuredConfig.project_name, ensuredConfig.address] = (0, agent_addressing_js_1.firstNamed)([runtimeConfig?.project_name, runtimeConfig?.address], [runTreeConfig.project_name, runTreeConfig.address]);
524
530
  let runEndedPromiseResolver;
525
531
  const runEndedPromise = new Promise((resolve) => {
526
532
  runEndedPromiseResolver = resolve;
@@ -645,9 +651,28 @@ function traceable(wrappedFunc, config) {
645
651
  async start(controller) {
646
652
  // eslint-disable-next-line no-constant-condition
647
653
  while (true) {
648
- const result = await (snapshot
649
- ? snapshot(() => otel_context.with(capturedOtelContext, () => reader.read()))
650
- : otel_context.with(capturedOtelContext, () => reader.read()));
654
+ let result;
655
+ try {
656
+ result = await (snapshot
657
+ ? snapshot(() => otel_context.with(capturedOtelContext, () => reader.read()))
658
+ : otel_context.with(capturedOtelContext, () => reader.read()));
659
+ }
660
+ catch (e) {
661
+ // The source stream errored: end the run with the error and the
662
+ // chunks received so far instead of leaving it pending.
663
+ finished = true;
664
+ await currentRunTree?.end(undefined, String(e));
665
+ await handleRunOutputs({
666
+ runTree: currentRunTree,
667
+ rawOutputs: await handleChunks(chunks),
668
+ processOutputsFn,
669
+ on_end,
670
+ postRunPromise,
671
+ deferredInputs,
672
+ skipChildPromiseDelay: true,
673
+ });
674
+ throw e;
675
+ }
651
676
  if (result.done) {
652
677
  finished = true;
653
678
  await handleRunOutputs({
package/dist/traceable.js CHANGED
@@ -5,6 +5,7 @@ import { ROOT, AsyncLocalStorageProviderSingleton, getCurrentRunTree, } from "./
5
5
  import { _LC_CHILD_RUN_END_PROMISES_KEY, _LC_CONTEXT_VARIABLES_KEY, _PROCESSING_FAILED_KEY, _processingFailed, } from "./singletons/constants.js";
6
6
  import { isKVMap, isReadableStream, isAsyncIterable, isIteratorLike, isThenable, isGenerator, isPromiseMethod, } from "./utils/asserts.js";
7
7
  import { __version__ } from "./index.js";
8
+ import { checkAddress, firstNamed, rejectConflicting, } from "./utils/agent_addressing.js";
8
9
  import { getOTELTrace, getOTELContext } from "./singletons/otel.js";
9
10
  import { getUuidFromOtelSpanId } from "./experimental/otel/utils.js";
10
11
  import { LANGSMITH_REFERENCE_EXAMPLE_ID, LANGSMITH_SESSION_NAME, LANGSMITH_TRACEABLE, } from "./experimental/otel/constants.js";
@@ -468,13 +469,15 @@ const convertSerializableArg = (arg, options) => {
468
469
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
469
470
  export function traceable(wrappedFunc, config) {
470
471
  const { aggregator, argsConfigPath, __finalTracedIteratorKey, processInputs, processOutputs, extractAttachments, on_start, ...runTreeConfig } = config ?? {};
472
+ runTreeConfig.address = checkAddress(runTreeConfig.address);
473
+ rejectConflicting(runTreeConfig.project_name, runTreeConfig.address);
471
474
  const processInputsFn = processInputs ?? ((x) => x);
472
475
  const processOutputsFn = processOutputs ?? ((x) => x);
473
476
  const extractAttachmentsFn = extractAttachments ?? ((...x) => [undefined, runInputsToMap(x)]);
474
477
  const traceableFunc = (...args) => {
475
478
  let ensuredConfig;
479
+ let runtimeConfig;
476
480
  try {
477
- let runtimeConfig;
478
481
  if (argsConfigPath) {
479
482
  const [index, path] = argsConfigPath;
480
483
  if (index === args.length - 1 && !path) {
@@ -516,7 +519,10 @@ export function traceable(wrappedFunc, config) {
516
519
  name: wrappedFunc.name || "<lambda>",
517
520
  ...runTreeConfig,
518
521
  };
522
+ runtimeConfig = undefined;
519
523
  }
524
+ // Runtime config outranks decorator config, whichever mode each names.
525
+ [ensuredConfig.project_name, ensuredConfig.address] = firstNamed([runtimeConfig?.project_name, runtimeConfig?.address], [runTreeConfig.project_name, runTreeConfig.address]);
520
526
  let runEndedPromiseResolver;
521
527
  const runEndedPromise = new Promise((resolve) => {
522
528
  runEndedPromiseResolver = resolve;
@@ -641,9 +647,28 @@ export function traceable(wrappedFunc, config) {
641
647
  async start(controller) {
642
648
  // eslint-disable-next-line no-constant-condition
643
649
  while (true) {
644
- const result = await (snapshot
645
- ? snapshot(() => otel_context.with(capturedOtelContext, () => reader.read()))
646
- : otel_context.with(capturedOtelContext, () => reader.read()));
650
+ let result;
651
+ try {
652
+ result = await (snapshot
653
+ ? snapshot(() => otel_context.with(capturedOtelContext, () => reader.read()))
654
+ : otel_context.with(capturedOtelContext, () => reader.read()));
655
+ }
656
+ catch (e) {
657
+ // The source stream errored: end the run with the error and the
658
+ // chunks received so far instead of leaving it pending.
659
+ finished = true;
660
+ await currentRunTree?.end(undefined, String(e));
661
+ await handleRunOutputs({
662
+ runTree: currentRunTree,
663
+ rawOutputs: await handleChunks(chunks),
664
+ processOutputsFn,
665
+ on_end,
666
+ postRunPromise,
667
+ deferredInputs,
668
+ skipChildPromiseDelay: true,
669
+ });
670
+ throw e;
671
+ }
647
672
  if (result.done) {
648
673
  finished = true;
649
674
  await handleRunOutputs({
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkAddress = checkAddress;
4
+ exports.rejectConflicting = rejectConflicting;
5
+ exports.firstNamed = firstNamed;
6
+ exports.resolveFromEnv = resolveFromEnv;
7
+ exports.logUntraced = logUntraced;
8
+ exports.warnOnEnv = warnOnEnv;
9
+ exports.applyToPayload = applyToPayload;
10
+ const address_js_1 = require("../address.cjs");
11
+ const env_js_1 = require("./env.cjs");
12
+ const warn_js_1 = require("./warn.cjs");
13
+ function isRendered(value) {
14
+ return (value != null &&
15
+ typeof value === "object" &&
16
+ "kind" in value);
17
+ }
18
+ /** Validate an address at an entry point; the SDK carries the frozen copy. */
19
+ function checkAddress(address) {
20
+ return address == null ? undefined : (0, address_js_1.normalizeAddress)(address);
21
+ }
22
+ function rejectConflicting(project, address) {
23
+ if (project && address) {
24
+ throw new Error(`A run is sent to a project (${JSON.stringify(project)}) or to an ` +
25
+ `address (${JSON.stringify(address)}), not both.`);
26
+ }
27
+ }
28
+ /** The highest level naming a project or an address, whichever it names. */
29
+ function firstNamed(...tiers) {
30
+ for (const [project, address] of tiers) {
31
+ rejectConflicting(project, address);
32
+ if (project || address) {
33
+ return [project || undefined, address];
34
+ }
35
+ }
36
+ return [undefined, undefined];
37
+ }
38
+ function getEnvProject() {
39
+ return ((0, env_js_1.getLangSmithEnvironmentVariable)("PROJECT") ||
40
+ (0, env_js_1.getEnvironmentVariable)("LANGCHAIN_SESSION") ||
41
+ undefined);
42
+ }
43
+ /**
44
+ * The env's destination: its address, else its project, else "default".
45
+ * @throws {EnvAddressError} For half an address, or an address and a project.
46
+ */
47
+ function resolveFromEnv() {
48
+ const project = getEnvProject();
49
+ const address = (0, address_js_1.addressFromEnv)();
50
+ if (project && address) {
51
+ throw new address_js_1.EnvAddressError("LANGSMITH_AGENT_* and a project are both set in the environment.");
52
+ }
53
+ return address ? [undefined, address] : [project ?? "default", undefined];
54
+ }
55
+ function logUntraced(error) {
56
+ (0, warn_js_1.warnOnce)(`LangSmith is not tracing this call: ${error.message}`);
57
+ }
58
+ /** Warn once, at client construction, if the env can't address runs. */
59
+ function warnOnEnv() {
60
+ if (!(0, address_js_1.envNames)().some((name) => (0, env_js_1.getEnvironmentVariable)(name))) {
61
+ return;
62
+ }
63
+ try {
64
+ resolveFromEnv();
65
+ }
66
+ catch (e) {
67
+ (0, warn_js_1.warnOnce)(`${e.message} Runs that name no destination in code are ` +
68
+ "not traced.");
69
+ }
70
+ }
71
+ /**
72
+ * Render `address` into its wire fields, in place. Without one, a create
73
+ * naming no project takes the env address. Already-rendered payloads (runs
74
+ * re-prepared when batched) are left alone.
75
+ *
76
+ * @throws {EnvAddressError} If the env names half an address.
77
+ */
78
+ function applyToPayload(run, { update = false } = {}) {
79
+ const payload = run;
80
+ // Already rendered into its wire object by an earlier call.
81
+ const rendered = isRendered(payload.address);
82
+ let address = rendered ? undefined : checkAddress(payload.address);
83
+ if (!rendered) {
84
+ delete payload.address;
85
+ }
86
+ const namedProject = payload.session_id != null || payload.session_name != null;
87
+ if (!address && !update && !namedProject && !rendered) {
88
+ address = (0, address_js_1.addressFromEnv)();
89
+ }
90
+ if (!address) {
91
+ return;
92
+ }
93
+ (0, warn_js_1.warnOnce)("Sending runs to an `address` is in beta and enabled per workspace; a " +
94
+ "workspace without it rejects the runs.");
95
+ Object.assign(payload, (0, address_js_1.toPayload)(address));
96
+ if (!namedProject) {
97
+ delete payload.session_name;
98
+ delete payload.session_id;
99
+ }
100
+ }
@@ -0,0 +1,30 @@
1
+ import { type Address, EnvAddressError } from "../address.js";
2
+ /** One precedence level: the `[project, address]` it names. */
3
+ export type Tier = [string | undefined, Address | undefined];
4
+ /** Validate an address at an entry point; the SDK carries the frozen copy. */
5
+ export declare function checkAddress(address: unknown): Readonly<Address> | undefined;
6
+ export declare function rejectConflicting(project: unknown, address: unknown): void;
7
+ /** The highest level naming a project or an address, whichever it names. */
8
+ export declare function firstNamed(...tiers: Tier[]): Tier;
9
+ /**
10
+ * The env's destination: its address, else its project, else "default".
11
+ * @throws {EnvAddressError} For half an address, or an address and a project.
12
+ */
13
+ export declare function resolveFromEnv(): Tier;
14
+ export declare function logUntraced(error: EnvAddressError): void;
15
+ /** Warn once, at client construction, if the env can't address runs. */
16
+ export declare function warnOnEnv(): void;
17
+ /**
18
+ * Render `address` into its wire fields, in place. Without one, a create
19
+ * naming no project takes the env address. Already-rendered payloads (runs
20
+ * re-prepared when batched) are left alone.
21
+ *
22
+ * @throws {EnvAddressError} If the env names half an address.
23
+ */
24
+ export declare function applyToPayload(run: {
25
+ address?: Address;
26
+ session_id?: string;
27
+ session_name?: string;
28
+ }, { update }?: {
29
+ update?: boolean;
30
+ }): void;
@@ -0,0 +1,91 @@
1
+ import { EnvAddressError, addressFromEnv, envNames, normalizeAddress, toPayload, } from "../address.js";
2
+ import { getEnvironmentVariable, getLangSmithEnvironmentVariable, } from "./env.js";
3
+ import { warnOnce } from "./warn.js";
4
+ function isRendered(value) {
5
+ return (value != null &&
6
+ typeof value === "object" &&
7
+ "kind" in value);
8
+ }
9
+ /** Validate an address at an entry point; the SDK carries the frozen copy. */
10
+ export function checkAddress(address) {
11
+ return address == null ? undefined : normalizeAddress(address);
12
+ }
13
+ export function rejectConflicting(project, address) {
14
+ if (project && address) {
15
+ throw new Error(`A run is sent to a project (${JSON.stringify(project)}) or to an ` +
16
+ `address (${JSON.stringify(address)}), not both.`);
17
+ }
18
+ }
19
+ /** The highest level naming a project or an address, whichever it names. */
20
+ export function firstNamed(...tiers) {
21
+ for (const [project, address] of tiers) {
22
+ rejectConflicting(project, address);
23
+ if (project || address) {
24
+ return [project || undefined, address];
25
+ }
26
+ }
27
+ return [undefined, undefined];
28
+ }
29
+ function getEnvProject() {
30
+ return (getLangSmithEnvironmentVariable("PROJECT") ||
31
+ getEnvironmentVariable("LANGCHAIN_SESSION") ||
32
+ undefined);
33
+ }
34
+ /**
35
+ * The env's destination: its address, else its project, else "default".
36
+ * @throws {EnvAddressError} For half an address, or an address and a project.
37
+ */
38
+ export function resolveFromEnv() {
39
+ const project = getEnvProject();
40
+ const address = addressFromEnv();
41
+ if (project && address) {
42
+ throw new EnvAddressError("LANGSMITH_AGENT_* and a project are both set in the environment.");
43
+ }
44
+ return address ? [undefined, address] : [project ?? "default", undefined];
45
+ }
46
+ export function logUntraced(error) {
47
+ warnOnce(`LangSmith is not tracing this call: ${error.message}`);
48
+ }
49
+ /** Warn once, at client construction, if the env can't address runs. */
50
+ export function warnOnEnv() {
51
+ if (!envNames().some((name) => getEnvironmentVariable(name))) {
52
+ return;
53
+ }
54
+ try {
55
+ resolveFromEnv();
56
+ }
57
+ catch (e) {
58
+ warnOnce(`${e.message} Runs that name no destination in code are ` +
59
+ "not traced.");
60
+ }
61
+ }
62
+ /**
63
+ * Render `address` into its wire fields, in place. Without one, a create
64
+ * naming no project takes the env address. Already-rendered payloads (runs
65
+ * re-prepared when batched) are left alone.
66
+ *
67
+ * @throws {EnvAddressError} If the env names half an address.
68
+ */
69
+ export function applyToPayload(run, { update = false } = {}) {
70
+ const payload = run;
71
+ // Already rendered into its wire object by an earlier call.
72
+ const rendered = isRendered(payload.address);
73
+ let address = rendered ? undefined : checkAddress(payload.address);
74
+ if (!rendered) {
75
+ delete payload.address;
76
+ }
77
+ const namedProject = payload.session_id != null || payload.session_name != null;
78
+ if (!address && !update && !namedProject && !rendered) {
79
+ address = addressFromEnv();
80
+ }
81
+ if (!address) {
82
+ return;
83
+ }
84
+ warnOnce("Sending runs to an `address` is in beta and enabled per workspace; a " +
85
+ "workspace without it rejects the runs.");
86
+ Object.assign(payload, toPayload(address));
87
+ if (!namedProject) {
88
+ delete payload.session_name;
89
+ delete payload.session_id;
90
+ }
91
+ }