veryfront 0.1.614 → 0.1.616
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/esm/cli/templates/manifest.d.ts +1 -0
- package/esm/cli/templates/manifest.js +2 -1
- package/esm/deno.d.ts +3 -0
- package/esm/deno.js +4 -1
- package/esm/src/agent/hosted/child-stream-watchdog.d.ts.map +1 -1
- package/esm/src/agent/hosted/child-stream-watchdog.js +47 -29
- package/esm/src/agent/hosted/cloud-prepared-chat-execution-runtime.d.ts +9 -0
- package/esm/src/agent/hosted/cloud-prepared-chat-execution-runtime.d.ts.map +1 -1
- package/esm/src/agent/hosted/cloud-prepared-chat-execution-runtime.js +26 -1
- package/esm/src/agent/hosted/stream-terminal-error.d.ts.map +1 -1
- package/esm/src/agent/hosted/stream-terminal-error.js +44 -0
- package/esm/src/agent/identity-contracts.d.ts +42 -0
- package/esm/src/agent/identity-contracts.d.ts.map +1 -0
- package/esm/src/agent/identity-contracts.js +25 -0
- package/esm/src/agent/index.d.ts +1 -0
- package/esm/src/agent/index.d.ts.map +1 -1
- package/esm/src/agent/index.js +1 -0
- package/esm/src/chat/message-prep.d.ts.map +1 -1
- package/esm/src/chat/message-prep.js +16 -2
- package/esm/src/integrations/_data.js +1 -1
- package/esm/src/integrations/_tool_summaries.d.ts.map +1 -1
- package/esm/src/integrations/_tool_summaries.js +197 -4
- package/esm/src/integrations/schema.d.ts +8 -0
- package/esm/src/integrations/schema.d.ts.map +1 -1
- package/esm/src/integrations/schema.js +2 -1
- package/esm/src/oauth/providers/base.d.ts +9 -0
- package/esm/src/oauth/providers/base.d.ts.map +1 -1
- package/esm/src/oauth/providers/base.js +23 -3
- package/esm/src/platform/adapters/redis/deno.d.ts +1 -0
- package/esm/src/platform/adapters/redis/deno.d.ts.map +1 -1
- package/esm/src/platform/adapters/redis/deno.js +5 -0
- package/esm/src/platform/adapters/redis/interface.d.ts +5 -0
- package/esm/src/platform/adapters/redis/interface.d.ts.map +1 -1
- package/esm/src/platform/adapters/redis/node.d.ts +1 -0
- package/esm/src/platform/adapters/redis/node.d.ts.map +1 -1
- package/esm/src/platform/adapters/redis/node.js +5 -0
- package/esm/src/platform/adapters/redis/types.d.ts +5 -0
- package/esm/src/platform/adapters/redis/types.d.ts.map +1 -1
- package/esm/src/provider/local/local-engine.d.ts +32 -0
- package/esm/src/provider/local/local-engine.d.ts.map +1 -1
- package/esm/src/provider/local/local-engine.js +66 -9
- package/esm/src/provider/runtime-loader/provider-usage.d.ts.map +1 -1
- package/esm/src/provider/runtime-loader/provider-usage.js +12 -1
- package/esm/src/rendering/rsc/server-renderer/tree-processor.js +5 -5
- package/esm/src/routing/api/api-route-matcher.d.ts.map +1 -1
- package/esm/src/routing/api/api-route-matcher.js +3 -2
- package/esm/src/routing/matchers/decode-param.d.ts +9 -0
- package/esm/src/routing/matchers/decode-param.d.ts.map +1 -0
- package/esm/src/routing/matchers/decode-param.js +17 -0
- package/esm/src/routing/matchers/route-matcher.d.ts.map +1 -1
- package/esm/src/routing/matchers/route-matcher.js +3 -4
- package/esm/src/routing/slug-mapper/dynamic-route-matcher.d.ts.map +1 -1
- package/esm/src/routing/slug-mapper/dynamic-route-matcher.js +8 -2
- package/esm/src/server/node-handler.d.ts.map +1 -1
- package/esm/src/server/node-handler.js +40 -5
- package/esm/src/tool/factory.d.ts.map +1 -1
- package/esm/src/tool/factory.js +4 -3
- package/esm/src/utils/version-constant.d.ts +1 -1
- package/esm/src/utils/version-constant.js +1 -1
- package/esm/src/workflow/backends/redis/index.d.ts +2 -0
- package/esm/src/workflow/backends/redis/index.d.ts.map +1 -1
- package/esm/src/workflow/backends/redis/index.js +39 -6
- package/esm/src/workflow/executor/dag/index.d.ts.map +1 -1
- package/esm/src/workflow/executor/dag/index.js +30 -5
- package/package.json +5 -1
|
@@ -18,6 +18,70 @@ import { throwIfLocalAIDisabled } from "./env.js";
|
|
|
18
18
|
const logger = serverLogger.component("local-llm");
|
|
19
19
|
/** Default maximum new tokens for local model generation */
|
|
20
20
|
const DEFAULT_MAX_NEW_TOKENS = 512;
|
|
21
|
+
/**
|
|
22
|
+
* Build a StoppingCriteriaList that halts generation as soon as any of the
|
|
23
|
+
* provided stop strings appears in the *newly generated* output.
|
|
24
|
+
*
|
|
25
|
+
* Transformers.js (>=3.x) does not expose a `stop_strings` generate option,
|
|
26
|
+
* so we decode the running sequence with the tokenizer and match the stop
|
|
27
|
+
* strings against the suffix — the documented `stopping_criteria` mechanism.
|
|
28
|
+
*
|
|
29
|
+
* Transformers.js passes the full sequence (prompt + generated tokens) to
|
|
30
|
+
* `_call` on every step. We must scan only the generated suffix: if a system
|
|
31
|
+
* or user message contains a configured stop string (e.g. an instruction that
|
|
32
|
+
* mentions "END"), scanning the whole sequence would return `true` on the very
|
|
33
|
+
* first generation step and truncate the response to empty.
|
|
34
|
+
*
|
|
35
|
+
* The prompt token length is not cheaply known where this list is built (the
|
|
36
|
+
* pipeline tokenizes `messages` internally), so per batch item we self-calibrate
|
|
37
|
+
* on the first `_call`: the sequence length seen on the first invocation is
|
|
38
|
+
* recorded as the prompt boundary, and only tokens at or after that boundary are
|
|
39
|
+
* decoded and matched on subsequent steps.
|
|
40
|
+
*/
|
|
41
|
+
function buildStopStringCriteria(transformers, tokenizer, stopSequences) {
|
|
42
|
+
const list = new transformers.StoppingCriteriaList();
|
|
43
|
+
const base = new transformers.StoppingCriteria();
|
|
44
|
+
const criterion = base;
|
|
45
|
+
// Per-batch-item prompt token length, captured on the first invocation.
|
|
46
|
+
const promptLengths = [];
|
|
47
|
+
criterion._call = (inputIds) => inputIds.map((ids, item) => {
|
|
48
|
+
if (promptLengths[item] === undefined) {
|
|
49
|
+
// First step for this item: everything seen so far is prompt. Record the
|
|
50
|
+
// boundary and never trip on the prompt itself.
|
|
51
|
+
promptLengths[item] = ids.length;
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
const generated = ids.slice(promptLengths[item]);
|
|
55
|
+
if (generated.length === 0)
|
|
56
|
+
return false;
|
|
57
|
+
const text = tokenizer.decode(generated);
|
|
58
|
+
return stopSequences.some((stop) => stop.length > 0 && text.includes(stop));
|
|
59
|
+
});
|
|
60
|
+
list.push(criterion);
|
|
61
|
+
return list;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Translate engine-level {@link GenerateOptions} into the options object passed
|
|
65
|
+
* to the Transformers.js text-generation pipeline.
|
|
66
|
+
*
|
|
67
|
+
* Exported for unit testing the option-forwarding seam (notably that
|
|
68
|
+
* `stopSequences` is not silently dropped) without downloading a model.
|
|
69
|
+
*/
|
|
70
|
+
export function buildPipeOptions(options, transformers, tokenizer, streamer) {
|
|
71
|
+
const { maxNewTokens = DEFAULT_MAX_NEW_TOKENS, temperature = 0.7, topP, topK, stopSequences, } = options;
|
|
72
|
+
const pipeOptions = {
|
|
73
|
+
max_new_tokens: maxNewTokens,
|
|
74
|
+
temperature,
|
|
75
|
+
top_p: topP,
|
|
76
|
+
top_k: topK,
|
|
77
|
+
do_sample: temperature > 0,
|
|
78
|
+
streamer,
|
|
79
|
+
};
|
|
80
|
+
if (stopSequences && stopSequences.length > 0) {
|
|
81
|
+
pipeOptions.stopping_criteria = buildStopStringCriteria(transformers, tokenizer, stopSequences);
|
|
82
|
+
}
|
|
83
|
+
return pipeOptions;
|
|
84
|
+
}
|
|
21
85
|
/** Cached pipeline instances keyed by HuggingFace model ID */
|
|
22
86
|
const pipelineCache = new Map();
|
|
23
87
|
/** Whether a model is currently being loaded (prevents concurrent loads) */
|
|
@@ -132,7 +196,6 @@ export async function* generateStream(modelId, messages, options = {}) {
|
|
|
132
196
|
const modelInfo = resolveLocalModel(modelId);
|
|
133
197
|
const pipe = await loadPipeline(modelInfo);
|
|
134
198
|
const transformers = await getTransformers();
|
|
135
|
-
const { maxNewTokens = DEFAULT_MAX_NEW_TOKENS, temperature = 0.7, topP, topK, } = options;
|
|
136
199
|
// Use a queue to bridge TextStreamer callbacks → async generator
|
|
137
200
|
const tokenQueue = [];
|
|
138
201
|
let resolveWaiting = null;
|
|
@@ -151,17 +214,11 @@ export async function* generateStream(modelId, messages, options = {}) {
|
|
|
151
214
|
flushWaiting();
|
|
152
215
|
},
|
|
153
216
|
});
|
|
217
|
+
const pipeOptions = buildPipeOptions(options, transformers, pipe.tokenizer, streamer);
|
|
154
218
|
// Start generation in the background
|
|
155
219
|
const generatePromise = (async () => {
|
|
156
220
|
try {
|
|
157
|
-
await pipe(messages,
|
|
158
|
-
max_new_tokens: maxNewTokens,
|
|
159
|
-
temperature,
|
|
160
|
-
top_p: topP,
|
|
161
|
-
top_k: topK,
|
|
162
|
-
do_sample: temperature > 0,
|
|
163
|
-
streamer,
|
|
164
|
-
});
|
|
221
|
+
await pipe(messages, pipeOptions);
|
|
165
222
|
}
|
|
166
223
|
finally {
|
|
167
224
|
done = true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider-usage.d.ts","sourceRoot":"","sources":["../../../../src/src/provider/runtime-loader/provider-usage.ts"],"names":[],"mappings":"AAEA,6CAA6C;AAC7C,MAAM,MAAM,YAAY,GAAG;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,YAAY,GAAG,SAAS,CAsBhF;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,YAAY,GAAG,SAAS,CAoB7E;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,YAAY,GAAG,SAAS,CAmB7E;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,OAAO,GAAG,YAAY,GAAG,SAAS,CAwBtF;AAED,qCAAqC;AACrC,wBAAgB,UAAU,CACxB,OAAO,EAAE,YAAY,GAAG,SAAS,EACjC,IAAI,EAAE,YAAY,GAAG,SAAS,GAC7B,YAAY,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"provider-usage.d.ts","sourceRoot":"","sources":["../../../../src/src/provider/runtime-loader/provider-usage.ts"],"names":[],"mappings":"AAEA,6CAA6C;AAC7C,MAAM,MAAM,YAAY,GAAG;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,YAAY,GAAG,SAAS,CAsBhF;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,YAAY,GAAG,SAAS,CAoB7E;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,YAAY,GAAG,SAAS,CAmB7E;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,OAAO,GAAG,YAAY,GAAG,SAAS,CAwBtF;AAED,qCAAqC;AACrC,wBAAgB,UAAU,CACxB,OAAO,EAAE,YAAY,GAAG,SAAS,EACjC,IAAI,EAAE,YAAY,GAAG,SAAS,GAC7B,YAAY,GAAG,SAAS,CAkC1B"}
|
|
@@ -100,10 +100,21 @@ export function mergeUsage(current, next) {
|
|
|
100
100
|
const cacheCreationInputTokens = next.cacheCreationInputTokens ??
|
|
101
101
|
current.cacheCreationInputTokens;
|
|
102
102
|
const cacheReadInputTokens = next.cacheReadInputTokens ?? current.cacheReadInputTokens;
|
|
103
|
+
// Prefer the provider-reported total (latest non-undefined wins, matching the
|
|
104
|
+
// ?? semantics used for input/output above). Providers like Gemini 2.5
|
|
105
|
+
// thinking models and OpenAI reasoning models report a total that exceeds
|
|
106
|
+
// input + output because it includes reasoning/thoughts tokens. Recomputing
|
|
107
|
+
// the sum would discard those, undercounting usage. Take the larger of the
|
|
108
|
+
// provider total and the recomputed sum so we never undercount.
|
|
109
|
+
const reportedTotal = next.totalTokens ?? current.totalTokens;
|
|
110
|
+
const recomputedTotal = (inputTokens ?? 0) + (outputTokens ?? 0);
|
|
111
|
+
const totalTokens = reportedTotal !== undefined
|
|
112
|
+
? Math.max(reportedTotal, recomputedTotal)
|
|
113
|
+
: recomputedTotal;
|
|
103
114
|
return {
|
|
104
115
|
inputTokens,
|
|
105
116
|
outputTokens,
|
|
106
|
-
totalTokens
|
|
117
|
+
totalTokens,
|
|
107
118
|
...(cacheCreationInputTokens !== undefined ? { cacheCreationInputTokens } : {}),
|
|
108
119
|
...(cacheReadInputTokens !== undefined ? { cacheReadInputTokens } : {}),
|
|
109
120
|
};
|
|
@@ -2,7 +2,7 @@ import * as React from "../../../../react/react.js";
|
|
|
2
2
|
import { serverLogger } from "../../../utils/index.js";
|
|
3
3
|
import { renderToStringAdapter } from "../../../react/index.js";
|
|
4
4
|
import { getComponentId, isClientComponent, registerClientRef, } from "./component-detector.js";
|
|
5
|
-
import { renderAttributes, treeToHTML } from "./html-generator.js";
|
|
5
|
+
import { escapeHtml, renderAttributes, treeToHTML } from "./html-generator.js";
|
|
6
6
|
import { serializeProps } from "./prop-serializer.js";
|
|
7
7
|
const logger = serverLogger.component("rsc");
|
|
8
8
|
/** Recursively renders a component tree to RSC nodes */
|
|
@@ -10,13 +10,13 @@ export async function renderTree(
|
|
|
10
10
|
// deno-lint-ignore no-explicit-any -- RSC components accept arbitrary props at runtime
|
|
11
11
|
Component, props, clientManifest, clientRefs) {
|
|
12
12
|
if (Component == null || typeof Component === "string" || typeof Component === "number") {
|
|
13
|
-
return { type: "html", html: Component == null ? "" : String(Component) };
|
|
13
|
+
return { type: "html", html: Component == null ? "" : escapeHtml(String(Component)) };
|
|
14
14
|
}
|
|
15
15
|
if (React.isValidElement(Component)) {
|
|
16
16
|
return processElement(Component, clientManifest, clientRefs);
|
|
17
17
|
}
|
|
18
18
|
if (typeof Component !== "function") {
|
|
19
|
-
return { type: "html", html: String(Component) };
|
|
19
|
+
return { type: "html", html: escapeHtml(String(Component)) };
|
|
20
20
|
}
|
|
21
21
|
const rscComponent = Component;
|
|
22
22
|
if (isClientComponent(rscComponent, clientManifest)) {
|
|
@@ -36,7 +36,7 @@ Component, props, clientManifest, clientRefs) {
|
|
|
36
36
|
return { type: "html", html: "" };
|
|
37
37
|
if (React.isValidElement(element))
|
|
38
38
|
return processElement(element, clientManifest, clientRefs);
|
|
39
|
-
return { type: "html", html: String(element) };
|
|
39
|
+
return { type: "html", html: escapeHtml(String(element)) };
|
|
40
40
|
}
|
|
41
41
|
catch (error) {
|
|
42
42
|
logger.error("Error rendering component:", error);
|
|
@@ -76,6 +76,6 @@ export function renderChildren(children, clientManifest, clientRefs) {
|
|
|
76
76
|
if (React.isValidElement(child)) {
|
|
77
77
|
return processElement(child, clientManifest, clientRefs);
|
|
78
78
|
}
|
|
79
|
-
return Promise.resolve({ type: "html", html: String(child) });
|
|
79
|
+
return Promise.resolve({ type: "html", html: escapeHtml(String(child)) });
|
|
80
80
|
}));
|
|
81
81
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-route-matcher.d.ts","sourceRoot":"","sources":["../../../../src/src/routing/api/api-route-matcher.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"api-route-matcher.d.ts","sourceRoot":"","sources":["../../../../src/src/routing/api/api-route-matcher.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAW9D,YAAY,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;AAElC,mDAAmD;AACnD,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,KAAK,CAAC;IACb,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,UAAU,EAAE,OAAO,CAAC;CACrB;AAED;;;;GAIG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,OAAO,CAAsC;IACrD,OAAO,CAAC,UAAU,CAAsC;;IAUxD,wCAAwC;IACxC,IAAI,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAEpC;IAED,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAkD7C,OAAO,CAAC,iBAAiB;IAKzB,OAAO,CAAC,oBAAoB;IAgB5B,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI;IAoBtC,OAAO,CAAC,aAAa;IAiCrB,UAAU,IAAI,KAAK,EAAE;IAIrB,KAAK,IAAI,IAAI;IAKb,UAAU,IAAI,IAAI;IAIlB,OAAO,IAAI,IAAI;CAGhB"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as dntShim from "../../../_dnt.shims.js";
|
|
2
2
|
import { getDisableLruIntervalEnv } from "../../config/env.js";
|
|
3
3
|
import { LRUCache } from "../../utils/lru-wrapper.js";
|
|
4
|
+
import { safeDecodeParam } from "../matchers/decode-param.js";
|
|
4
5
|
/** Max entries in the route-match LRU cache */
|
|
5
6
|
const ROUTE_CACHE_MAX_ENTRIES = 500;
|
|
6
7
|
/** Time-to-live for cached route matches (5 minutes) */
|
|
@@ -121,10 +122,10 @@ export class ApiRouteMatcher {
|
|
|
121
122
|
const value = match[i + 1];
|
|
122
123
|
if (catchAllParamNames.has(paramName)) {
|
|
123
124
|
const segments = value ? value.split("/").filter((segment) => segment.length > 0) : [];
|
|
124
|
-
params[paramName] = segments.map((segment) =>
|
|
125
|
+
params[paramName] = segments.map((segment) => safeDecodeParam(segment));
|
|
125
126
|
continue;
|
|
126
127
|
}
|
|
127
|
-
params[paramName] =
|
|
128
|
+
params[paramName] = safeDecodeParam(value ?? "");
|
|
128
129
|
}
|
|
129
130
|
return params;
|
|
130
131
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decode a single URL path segment, tolerating malformed percent-encoding.
|
|
3
|
+
*
|
|
4
|
+
* `decodeURIComponent` throws `URIError` on invalid sequences (e.g. `%zz`).
|
|
5
|
+
* For route param extraction we prefer to fall back to the raw value so a
|
|
6
|
+
* malformed request yields a normal route miss / 4xx instead of crashing.
|
|
7
|
+
*/
|
|
8
|
+
export declare function safeDecodeParam(value: string): string;
|
|
9
|
+
//# sourceMappingURL=decode-param.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decode-param.d.ts","sourceRoot":"","sources":["../../../../src/src/routing/matchers/decode-param.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOrD"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decode a single URL path segment, tolerating malformed percent-encoding.
|
|
3
|
+
*
|
|
4
|
+
* `decodeURIComponent` throws `URIError` on invalid sequences (e.g. `%zz`).
|
|
5
|
+
* For route param extraction we prefer to fall back to the raw value so a
|
|
6
|
+
* malformed request yields a normal route miss / 4xx instead of crashing.
|
|
7
|
+
*/
|
|
8
|
+
export function safeDecodeParam(value) {
|
|
9
|
+
try {
|
|
10
|
+
return decodeURIComponent(value);
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
if (error instanceof URIError)
|
|
14
|
+
return value;
|
|
15
|
+
throw error;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-matcher.d.ts","sourceRoot":"","sources":["../../../../src/src/routing/matchers/route-matcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"route-matcher.d.ts","sourceRoot":"","sources":["../../../../src/src/routing/matchers/route-matcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AA0BpD,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,UAAU,GAAG,IAAI,CAa5E"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { safeDecodeParam } from "./decode-param.js";
|
|
1
2
|
const CATCH_ALL_PATTERN = /\[\[?\.\.\.(\w+)\]\]?/g;
|
|
2
3
|
function extractCatchAllParams(pattern) {
|
|
3
4
|
const params = new Set();
|
|
@@ -16,7 +17,7 @@ function decodeCatchAllValue(value) {
|
|
|
16
17
|
return value
|
|
17
18
|
.split("/")
|
|
18
19
|
.filter(Boolean)
|
|
19
|
-
.map((segment) =>
|
|
20
|
+
.map((segment) => safeDecodeParam(segment));
|
|
20
21
|
}
|
|
21
22
|
export function matchRoute(pathname, route) {
|
|
22
23
|
const match = pathname.match(route.regex);
|
|
@@ -26,9 +27,7 @@ export function matchRoute(pathname, route) {
|
|
|
26
27
|
const params = {};
|
|
27
28
|
for (const [index, name] of (route.paramNames ?? []).entries()) {
|
|
28
29
|
const value = match[index + 1] ?? "";
|
|
29
|
-
params[name] = catchAllParams.has(name)
|
|
30
|
-
? decodeCatchAllValue(value)
|
|
31
|
-
: decodeURIComponent(value);
|
|
30
|
+
params[name] = catchAllParams.has(name) ? decodeCatchAllValue(value) : safeDecodeParam(value);
|
|
32
31
|
}
|
|
33
32
|
return { params, route };
|
|
34
33
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-route-matcher.d.ts","sourceRoot":"","sources":["../../../../src/src/routing/slug-mapper/dynamic-route-matcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"dynamic-route-matcher.d.ts","sourceRoot":"","sources":["../../../../src/src/routing/slug-mapper/dynamic-route-matcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAkBjE,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAsC/E;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAErE"}
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
export { isDynamicRoute } from "../../utils/route-path-utils.js";
|
|
2
|
+
function isOptionalSpreadParam(part) {
|
|
3
|
+
return part.startsWith("[[...") && part.endsWith("]]");
|
|
4
|
+
}
|
|
2
5
|
function isSpreadParam(part) {
|
|
3
|
-
return part.startsWith("[...") && part.endsWith("]");
|
|
6
|
+
return isOptionalSpreadParam(part) || (part.startsWith("[...") && part.endsWith("]"));
|
|
4
7
|
}
|
|
5
8
|
function isDynamicParam(part) {
|
|
6
9
|
return part.startsWith("[") && part.endsWith("]");
|
|
7
10
|
}
|
|
11
|
+
function spreadParamName(part) {
|
|
12
|
+
return isOptionalSpreadParam(part) ? part.slice(5, -2) : part.slice(4, -1);
|
|
13
|
+
}
|
|
8
14
|
export function extractParams(pattern, slug) {
|
|
9
15
|
const patternParts = pattern.split("/").filter(Boolean);
|
|
10
16
|
const slugParts = slug.split("/").filter(Boolean);
|
|
@@ -16,7 +22,7 @@ export function extractParams(pattern, slug) {
|
|
|
16
22
|
let slugIndex = 0;
|
|
17
23
|
for (const patternPart of patternParts) {
|
|
18
24
|
if (isSpreadParam(patternPart)) {
|
|
19
|
-
const paramName = patternPart
|
|
25
|
+
const paramName = spreadParamName(patternPart);
|
|
20
26
|
params[paramName] = slugParts.slice(slugIndex);
|
|
21
27
|
return params;
|
|
22
28
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-handler.d.ts","sourceRoot":"","sources":["../../../src/src/server/node-handler.ts"],"names":[],"mappings":"AAEA,sEAAsE;AACtE,wBAAgB,aAAa,CAC3B,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,GACtD,CAAC,GAAG,EAAE,OAAO,WAAW,EAAE,eAAe,EAAE,GAAG,EAAE,OAAO,WAAW,EAAE,cAAc,KAAK,IAAI,
|
|
1
|
+
{"version":3,"file":"node-handler.d.ts","sourceRoot":"","sources":["../../../src/src/server/node-handler.ts"],"names":[],"mappings":"AAEA,sEAAsE;AACtE,wBAAgB,aAAa,CAC3B,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,GACtD,CAAC,GAAG,EAAE,OAAO,WAAW,EAAE,eAAe,EAAE,GAAG,EAAE,OAAO,WAAW,EAAE,cAAc,KAAK,IAAI,CAqE7F"}
|
|
@@ -4,12 +4,14 @@ export function toNodeHandler(handler) {
|
|
|
4
4
|
return async (req, res) => {
|
|
5
5
|
try {
|
|
6
6
|
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
7
|
-
const headers =
|
|
7
|
+
const headers = new Headers();
|
|
8
8
|
for (const [key, value] of Object.entries(req.headers)) {
|
|
9
9
|
if (typeof value === "string")
|
|
10
|
-
headers
|
|
11
|
-
else if (Array.isArray(value))
|
|
12
|
-
|
|
10
|
+
headers.append(key, value);
|
|
11
|
+
else if (Array.isArray(value)) {
|
|
12
|
+
for (const entry of value)
|
|
13
|
+
headers.append(key, entry);
|
|
14
|
+
}
|
|
13
15
|
}
|
|
14
16
|
const method = req.method ?? "GET";
|
|
15
17
|
const body = method === "GET" || method === "HEAD" ? null : req;
|
|
@@ -23,7 +25,40 @@ export function toNodeHandler(handler) {
|
|
|
23
25
|
const response = await handler(new Request(url.toString(), init));
|
|
24
26
|
if (response.status === 101)
|
|
25
27
|
return;
|
|
26
|
-
|
|
28
|
+
const outHeaders = {};
|
|
29
|
+
const setCookies = [];
|
|
30
|
+
// Headers.prototype.getSetCookie landed in Node ~18.14, but our published
|
|
31
|
+
// engines.node is ">=18.0.0". On early 18.x it is undefined, so calling it
|
|
32
|
+
// unconditionally throws for every response and turns valid requests into
|
|
33
|
+
// 500s. Feature-detect it and fall back to the header iterator.
|
|
34
|
+
const getSetCookie = response.headers.getSetCookie;
|
|
35
|
+
if (typeof getSetCookie === "function") {
|
|
36
|
+
// Modern path: getSetCookie returns each Set-Cookie as a distinct value.
|
|
37
|
+
setCookies.push(...getSetCookie.call(response.headers));
|
|
38
|
+
for (const [key, value] of response.headers) {
|
|
39
|
+
if (key.toLowerCase() === "set-cookie")
|
|
40
|
+
continue;
|
|
41
|
+
outHeaders[key] = value;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
// Fallback for runtimes without getSetCookie. The undici-based Headers
|
|
46
|
+
// iterator yields each Set-Cookie as its own entry (it is the one header
|
|
47
|
+
// that is NOT comma-joined during iteration), so iterating preserves
|
|
48
|
+
// multiples where the platform allows it. If a runtime does collapse
|
|
49
|
+
// them into a single comma-joined string we still pass that one value
|
|
50
|
+
// through unchanged rather than throwing — degrade gracefully, never 500.
|
|
51
|
+
for (const [key, value] of response.headers) {
|
|
52
|
+
if (key.toLowerCase() === "set-cookie") {
|
|
53
|
+
setCookies.push(value);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
outHeaders[key] = value;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (setCookies.length > 0)
|
|
60
|
+
outHeaders["Set-Cookie"] = setCookies;
|
|
61
|
+
res.writeHead(response.status, outHeaders);
|
|
27
62
|
if (response.body) {
|
|
28
63
|
const reader = response.body.getReader();
|
|
29
64
|
while (true) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../src/src/tool/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AACzE,OAAO,KAAK,EAAE,UAAU,EAAU,MAAM,+BAA+B,CAAC;AAyJxE,sCAAsC;AACtC,wBAAgB,IAAI,CAAC,MAAM,GAAG,OAAO,EAAE,OAAO,GAAG,OAAO,EACtD,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,GAClC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../src/src/tool/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AACzE,OAAO,KAAK,EAAE,UAAU,EAAU,MAAM,+BAA+B,CAAC;AAyJxE,sCAAsC;AACtC,wBAAgB,IAAI,CAAC,MAAM,GAAG,OAAO,EAAE,OAAO,GAAG,OAAO,EACtD,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,GAClC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAgDvB;AAED,0CAA0C;AAC1C,MAAM,WAAW,iBAAiB;IAChC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB,eAAe,CAAC,EAAE,UAAU,CAAC;IAC7B,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,oBAAoB,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACxF,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC;IAC7C,GAAG,CAAC,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;CACzB;AAED,wCAAwC;AACxC,wBAAgB,WAAW,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CA4B7E"}
|
package/esm/src/tool/factory.js
CHANGED
|
@@ -102,9 +102,10 @@ export function tool(config) {
|
|
|
102
102
|
outputSchema: config.outputSchema,
|
|
103
103
|
outputSchemaJson,
|
|
104
104
|
execute: async (input, context) => {
|
|
105
|
+
let validated = input;
|
|
105
106
|
if (hasSchemaParse(config.inputSchema)) {
|
|
106
107
|
try {
|
|
107
|
-
config.inputSchema.parse(input);
|
|
108
|
+
validated = config.inputSchema.parse(input);
|
|
108
109
|
}
|
|
109
110
|
catch (error) {
|
|
110
111
|
throw toError(createError({
|
|
@@ -113,7 +114,7 @@ export function tool(config) {
|
|
|
113
114
|
}));
|
|
114
115
|
}
|
|
115
116
|
}
|
|
116
|
-
return await config.execute(
|
|
117
|
+
return await config.execute(validated, context);
|
|
117
118
|
},
|
|
118
119
|
mcp: config.mcp,
|
|
119
120
|
};
|
|
@@ -133,7 +134,7 @@ export function dynamicTool(config) {
|
|
|
133
134
|
inputSchemaJson,
|
|
134
135
|
execute: async (input, context) => {
|
|
135
136
|
if (hasSchemaParse(config.inputSchema)) {
|
|
136
|
-
config.inputSchema.parse(input);
|
|
137
|
+
input = config.inputSchema.parse(input);
|
|
137
138
|
}
|
|
138
139
|
else if (input === undefined) {
|
|
139
140
|
input = {};
|
|
@@ -9,6 +9,8 @@ export declare class RedisBackend implements WorkflowBackend {
|
|
|
9
9
|
private connectionPromise;
|
|
10
10
|
private config;
|
|
11
11
|
private initialized;
|
|
12
|
+
/** Per-run lock tokens for ownership-checked release/extend (Redlock pattern). */
|
|
13
|
+
private lockValues;
|
|
12
14
|
constructor(config?: RedisBackendConfig);
|
|
13
15
|
private runKey;
|
|
14
16
|
private checkpointsKey;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/src/workflow/backends/redis/index.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,gBAAgB,EAChB,UAAU,EACV,eAAe,EACf,SAAS,EACT,iBAAiB,EACjB,WAAW,EAEZ,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAYnD,YAAY,EAAE,YAAY,EAAE,MAAM,2CAA2C,CAAC;AAC9E,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,OAAO,KAAK,EAAE,kBAAkB,EAA8B,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/src/workflow/backends/redis/index.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,gBAAgB,EAChB,UAAU,EACV,eAAe,EACf,SAAS,EACT,iBAAiB,EACjB,WAAW,EAEZ,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAYnD,YAAY,EAAE,YAAY,EAAE,MAAM,2CAA2C,CAAC;AAC9E,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,OAAO,KAAK,EAAE,kBAAkB,EAA8B,MAAM,YAAY,CAAC;AAmBjF,+BAA+B;AAC/B,qBAAa,YAAa,YAAW,eAAe;IAClD,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,iBAAiB,CAAsC;IAC/D,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,WAAW,CAAS;IAC5B,kFAAkF;IAClF,OAAO,CAAC,UAAU,CAA6B;gBAEnC,MAAM,GAAE,kBAAuB;IAa3C,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,YAAY;IAqBpB,OAAO,CAAC,cAAc;IAqEtB,OAAO,CAAC,YAAY;YAcN,gBAAgB;IAiCxB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAoB3B,SAAS,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAY1C,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAUlD,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAmCpE,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBvC,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAuCnD,SAAS,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC;IAK7C,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAWpE,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAS9D,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAUpD,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBlF,OAAO,CAAC,aAAa;IAUf,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAM9D,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAKtF,cAAc,CAClB,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,gBAAgB,GACzB,OAAO,CAAC,IAAI,CAAC;IA8BV,oBAAoB,CAAC,MAAM,CAAC,EAAE;QAClC,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,MAAM,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;KAChC,GAAG,OAAO,CAAC,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,eAAe,CAAA;KAAE,CAAC,CAAC;IAsC1D,OAAO,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAc9C,OAAO,IAAI,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAwBlD,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKnC,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlC,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAa9D,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAezC,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAiB7D,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKzC,eAAe,CAAC,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAWjE,eAAe,CACnB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAAC,OAAO,CAAC;IAmCb,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAW/B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAgB/B"}
|
|
@@ -12,12 +12,25 @@ import { requeueRun } from "../shared/requeue-run.js";
|
|
|
12
12
|
import { INITIALIZATION_ERROR, INVALID_ARGUMENT, RESOURCE_NOT_FOUND } from "../../../errors/index.js";
|
|
13
13
|
import { DenoRedisAdapter, getRedisModule, NodeRedisAdapter, } from "../../../platform/adapters/redis/index.js";
|
|
14
14
|
const logger = agentLogger.component("redis-backend");
|
|
15
|
+
/**
|
|
16
|
+
* Atomic compare-and-delete: delete the lock only if it still holds our token.
|
|
17
|
+
* Server-side Lua (Redis EVAL) so the GET and DEL are one indivisible step,
|
|
18
|
+
* preventing a stale owner from deleting another worker's reacquired lock.
|
|
19
|
+
*/
|
|
20
|
+
const RELEASE_LOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
|
|
21
|
+
/**
|
|
22
|
+
* Atomic compare-and-pexpire: extend the lock TTL only if it still holds our
|
|
23
|
+
* token. Same TOCTOU protection as the release script.
|
|
24
|
+
*/
|
|
25
|
+
const EXTEND_LOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('pexpire', KEYS[1], ARGV[2]) else return 0 end";
|
|
15
26
|
/** Implement redis backend. */
|
|
16
27
|
export class RedisBackend {
|
|
17
28
|
client = null;
|
|
18
29
|
connectionPromise = null;
|
|
19
30
|
config;
|
|
20
31
|
initialized = false;
|
|
32
|
+
/** Per-run lock tokens for ownership-checked release/extend (Redlock pattern). */
|
|
33
|
+
lockValues = new Map();
|
|
21
34
|
constructor(config = {}) {
|
|
22
35
|
this.config = {
|
|
23
36
|
prefix: "vf:workflow:",
|
|
@@ -442,20 +455,40 @@ export class RedisBackend {
|
|
|
442
455
|
const client = await this.ensureClient();
|
|
443
456
|
const lockValue = dntShim.crypto.randomUUID();
|
|
444
457
|
const result = await client.set(this.lockKey(runId), lockValue, { nx: true, px: duration });
|
|
445
|
-
|
|
458
|
+
if (result === "OK") {
|
|
459
|
+
// Remember our token so release/extend can verify ownership (Redlock).
|
|
460
|
+
this.lockValues.set(runId, lockValue);
|
|
461
|
+
return true;
|
|
462
|
+
}
|
|
463
|
+
return false;
|
|
446
464
|
}
|
|
447
465
|
async releaseLock(runId) {
|
|
448
466
|
const client = await this.ensureClient();
|
|
449
|
-
|
|
467
|
+
const key = this.lockKey(runId);
|
|
468
|
+
const ourValue = this.lockValues.get(runId);
|
|
469
|
+
// Only release if we still own the lock (compare-and-delete). Without a
|
|
470
|
+
// known token we never owned it, so do nothing.
|
|
471
|
+
if (ourValue === undefined)
|
|
472
|
+
return;
|
|
473
|
+
// Atomic GET + DEL via Lua so a stale owner cannot delete a lock that was
|
|
474
|
+
// reacquired by another worker between the check and the delete (TOCTOU).
|
|
475
|
+
await client.eval(RELEASE_LOCK_SCRIPT, [key], [ourValue]);
|
|
476
|
+
this.lockValues.delete(runId);
|
|
450
477
|
}
|
|
451
478
|
async extendLock(runId, duration) {
|
|
452
479
|
const client = await this.ensureClient();
|
|
453
480
|
const key = this.lockKey(runId);
|
|
454
|
-
const
|
|
455
|
-
if
|
|
481
|
+
const ourValue = this.lockValues.get(runId);
|
|
482
|
+
// Only extend if we still own the lock (compare-and-pexpire).
|
|
483
|
+
if (ourValue === undefined)
|
|
456
484
|
return false;
|
|
457
|
-
|
|
458
|
-
|
|
485
|
+
// Atomic GET + PEXPIRE via Lua. PEXPIRE returns 1 when the key existed and
|
|
486
|
+
// the TTL was set, 0 otherwise (e.g. our token no longer owns the lock).
|
|
487
|
+
const result = await client.eval(EXTEND_LOCK_SCRIPT, [key], [
|
|
488
|
+
ourValue,
|
|
489
|
+
String(duration),
|
|
490
|
+
]);
|
|
491
|
+
return Number(result) === 1;
|
|
459
492
|
}
|
|
460
493
|
async isLocked(runId) {
|
|
461
494
|
const client = await this.ensureClient();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/src/workflow/executor/dag/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAYV,YAAY,EAEZ,WAAW,EACZ,MAAM,gBAAgB,CAAC;AAIxB,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAE7F,OAAO,KAAK,EACV,kBAAkB,EAClB,iBAAiB,EAGlB,MAAM,YAAY,CAAC;AAIpB,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAA4B;gBAE9B,MAAM,EAAE,iBAAiB;IAQ/B,OAAO,CACX,KAAK,EAAE,YAAY,EAAE,EACrB,GAAG,EAAE,WAAW,EAChB,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,kBAAkB,CAAC;YAsGhB,WAAW;YA8CX,eAAe;YA0Bf,mBAAmB;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/src/workflow/executor/dag/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAYV,YAAY,EAEZ,WAAW,EACZ,MAAM,gBAAgB,CAAC;AAIxB,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAE7F,OAAO,KAAK,EACV,kBAAkB,EAClB,iBAAiB,EAGlB,MAAM,YAAY,CAAC;AAIpB,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAA4B;gBAE9B,MAAM,EAAE,iBAAiB;IAQ/B,OAAO,CACX,KAAK,EAAE,YAAY,EAAE,EACrB,GAAG,EAAE,WAAW,EAChB,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,kBAAkB,CAAC;YAsGhB,WAAW;YA8CX,eAAe;YA0Bf,mBAAmB;YA4CnB,cAAc;YAqGd,iBAAiB;YA+DjB,eAAe;YA8Bf,sBAAsB;YA+DtB,eAAe;YAsKf,UAAU;CAoBzB"}
|
|
@@ -160,7 +160,9 @@ export class DAGExecutor {
|
|
|
160
160
|
workflowId: "",
|
|
161
161
|
status: "running",
|
|
162
162
|
input: context.input,
|
|
163
|
-
|
|
163
|
+
// Carry already-accumulated child states so completed children are
|
|
164
|
+
// skipped on resume instead of re-executing (H8).
|
|
165
|
+
nodeStates,
|
|
164
166
|
currentNodes: [],
|
|
165
167
|
context,
|
|
166
168
|
checkpoints: [],
|
|
@@ -232,7 +234,9 @@ export class DAGExecutor {
|
|
|
232
234
|
workflowId: "",
|
|
233
235
|
status: "running",
|
|
234
236
|
input: context.input,
|
|
235
|
-
|
|
237
|
+
// Carry already-accumulated child states so completed children are
|
|
238
|
+
// skipped on resume instead of re-executing (H8).
|
|
239
|
+
nodeStates,
|
|
236
240
|
currentNodes: [],
|
|
237
241
|
context: { ...context },
|
|
238
242
|
checkpoints: [],
|
|
@@ -281,7 +285,9 @@ export class DAGExecutor {
|
|
|
281
285
|
workflowId: "",
|
|
282
286
|
status: "running",
|
|
283
287
|
input: context.input,
|
|
284
|
-
|
|
288
|
+
// Carry already-accumulated child states so completed children are
|
|
289
|
+
// skipped on resume instead of re-executing (H8).
|
|
290
|
+
nodeStates,
|
|
285
291
|
currentNodes: [],
|
|
286
292
|
context,
|
|
287
293
|
checkpoints: [],
|
|
@@ -384,9 +390,15 @@ export class DAGExecutor {
|
|
|
384
390
|
let exitReason = "condition";
|
|
385
391
|
let lastError;
|
|
386
392
|
const existingLoopState = context[`${node.id}_loop_state`];
|
|
393
|
+
// Child node states for the in-flight (resumed) iteration, so its already
|
|
394
|
+
// completed steps are not re-executed on resume (H9).
|
|
395
|
+
let resumeIterationNodeStates;
|
|
396
|
+
let resumeIteration;
|
|
387
397
|
if (existingLoopState) {
|
|
388
398
|
iteration = existingLoopState.iteration;
|
|
389
399
|
previousResults.push(...existingLoopState.previousResults);
|
|
400
|
+
resumeIterationNodeStates = existingLoopState.iterationNodeStates;
|
|
401
|
+
resumeIteration = existingLoopState.iteration;
|
|
390
402
|
}
|
|
391
403
|
while (iteration < config.maxIterations) {
|
|
392
404
|
const loopContext = {
|
|
@@ -403,12 +415,19 @@ export class DAGExecutor {
|
|
|
403
415
|
const steps = typeof config.steps === "function"
|
|
404
416
|
? config.steps(context, loopContext)
|
|
405
417
|
: config.steps;
|
|
418
|
+
// On resume, rehydrate the in-flight iteration's child node states so its
|
|
419
|
+
// already-completed steps are skipped instead of re-executed (H9).
|
|
420
|
+
const iterationNodeStates = resumeIteration === iteration && resumeIterationNodeStates
|
|
421
|
+
? { ...resumeIterationNodeStates }
|
|
422
|
+
: {};
|
|
423
|
+
// Only rehydrate once; subsequent iterations start fresh.
|
|
424
|
+
resumeIterationNodeStates = undefined;
|
|
406
425
|
const result = await this.execute(steps, {
|
|
407
426
|
id: `${node.id}_iter_${iteration}`,
|
|
408
427
|
workflowId: "",
|
|
409
428
|
status: "running",
|
|
410
429
|
input: context.input,
|
|
411
|
-
nodeStates:
|
|
430
|
+
nodeStates: iterationNodeStates,
|
|
412
431
|
currentNodes: [],
|
|
413
432
|
context: { ...context, _loop: loopContext },
|
|
414
433
|
checkpoints: [],
|
|
@@ -428,7 +447,13 @@ export class DAGExecutor {
|
|
|
428
447
|
state,
|
|
429
448
|
contextUpdates: {
|
|
430
449
|
...result.context,
|
|
431
|
-
[`${node.id}_loop_state`]: {
|
|
450
|
+
[`${node.id}_loop_state`]: {
|
|
451
|
+
iteration,
|
|
452
|
+
previousResults,
|
|
453
|
+
// Persist the in-flight iteration's child states so completed
|
|
454
|
+
// steps are not re-executed when this iteration resumes (H9).
|
|
455
|
+
iterationNodeStates: result.nodeStates,
|
|
456
|
+
},
|
|
432
457
|
},
|
|
433
458
|
waiting: true,
|
|
434
459
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "veryfront",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.616",
|
|
4
4
|
"description": "The simplest way to build AI-powered apps",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -73,6 +73,10 @@
|
|
|
73
73
|
"import": "./esm/src/agent/index.js",
|
|
74
74
|
"types": "./esm/src/agent/index.d.ts"
|
|
75
75
|
},
|
|
76
|
+
"./agent/identity": {
|
|
77
|
+
"import": "./esm/src/agent/identity-contracts.js",
|
|
78
|
+
"types": "./esm/src/agent/identity-contracts.d.ts"
|
|
79
|
+
},
|
|
76
80
|
"./agent/testing": {
|
|
77
81
|
"import": "./esm/src/agent/testing/index.js",
|
|
78
82
|
"types": "./esm/src/agent/testing/index.d.ts"
|