deepagents 1.12.2 → 1.12.4
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/{agent-pS9QvkWZ.d.ts → agent-BNyBUA4W.d.ts} +109 -8
- package/dist/{agent-DwU6Gs2-.d.cts → agent-DMJjn99p.d.cts} +109 -8
- package/dist/browser.cjs +1 -1
- package/dist/browser.d.cts +1 -1
- package/dist/browser.d.ts +1 -1
- package/dist/browser.js +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/{langsmith-D2d3Dwcc.cjs → langsmith-BJ2PdYqB.cjs} +558 -122
- package/dist/langsmith-BJ2PdYqB.cjs.map +1 -0
- package/dist/{langsmith-b3Dpu8rS.js → langsmith-DRyafCNe.js} +557 -121
- package/dist/langsmith-DRyafCNe.js.map +1 -0
- package/dist/node.cjs +2 -2
- package/dist/node.d.cts +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +2 -2
- package/dist/{src-DMUJ51B3.js → src-DMHrmLGK.js} +8 -5
- package/dist/{src-DMUJ51B3.js.map → src-DMHrmLGK.js.map} +1 -1
- package/dist/{src-Dkrvbmp1.cjs → src-eTaw7gha.cjs} +8 -5
- package/dist/src-eTaw7gha.cjs.map +1 -0
- package/package.json +1 -1
- package/dist/langsmith-D2d3Dwcc.cjs.map +0 -1
- package/dist/langsmith-b3Dpu8rS.js.map +0 -1
- package/dist/src-Dkrvbmp1.cjs.map +0 -1
|
@@ -15,7 +15,7 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
15
|
}
|
|
16
16
|
return to;
|
|
17
17
|
};
|
|
18
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
19
19
|
value: mod,
|
|
20
20
|
enumerable: true
|
|
21
21
|
}) : target, mod));
|
|
@@ -326,13 +326,13 @@ function performStringReplacement(content, oldString, newString, replaceAll) {
|
|
|
326
326
|
function truncateIfTooLong(result) {
|
|
327
327
|
if (Array.isArray(result)) {
|
|
328
328
|
const totalChars = result.reduce((sum, item) => sum + item.length, 0);
|
|
329
|
-
if (totalChars >
|
|
329
|
+
if (totalChars > 8e4) {
|
|
330
330
|
const truncateAt = Math.floor(result.length * TOOL_RESULT_TOKEN_LIMIT * 4 / totalChars);
|
|
331
331
|
return [...result.slice(0, truncateAt), TRUNCATION_GUIDANCE];
|
|
332
332
|
}
|
|
333
333
|
return result;
|
|
334
334
|
}
|
|
335
|
-
if (result.length >
|
|
335
|
+
if (result.length > 8e4) return result.substring(0, TOOL_RESULT_TOKEN_LIMIT * 4) + "\n... [results truncated, try being more specific with your parameters]";
|
|
336
336
|
return result;
|
|
337
337
|
}
|
|
338
338
|
/**
|
|
@@ -425,6 +425,30 @@ function globSearchFiles(files, pattern, path = "/") {
|
|
|
425
425
|
return matches.map(([fp]) => fp).join("\n");
|
|
426
426
|
}
|
|
427
427
|
/**
|
|
428
|
+
* Format grep search results based on output mode.
|
|
429
|
+
*
|
|
430
|
+
* @param results - Dictionary mapping file paths to list of [line_num, line_content] tuples
|
|
431
|
+
* @param outputMode - Output format - "files_with_matches", "content", or "count"
|
|
432
|
+
* @returns Formatted string output
|
|
433
|
+
*/
|
|
434
|
+
function formatGrepResults(results, outputMode) {
|
|
435
|
+
if (outputMode === "files_with_matches") return Object.keys(results).sort().join("\n");
|
|
436
|
+
if (outputMode === "count") {
|
|
437
|
+
const lines = [];
|
|
438
|
+
for (const filePath of Object.keys(results).sort()) {
|
|
439
|
+
const count = results[filePath].length;
|
|
440
|
+
lines.push(`${filePath}: ${count}`);
|
|
441
|
+
}
|
|
442
|
+
return lines.join("\n");
|
|
443
|
+
}
|
|
444
|
+
const lines = [];
|
|
445
|
+
for (const filePath of Object.keys(results).sort()) {
|
|
446
|
+
lines.push(`${filePath}:`);
|
|
447
|
+
for (const [lineNum, line] of results[filePath]) lines.push(` ${lineNum}: ${line}`);
|
|
448
|
+
}
|
|
449
|
+
return lines.join("\n");
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
428
452
|
* Return structured grep matches from an in-memory files mapping.
|
|
429
453
|
*
|
|
430
454
|
* Performs literal text search (not regex). Binary files are skipped.
|
|
@@ -455,6 +479,24 @@ function grepMatchesFromFiles(files, pattern, path = null, glob = null) {
|
|
|
455
479
|
return matches;
|
|
456
480
|
}
|
|
457
481
|
/**
|
|
482
|
+
* Group structured matches into the legacy dict form used by formatters.
|
|
483
|
+
*/
|
|
484
|
+
function buildGrepResultsDict(matches) {
|
|
485
|
+
const grouped = {};
|
|
486
|
+
for (const m of matches) {
|
|
487
|
+
if (!grouped[m.path]) grouped[m.path] = [];
|
|
488
|
+
grouped[m.path].push([m.line, m.text]);
|
|
489
|
+
}
|
|
490
|
+
return grouped;
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Format structured grep matches using existing formatting logic.
|
|
494
|
+
*/
|
|
495
|
+
function formatGrepMatches(matches, outputMode) {
|
|
496
|
+
if (matches.length === 0) return "No matches found";
|
|
497
|
+
return formatGrepResults(buildGrepResultsDict(matches), outputMode);
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
458
500
|
* Determine MIME type from a file path's extension.
|
|
459
501
|
*
|
|
460
502
|
* Defaults to "text/plain" for unknown extensions. Only the known non-text
|
|
@@ -1445,7 +1487,7 @@ const TOOLS_EXCLUDED_FROM_EVICTION = FILESYSTEM_TOOL_NAMES.filter((name) => name
|
|
|
1445
1487
|
* Base64-encoded content is ~33% larger, so 10MB raw ≈ 13.3MB in context.
|
|
1446
1488
|
* This keeps inline multimodal payloads within all major provider limits.
|
|
1447
1489
|
*/
|
|
1448
|
-
const MAX_BINARY_READ_SIZE_BYTES =
|
|
1490
|
+
const MAX_BINARY_READ_SIZE_BYTES = 10485760;
|
|
1449
1491
|
/**
|
|
1450
1492
|
* Template for truncation message in read_file.
|
|
1451
1493
|
* {file_path} will be filled in at runtime.
|
|
@@ -1534,8 +1576,9 @@ function buildEvictedHumanContent(message, replacementText) {
|
|
|
1534
1576
|
*/
|
|
1535
1577
|
function buildTruncatedHumanMessage(message, filePath) {
|
|
1536
1578
|
const contentSample = createContentPreview(extractTextFromMessage(message));
|
|
1579
|
+
const evictedContent = buildEvictedHumanContent(message, TOO_LARGE_HUMAN_MSG.replace("{file_path}", filePath).replace("{content_sample}", contentSample));
|
|
1537
1580
|
return new langchain.HumanMessage({
|
|
1538
|
-
content:
|
|
1581
|
+
content: evictedContent,
|
|
1539
1582
|
id: message.id,
|
|
1540
1583
|
additional_kwargs: { ...message.additional_kwargs },
|
|
1541
1584
|
response_metadata: { ...message.response_metadata }
|
|
@@ -1809,7 +1852,7 @@ function createReadFileTool(backend, options) {
|
|
|
1809
1852
|
const sizeBytes = Math.ceil(base64Data.length * 3 / 4);
|
|
1810
1853
|
if (sizeBytes > 10485760) return [{
|
|
1811
1854
|
type: "text",
|
|
1812
|
-
text: `Error: file too large to read (${Math.round(sizeBytes /
|
|
1855
|
+
text: `Error: file too large to read (${Math.round(sizeBytes / 1048576)}MB exceeds ${MAX_BINARY_READ_SIZE_BYTES / 1048576}MB limit for binary files)`
|
|
1813
1856
|
}];
|
|
1814
1857
|
if (mimeType.startsWith("image/")) return [{
|
|
1815
1858
|
type: "image",
|
|
@@ -1956,23 +1999,14 @@ function createGrepTool(backend, options) {
|
|
|
1956
1999
|
const permissionError = checkPermission(permissions, "read", input.path ?? "/");
|
|
1957
2000
|
if (permissionError !== void 0) return toolError(runtime, "grep", permissionError);
|
|
1958
2001
|
const resolvedBackend = await resolveBackend(backend, runtime);
|
|
1959
|
-
const { pattern, path = "/", glob = null } = input;
|
|
2002
|
+
const { pattern, path = "/", glob = null, output_mode = "content" } = input;
|
|
1960
2003
|
const maxCount = input.max_count ?? grepMaxCount;
|
|
1961
2004
|
const result = await resolvedBackend.grep(pattern, path, glob, maxCount);
|
|
1962
2005
|
if (result.error) return result.error;
|
|
1963
2006
|
const matches = filterByPermissions(result.matches ?? [], permissions, "read", (m) => m.path);
|
|
1964
2007
|
if (matches.length === 0) return `No matches found for pattern '${pattern}'`;
|
|
1965
|
-
const
|
|
1966
|
-
let
|
|
1967
|
-
for (const match of matches) {
|
|
1968
|
-
if (match.path !== currentFile) {
|
|
1969
|
-
currentFile = match.path;
|
|
1970
|
-
lines.push(`\n${currentFile}:`);
|
|
1971
|
-
}
|
|
1972
|
-
lines.push(` ${match.line}: ${match.text}`);
|
|
1973
|
-
}
|
|
1974
|
-
const truncated = truncateIfTooLong(lines);
|
|
1975
|
-
let content = Array.isArray(truncated) ? truncated.join("\n") : truncated;
|
|
2008
|
+
const truncated = truncateIfTooLong(formatGrepMatches(matches, output_mode));
|
|
2009
|
+
let content = typeof truncated === "string" ? truncated : truncated.join("\n");
|
|
1976
2010
|
if (result.truncated) content += `\n\n${GREP_TRUNCATION_NOTE}`;
|
|
1977
2011
|
return content;
|
|
1978
2012
|
}, {
|
|
@@ -1982,7 +2016,12 @@ function createGrepTool(backend, options) {
|
|
|
1982
2016
|
pattern: zod_v4.z.string().describe("Literal text pattern to search for (not regex)"),
|
|
1983
2017
|
path: zod_v4.z.string().optional().default("/").describe("Base path to search from (default: /)"),
|
|
1984
2018
|
glob: zod_v4.z.string().optional().nullable().default(null).describe("Optional glob pattern to filter files (e.g., '*.py')"),
|
|
1985
|
-
max_count: zod_v4.z.number().int().positive().optional().nullable().default(null).describe("Optional cap on the total number of matches returned across all files. Leave unset to use the configured default. When the cap is hit, results are truncated and a note says so; narrow the pattern or path to see the rest.")
|
|
2019
|
+
max_count: zod_v4.z.coerce.number().int().positive().optional().nullable().default(null).describe("Optional cap on the total number of matches returned across all files. Leave unset to use the configured default. When the cap is hit, results are truncated and a note says so; narrow the pattern or path to see the rest."),
|
|
2020
|
+
output_mode: zod_v4.z.enum([
|
|
2021
|
+
"files_with_matches",
|
|
2022
|
+
"content",
|
|
2023
|
+
"count"
|
|
2024
|
+
]).optional().default("content").describe("Output format: 'files_with_matches' lists matching file paths, 'content' shows matching lines (default), 'count' shows match counts per file")
|
|
1986
2025
|
})
|
|
1987
2026
|
});
|
|
1988
2027
|
}
|
|
@@ -2119,9 +2158,10 @@ function createFilesystemMiddleware(options = {}) {
|
|
|
2119
2158
|
const evictPath = `/large_tool_results/${sanitizeToolCallId(fallbackToolCallId || msg.tool_call_id)}.txt`;
|
|
2120
2159
|
const writeResult = await resolvedBackend.write(evictPath, textContent);
|
|
2121
2160
|
const contentSample = createContentPreview(textContent);
|
|
2161
|
+
const replacementText = writeResult.error ? `Tool result too large, but the result could not be saved to the filesystem: ${writeResult.error}` : TOO_LARGE_TOOL_MSG.replace("{tool_call_id}", msg.tool_call_id).replace("{file_path}", evictPath).replace("{content_sample}", contentSample);
|
|
2122
2162
|
return {
|
|
2123
2163
|
message: new langchain.ToolMessage({
|
|
2124
|
-
content:
|
|
2164
|
+
content: replacementText,
|
|
2125
2165
|
tool_call_id: msg.tool_call_id,
|
|
2126
2166
|
name: msg.name,
|
|
2127
2167
|
id: msg.id,
|
|
@@ -2238,23 +2278,18 @@ const SUBAGENT_RESPONSE_FORMAT_CONFIG_KEY = "__deepagents_subagent_response_form
|
|
|
2238
2278
|
*/
|
|
2239
2279
|
const DEFAULT_SUBAGENT_PROMPT = "In order to complete the objective that the user asks of you, you have access to a number of standard tools.";
|
|
2240
2280
|
/**
|
|
2241
|
-
* State keys
|
|
2242
|
-
* updates from subagents.
|
|
2243
|
-
*
|
|
2244
|
-
* When returning updates:
|
|
2245
|
-
* 1. The messages key is handled explicitly to ensure only the final message is included
|
|
2246
|
-
* 2. The todos and structuredResponse keys are excluded as they do not have a defined reducer
|
|
2247
|
-
* and no clear meaning for returning them from a subagent to the main agent.
|
|
2248
|
-
* 3. The skillsMetadata and memoryContents keys are automatically excluded from subagent output
|
|
2249
|
-
* to prevent parent state from leaking to child agents. Each agent loads its own skills/memory
|
|
2250
|
-
* independently based on its middleware configuration.
|
|
2281
|
+
* State keys excluded when passing state to subagents and when returning
|
|
2282
|
+
* updates from subagents. Summarization keys are excluded because their
|
|
2283
|
+
* cutoffIndex is only valid against the message list it was computed from.
|
|
2251
2284
|
*/
|
|
2252
2285
|
const EXCLUDED_STATE_KEYS = [
|
|
2253
2286
|
"messages",
|
|
2254
2287
|
"todos",
|
|
2255
2288
|
"structuredResponse",
|
|
2256
2289
|
"skillsMetadata",
|
|
2257
|
-
"memoryContents"
|
|
2290
|
+
"memoryContents",
|
|
2291
|
+
"_summarizationEvent",
|
|
2292
|
+
"_summarizationSessionId"
|
|
2258
2293
|
];
|
|
2259
2294
|
/**
|
|
2260
2295
|
* Default description for the general-purpose subagent.
|
|
@@ -2466,6 +2501,7 @@ function createTaskTool(options) {
|
|
|
2466
2501
|
}
|
|
2467
2502
|
return subagentGraphs[subagentType];
|
|
2468
2503
|
}
|
|
2504
|
+
const finalTaskDescription = taskDescription ? taskDescription : getTaskToolDescription(subagentDescriptions);
|
|
2469
2505
|
return (0, langchain.tool)(async (input, config) => {
|
|
2470
2506
|
const { description, subagent_type } = input;
|
|
2471
2507
|
if (!(subagent_type in subagentGraphs)) {
|
|
@@ -2475,6 +2511,7 @@ function createTaskTool(options) {
|
|
|
2475
2511
|
const subagent = selectSubagent(subagent_type, config);
|
|
2476
2512
|
const subagentState = filterStateForSubagent((0, _langchain_langgraph.getCurrentTaskInput)());
|
|
2477
2513
|
subagentState.messages = [new _langchain_core_messages.HumanMessage({ content: description })];
|
|
2514
|
+
subagentState._summarizationSessionId = `session_${crypto.randomUUID().substring(0, 8)}`;
|
|
2478
2515
|
const subagentConfig = {
|
|
2479
2516
|
...config,
|
|
2480
2517
|
metadata: {
|
|
@@ -2501,7 +2538,7 @@ function createTaskTool(options) {
|
|
|
2501
2538
|
return returnCommandWithStateUpdate(result, config.toolCall.id);
|
|
2502
2539
|
}, {
|
|
2503
2540
|
name: "task",
|
|
2504
|
-
description:
|
|
2541
|
+
description: finalTaskDescription,
|
|
2505
2542
|
schema: zod_v4.z.object({
|
|
2506
2543
|
description: zod_v4.z.string().describe("The task to execute with the selected agent"),
|
|
2507
2544
|
subagent_type: zod_v4.z.string().describe(`Name of the agent to use. Available: ${Object.keys(subagentGraphs).join(", ")}`)
|
|
@@ -2513,18 +2550,19 @@ function createTaskTool(options) {
|
|
|
2513
2550
|
*/
|
|
2514
2551
|
function createSubAgentMiddleware(options) {
|
|
2515
2552
|
const { defaultModel, defaultTools = [], defaultMiddleware = null, generalPurposeMiddleware = null, defaultInterruptOn = null, subagents = [], systemPrompt = null, generalPurposeAgent = true, taskDescription = null } = options;
|
|
2553
|
+
const taskTool = createTaskTool({
|
|
2554
|
+
defaultModel,
|
|
2555
|
+
defaultTools,
|
|
2556
|
+
defaultMiddleware,
|
|
2557
|
+
generalPurposeMiddleware,
|
|
2558
|
+
defaultInterruptOn,
|
|
2559
|
+
subagents,
|
|
2560
|
+
generalPurposeAgent,
|
|
2561
|
+
taskDescription
|
|
2562
|
+
});
|
|
2516
2563
|
return (0, langchain.createMiddleware)({
|
|
2517
2564
|
name: "subAgentMiddleware",
|
|
2518
|
-
tools: [
|
|
2519
|
-
defaultModel,
|
|
2520
|
-
defaultTools,
|
|
2521
|
-
defaultMiddleware,
|
|
2522
|
-
generalPurposeMiddleware,
|
|
2523
|
-
defaultInterruptOn,
|
|
2524
|
-
subagents,
|
|
2525
|
-
generalPurposeAgent,
|
|
2526
|
-
taskDescription
|
|
2527
|
-
})],
|
|
2565
|
+
tools: [taskTool],
|
|
2528
2566
|
wrapModelCall: async (request, handler) => {
|
|
2529
2567
|
if (systemPrompt !== null) return handler({
|
|
2530
2568
|
...request,
|
|
@@ -2714,6 +2752,43 @@ function isAnthropicModel(model) {
|
|
|
2714
2752
|
return model.getName() === "ChatAnthropic";
|
|
2715
2753
|
}
|
|
2716
2754
|
/**
|
|
2755
|
+
* A one-shot promise whose settlement is controlled externally.
|
|
2756
|
+
*
|
|
2757
|
+
* Use this when one part of a workflow must wait for an event that is owned
|
|
2758
|
+
* elsewhere—for example, a queued mutation waiting for the worker that will
|
|
2759
|
+
* push it. `Deferred` is awaitable because it implements `PromiseLike`, and
|
|
2760
|
+
* `.promise` is available when a concrete `Promise` is required.
|
|
2761
|
+
*
|
|
2762
|
+
* The first call to `resolve` or `reject` wins; later calls are ignored. This
|
|
2763
|
+
* class deliberately does not provide cancellation, reset, or notification
|
|
2764
|
+
* semantics. It models exactly one eventual outcome.
|
|
2765
|
+
*/
|
|
2766
|
+
var Deferred = class {
|
|
2767
|
+
promise;
|
|
2768
|
+
settled = false;
|
|
2769
|
+
resolvePromise;
|
|
2770
|
+
rejectPromise;
|
|
2771
|
+
constructor() {
|
|
2772
|
+
this.promise = new Promise((resolve, reject) => {
|
|
2773
|
+
this.resolvePromise = resolve;
|
|
2774
|
+
this.rejectPromise = reject;
|
|
2775
|
+
});
|
|
2776
|
+
}
|
|
2777
|
+
resolve(value) {
|
|
2778
|
+
if (this.settled) return;
|
|
2779
|
+
this.settled = true;
|
|
2780
|
+
this.resolvePromise(value);
|
|
2781
|
+
}
|
|
2782
|
+
reject(reason) {
|
|
2783
|
+
if (this.settled) return;
|
|
2784
|
+
this.settled = true;
|
|
2785
|
+
this.rejectPromise(reason);
|
|
2786
|
+
}
|
|
2787
|
+
then(onfulfilled, onrejected) {
|
|
2788
|
+
return this.promise.then(onfulfilled, onrejected);
|
|
2789
|
+
}
|
|
2790
|
+
};
|
|
2791
|
+
/**
|
|
2717
2792
|
* Detect whether a model is an AWS Bedrock Converse model.
|
|
2718
2793
|
*
|
|
2719
2794
|
* Accepts the wider `RunnableInterface` shape (the type of `request.model`
|
|
@@ -3029,7 +3104,7 @@ function createMemoryMiddleware(options) {
|
|
|
3029
3104
|
* });
|
|
3030
3105
|
* ```
|
|
3031
3106
|
*/
|
|
3032
|
-
const MAX_SKILL_FILE_SIZE =
|
|
3107
|
+
const MAX_SKILL_FILE_SIZE = 10485760;
|
|
3033
3108
|
const DEFAULT_SKILL_READ_LINE_LIMIT = 1e3;
|
|
3034
3109
|
const MAX_SKILL_NAME_LENGTH = 64;
|
|
3035
3110
|
const MAX_SKILL_DESCRIPTION_LENGTH = 1024;
|
|
@@ -3797,7 +3872,6 @@ function createCompletionCallbackMiddleware(options) {
|
|
|
3797
3872
|
* from `langchain` directly.
|
|
3798
3873
|
*/
|
|
3799
3874
|
const DEFAULT_MESSAGES_TO_KEEP = 20;
|
|
3800
|
-
const DEFAULT_TRIM_TOKEN_LIMIT = 4e3;
|
|
3801
3875
|
const FALLBACK_TRIGGER = {
|
|
3802
3876
|
type: "tokens",
|
|
3803
3877
|
value: 17e4
|
|
@@ -3914,7 +3988,7 @@ function isSummaryMessage(msg) {
|
|
|
3914
3988
|
* @returns AgentMiddleware for summarization and history offloading
|
|
3915
3989
|
*/
|
|
3916
3990
|
function createSummarizationMiddleware(options) {
|
|
3917
|
-
const { model, backend, summaryPrompt = DEFAULT_SUMMARY_PROMPT, trimTokensToSummarize
|
|
3991
|
+
const { model, backend, summaryPrompt = DEFAULT_SUMMARY_PROMPT, trimTokensToSummarize, historyPathPrefix = "/conversation_history" } = options;
|
|
3918
3992
|
let trigger = options.trigger;
|
|
3919
3993
|
let keep = options.keep ?? {
|
|
3920
3994
|
type: "messages",
|
|
@@ -4118,7 +4192,9 @@ function createSummarizationMiddleware(options) {
|
|
|
4118
4192
|
* This gives a more accurate picture of what actually gets sent to the model.
|
|
4119
4193
|
*/
|
|
4120
4194
|
function countTotalTokens(messages, systemMessage, tools) {
|
|
4121
|
-
|
|
4195
|
+
const countedMessages = systemMessage && langchain.SystemMessage.isInstance(systemMessage) ? [systemMessage, ...messages] : [...messages];
|
|
4196
|
+
const toolsArray = tools && Array.isArray(tools) && tools.length > 0 ? tools : null;
|
|
4197
|
+
return (0, langchain.countTokensApproximately)(countedMessages, toolsArray);
|
|
4122
4198
|
}
|
|
4123
4199
|
/**
|
|
4124
4200
|
* Truncate ToolMessage content so that the total payload fits within the
|
|
@@ -4265,7 +4341,8 @@ function createSummarizationMiddleware(options) {
|
|
|
4265
4341
|
*/
|
|
4266
4342
|
async function createSummary(messages, chatModel) {
|
|
4267
4343
|
let messagesToSummarize = messages;
|
|
4268
|
-
|
|
4344
|
+
const tokens = (0, langchain.countTokensApproximately)(messages);
|
|
4345
|
+
if (trimTokensToSummarize !== void 0 && tokens > trimTokensToSummarize) {
|
|
4269
4346
|
let kept = 0;
|
|
4270
4347
|
const trimmedMessages = [];
|
|
4271
4348
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -4278,8 +4355,7 @@ function createSummarizationMiddleware(options) {
|
|
|
4278
4355
|
}
|
|
4279
4356
|
const conversation = (0, _langchain_core_messages.getBufferString)(messagesToSummarize);
|
|
4280
4357
|
const prompt = summaryPrompt.replace("{conversation}", conversation);
|
|
4281
|
-
|
|
4282
|
-
return typeof response.content === "string" ? response.content : JSON.stringify(response.content);
|
|
4358
|
+
return (await chatModel.invoke([new langchain.HumanMessage({ content: prompt })])).text;
|
|
4283
4359
|
}
|
|
4284
4360
|
/**
|
|
4285
4361
|
* Build the summary message with file path reference.
|
|
@@ -6572,9 +6648,36 @@ var StoreBackend = class {
|
|
|
6572
6648
|
/**
|
|
6573
6649
|
* ContextHubBackend: Store files in a LangSmith Hub agent repo (persistent).
|
|
6574
6650
|
*/
|
|
6575
|
-
const
|
|
6651
|
+
const CONTEXT_URL_COMMIT_PATH_RE = /^\/context\/([^/]+)\/([0-9a-f]{8})$/;
|
|
6652
|
+
const LEGACY_URL_COMMIT_PATH_RE = /^\/hub\/([^/]+)\/([^/:]+):([0-9a-f]{8})$/;
|
|
6653
|
+
const MUTATION_COALESCE_MS = 50;
|
|
6654
|
+
const MAX_CONFLICT_RETRIES = 3;
|
|
6576
6655
|
const TEXT_MIME_TYPE = "text/plain";
|
|
6577
6656
|
const FNMATCH_OPTIONS = { bash: true };
|
|
6657
|
+
function parseHubTargetIdentifier(identifier) {
|
|
6658
|
+
if (!identifier || identifier.split("/").length > 2 || identifier.startsWith("/") || identifier.endsWith("/") || identifier.split(":").length > 2) return null;
|
|
6659
|
+
const [ownerNamePart] = identifier.split(":");
|
|
6660
|
+
if (ownerNamePart.includes("/")) {
|
|
6661
|
+
const [owner, name] = ownerNamePart.split("/", 2);
|
|
6662
|
+
return owner && name ? [owner, name] : null;
|
|
6663
|
+
}
|
|
6664
|
+
return ownerNamePart ? ["-", ownerNamePart] : null;
|
|
6665
|
+
}
|
|
6666
|
+
function parseCommitHashFromUrl(url, identifier) {
|
|
6667
|
+
try {
|
|
6668
|
+
const pathname = decodeURIComponent(new URL(url).pathname);
|
|
6669
|
+
const target = parseHubTargetIdentifier(identifier);
|
|
6670
|
+
if (target === null) return null;
|
|
6671
|
+
const [targetOwner, targetName] = target;
|
|
6672
|
+
const contextMatch = CONTEXT_URL_COMMIT_PATH_RE.exec(pathname);
|
|
6673
|
+
if (contextMatch !== null && contextMatch[1] === targetName) return contextMatch[2];
|
|
6674
|
+
const legacyMatch = LEGACY_URL_COMMIT_PATH_RE.exec(pathname);
|
|
6675
|
+
if (legacyMatch !== null && legacyMatch[1] === targetOwner && legacyMatch[2] === targetName) return legacyMatch[3];
|
|
6676
|
+
return null;
|
|
6677
|
+
} catch {
|
|
6678
|
+
return null;
|
|
6679
|
+
}
|
|
6680
|
+
}
|
|
6578
6681
|
function getErrorMessage(error) {
|
|
6579
6682
|
if (typeof error === "string") return error;
|
|
6580
6683
|
if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") return error.message;
|
|
@@ -6613,6 +6716,12 @@ function getLangSmithStatus(error) {
|
|
|
6613
6716
|
const maybeError = error;
|
|
6614
6717
|
if (typeof maybeError.status === "number") return maybeError.status;
|
|
6615
6718
|
}
|
|
6719
|
+
function createLangSmithConflictError(message) {
|
|
6720
|
+
const error = new Error(message);
|
|
6721
|
+
error.name = "LangSmithConflictError";
|
|
6722
|
+
error.status = 409;
|
|
6723
|
+
return error;
|
|
6724
|
+
}
|
|
6616
6725
|
function mapHubFileOperationError(error) {
|
|
6617
6726
|
const status = getLangSmithStatus(error);
|
|
6618
6727
|
if (status === 401 || status === 403) return "permission_denied";
|
|
@@ -6622,12 +6731,49 @@ function mapHubFileOperationError(error) {
|
|
|
6622
6731
|
/**
|
|
6623
6732
|
* Backend that stores files in a LangSmith Hub agent repo (persistent).
|
|
6624
6733
|
*/
|
|
6734
|
+
/**
|
|
6735
|
+
* Backend that stores files in a LangSmith Hub agent repository.
|
|
6736
|
+
*
|
|
6737
|
+
* ## Mutation model
|
|
6738
|
+
*
|
|
6739
|
+
* Mutations are accepted in call order, coalesced for a short window, and
|
|
6740
|
+
* pushed by one worker. Only one batch is in flight at a time; mutations that
|
|
6741
|
+
* arrive during a push form the next batch. This serializes one backend
|
|
6742
|
+
* instance's writes while still reducing the number of Hub commits.
|
|
6743
|
+
*
|
|
6744
|
+
* Reads use an optimistic view: the last durable cache overlaid with the
|
|
6745
|
+
* in-flight batch and then the pending batch. A read can therefore observe an
|
|
6746
|
+
* accepted mutation before it is durable; a failed push invalidates that view
|
|
6747
|
+
* and the next operation reloads from Hub.
|
|
6748
|
+
*
|
|
6749
|
+
* A `409` parent conflict triggers an authoritative pull and rematerializes
|
|
6750
|
+
* the in-flight batch over the fetched tree before retrying. Edits replay their
|
|
6751
|
+
* original replacement intent; absolute writes, deletes, and uploads replay as
|
|
6752
|
+
* absolute changes. Retries are bounded by `MAX_CONFLICT_RETRIES`.
|
|
6753
|
+
*/
|
|
6625
6754
|
var ContextHubBackend = class ContextHubBackend {
|
|
6626
6755
|
identifier;
|
|
6627
6756
|
client;
|
|
6757
|
+
/** Last durable Hub file state; `null` means the next access must load it. */
|
|
6628
6758
|
cache = null;
|
|
6629
6759
|
linkedEntries = {};
|
|
6760
|
+
/** Parent hash for the durable cache, used for optimistic-concurrency pushes. */
|
|
6630
6761
|
commitHash = null;
|
|
6762
|
+
/** Shared cold-load promise so concurrent first operations perform one pull. */
|
|
6763
|
+
loadPromise = null;
|
|
6764
|
+
/** Promise chain serializing mutation acceptance and optimistic projections. */
|
|
6765
|
+
mutationOrder = Promise.resolve();
|
|
6766
|
+
/** Mutations accepted for the next coalesced push. */
|
|
6767
|
+
pendingBatch = null;
|
|
6768
|
+
/** The batch currently submitted to Hub and visible to optimistic reads. */
|
|
6769
|
+
inFlightBatch = null;
|
|
6770
|
+
/** The single queue-draining worker, when active. */
|
|
6771
|
+
workerPromise = null;
|
|
6772
|
+
/**
|
|
6773
|
+
* Blocks cache consumers while a successful push without a parseable commit
|
|
6774
|
+
* hash is being confirmed by an authoritative pull.
|
|
6775
|
+
*/
|
|
6776
|
+
snapshotPublication = null;
|
|
6631
6777
|
constructor(identifier, options = {}) {
|
|
6632
6778
|
this.identifier = identifier;
|
|
6633
6779
|
this.client = options.client ?? new langsmith.Client();
|
|
@@ -6638,49 +6784,319 @@ var ContextHubBackend = class ContextHubBackend {
|
|
|
6638
6784
|
static toHubUnavailableError(error) {
|
|
6639
6785
|
return `Hub unavailable: ${getErrorMessage(error)}`;
|
|
6640
6786
|
}
|
|
6641
|
-
async
|
|
6787
|
+
async fetchTree() {
|
|
6642
6788
|
let context;
|
|
6643
6789
|
try {
|
|
6644
6790
|
context = await this.client.pullAgent(this.identifier);
|
|
6645
6791
|
} catch (error) {
|
|
6646
|
-
if (isLangSmithNotFoundError(error)) {
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
}
|
|
6792
|
+
if (isLangSmithNotFoundError(error)) return {
|
|
6793
|
+
cache: {},
|
|
6794
|
+
linkedEntries: {},
|
|
6795
|
+
commitHash: null
|
|
6796
|
+
};
|
|
6652
6797
|
throw error;
|
|
6653
6798
|
}
|
|
6654
|
-
|
|
6655
|
-
|
|
6656
|
-
|
|
6657
|
-
|
|
6658
|
-
|
|
6799
|
+
const cache = {};
|
|
6800
|
+
const linkedEntries = {};
|
|
6801
|
+
for (const [path, entry] of Object.entries(context.files)) if (entry.type === "file") cache[path] = entry.content;
|
|
6802
|
+
else if ((entry.type === "agent" || entry.type === "skill") && typeof entry.repo_handle === "string") linkedEntries[path] = entry.repo_handle;
|
|
6803
|
+
return {
|
|
6804
|
+
cache,
|
|
6805
|
+
linkedEntries,
|
|
6806
|
+
commitHash: context.commit_hash
|
|
6807
|
+
};
|
|
6659
6808
|
}
|
|
6660
|
-
|
|
6661
|
-
|
|
6809
|
+
publishSnapshot(snapshot) {
|
|
6810
|
+
this.cache = snapshot.cache;
|
|
6811
|
+
this.linkedEntries = snapshot.linkedEntries;
|
|
6812
|
+
this.commitHash = snapshot.commitHash;
|
|
6813
|
+
}
|
|
6814
|
+
async loadTree() {
|
|
6815
|
+
this.publishSnapshot(await this.fetchTree());
|
|
6816
|
+
}
|
|
6817
|
+
beginSnapshotPublication() {
|
|
6818
|
+
if (this.snapshotPublication !== null) throw new Error("Context Hub snapshot publication is already pending");
|
|
6819
|
+
this.snapshotPublication = new Deferred();
|
|
6820
|
+
}
|
|
6821
|
+
finishSnapshotPublication() {
|
|
6822
|
+
const publication = this.snapshotPublication;
|
|
6823
|
+
this.snapshotPublication = null;
|
|
6824
|
+
publication?.resolve();
|
|
6825
|
+
}
|
|
6826
|
+
async ensureCacheLoaded() {
|
|
6827
|
+
while (this.snapshotPublication !== null) await this.snapshotPublication;
|
|
6828
|
+
if (this.cache === null) {
|
|
6829
|
+
let loadPromise = this.loadPromise;
|
|
6830
|
+
if (loadPromise === null) {
|
|
6831
|
+
loadPromise = this.loadTree();
|
|
6832
|
+
this.loadPromise = loadPromise;
|
|
6833
|
+
}
|
|
6834
|
+
try {
|
|
6835
|
+
await loadPromise;
|
|
6836
|
+
} finally {
|
|
6837
|
+
if (this.loadPromise === loadPromise) this.loadPromise = null;
|
|
6838
|
+
}
|
|
6839
|
+
}
|
|
6662
6840
|
if (this.cache === null) throw new Error("Context Hub cache failed to initialize");
|
|
6663
|
-
|
|
6664
|
-
|
|
6665
|
-
|
|
6666
|
-
|
|
6667
|
-
|
|
6668
|
-
|
|
6669
|
-
|
|
6670
|
-
|
|
6841
|
+
}
|
|
6842
|
+
async ensureCache() {
|
|
6843
|
+
await this.ensureCacheLoaded();
|
|
6844
|
+
return this.visibleCache();
|
|
6845
|
+
}
|
|
6846
|
+
static applyChanges(cache, changes) {
|
|
6847
|
+
const next = { ...cache };
|
|
6848
|
+
for (const [path, content] of Object.entries(changes)) if (content === null) delete next[path];
|
|
6849
|
+
else next[path] = content;
|
|
6850
|
+
return next;
|
|
6851
|
+
}
|
|
6852
|
+
/**
|
|
6853
|
+
* Build the read-your-writes view without publishing speculative data as the
|
|
6854
|
+
* durable cache. Later batches overlay earlier ones, matching worker order.
|
|
6855
|
+
*/
|
|
6856
|
+
visibleCache() {
|
|
6857
|
+
let visible = { ...this.cache ?? {} };
|
|
6858
|
+
if (this.inFlightBatch !== null) visible = ContextHubBackend.applyChanges(visible, this.inFlightBatch.changes);
|
|
6859
|
+
if (this.pendingBatch !== null) visible = ContextHubBackend.applyChanges(visible, this.pendingBatch.changes);
|
|
6860
|
+
return visible;
|
|
6861
|
+
}
|
|
6862
|
+
invalidateCache() {
|
|
6863
|
+
this.cache = null;
|
|
6864
|
+
this.linkedEntries = {};
|
|
6865
|
+
this.commitHash = null;
|
|
6866
|
+
this.loadPromise = null;
|
|
6867
|
+
}
|
|
6868
|
+
async acquireMutationTurn() {
|
|
6869
|
+
let release;
|
|
6870
|
+
const previous = this.mutationOrder;
|
|
6871
|
+
this.mutationOrder = new Promise((resolve) => {
|
|
6872
|
+
release = resolve;
|
|
6873
|
+
});
|
|
6874
|
+
await previous;
|
|
6875
|
+
return release;
|
|
6876
|
+
}
|
|
6877
|
+
/**
|
|
6878
|
+
* Serialize validation and enqueueing so each operation is evaluated against
|
|
6879
|
+
* a stable optimistic projection. Cache loading begins before acquiring the
|
|
6880
|
+
* turn, allowing concurrent cold-start callers to share the same pull.
|
|
6881
|
+
*/
|
|
6882
|
+
async acceptMutation(operation) {
|
|
6883
|
+
const turn = this.acquireMutationTurn();
|
|
6884
|
+
const cacheOutcome = this.ensureCacheLoaded().then(() => ({ loaded: true }), (error) => ({
|
|
6885
|
+
loaded: false,
|
|
6886
|
+
error
|
|
6887
|
+
}));
|
|
6888
|
+
const release = await turn;
|
|
6889
|
+
try {
|
|
6890
|
+
const outcome = await cacheOutcome;
|
|
6891
|
+
if (!outcome.loaded) throw outcome.error;
|
|
6892
|
+
while (this.cache === null) await this.ensureCacheLoaded();
|
|
6893
|
+
return operation(this.visibleCache());
|
|
6894
|
+
} finally {
|
|
6895
|
+
release();
|
|
6896
|
+
}
|
|
6897
|
+
}
|
|
6898
|
+
/**
|
|
6899
|
+
* Start a batch's coalescing window. The worker waits for this signal before
|
|
6900
|
+
* detaching the batch; cancellation resolves it immediately so failures do
|
|
6901
|
+
* not leave the worker waiting on a timer.
|
|
6902
|
+
*/
|
|
6903
|
+
createMutationBatch() {
|
|
6904
|
+
const batch = {
|
|
6905
|
+
changes: {},
|
|
6906
|
+
waiters: [],
|
|
6907
|
+
ready: new Deferred(),
|
|
6908
|
+
timer: null
|
|
6671
6909
|
};
|
|
6672
|
-
|
|
6673
|
-
|
|
6674
|
-
|
|
6910
|
+
batch.timer = setTimeout(() => {
|
|
6911
|
+
batch.timer = null;
|
|
6912
|
+
batch.ready.resolve();
|
|
6913
|
+
}, MUTATION_COALESCE_MS);
|
|
6914
|
+
return batch;
|
|
6915
|
+
}
|
|
6916
|
+
cancelBatchTimer(batch) {
|
|
6917
|
+
if (batch.timer !== null) {
|
|
6918
|
+
clearTimeout(batch.timer);
|
|
6919
|
+
batch.timer = null;
|
|
6920
|
+
}
|
|
6921
|
+
batch.ready.resolve();
|
|
6922
|
+
}
|
|
6923
|
+
enqueueCommit(changes, intent = {
|
|
6924
|
+
kind: "changes",
|
|
6925
|
+
changes: { ...changes }
|
|
6926
|
+
}) {
|
|
6927
|
+
if (Object.keys(changes).length === 0) return Promise.resolve();
|
|
6928
|
+
let batch = this.pendingBatch;
|
|
6929
|
+
if (batch === null) {
|
|
6930
|
+
batch = this.createMutationBatch();
|
|
6931
|
+
this.pendingBatch = batch;
|
|
6932
|
+
}
|
|
6933
|
+
Object.assign(batch.changes, changes);
|
|
6934
|
+
const completion = new Deferred();
|
|
6935
|
+
batch.waiters.push({
|
|
6936
|
+
intent,
|
|
6937
|
+
completion
|
|
6675
6938
|
});
|
|
6676
|
-
|
|
6677
|
-
|
|
6678
|
-
|
|
6679
|
-
|
|
6680
|
-
|
|
6681
|
-
|
|
6682
|
-
|
|
6683
|
-
|
|
6939
|
+
this.startWorker();
|
|
6940
|
+
return completion.promise;
|
|
6941
|
+
}
|
|
6942
|
+
/**
|
|
6943
|
+
* Replay ordered intents over an authoritative base after a conflict. This
|
|
6944
|
+
* rebuilds the push payload and optimistic overlay. An edit that no longer
|
|
6945
|
+
* applies throws the supplied conflict error; absolute changes are reapplied.
|
|
6946
|
+
*/
|
|
6947
|
+
rematerializeBatch(batch, base, conflictError) {
|
|
6948
|
+
let cache = { ...base };
|
|
6949
|
+
const changes = {};
|
|
6950
|
+
for (const waiter of batch.waiters) {
|
|
6951
|
+
const { intent } = waiter;
|
|
6952
|
+
if (intent.kind === "changes") {
|
|
6953
|
+
Object.assign(changes, intent.changes);
|
|
6954
|
+
cache = ContextHubBackend.applyChanges(cache, intent.changes);
|
|
6955
|
+
continue;
|
|
6956
|
+
}
|
|
6957
|
+
const current = cache[intent.path];
|
|
6958
|
+
if (current === void 0) throw conflictError;
|
|
6959
|
+
const replacementResult = performStringReplacement(current, intent.oldString, intent.newString, intent.replaceAll);
|
|
6960
|
+
if (typeof replacementResult === "string") throw conflictError;
|
|
6961
|
+
const [newContent, occurrences] = replacementResult;
|
|
6962
|
+
const editChanges = { [intent.path]: newContent };
|
|
6963
|
+
Object.assign(changes, editChanges);
|
|
6964
|
+
cache = ContextHubBackend.applyChanges(cache, editChanges);
|
|
6965
|
+
intent.updateOccurrences(occurrences);
|
|
6966
|
+
}
|
|
6967
|
+
batch.changes = changes;
|
|
6968
|
+
return cache;
|
|
6969
|
+
}
|
|
6970
|
+
rematerializeAfterConflict(batch, snapshot, conflictError) {
|
|
6971
|
+
const cache = this.rematerializeBatch(batch, snapshot.cache, conflictError);
|
|
6972
|
+
let pendingReplayError = null;
|
|
6973
|
+
if (this.pendingBatch !== null) try {
|
|
6974
|
+
this.rematerializeBatch(this.pendingBatch, cache, conflictError);
|
|
6975
|
+
} catch (error) {
|
|
6976
|
+
if (error !== conflictError) throw error;
|
|
6977
|
+
pendingReplayError = error;
|
|
6978
|
+
}
|
|
6979
|
+
this.publishSnapshot(snapshot);
|
|
6980
|
+
if (pendingReplayError !== null) this.failPendingBatch(pendingReplayError);
|
|
6981
|
+
}
|
|
6982
|
+
rematerializePendingBatch(snapshot) {
|
|
6983
|
+
if (this.pendingBatch === null) return null;
|
|
6984
|
+
const conflictError = createLangSmithConflictError("Pending Context Hub mutation conflicts with authoritative state");
|
|
6985
|
+
try {
|
|
6986
|
+
this.rematerializeBatch(this.pendingBatch, snapshot.cache, conflictError);
|
|
6987
|
+
return null;
|
|
6988
|
+
} catch (error) {
|
|
6989
|
+
if (error !== conflictError) throw error;
|
|
6990
|
+
return conflictError;
|
|
6991
|
+
}
|
|
6992
|
+
}
|
|
6993
|
+
startWorker() {
|
|
6994
|
+
if (this.workerPromise !== null) return;
|
|
6995
|
+
const worker = this.drainMutationQueue().catch((error) => {
|
|
6996
|
+
this.failAllBatches(error);
|
|
6997
|
+
}).finally(() => {
|
|
6998
|
+
if (this.workerPromise === worker) {
|
|
6999
|
+
this.workerPromise = null;
|
|
7000
|
+
if (this.pendingBatch !== null) this.startWorker();
|
|
7001
|
+
}
|
|
7002
|
+
});
|
|
7003
|
+
this.workerPromise = worker;
|
|
7004
|
+
}
|
|
7005
|
+
/**
|
|
7006
|
+
* Drain coalesced batches sequentially. A completed batch publishes durable
|
|
7007
|
+
* state before settling its callers; a failed batch invalidates local state
|
|
7008
|
+
* and rejects both in-flight and queued callers so the next mutation reloads.
|
|
7009
|
+
*/
|
|
7010
|
+
async drainMutationQueue() {
|
|
7011
|
+
while (this.pendingBatch !== null) {
|
|
7012
|
+
const batch = this.pendingBatch;
|
|
7013
|
+
await batch.ready;
|
|
7014
|
+
if (this.pendingBatch !== batch) continue;
|
|
7015
|
+
this.pendingBatch = null;
|
|
7016
|
+
this.inFlightBatch = batch;
|
|
7017
|
+
let pendingReplayError = null;
|
|
7018
|
+
try {
|
|
7019
|
+
const result = await this.pushBatch(batch);
|
|
7020
|
+
if (result.kind === "snapshot") {
|
|
7021
|
+
pendingReplayError = this.rematerializePendingBatch(result.snapshot);
|
|
7022
|
+
this.publishSnapshot(result.snapshot);
|
|
7023
|
+
} else {
|
|
7024
|
+
this.cache = ContextHubBackend.applyChanges(this.cache ?? {}, batch.changes);
|
|
7025
|
+
this.commitHash = result.commitHash;
|
|
7026
|
+
}
|
|
7027
|
+
} catch (error) {
|
|
7028
|
+
this.inFlightBatch = null;
|
|
7029
|
+
this.invalidateCache();
|
|
7030
|
+
this.finishSnapshotPublication();
|
|
7031
|
+
for (const waiter of batch.waiters) waiter.completion.reject(error);
|
|
7032
|
+
this.failPendingBatch(error);
|
|
7033
|
+
return;
|
|
7034
|
+
}
|
|
7035
|
+
this.inFlightBatch = null;
|
|
7036
|
+
this.finishSnapshotPublication();
|
|
7037
|
+
for (const waiter of batch.waiters) waiter.completion.resolve();
|
|
7038
|
+
if (pendingReplayError !== null) {
|
|
7039
|
+
this.failPendingBatch(pendingReplayError);
|
|
7040
|
+
return;
|
|
7041
|
+
}
|
|
7042
|
+
}
|
|
7043
|
+
}
|
|
7044
|
+
failPendingBatch(error) {
|
|
7045
|
+
const pending = this.pendingBatch;
|
|
7046
|
+
if (pending === null) return;
|
|
7047
|
+
this.pendingBatch = null;
|
|
7048
|
+
this.cancelBatchTimer(pending);
|
|
7049
|
+
for (const waiter of pending.waiters) waiter.completion.reject(error);
|
|
7050
|
+
}
|
|
7051
|
+
failAllBatches(error) {
|
|
7052
|
+
const inFlight = this.inFlightBatch;
|
|
7053
|
+
this.inFlightBatch = null;
|
|
7054
|
+
this.invalidateCache();
|
|
7055
|
+
this.finishSnapshotPublication();
|
|
7056
|
+
if (inFlight !== null) {
|
|
7057
|
+
this.cancelBatchTimer(inFlight);
|
|
7058
|
+
for (const waiter of inFlight.waiters) waiter.completion.reject(error);
|
|
7059
|
+
}
|
|
7060
|
+
this.failPendingBatch(error);
|
|
7061
|
+
}
|
|
7062
|
+
/**
|
|
7063
|
+
* Push a materialized batch with the durable commit as its parent. On a 409,
|
|
7064
|
+
* refresh Hub state, replay the batch, and retry with the new parent. A push
|
|
7065
|
+
* response without a trustworthy hash is confirmed by a pull before callers
|
|
7066
|
+
* are allowed to observe it as durable.
|
|
7067
|
+
*/
|
|
7068
|
+
async pushBatch(batch) {
|
|
7069
|
+
for (let attempt = 0;; attempt += 1) {
|
|
7070
|
+
const payload = {};
|
|
7071
|
+
for (const [path, content] of Object.entries(batch.changes)) payload[path] = content === null ? null : {
|
|
7072
|
+
type: "file",
|
|
7073
|
+
content
|
|
7074
|
+
};
|
|
7075
|
+
let url;
|
|
7076
|
+
try {
|
|
7077
|
+
url = await this.client.pushAgent(this.identifier, {
|
|
7078
|
+
files: payload,
|
|
7079
|
+
...this.commitHash ? { parentCommit: this.commitHash } : {}
|
|
7080
|
+
});
|
|
7081
|
+
} catch (error) {
|
|
7082
|
+
if (getLangSmithStatus(error) !== 409 || attempt >= MAX_CONFLICT_RETRIES) throw error;
|
|
7083
|
+
const snapshot = await this.fetchTree();
|
|
7084
|
+
this.rematerializeAfterConflict(batch, snapshot, error);
|
|
7085
|
+
continue;
|
|
7086
|
+
}
|
|
7087
|
+
const pushedCommitHash = parseCommitHashFromUrl(url, this.identifier);
|
|
7088
|
+
if (pushedCommitHash === null) {
|
|
7089
|
+
this.beginSnapshotPublication();
|
|
7090
|
+
const snapshot = await this.fetchTree();
|
|
7091
|
+
if (snapshot.commitHash === null) throw new Error("Context Hub commit succeeded but its hash could not be resolved");
|
|
7092
|
+
return {
|
|
7093
|
+
kind: "snapshot",
|
|
7094
|
+
snapshot
|
|
7095
|
+
};
|
|
7096
|
+
}
|
|
7097
|
+
return {
|
|
7098
|
+
kind: "commit",
|
|
7099
|
+
commitHash: pushedCommitHash
|
|
6684
7100
|
};
|
|
6685
7101
|
}
|
|
6686
7102
|
}
|
|
@@ -6808,53 +7224,71 @@ var ContextHubBackend = class ContextHubBackend {
|
|
|
6808
7224
|
async write(filePath, content) {
|
|
6809
7225
|
const hubPath = ContextHubBackend.stripPrefix(filePath);
|
|
6810
7226
|
try {
|
|
6811
|
-
await this.
|
|
6812
|
-
|
|
7227
|
+
const accepted = await this.acceptMutation(() => {
|
|
7228
|
+
return {
|
|
7229
|
+
result: {
|
|
7230
|
+
path: filePath,
|
|
7231
|
+
filesUpdate: null
|
|
7232
|
+
},
|
|
7233
|
+
completion: this.enqueueCommit({ [hubPath]: content })
|
|
7234
|
+
};
|
|
7235
|
+
});
|
|
7236
|
+
await accepted.completion;
|
|
7237
|
+
return accepted.result;
|
|
6813
7238
|
} catch (error) {
|
|
6814
|
-
if (isLangSmithError(error)) {
|
|
6815
|
-
this.cache = null;
|
|
6816
|
-
return { error: ContextHubBackend.toHubUnavailableError(error) };
|
|
6817
|
-
}
|
|
7239
|
+
if (isLangSmithError(error)) return { error: ContextHubBackend.toHubUnavailableError(error) };
|
|
6818
7240
|
throw error;
|
|
6819
7241
|
}
|
|
6820
|
-
return {
|
|
6821
|
-
path: filePath,
|
|
6822
|
-
filesUpdate: null
|
|
6823
|
-
};
|
|
6824
7242
|
}
|
|
6825
7243
|
async edit(filePath, oldString, newString, replaceAll = false) {
|
|
6826
7244
|
const hubPath = ContextHubBackend.stripPrefix(filePath);
|
|
6827
7245
|
try {
|
|
6828
|
-
const
|
|
6829
|
-
|
|
6830
|
-
|
|
6831
|
-
|
|
6832
|
-
|
|
6833
|
-
|
|
6834
|
-
|
|
6835
|
-
|
|
6836
|
-
|
|
6837
|
-
|
|
6838
|
-
|
|
7246
|
+
const accepted = await this.acceptMutation((cache) => {
|
|
7247
|
+
const current = cache[hubPath];
|
|
7248
|
+
if (current === void 0) return { result: { error: `Error: File '${filePath}' not found` } };
|
|
7249
|
+
const replacementResult = performStringReplacement(current, oldString, newString, replaceAll);
|
|
7250
|
+
if (typeof replacementResult === "string") return { result: { error: replacementResult } };
|
|
7251
|
+
const [newContent, occurrences] = replacementResult;
|
|
7252
|
+
const result = {
|
|
7253
|
+
path: filePath,
|
|
7254
|
+
filesUpdate: null,
|
|
7255
|
+
occurrences
|
|
7256
|
+
};
|
|
7257
|
+
return {
|
|
7258
|
+
result,
|
|
7259
|
+
completion: this.enqueueCommit({ [hubPath]: newContent }, {
|
|
7260
|
+
kind: "edit",
|
|
7261
|
+
path: hubPath,
|
|
7262
|
+
oldString,
|
|
7263
|
+
newString,
|
|
7264
|
+
replaceAll,
|
|
7265
|
+
updateOccurrences: (replayedOccurrences) => {
|
|
7266
|
+
result.occurrences = replayedOccurrences;
|
|
7267
|
+
}
|
|
7268
|
+
})
|
|
7269
|
+
};
|
|
7270
|
+
});
|
|
7271
|
+
await accepted.completion;
|
|
7272
|
+
return accepted.result;
|
|
6839
7273
|
} catch (error) {
|
|
6840
|
-
if (isLangSmithError(error)) {
|
|
6841
|
-
this.cache = null;
|
|
6842
|
-
return { error: ContextHubBackend.toHubUnavailableError(error) };
|
|
6843
|
-
}
|
|
7274
|
+
if (isLangSmithError(error)) return { error: ContextHubBackend.toHubUnavailableError(error) };
|
|
6844
7275
|
throw error;
|
|
6845
7276
|
}
|
|
6846
7277
|
}
|
|
6847
7278
|
async delete(filePath) {
|
|
6848
7279
|
const hubPath = ContextHubBackend.stripPrefix(filePath);
|
|
6849
7280
|
try {
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
|
|
7281
|
+
const accepted = await this.acceptMutation((cache) => {
|
|
7282
|
+
if (!(hubPath in cache)) return { result: { error: `Error: File '${filePath}' not found` } };
|
|
7283
|
+
return {
|
|
7284
|
+
result: { path: filePath },
|
|
7285
|
+
completion: this.enqueueCommit({ [hubPath]: null })
|
|
7286
|
+
};
|
|
7287
|
+
});
|
|
7288
|
+
await accepted.completion;
|
|
7289
|
+
return accepted.result;
|
|
6853
7290
|
} catch (error) {
|
|
6854
|
-
if (isLangSmithError(error)) {
|
|
6855
|
-
this.cache = null;
|
|
6856
|
-
return { error: ContextHubBackend.toHubUnavailableError(error) };
|
|
6857
|
-
}
|
|
7291
|
+
if (isLangSmithError(error)) return { error: ContextHubBackend.toHubUnavailableError(error) };
|
|
6858
7292
|
throw error;
|
|
6859
7293
|
}
|
|
6860
7294
|
}
|
|
@@ -6871,13 +7305,15 @@ var ContextHubBackend = class ContextHubBackend {
|
|
|
6871
7305
|
}
|
|
6872
7306
|
let commitError = null;
|
|
6873
7307
|
if (Object.keys(validFiles).length > 0) try {
|
|
6874
|
-
await this.
|
|
6875
|
-
|
|
7308
|
+
await (await this.acceptMutation(() => {
|
|
7309
|
+
return {
|
|
7310
|
+
result: null,
|
|
7311
|
+
completion: this.enqueueCommit(validFiles)
|
|
7312
|
+
};
|
|
7313
|
+
})).completion;
|
|
6876
7314
|
} catch (error) {
|
|
6877
|
-
if (isLangSmithError(error))
|
|
6878
|
-
|
|
6879
|
-
commitError = mapHubFileOperationError(error);
|
|
6880
|
-
} else throw error;
|
|
7315
|
+
if (isLangSmithError(error)) commitError = mapHubFileOperationError(error);
|
|
7316
|
+
else throw error;
|
|
6881
7317
|
}
|
|
6882
7318
|
return decoded.map(([path, text]) => {
|
|
6883
7319
|
if (text === null) return {
|
|
@@ -7873,4 +8309,4 @@ Object.defineProperty(exports, "serializeProfile", {
|
|
|
7873
8309
|
}
|
|
7874
8310
|
});
|
|
7875
8311
|
|
|
7876
|
-
//# sourceMappingURL=langsmith-
|
|
8312
|
+
//# sourceMappingURL=langsmith-BJ2PdYqB.cjs.map
|