langsmith 0.10.1 → 0.10.3
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/README.md +6 -6
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/sandbox/client.cjs +9 -2
- package/dist/sandbox/client.d.ts +4 -2
- package/dist/sandbox/client.js +10 -3
- package/dist/sandbox/errors.cjs +8 -1
- package/dist/sandbox/errors.d.ts +2 -1
- package/dist/sandbox/errors.js +8 -1
- package/dist/sandbox/helpers.cjs +24 -1
- package/dist/sandbox/helpers.d.ts +10 -0
- package/dist/sandbox/helpers.js +23 -1
- package/dist/sandbox/sandbox.cjs +3 -0
- package/dist/sandbox/sandbox.js +3 -0
- package/dist/sandbox/types.d.ts +10 -1
- package/dist/sandbox/ws_execute.cjs +15 -5
- package/dist/sandbox/ws_execute.js +15 -5
- package/dist/vitest/reporter.cjs +17 -6
- package/dist/vitest/reporter.d.ts +2 -2
- package/dist/vitest/reporter.js +17 -6
- package/dist/vitest/utils/reporter.cjs +2 -1
- package/dist/vitest/utils/reporter.d.ts +5 -19
- package/dist/vitest/utils/reporter.js +2 -1
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -341,7 +341,7 @@ import { wrapOpenAI } from "langsmith/wrappers";
|
|
|
341
341
|
const openai = wrapOpenAI(new OpenAI());
|
|
342
342
|
|
|
343
343
|
await openai.chat.completions.create({
|
|
344
|
-
model: "gpt-
|
|
344
|
+
model: "gpt-5.4",
|
|
345
345
|
messages: [{ content: "Hi there!", role: "user" }],
|
|
346
346
|
});
|
|
347
347
|
```
|
|
@@ -359,7 +359,7 @@ const createCompletion = traceable(
|
|
|
359
359
|
);
|
|
360
360
|
|
|
361
361
|
await createCompletion({
|
|
362
|
-
model: "gpt-
|
|
362
|
+
model: "gpt-5.4",
|
|
363
363
|
messages: [{ content: "Hi there!", role: "user" }],
|
|
364
364
|
});
|
|
365
365
|
```
|
|
@@ -375,7 +375,7 @@ within other functions wrapped with `traceable`.
|
|
|
375
375
|
```ts
|
|
376
376
|
const nestedTrace = traceable(async (text: string) => {
|
|
377
377
|
const completion = await openai.chat.completions.create({
|
|
378
|
-
model: "gpt-
|
|
378
|
+
model: "gpt-5.4",
|
|
379
379
|
messages: [{ content: text, role: "user" }],
|
|
380
380
|
});
|
|
381
381
|
return completion;
|
|
@@ -389,7 +389,7 @@ await nestedTrace("Why is the sky blue?");
|
|
|
389
389
|
"id": "chatcmpl-8sPToJQLLVepJvyeTfzZMOMVIKjMo",
|
|
390
390
|
"object": "chat.completion",
|
|
391
391
|
"created": 1707978348,
|
|
392
|
-
"model": "gpt-
|
|
392
|
+
"model": "gpt-5.4",
|
|
393
393
|
"choices": [
|
|
394
394
|
{
|
|
395
395
|
"index": 0,
|
|
@@ -435,14 +435,14 @@ const handler = traceable(
|
|
|
435
435
|
const openai = wrapOpenAI(new OpenAI());
|
|
436
436
|
|
|
437
437
|
const completion = await openai.chat.completions.create({
|
|
438
|
-
model: "gpt-
|
|
438
|
+
model: "gpt-5.4",
|
|
439
439
|
messages: [{ content: "Why is the sky blue?", role: "user" }],
|
|
440
440
|
});
|
|
441
441
|
|
|
442
442
|
const response1 = completion.choices[0].message.content;
|
|
443
443
|
|
|
444
444
|
const completion2 = await openai.chat.completions.create({
|
|
445
|
-
model: "gpt-
|
|
445
|
+
model: "gpt-5.4",
|
|
446
446
|
messages: [
|
|
447
447
|
{ content: "Why is the sky blue?", role: "user" },
|
|
448
448
|
{ content: response1, role: "assistant" },
|
package/dist/index.cjs
CHANGED
|
@@ -35,6 +35,6 @@ Object.defineProperty(exports, "UnprocessableEntityError", { enumerable: true, g
|
|
|
35
35
|
Object.defineProperty(exports, "RateLimitError", { enumerable: true, get: function () { return error_js_1.RateLimitError; } });
|
|
36
36
|
Object.defineProperty(exports, "InternalServerError", { enumerable: true, get: function () { return error_js_1.InternalServerError; } });
|
|
37
37
|
// Update using pnpm bump-version
|
|
38
|
-
exports.__version__ = "0.10.
|
|
38
|
+
exports.__version__ = "0.10.3";
|
|
39
39
|
// Metadata key to hide a traced run from LangSmith's Messages View.
|
|
40
40
|
exports.LS_MESSAGE_VIEW_EXCLUDE = "ls_message_view_exclude";
|
package/dist/index.d.ts
CHANGED
|
@@ -7,5 +7,5 @@ export { computeRunIdForSecondaryReplica, uuid7, uuid7FromTime, } from "./uuid.j
|
|
|
7
7
|
export { isTracingEnabled } from "./utils/guard.js";
|
|
8
8
|
export { Cache, PromptCache, type CacheConfig, type CacheMetrics, configureGlobalPromptCache, promptCacheSingleton, } from "./utils/prompt_cache/index.js";
|
|
9
9
|
export { LangsmithError, APIError, APIUserAbortError, APIConnectionError, APIConnectionTimeoutError, BadRequestError, AuthenticationError, PermissionDeniedError, NotFoundError, ConflictError, UnprocessableEntityError, RateLimitError, InternalServerError, } from "./_openapi_client/core/error.js";
|
|
10
|
-
export declare const __version__ = "0.10.
|
|
10
|
+
export declare const __version__ = "0.10.3";
|
|
11
11
|
export declare const LS_MESSAGE_VIEW_EXCLUDE: "ls_message_view_exclude";
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,6 @@ export { isTracingEnabled } from "./utils/guard.js";
|
|
|
7
7
|
export { Cache, PromptCache, configureGlobalPromptCache, promptCacheSingleton, } from "./utils/prompt_cache/index.js";
|
|
8
8
|
export { LangsmithError, APIError, APIUserAbortError, APIConnectionError, APIConnectionTimeoutError, BadRequestError, AuthenticationError, PermissionDeniedError, NotFoundError, ConflictError, UnprocessableEntityError, RateLimitError, InternalServerError, } from "./_openapi_client/core/error.js";
|
|
9
9
|
// Update using pnpm bump-version
|
|
10
|
-
export const __version__ = "0.10.
|
|
10
|
+
export const __version__ = "0.10.3";
|
|
11
11
|
// Metadata key to hide a traced run from LangSmith's Messages View.
|
|
12
12
|
export const LS_MESSAGE_VIEW_EXCLUDE = "ls_message_view_exclude";
|
package/dist/sandbox/client.cjs
CHANGED
|
@@ -523,12 +523,13 @@ class SandboxClient {
|
|
|
523
523
|
const options = typeof newNameOrOptions === "string"
|
|
524
524
|
? { newName: newNameOrOptions }
|
|
525
525
|
: newNameOrOptions;
|
|
526
|
-
const { newName, idleTtlSeconds, deleteAfterStopSeconds } = options;
|
|
526
|
+
const { newName, idleTtlSeconds, deleteAfterStopSeconds, proxyConfig } = options;
|
|
527
527
|
(0, helpers_js_1.validateTtl)(idleTtlSeconds, "idleTtlSeconds");
|
|
528
528
|
(0, helpers_js_1.validateTtl)(deleteAfterStopSeconds, "deleteAfterStopSeconds");
|
|
529
529
|
if (newName === undefined &&
|
|
530
530
|
idleTtlSeconds === undefined &&
|
|
531
|
-
deleteAfterStopSeconds === undefined
|
|
531
|
+
deleteAfterStopSeconds === undefined &&
|
|
532
|
+
proxyConfig === undefined) {
|
|
532
533
|
return this.getSandbox(name);
|
|
533
534
|
}
|
|
534
535
|
const url = this._boxUrl(name);
|
|
@@ -542,6 +543,9 @@ class SandboxClient {
|
|
|
542
543
|
if (deleteAfterStopSeconds !== undefined) {
|
|
543
544
|
payload.delete_after_stop_seconds = deleteAfterStopSeconds;
|
|
544
545
|
}
|
|
546
|
+
if (proxyConfig !== undefined) {
|
|
547
|
+
payload.proxy_config = proxyConfig;
|
|
548
|
+
}
|
|
545
549
|
const response = await this._fetch(url, {
|
|
546
550
|
method: "PATCH",
|
|
547
551
|
headers: { "Content-Type": "application/json" },
|
|
@@ -556,6 +560,9 @@ class SandboxClient {
|
|
|
556
560
|
? `Sandbox name '${newName}' already in use`
|
|
557
561
|
: "Sandbox update conflict (name may already be in use)", "sandbox");
|
|
558
562
|
}
|
|
563
|
+
if (proxyConfig !== undefined) {
|
|
564
|
+
await (0, helpers_js_1.throwIfNotReady)(response, name);
|
|
565
|
+
}
|
|
559
566
|
await (0, helpers_js_1.handleClientHttpError)(response);
|
|
560
567
|
}
|
|
561
568
|
const data = (await response.json());
|
package/dist/sandbox/client.d.ts
CHANGED
|
@@ -122,8 +122,8 @@ export declare class SandboxClient {
|
|
|
122
122
|
*/
|
|
123
123
|
updateSandbox(name: string, newName: string): Promise<Sandbox>;
|
|
124
124
|
/**
|
|
125
|
-
* Update a sandbox's name
|
|
126
|
-
* delete-after-stop).
|
|
125
|
+
* Update a sandbox's name, retention settings (idle stop and
|
|
126
|
+
* delete-after-stop), and/or proxy config.
|
|
127
127
|
*
|
|
128
128
|
* @param name - Current sandbox name.
|
|
129
129
|
* @param options - Fields to update. Omit a field to leave it unchanged.
|
|
@@ -131,6 +131,8 @@ export declare class SandboxClient {
|
|
|
131
131
|
* @throws LangSmithResourceNotFoundError if sandbox not found.
|
|
132
132
|
* @throws LangSmithResourceNameConflictError if newName is already in use.
|
|
133
133
|
* @throws LangSmithValidationError if retention values are invalid.
|
|
134
|
+
* @throws LangSmithSandboxNotReadyError if proxyConfig was given and the
|
|
135
|
+
* sandbox is not `ready`.
|
|
134
136
|
*/
|
|
135
137
|
updateSandbox(name: string, options: UpdateSandboxOptions): Promise<Sandbox>;
|
|
136
138
|
/**
|
package/dist/sandbox/client.js
CHANGED
|
@@ -7,7 +7,7 @@ import { AsyncCaller } from "../utils/async_caller.js";
|
|
|
7
7
|
import { Langsmith as OpenAPILangsmith } from "../_openapi_client/index.js";
|
|
8
8
|
import { Sandbox } from "./sandbox.js";
|
|
9
9
|
import { LangSmithResourceCreationError, LangSmithResourceNameConflictError, LangSmithResourceNotFoundError, LangSmithResourceTimeoutError, LangSmithSandboxAPIError, LangSmithValidationError, } from "./errors.js";
|
|
10
|
-
import { handleClientHttpError, handleSandboxCreationError, validateTtl, } from "./helpers.js";
|
|
10
|
+
import { handleClientHttpError, handleSandboxCreationError, throwIfNotReady, validateTtl, } from "./helpers.js";
|
|
11
11
|
import { validateMountConfigProxyConfig } from "./mounts.js";
|
|
12
12
|
import { v4 as uuidv4 } from "../utils/uuid/src/index.js";
|
|
13
13
|
/**
|
|
@@ -520,12 +520,13 @@ export class SandboxClient {
|
|
|
520
520
|
const options = typeof newNameOrOptions === "string"
|
|
521
521
|
? { newName: newNameOrOptions }
|
|
522
522
|
: newNameOrOptions;
|
|
523
|
-
const { newName, idleTtlSeconds, deleteAfterStopSeconds } = options;
|
|
523
|
+
const { newName, idleTtlSeconds, deleteAfterStopSeconds, proxyConfig } = options;
|
|
524
524
|
validateTtl(idleTtlSeconds, "idleTtlSeconds");
|
|
525
525
|
validateTtl(deleteAfterStopSeconds, "deleteAfterStopSeconds");
|
|
526
526
|
if (newName === undefined &&
|
|
527
527
|
idleTtlSeconds === undefined &&
|
|
528
|
-
deleteAfterStopSeconds === undefined
|
|
528
|
+
deleteAfterStopSeconds === undefined &&
|
|
529
|
+
proxyConfig === undefined) {
|
|
529
530
|
return this.getSandbox(name);
|
|
530
531
|
}
|
|
531
532
|
const url = this._boxUrl(name);
|
|
@@ -539,6 +540,9 @@ export class SandboxClient {
|
|
|
539
540
|
if (deleteAfterStopSeconds !== undefined) {
|
|
540
541
|
payload.delete_after_stop_seconds = deleteAfterStopSeconds;
|
|
541
542
|
}
|
|
543
|
+
if (proxyConfig !== undefined) {
|
|
544
|
+
payload.proxy_config = proxyConfig;
|
|
545
|
+
}
|
|
542
546
|
const response = await this._fetch(url, {
|
|
543
547
|
method: "PATCH",
|
|
544
548
|
headers: { "Content-Type": "application/json" },
|
|
@@ -553,6 +557,9 @@ export class SandboxClient {
|
|
|
553
557
|
? `Sandbox name '${newName}' already in use`
|
|
554
558
|
: "Sandbox update conflict (name may already be in use)", "sandbox");
|
|
555
559
|
}
|
|
560
|
+
if (proxyConfig !== undefined) {
|
|
561
|
+
await throwIfNotReady(response, name);
|
|
562
|
+
}
|
|
556
563
|
await handleClientHttpError(response);
|
|
557
564
|
}
|
|
558
565
|
const data = (await response.json());
|
package/dist/sandbox/errors.cjs
CHANGED
|
@@ -60,9 +60,16 @@ exports.LangSmithSandboxConnectionError = LangSmithSandboxConnectionError;
|
|
|
60
60
|
* deduplicate an attempt whose outcome is unknown.
|
|
61
61
|
*/
|
|
62
62
|
class LangSmithSandboxRetryableConnectionError extends LangSmithSandboxConnectionError {
|
|
63
|
-
constructor(message) {
|
|
63
|
+
constructor(message, retryAfterSeconds) {
|
|
64
64
|
super(message);
|
|
65
|
+
Object.defineProperty(this, "retryAfterSeconds", {
|
|
66
|
+
enumerable: true,
|
|
67
|
+
configurable: true,
|
|
68
|
+
writable: true,
|
|
69
|
+
value: void 0
|
|
70
|
+
});
|
|
65
71
|
this.name = "LangSmithSandboxRetryableConnectionError";
|
|
72
|
+
this.retryAfterSeconds = retryAfterSeconds;
|
|
66
73
|
}
|
|
67
74
|
}
|
|
68
75
|
exports.LangSmithSandboxRetryableConnectionError = LangSmithSandboxRetryableConnectionError;
|
package/dist/sandbox/errors.d.ts
CHANGED
|
@@ -38,7 +38,8 @@ export declare class LangSmithSandboxConnectionError extends LangSmithSandboxErr
|
|
|
38
38
|
* deduplicate an attempt whose outcome is unknown.
|
|
39
39
|
*/
|
|
40
40
|
export declare class LangSmithSandboxRetryableConnectionError extends LangSmithSandboxConnectionError {
|
|
41
|
-
|
|
41
|
+
readonly retryAfterSeconds?: number;
|
|
42
|
+
constructor(message: string, retryAfterSeconds?: number);
|
|
42
43
|
}
|
|
43
44
|
/**
|
|
44
45
|
* Raised when a resource is not found.
|
package/dist/sandbox/errors.js
CHANGED
|
@@ -53,9 +53,16 @@ export class LangSmithSandboxConnectionError extends LangSmithSandboxError {
|
|
|
53
53
|
* deduplicate an attempt whose outcome is unknown.
|
|
54
54
|
*/
|
|
55
55
|
export class LangSmithSandboxRetryableConnectionError extends LangSmithSandboxConnectionError {
|
|
56
|
-
constructor(message) {
|
|
56
|
+
constructor(message, retryAfterSeconds) {
|
|
57
57
|
super(message);
|
|
58
|
+
Object.defineProperty(this, "retryAfterSeconds", {
|
|
59
|
+
enumerable: true,
|
|
60
|
+
configurable: true,
|
|
61
|
+
writable: true,
|
|
62
|
+
value: void 0
|
|
63
|
+
});
|
|
58
64
|
this.name = "LangSmithSandboxRetryableConnectionError";
|
|
65
|
+
this.retryAfterSeconds = retryAfterSeconds;
|
|
59
66
|
}
|
|
60
67
|
}
|
|
61
68
|
// =============================================================================
|
package/dist/sandbox/helpers.cjs
CHANGED
|
@@ -11,6 +11,7 @@ exports.parseErrorResponse = parseErrorResponse;
|
|
|
11
11
|
exports.parseValidationError = parseValidationError;
|
|
12
12
|
exports.extractQuotaType = extractQuotaType;
|
|
13
13
|
exports.handleSandboxCreationError = handleSandboxCreationError;
|
|
14
|
+
exports.throwIfNotReady = throwIfNotReady;
|
|
14
15
|
exports.handleClientHttpError = handleClientHttpError;
|
|
15
16
|
exports.handleSandboxHttpError = handleSandboxHttpError;
|
|
16
17
|
const errors_js_1 = require("./errors.cjs");
|
|
@@ -46,9 +47,12 @@ async function parseErrorResponse(response) {
|
|
|
46
47
|
const detail = data?.detail;
|
|
47
48
|
// Standardized format: {"detail": {"error": "...", "message": "..."}}
|
|
48
49
|
if (detail && typeof detail === "object" && !Array.isArray(detail)) {
|
|
50
|
+
const message = detail.message || `HTTP ${response.status}: ${response.statusText}`;
|
|
49
51
|
return {
|
|
50
52
|
errorType: detail.error,
|
|
51
|
-
message: detail.
|
|
53
|
+
message: typeof detail.error_id === "string" && detail.error_id
|
|
54
|
+
? `${message} (error_id=${detail.error_id})`
|
|
55
|
+
: message,
|
|
52
56
|
};
|
|
53
57
|
}
|
|
54
58
|
// Pydantic validation error format: {"detail": [{"loc": [...], "msg": "..."}]}
|
|
@@ -162,6 +166,25 @@ async function handleSandboxCreationError(response) {
|
|
|
162
166
|
// Fall through to generic handling — pass clone since body is already consumed
|
|
163
167
|
return handleClientHttpError(clonedResponse);
|
|
164
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* Throw `LangSmithSandboxNotReadyError` when the API rejected a proxy-config
|
|
171
|
+
* update because the sandbox is not `ready`.
|
|
172
|
+
*
|
|
173
|
+
* That status check is the sole source of `InvalidRequest` on the update
|
|
174
|
+
* endpoint for the fields this client sends, so a typed error lets callers
|
|
175
|
+
* start the sandbox and retry instead of matching on status codes. Reads a
|
|
176
|
+
* clone so the caller's own error handling can still consume the body.
|
|
177
|
+
*/
|
|
178
|
+
async function throwIfNotReady(response, name) {
|
|
179
|
+
if (response.status !== 400) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const data = await parseErrorResponse(response.clone());
|
|
183
|
+
if (data.errorType !== "InvalidRequest") {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
throw new errors_js_1.LangSmithSandboxNotReadyError(data.message || `Sandbox '${name}' is not ready`);
|
|
187
|
+
}
|
|
165
188
|
/**
|
|
166
189
|
* Handle HTTP errors and raise appropriate exceptions (for client operations).
|
|
167
190
|
*/
|
|
@@ -49,6 +49,16 @@ export declare function extractQuotaType(message: string): string | undefined;
|
|
|
49
49
|
* - Other: Falls through to generic error handling
|
|
50
50
|
*/
|
|
51
51
|
export declare function handleSandboxCreationError(response: Response): Promise<never>;
|
|
52
|
+
/**
|
|
53
|
+
* Throw `LangSmithSandboxNotReadyError` when the API rejected a proxy-config
|
|
54
|
+
* update because the sandbox is not `ready`.
|
|
55
|
+
*
|
|
56
|
+
* That status check is the sole source of `InvalidRequest` on the update
|
|
57
|
+
* endpoint for the fields this client sends, so a typed error lets callers
|
|
58
|
+
* start the sandbox and retry instead of matching on status codes. Reads a
|
|
59
|
+
* clone so the caller's own error handling can still consume the body.
|
|
60
|
+
*/
|
|
61
|
+
export declare function throwIfNotReady(response: Response, name: string): Promise<void>;
|
|
52
62
|
/**
|
|
53
63
|
* Handle HTTP errors and raise appropriate exceptions (for client operations).
|
|
54
64
|
*/
|
package/dist/sandbox/helpers.js
CHANGED
|
@@ -37,9 +37,12 @@ export async function parseErrorResponse(response) {
|
|
|
37
37
|
const detail = data?.detail;
|
|
38
38
|
// Standardized format: {"detail": {"error": "...", "message": "..."}}
|
|
39
39
|
if (detail && typeof detail === "object" && !Array.isArray(detail)) {
|
|
40
|
+
const message = detail.message || `HTTP ${response.status}: ${response.statusText}`;
|
|
40
41
|
return {
|
|
41
42
|
errorType: detail.error,
|
|
42
|
-
message: detail.
|
|
43
|
+
message: typeof detail.error_id === "string" && detail.error_id
|
|
44
|
+
? `${message} (error_id=${detail.error_id})`
|
|
45
|
+
: message,
|
|
43
46
|
};
|
|
44
47
|
}
|
|
45
48
|
// Pydantic validation error format: {"detail": [{"loc": [...], "msg": "..."}]}
|
|
@@ -153,6 +156,25 @@ export async function handleSandboxCreationError(response) {
|
|
|
153
156
|
// Fall through to generic handling — pass clone since body is already consumed
|
|
154
157
|
return handleClientHttpError(clonedResponse);
|
|
155
158
|
}
|
|
159
|
+
/**
|
|
160
|
+
* Throw `LangSmithSandboxNotReadyError` when the API rejected a proxy-config
|
|
161
|
+
* update because the sandbox is not `ready`.
|
|
162
|
+
*
|
|
163
|
+
* That status check is the sole source of `InvalidRequest` on the update
|
|
164
|
+
* endpoint for the fields this client sends, so a typed error lets callers
|
|
165
|
+
* start the sandbox and retry instead of matching on status codes. Reads a
|
|
166
|
+
* clone so the caller's own error handling can still consume the body.
|
|
167
|
+
*/
|
|
168
|
+
export async function throwIfNotReady(response, name) {
|
|
169
|
+
if (response.status !== 400) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const data = await parseErrorResponse(response.clone());
|
|
173
|
+
if (data.errorType !== "InvalidRequest") {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
throw new LangSmithSandboxNotReadyError(data.message || `Sandbox '${name}' is not ready`);
|
|
177
|
+
}
|
|
156
178
|
/**
|
|
157
179
|
* Handle HTTP errors and raise appropriate exceptions (for client operations).
|
|
158
180
|
*/
|
package/dist/sandbox/sandbox.cjs
CHANGED
|
@@ -272,6 +272,9 @@ class Sandbox {
|
|
|
272
272
|
throw e;
|
|
273
273
|
}
|
|
274
274
|
let delay = Math.min(command_handle_js_1.CommandHandle.BACKOFF_BASE * 2 ** (attempt - 1), command_handle_js_1.CommandHandle.BACKOFF_MAX);
|
|
275
|
+
if (e instanceof errors_js_1.LangSmithSandboxRetryableConnectionError) {
|
|
276
|
+
delay = e.retryAfterSeconds ?? delay * (0.8 + Math.random() * 0.2);
|
|
277
|
+
}
|
|
275
278
|
const remaining = (0, ws_execute_js_1.remainingBudget)(deadline);
|
|
276
279
|
if (remaining !== undefined) {
|
|
277
280
|
if (remaining <= 0) {
|
package/dist/sandbox/sandbox.js
CHANGED
|
@@ -269,6 +269,9 @@ export class Sandbox {
|
|
|
269
269
|
throw e;
|
|
270
270
|
}
|
|
271
271
|
let delay = Math.min(CommandHandle.BACKOFF_BASE * 2 ** (attempt - 1), CommandHandle.BACKOFF_MAX);
|
|
272
|
+
if (e instanceof LangSmithSandboxRetryableConnectionError) {
|
|
273
|
+
delay = e.retryAfterSeconds ?? delay * (0.8 + Math.random() * 0.2);
|
|
274
|
+
}
|
|
272
275
|
const remaining = remainingBudget(deadline);
|
|
273
276
|
if (remaining !== undefined) {
|
|
274
277
|
if (remaining <= 0) {
|
package/dist/sandbox/types.d.ts
CHANGED
|
@@ -643,7 +643,7 @@ export interface StartSandboxOptions {
|
|
|
643
643
|
signal?: AbortSignal;
|
|
644
644
|
}
|
|
645
645
|
/**
|
|
646
|
-
* Options for updating a sandbox (name
|
|
646
|
+
* Options for updating a sandbox (name, retention settings, proxy config).
|
|
647
647
|
*/
|
|
648
648
|
export interface UpdateSandboxOptions {
|
|
649
649
|
/** New display name. */
|
|
@@ -660,6 +660,15 @@ export interface UpdateSandboxOptions {
|
|
|
660
660
|
* `undefined`) to leave the existing value unchanged.
|
|
661
661
|
*/
|
|
662
662
|
deleteAfterStopSeconds?: number;
|
|
663
|
+
/**
|
|
664
|
+
* Replacement proxy configuration, sent to the server as-is (same shape as
|
|
665
|
+
* `createSandbox`). Rules replace the existing set rather than merging into
|
|
666
|
+
* it, so include every rule the sandbox should keep. Opaque header values
|
|
667
|
+
* carry over from the current config, so rotating one credential does not
|
|
668
|
+
* mean re-supplying secrets that can no longer be read. The sandbox must be
|
|
669
|
+
* `ready`; start a stopped one first.
|
|
670
|
+
*/
|
|
671
|
+
proxyConfig?: SandboxProxyConfig;
|
|
663
672
|
}
|
|
664
673
|
/**
|
|
665
674
|
* Options for waiting for a sandbox to become ready.
|
|
@@ -35,7 +35,14 @@ exports.WS_OPEN_TIMEOUT = envTimeout("SANDBOX_WS_TIMEOUT_OPEN", 30);
|
|
|
35
35
|
// Ceiling on the whole connect phase. Without it, retrying a blackholed
|
|
36
36
|
// handshake costs MAX_AUTO_RECONNECTS + 1 full open timeouts plus backoff.
|
|
37
37
|
exports.WS_CONNECT_BUDGET = envTimeout("SANDBOX_WS_TIMEOUT_CONNECT_BUDGET", 120);
|
|
38
|
-
const RETRYABLE_HANDSHAKE_STATUS_CODES = new Set([500, 502, 503, 504]);
|
|
38
|
+
const RETRYABLE_HANDSHAKE_STATUS_CODES = new Set([429, 500, 502, 503, 504]);
|
|
39
|
+
function retryAfterSeconds(headers) {
|
|
40
|
+
const raw = headers?.["retry-after"];
|
|
41
|
+
if (typeof raw !== "string" || raw.trim() === "")
|
|
42
|
+
return undefined;
|
|
43
|
+
const value = Number(raw);
|
|
44
|
+
return Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
45
|
+
}
|
|
39
46
|
function nowSeconds() {
|
|
40
47
|
return performance.now() / 1000;
|
|
41
48
|
}
|
|
@@ -211,10 +218,13 @@ async function connectWs(url, headers, openTimeout = exports.WS_OPEN_TIMEOUT) {
|
|
|
211
218
|
if (settled)
|
|
212
219
|
return;
|
|
213
220
|
settled = true;
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
221
|
+
const message = `WebSocket upgrade to ${url} rejected by server (HTTP ${res.statusCode})`;
|
|
222
|
+
if (RETRYABLE_HANDSHAKE_STATUS_CODES.has(res.statusCode ?? 0)) {
|
|
223
|
+
reject(new errors_js_1.LangSmithSandboxRetryableConnectionError(message, retryAfterSeconds(res.headers)));
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
reject(new errors_js_1.LangSmithSandboxConnectionError(message));
|
|
227
|
+
}
|
|
218
228
|
});
|
|
219
229
|
// Stays attached after settling so a late error has a listener and cannot
|
|
220
230
|
// become an unhandled 'error' event.
|
|
@@ -23,7 +23,14 @@ export const WS_OPEN_TIMEOUT = envTimeout("SANDBOX_WS_TIMEOUT_OPEN", 30);
|
|
|
23
23
|
// Ceiling on the whole connect phase. Without it, retrying a blackholed
|
|
24
24
|
// handshake costs MAX_AUTO_RECONNECTS + 1 full open timeouts plus backoff.
|
|
25
25
|
export const WS_CONNECT_BUDGET = envTimeout("SANDBOX_WS_TIMEOUT_CONNECT_BUDGET", 120);
|
|
26
|
-
const RETRYABLE_HANDSHAKE_STATUS_CODES = new Set([500, 502, 503, 504]);
|
|
26
|
+
const RETRYABLE_HANDSHAKE_STATUS_CODES = new Set([429, 500, 502, 503, 504]);
|
|
27
|
+
function retryAfterSeconds(headers) {
|
|
28
|
+
const raw = headers?.["retry-after"];
|
|
29
|
+
if (typeof raw !== "string" || raw.trim() === "")
|
|
30
|
+
return undefined;
|
|
31
|
+
const value = Number(raw);
|
|
32
|
+
return Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
33
|
+
}
|
|
27
34
|
function nowSeconds() {
|
|
28
35
|
return performance.now() / 1000;
|
|
29
36
|
}
|
|
@@ -198,10 +205,13 @@ async function connectWs(url, headers, openTimeout = WS_OPEN_TIMEOUT) {
|
|
|
198
205
|
if (settled)
|
|
199
206
|
return;
|
|
200
207
|
settled = true;
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
208
|
+
const message = `WebSocket upgrade to ${url} rejected by server (HTTP ${res.statusCode})`;
|
|
209
|
+
if (RETRYABLE_HANDSHAKE_STATUS_CODES.has(res.statusCode ?? 0)) {
|
|
210
|
+
reject(new LangSmithSandboxRetryableConnectionError(message, retryAfterSeconds(res.headers)));
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
reject(new LangSmithSandboxConnectionError(message));
|
|
214
|
+
}
|
|
205
215
|
});
|
|
206
216
|
// Stays attached after settling so a late error has a listener and cannot
|
|
207
217
|
// become an unhandled 'error' event.
|
package/dist/vitest/reporter.cjs
CHANGED
|
@@ -6,15 +6,26 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const reporters_1 = require("vitest/reporters");
|
|
7
7
|
const reporter_js_1 = require("./utils/reporter.cjs");
|
|
8
8
|
class LangSmithEvalReporter extends reporters_1.DefaultReporter {
|
|
9
|
+
constructor() {
|
|
10
|
+
super(...arguments);
|
|
11
|
+
Object.defineProperty(this, "skipOnFinished", {
|
|
12
|
+
enumerable: true,
|
|
13
|
+
configurable: true,
|
|
14
|
+
writable: true,
|
|
15
|
+
value: false
|
|
16
|
+
});
|
|
17
|
+
}
|
|
9
18
|
async onFinished(files, errors) {
|
|
10
|
-
|
|
19
|
+
const onFinished = reporters_1.DefaultReporter.prototype.onFinished;
|
|
20
|
+
onFinished?.call(this, files, errors);
|
|
21
|
+
if (this.skipOnFinished)
|
|
22
|
+
return;
|
|
11
23
|
await (0, reporter_js_1.printVitestReporterTable)(files, this.ctx);
|
|
12
24
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
await (0, reporter_js_1.printVitestTestModulesReporterTable)(testModules);
|
|
25
|
+
async onTestRunEnd(...args) {
|
|
26
|
+
super.onTestRunEnd(...args);
|
|
27
|
+
this.skipOnFinished = true;
|
|
28
|
+
await (0, reporter_js_1.printVitestTestModulesReporterTable)(args[0]);
|
|
18
29
|
}
|
|
19
30
|
}
|
|
20
31
|
exports.default = LangSmithEvalReporter;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { DefaultReporter } from "vitest/reporters";
|
|
2
2
|
import { RunnerTestFile } from "vitest";
|
|
3
|
-
import { type VitestTestModule } from "./utils/reporter.js";
|
|
4
3
|
declare class LangSmithEvalReporter extends DefaultReporter {
|
|
4
|
+
private skipOnFinished;
|
|
5
5
|
onFinished(files: RunnerTestFile[], errors: unknown[]): Promise<void>;
|
|
6
|
-
onTestRunEnd(
|
|
6
|
+
onTestRunEnd(...args: Parameters<DefaultReporter["onTestRunEnd"]>): Promise<void>;
|
|
7
7
|
}
|
|
8
8
|
export default LangSmithEvalReporter;
|
package/dist/vitest/reporter.js
CHANGED
|
@@ -4,15 +4,26 @@
|
|
|
4
4
|
import { DefaultReporter } from "vitest/reporters";
|
|
5
5
|
import { printVitestReporterTable, printVitestTestModulesReporterTable, } from "./utils/reporter.js";
|
|
6
6
|
class LangSmithEvalReporter extends DefaultReporter {
|
|
7
|
+
constructor() {
|
|
8
|
+
super(...arguments);
|
|
9
|
+
Object.defineProperty(this, "skipOnFinished", {
|
|
10
|
+
enumerable: true,
|
|
11
|
+
configurable: true,
|
|
12
|
+
writable: true,
|
|
13
|
+
value: false
|
|
14
|
+
});
|
|
15
|
+
}
|
|
7
16
|
async onFinished(files, errors) {
|
|
8
|
-
|
|
17
|
+
const onFinished = DefaultReporter.prototype.onFinished;
|
|
18
|
+
onFinished?.call(this, files, errors);
|
|
19
|
+
if (this.skipOnFinished)
|
|
20
|
+
return;
|
|
9
21
|
await printVitestReporterTable(files, this.ctx);
|
|
10
22
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
await printVitestTestModulesReporterTable(testModules);
|
|
23
|
+
async onTestRunEnd(...args) {
|
|
24
|
+
super.onTestRunEnd(...args);
|
|
25
|
+
this.skipOnFinished = true;
|
|
26
|
+
await printVitestTestModulesReporterTable(args[0]);
|
|
16
27
|
}
|
|
17
28
|
}
|
|
18
29
|
export default LangSmithEvalReporter;
|
|
@@ -32,7 +32,8 @@ const printVitestTestModulesReporterTable = async (testModules) => {
|
|
|
32
32
|
duration: Math.round(test.diagnostic()?.duration ?? 0),
|
|
33
33
|
};
|
|
34
34
|
});
|
|
35
|
-
|
|
35
|
+
const state = testModule.state();
|
|
36
|
+
await (0, reporter_js_1.printReporterTable)(testModule.relativeModuleId, tests, state === "pending" || state === "queued" ? "skipped" : state);
|
|
36
37
|
}
|
|
37
38
|
};
|
|
38
39
|
exports.printVitestTestModulesReporterTable = printVitestTestModulesReporterTable;
|
|
@@ -1,31 +1,17 @@
|
|
|
1
1
|
export declare const printVitestReporterTable: (files: any, ctx: any) => Promise<void>;
|
|
2
2
|
export interface VitestTestModule {
|
|
3
3
|
children: {
|
|
4
|
-
allTests: () => {
|
|
4
|
+
allTests: () => Iterable<{
|
|
5
5
|
name: string;
|
|
6
6
|
result: () => {
|
|
7
7
|
state: "pending" | "passed" | "failed" | "skipped";
|
|
8
8
|
};
|
|
9
9
|
diagnostic: () => {
|
|
10
10
|
duration: number;
|
|
11
|
-
};
|
|
12
|
-
}
|
|
11
|
+
} | undefined;
|
|
12
|
+
}>;
|
|
13
13
|
};
|
|
14
|
-
state: () => "skipped" | "passed" | "failed";
|
|
14
|
+
state: () => "pending" | "queued" | "skipped" | "passed" | "failed";
|
|
15
15
|
relativeModuleId: string;
|
|
16
16
|
}
|
|
17
|
-
export declare const printVitestTestModulesReporterTable: (testModules:
|
|
18
|
-
children: {
|
|
19
|
-
allTests: () => {
|
|
20
|
-
name: string;
|
|
21
|
-
result: () => {
|
|
22
|
-
state: "pending" | "passed" | "failed" | "skipped";
|
|
23
|
-
};
|
|
24
|
-
diagnostic: () => {
|
|
25
|
-
duration: number;
|
|
26
|
-
};
|
|
27
|
-
}[];
|
|
28
|
-
};
|
|
29
|
-
state: () => "skipped" | "passed" | "failed";
|
|
30
|
-
relativeModuleId: string;
|
|
31
|
-
}[]) => Promise<void>;
|
|
17
|
+
export declare const printVitestTestModulesReporterTable: (testModules: readonly VitestTestModule[]) => Promise<void>;
|
|
@@ -28,6 +28,7 @@ export const printVitestTestModulesReporterTable = async (testModules) => {
|
|
|
28
28
|
duration: Math.round(test.diagnostic()?.duration ?? 0),
|
|
29
29
|
};
|
|
30
30
|
});
|
|
31
|
-
|
|
31
|
+
const state = testModule.state();
|
|
32
|
+
await printReporterTable(testModule.relativeModuleId, tests, state === "pending" || state === "queued" ? "skipped" : state);
|
|
32
33
|
}
|
|
33
34
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "langsmith",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.3",
|
|
4
4
|
"description": "Client library to connect to the LangSmith Observability and Evaluation Platform.",
|
|
5
5
|
"packageManager": "pnpm@10.33.0",
|
|
6
6
|
"files": [
|
|
@@ -168,7 +168,7 @@
|
|
|
168
168
|
"@anthropic-ai/sdk": "^0.98.0",
|
|
169
169
|
"@babel/preset-env": "^8.0.2",
|
|
170
170
|
"@earendil-works/pi-coding-agent": "0.80.10",
|
|
171
|
-
"@faker-js/faker": "^
|
|
171
|
+
"@faker-js/faker": "^10.5.0",
|
|
172
172
|
"@google/genai": "^2.0.1",
|
|
173
173
|
"@jest/globals": "^30.4.1",
|
|
174
174
|
"@jest/reporters": "^30.2.0",
|
|
@@ -192,6 +192,7 @@
|
|
|
192
192
|
"@typescript-eslint/parser": "^8.62.1",
|
|
193
193
|
"ai": "7.0.36",
|
|
194
194
|
"babel-jest": "^30.2.0",
|
|
195
|
+
"browserslist": "4.28.7",
|
|
195
196
|
"cross-env": "^10.1.0",
|
|
196
197
|
"dotenv": "^17.3.1",
|
|
197
198
|
"jest": "^29.5.0",
|
|
@@ -208,7 +209,7 @@
|
|
|
208
209
|
"typedoc": "^0.28.16",
|
|
209
210
|
"typedoc-plugin-expand-object-like-types": "^0.1.2",
|
|
210
211
|
"typescript": "^6.0.3",
|
|
211
|
-
"vitest": "^
|
|
212
|
+
"vitest": "^4.1.11",
|
|
212
213
|
"ws": "^8.19.0",
|
|
213
214
|
"zod": "^4.3.6"
|
|
214
215
|
},
|
|
@@ -485,10 +486,10 @@
|
|
|
485
486
|
"brace-expansion@<1.1.16": "1.1.16",
|
|
486
487
|
"brace-expansion@>=2.0.0 <2.1.2": "2.1.2",
|
|
487
488
|
"brace-expansion@>=3.0.0 <5.0.7": "5.0.7",
|
|
488
|
-
"fast-uri": "3.1.
|
|
489
|
+
"fast-uri": "3.1.6",
|
|
489
490
|
"postcss": "8.5.23",
|
|
490
491
|
"js-yaml": "4.3.1",
|
|
491
|
-
"hono": "4.
|
|
492
|
+
"hono": "4.13.5",
|
|
492
493
|
"ip-address": "10.3.1",
|
|
493
494
|
"undici@>=7.0.0 <8.0.0": "7.29.0",
|
|
494
495
|
"undici@>=8.0.0 <9.0.0": "8.9.0",
|