@anvia/studio 0.7.33 → 0.7.35
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/index.d.ts +131 -6
- package/dist/index.js +1152 -85
- package/dist/index.js.map +1 -1
- package/dist/ui/assets/{PathIcon-DX1w_Nk9.js → PathIcon-B7p7vF2d.js} +1 -1
- package/dist/ui/assets/{PathIcon-DX1w_Nk9.js.map → PathIcon-B7p7vF2d.js.map} +1 -1
- package/dist/ui/assets/{PlayIcon-Culan3xL.js → PlayIcon-Dt2gGfiW.js} +1 -1
- package/dist/ui/assets/{PlayIcon-Culan3xL.js.map → PlayIcon-Dt2gGfiW.js.map} +1 -1
- package/dist/ui/assets/{RefreshIcon-B3w42HZJ.js → RefreshIcon-BOrxG7tt.js} +1 -1
- package/dist/ui/assets/{RefreshIcon-B3w42HZJ.js.map → RefreshIcon-BOrxG7tt.js.map} +1 -1
- package/dist/ui/assets/{agents-page-nyn87qWN.js → agents-page-BdDnlVyJ.js} +2 -2
- package/dist/ui/assets/{agents-page-nyn87qWN.js.map → agents-page-BdDnlVyJ.js.map} +1 -1
- package/dist/ui/assets/{badge-BH7gz3tB.js → badge-CeTBFuRX.js} +1 -1
- package/dist/ui/assets/{badge-BH7gz3tB.js.map → badge-CeTBFuRX.js.map} +1 -1
- package/dist/ui/assets/chunk-FE5QL4G3-C1KRmMy_.js +2 -0
- package/dist/ui/assets/chunk-FE5QL4G3-C1KRmMy_.js.map +1 -0
- package/dist/ui/assets/evals-page-1QJywSGG.js +2 -0
- package/dist/ui/assets/{evals-page-DN6aZ9dT.js.map → evals-page-1QJywSGG.js.map} +1 -1
- package/dist/ui/assets/{format-C9HPGfkW.js → format-D43vTqrO.js} +2 -2
- package/dist/ui/assets/format-D43vTqrO.js.map +1 -0
- package/dist/ui/assets/index-BncPUHdf.css +1 -0
- package/dist/ui/assets/index-DsFqmLt-.js +55 -0
- package/dist/ui/assets/index-DsFqmLt-.js.map +1 -0
- package/dist/ui/assets/{knowledge-page-DYwhR8Ly.js → knowledge-page-Bh0HYSnR.js} +2 -2
- package/dist/ui/assets/{knowledge-page-DYwhR8Ly.js.map → knowledge-page-Bh0HYSnR.js.map} +1 -1
- package/dist/ui/assets/{mcps-page-B3e3YF6D.js → mcps-page-BoMVJjHL.js} +2 -2
- package/dist/ui/assets/{mcps-page-B3e3YF6D.js.map → mcps-page-BoMVJjHL.js.map} +1 -1
- package/dist/ui/assets/memory-page-B-LtyVhA.js +4 -0
- package/dist/ui/assets/memory-page-B-LtyVhA.js.map +1 -0
- package/dist/ui/assets/{pipelines-page-DdYD59h4.js → pipelines-page-DeIlIHCP.js} +3 -3
- package/dist/ui/assets/{pipelines-page-DdYD59h4.js.map → pipelines-page-DeIlIHCP.js.map} +1 -1
- package/dist/ui/assets/{renderers-m-gqGocU.js → renderers-IW2ZAJZw.js} +2 -2
- package/dist/ui/assets/{renderers-m-gqGocU.js.map → renderers-IW2ZAJZw.js.map} +1 -1
- package/dist/ui/assets/sandboxes-page-B_Lf-UtP.js +2 -0
- package/dist/ui/assets/sandboxes-page-B_Lf-UtP.js.map +1 -0
- package/dist/ui/assets/{sessions-page-SAmGnFSU.js → sessions-page-DjAuU-RX.js} +2 -2
- package/dist/ui/assets/{sessions-page-SAmGnFSU.js.map → sessions-page-DjAuU-RX.js.map} +1 -1
- package/dist/ui/assets/{status-page-g__90tnX.js → status-page-BaDIKvef.js} +2 -2
- package/dist/ui/assets/{status-page-g__90tnX.js.map → status-page-BaDIKvef.js.map} +1 -1
- package/dist/ui/assets/tool-runner-C5vzRzrt.js +2 -0
- package/dist/ui/assets/{tool-runner-CcqIP2r3.js.map → tool-runner-C5vzRzrt.js.map} +1 -1
- package/dist/ui/assets/{tools-page-By0mx0ua.js → tools-page-ClNVTM2F.js} +2 -2
- package/dist/ui/assets/{tools-page-By0mx0ua.js.map → tools-page-ClNVTM2F.js.map} +1 -1
- package/dist/ui/assets/trace-browser-BQ_YWG4y.js +10 -0
- package/dist/ui/assets/{trace-browser-BDet5qes.js.map → trace-browser-BQ_YWG4y.js.map} +1 -1
- package/dist/ui/assets/transcript-item-CdsVcgad.js +3 -0
- package/dist/ui/assets/transcript-item-CdsVcgad.js.map +1 -0
- package/dist/ui/index.html +4 -3
- package/package.json +4 -4
- package/dist/ui/assets/evals-page-DN6aZ9dT.js +0 -2
- package/dist/ui/assets/format-C9HPGfkW.js.map +0 -1
- package/dist/ui/assets/index-D7w3LxHM.js +0 -55
- package/dist/ui/assets/index-D7w3LxHM.js.map +0 -1
- package/dist/ui/assets/index-Dl647xDC.css +0 -1
- package/dist/ui/assets/memory-page-BeR1NNHY.js +0 -2
- package/dist/ui/assets/memory-page-BeR1NNHY.js.map +0 -1
- package/dist/ui/assets/tool-runner-CcqIP2r3.js +0 -2
- package/dist/ui/assets/trace-browser-BDet5qes.js +0 -10
- package/dist/ui/assets/transcript-item-3F08pehk.js +0 -3
- package/dist/ui/assets/transcript-item-3F08pehk.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -634,6 +634,11 @@ function registerStudioUi(app, options) {
|
|
|
634
634
|
app.get(`${options.path}/tools`, (c) => redirectWithQuery(c, "/tools"));
|
|
635
635
|
app.get(`${options.path}/mcps`, (c) => redirectWithQuery(c, "/mcps"));
|
|
636
636
|
app.get(`${options.path}/pipelines`, (c) => redirectWithQuery(c, "/pipelines"));
|
|
637
|
+
app.get(`${options.path}/sandboxes`, (c) => redirectWithQuery(c, "/sandboxes"));
|
|
638
|
+
app.get(
|
|
639
|
+
`${options.path}/sandboxes/:sandboxRef`,
|
|
640
|
+
(c) => redirectWithQuery(c, `/sandboxes/${encodeURIComponent(c.req.param("sandboxRef"))}`)
|
|
641
|
+
);
|
|
637
642
|
app.get(`${options.path}/evals`, (c) => redirectWithQuery(c, "/evals"));
|
|
638
643
|
app.get(`${options.path}/memory`, (c) => redirectWithQuery(c, "/memory"));
|
|
639
644
|
app.get(`${options.path}/status`, (c) => redirectWithQuery(c, "/status"));
|
|
@@ -654,6 +659,8 @@ function registerStudioUi(app, options) {
|
|
|
654
659
|
app.get("/tools", async (c) => c.html(await renderShell()));
|
|
655
660
|
app.get("/mcps", async (c) => c.html(await renderShell()));
|
|
656
661
|
app.get("/pipelines", shellWhenHtml(renderShell));
|
|
662
|
+
app.get("/sandboxes", shellWhenHtml(renderShell));
|
|
663
|
+
app.get("/sandboxes/:sandboxRef", shellWhenHtml(renderShell));
|
|
657
664
|
app.get("/evals", shellWhenHtml(renderShell));
|
|
658
665
|
app.get("/memory", async (c) => c.html(await renderShell()));
|
|
659
666
|
app.get("/status", shellWhenHtml(renderShell));
|
|
@@ -764,8 +771,8 @@ function renderLegacyStudioUiShell(props) {
|
|
|
764
771
|
"</html>"
|
|
765
772
|
].join("");
|
|
766
773
|
}
|
|
767
|
-
function normalizeUiPath(
|
|
768
|
-
const trimmed =
|
|
774
|
+
function normalizeUiPath(path2) {
|
|
775
|
+
const trimmed = path2.trim();
|
|
769
776
|
const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
770
777
|
const withoutTrailingSlash = withSlash.length > 1 ? withSlash.replace(/\/+$/, "") : withSlash;
|
|
771
778
|
if (withoutTrailingSlash.length === 0 || withoutTrailingSlash === "/") {
|
|
@@ -1808,8 +1815,69 @@ async function* mergeRunAndApprovalEvents(runEvents, approvalEvents) {
|
|
|
1808
1815
|
approvalEvents.close();
|
|
1809
1816
|
}
|
|
1810
1817
|
}
|
|
1811
|
-
function streamAgentRunEvents(_c, events) {
|
|
1812
|
-
return streamStudioJsonl(events);
|
|
1818
|
+
function streamAgentRunEvents(_c, events, options = {}) {
|
|
1819
|
+
return streamStudioJsonl(withAgentRunCancellation(events, options.onCancel));
|
|
1820
|
+
}
|
|
1821
|
+
function withAgentRunCancellation(events, onCancel) {
|
|
1822
|
+
const iterator = events[Symbol.asyncIterator]();
|
|
1823
|
+
let done = false;
|
|
1824
|
+
let terminal = false;
|
|
1825
|
+
let cancellationHandled = false;
|
|
1826
|
+
const handleCancellation = async () => {
|
|
1827
|
+
if (cancellationHandled) {
|
|
1828
|
+
return;
|
|
1829
|
+
}
|
|
1830
|
+
cancellationHandled = true;
|
|
1831
|
+
await onCancel?.();
|
|
1832
|
+
};
|
|
1833
|
+
return {
|
|
1834
|
+
[Symbol.asyncIterator]() {
|
|
1835
|
+
return {
|
|
1836
|
+
async next() {
|
|
1837
|
+
if (done) {
|
|
1838
|
+
return { done: true, value: void 0 };
|
|
1839
|
+
}
|
|
1840
|
+
try {
|
|
1841
|
+
const next = await iterator.next();
|
|
1842
|
+
if (next.done === true) {
|
|
1843
|
+
done = true;
|
|
1844
|
+
if (!terminal) {
|
|
1845
|
+
await handleCancellation();
|
|
1846
|
+
}
|
|
1847
|
+
return next;
|
|
1848
|
+
}
|
|
1849
|
+
if (next.value.type === "final" || next.value.type === "error") {
|
|
1850
|
+
terminal = true;
|
|
1851
|
+
}
|
|
1852
|
+
return next;
|
|
1853
|
+
} catch (error) {
|
|
1854
|
+
done = true;
|
|
1855
|
+
terminal = true;
|
|
1856
|
+
throw error;
|
|
1857
|
+
}
|
|
1858
|
+
},
|
|
1859
|
+
async return() {
|
|
1860
|
+
if (done) {
|
|
1861
|
+
return { done: true, value: void 0 };
|
|
1862
|
+
}
|
|
1863
|
+
done = true;
|
|
1864
|
+
let closeError;
|
|
1865
|
+
const closePromise = iterator.return?.().catch((error) => {
|
|
1866
|
+
closeError = error;
|
|
1867
|
+
return { done: true, value: void 0 };
|
|
1868
|
+
});
|
|
1869
|
+
if (!terminal) {
|
|
1870
|
+
await handleCancellation();
|
|
1871
|
+
}
|
|
1872
|
+
await closePromise;
|
|
1873
|
+
if (closeError !== void 0) {
|
|
1874
|
+
throw closeError;
|
|
1875
|
+
}
|
|
1876
|
+
return { done: true, value: void 0 };
|
|
1877
|
+
}
|
|
1878
|
+
};
|
|
1879
|
+
}
|
|
1880
|
+
};
|
|
1813
1881
|
}
|
|
1814
1882
|
function traceForRun(trace, agentId, session) {
|
|
1815
1883
|
const options = {};
|
|
@@ -1822,53 +1890,144 @@ function traceForRun(trace, agentId, session) {
|
|
|
1822
1890
|
else if (session !== void 0) options.sessionId = session.id;
|
|
1823
1891
|
return options;
|
|
1824
1892
|
}
|
|
1825
|
-
|
|
1893
|
+
function createPersistedStreamingSessionTranscript(props) {
|
|
1826
1894
|
const transcript = [messageToTranscriptEntry(props.message, 0)];
|
|
1827
1895
|
const title = optionalTitle(props.message);
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
});
|
|
1835
|
-
try {
|
|
1836
|
-
for await (const event of props.stream) {
|
|
1837
|
-
acceptTranscriptStreamEvent(transcript, event);
|
|
1838
|
-
const saveInput = {
|
|
1839
|
-
id: props.session.id,
|
|
1840
|
-
runId: props.runId,
|
|
1841
|
-
...title,
|
|
1842
|
-
transcript,
|
|
1843
|
-
status: event.type === "final" ? "success" : event.type === "error" ? "error" : "running"
|
|
1844
|
-
};
|
|
1845
|
-
if (event.type === "error") saveInput.error = serializeError(event.error);
|
|
1846
|
-
const nextSession = await props.store.saveSessionRunTranscript(saveInput);
|
|
1896
|
+
let status = "running";
|
|
1897
|
+
let saveTail = Promise.resolve();
|
|
1898
|
+
const isCancelled = () => status === "cancelled";
|
|
1899
|
+
const saveTranscript = (input) => {
|
|
1900
|
+
const operation = saveTail.then(async () => {
|
|
1901
|
+
const nextSession = await props.store.saveSessionRunTranscript(input);
|
|
1847
1902
|
if (nextSession === void 0) {
|
|
1848
1903
|
throw new Error("Session not found");
|
|
1849
1904
|
}
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
messages: generatedMessages
|
|
1857
|
-
});
|
|
1858
|
-
}
|
|
1859
|
-
yield event;
|
|
1860
|
-
}
|
|
1861
|
-
} catch (error) {
|
|
1862
|
-
appendTranscriptAssistantError(transcript, errorText(error));
|
|
1863
|
-
await props.store.saveSessionRunTranscript({
|
|
1905
|
+
});
|
|
1906
|
+
saveTail = operation.catch(() => void 0);
|
|
1907
|
+
return operation;
|
|
1908
|
+
};
|
|
1909
|
+
const events = (async function* () {
|
|
1910
|
+
await saveTranscript({
|
|
1864
1911
|
id: props.session.id,
|
|
1865
1912
|
runId: props.runId,
|
|
1866
1913
|
...title,
|
|
1867
1914
|
transcript,
|
|
1868
|
-
status: "
|
|
1869
|
-
error: serializeError(error)
|
|
1915
|
+
status: "running"
|
|
1870
1916
|
});
|
|
1871
|
-
|
|
1917
|
+
if (isCancelled()) {
|
|
1918
|
+
return;
|
|
1919
|
+
}
|
|
1920
|
+
try {
|
|
1921
|
+
for await (const event of props.stream) {
|
|
1922
|
+
if (isCancelled()) {
|
|
1923
|
+
return;
|
|
1924
|
+
}
|
|
1925
|
+
acceptTranscriptStreamEvent(transcript, event);
|
|
1926
|
+
if (event.type === "final" || event.type === "error") {
|
|
1927
|
+
assignTranscriptRunDuration(transcript, Date.now() - props.startedAt);
|
|
1928
|
+
}
|
|
1929
|
+
const eventStatus = event.type === "final" ? "success" : event.type === "error" ? "error" : "running";
|
|
1930
|
+
const saveInput = {
|
|
1931
|
+
id: props.session.id,
|
|
1932
|
+
runId: props.runId,
|
|
1933
|
+
...title,
|
|
1934
|
+
transcript,
|
|
1935
|
+
status: eventStatus
|
|
1936
|
+
};
|
|
1937
|
+
if (event.type === "error") saveInput.error = serializeError(event.error);
|
|
1938
|
+
await saveTranscript(saveInput);
|
|
1939
|
+
const generatedMessages = event.type === "final" ? event.messages.slice(1) : [];
|
|
1940
|
+
if (props.persistGeneratedMessages === true && generatedMessages.length > 0) {
|
|
1941
|
+
await props.store.append({
|
|
1942
|
+
context: { sessionId: props.session.id },
|
|
1943
|
+
runId: props.runId,
|
|
1944
|
+
turn: 1,
|
|
1945
|
+
messages: generatedMessages
|
|
1946
|
+
});
|
|
1947
|
+
}
|
|
1948
|
+
if (isCancelled()) {
|
|
1949
|
+
return;
|
|
1950
|
+
}
|
|
1951
|
+
status = eventStatus;
|
|
1952
|
+
yield event;
|
|
1953
|
+
}
|
|
1954
|
+
} catch (error) {
|
|
1955
|
+
if (status !== "running") {
|
|
1956
|
+
throw error;
|
|
1957
|
+
}
|
|
1958
|
+
status = "error";
|
|
1959
|
+
appendTranscriptAssistantError(transcript, errorText(error));
|
|
1960
|
+
assignTranscriptRunDuration(transcript, Date.now() - props.startedAt);
|
|
1961
|
+
await saveTranscript({
|
|
1962
|
+
id: props.session.id,
|
|
1963
|
+
runId: props.runId,
|
|
1964
|
+
...title,
|
|
1965
|
+
transcript,
|
|
1966
|
+
status: "error",
|
|
1967
|
+
error: serializeError(error)
|
|
1968
|
+
});
|
|
1969
|
+
throw error;
|
|
1970
|
+
}
|
|
1971
|
+
})();
|
|
1972
|
+
return {
|
|
1973
|
+
events,
|
|
1974
|
+
async cancel() {
|
|
1975
|
+
if (status !== "running") {
|
|
1976
|
+
return;
|
|
1977
|
+
}
|
|
1978
|
+
status = "cancelled";
|
|
1979
|
+
cancelPendingTranscriptInputs(transcript, (/* @__PURE__ */ new Date()).toISOString());
|
|
1980
|
+
assignTranscriptRunDuration(transcript, Date.now() - props.startedAt);
|
|
1981
|
+
await saveTranscript({
|
|
1982
|
+
id: props.session.id,
|
|
1983
|
+
runId: props.runId,
|
|
1984
|
+
...title,
|
|
1985
|
+
transcript,
|
|
1986
|
+
status: "cancelled"
|
|
1987
|
+
});
|
|
1988
|
+
}
|
|
1989
|
+
};
|
|
1990
|
+
}
|
|
1991
|
+
function assignTranscriptRunDuration(transcript, durationMs2) {
|
|
1992
|
+
const normalizedDurationMs = Math.max(0, durationMs2);
|
|
1993
|
+
for (let index = transcript.length - 1; index >= 0; index -= 1) {
|
|
1994
|
+
const entry = transcript[index];
|
|
1995
|
+
if (entry?.kind === "message" && entry.role === "assistant") {
|
|
1996
|
+
entry.durationMs = normalizedDurationMs;
|
|
1997
|
+
return;
|
|
1998
|
+
}
|
|
1999
|
+
if (entry?.kind === "message" && entry.role === "user") {
|
|
2000
|
+
break;
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
transcript.push({
|
|
2004
|
+
entryId: transcript.length,
|
|
2005
|
+
kind: "message",
|
|
2006
|
+
role: "assistant",
|
|
2007
|
+
text: "",
|
|
2008
|
+
durationMs: normalizedDurationMs
|
|
2009
|
+
});
|
|
2010
|
+
}
|
|
2011
|
+
function cancelPendingTranscriptInputs(transcript, cancelledAt) {
|
|
2012
|
+
for (const entry of transcript) {
|
|
2013
|
+
if (entry.kind !== "tool") {
|
|
2014
|
+
continue;
|
|
2015
|
+
}
|
|
2016
|
+
if (entry.approval?.status === "pending") {
|
|
2017
|
+
entry.approval = {
|
|
2018
|
+
...entry.approval,
|
|
2019
|
+
status: "cancelled",
|
|
2020
|
+
resolvedAt: cancelledAt,
|
|
2021
|
+
reason: "Run cancelled in Anvia Studio."
|
|
2022
|
+
};
|
|
2023
|
+
}
|
|
2024
|
+
if (entry.question?.status === "pending") {
|
|
2025
|
+
entry.question = {
|
|
2026
|
+
...entry.question,
|
|
2027
|
+
status: "cancelled",
|
|
2028
|
+
cancelledAt
|
|
2029
|
+
};
|
|
2030
|
+
}
|
|
1872
2031
|
}
|
|
1873
2032
|
}
|
|
1874
2033
|
function acceptTranscriptStreamEvent(transcript, event) {
|
|
@@ -1985,6 +2144,9 @@ function acceptTranscriptStreamEvent(transcript, event) {
|
|
|
1985
2144
|
questions: event.question.questions
|
|
1986
2145
|
};
|
|
1987
2146
|
if (event.question.answeredAt !== void 0) question.answeredAt = event.question.answeredAt;
|
|
2147
|
+
if (event.question.cancelledAt !== void 0) {
|
|
2148
|
+
question.cancelledAt = event.question.cancelledAt;
|
|
2149
|
+
}
|
|
1988
2150
|
if (event.question.answers !== void 0) question.answers = event.question.answers;
|
|
1989
2151
|
matched.question = question;
|
|
1990
2152
|
}
|
|
@@ -2476,6 +2638,19 @@ function runCompletedLog(props) {
|
|
|
2476
2638
|
metadata
|
|
2477
2639
|
};
|
|
2478
2640
|
}
|
|
2641
|
+
function runCancelledLog(sessionId, runId, startedAt) {
|
|
2642
|
+
return {
|
|
2643
|
+
sessionId,
|
|
2644
|
+
runId,
|
|
2645
|
+
level: "info",
|
|
2646
|
+
category: "run",
|
|
2647
|
+
event: "run.cancelled",
|
|
2648
|
+
message: "Run cancelled",
|
|
2649
|
+
metadata: {
|
|
2650
|
+
durationMs: Date.now() - startedAt
|
|
2651
|
+
}
|
|
2652
|
+
};
|
|
2653
|
+
}
|
|
2479
2654
|
function memorySavedLog(props) {
|
|
2480
2655
|
const metadata = {};
|
|
2481
2656
|
if (props.messageCount !== void 0) metadata.messageCount = props.messageCount;
|
|
@@ -2662,6 +2837,7 @@ function logsFromStreamEvent(props) {
|
|
|
2662
2837
|
];
|
|
2663
2838
|
}
|
|
2664
2839
|
if (event.type === "tool_question_result") {
|
|
2840
|
+
const cancelled = event.question.status === "cancelled";
|
|
2665
2841
|
const metadata = {
|
|
2666
2842
|
questionId: event.question.id,
|
|
2667
2843
|
toolName: event.question.toolName,
|
|
@@ -2677,8 +2853,8 @@ function logsFromStreamEvent(props) {
|
|
|
2677
2853
|
runId,
|
|
2678
2854
|
level: "info",
|
|
2679
2855
|
category: "question",
|
|
2680
|
-
event: "question.answered",
|
|
2681
|
-
message: `Question answered for ${event.question.toolName}`,
|
|
2856
|
+
event: cancelled ? "question.cancelled" : "question.answered",
|
|
2857
|
+
message: cancelled ? `Question cancelled for ${event.question.toolName}` : `Question answered for ${event.question.toolName}`,
|
|
2682
2858
|
metadata
|
|
2683
2859
|
}
|
|
2684
2860
|
];
|
|
@@ -3055,21 +3231,45 @@ function handleStreamingAgentRun(c, run, props) {
|
|
|
3055
3231
|
run.request.requestHook(effectiveHook);
|
|
3056
3232
|
}
|
|
3057
3233
|
const runStream = mergeRunAndApprovalEvents(run.request.stream(), runtimeEvents);
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3234
|
+
let stream = runStream;
|
|
3235
|
+
let persistedRun;
|
|
3236
|
+
if (run.session !== void 0 && run.sessionStore !== void 0) {
|
|
3237
|
+
persistedRun = createPersistedStreamingSessionTranscript({
|
|
3238
|
+
stream: streamSessionRunLogs({
|
|
3239
|
+
stream: runStream,
|
|
3240
|
+
store: run.sessionStore,
|
|
3241
|
+
session: run.session,
|
|
3242
|
+
runId: run.runId,
|
|
3243
|
+
startedAt: run.runStartedAt
|
|
3244
|
+
}),
|
|
3061
3245
|
store: run.sessionStore,
|
|
3062
3246
|
session: run.session,
|
|
3247
|
+
message: run.body.message,
|
|
3063
3248
|
runId: run.runId,
|
|
3064
|
-
startedAt: run.runStartedAt
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3249
|
+
startedAt: run.runStartedAt,
|
|
3250
|
+
persistGeneratedMessages: run.shouldPersistSessionMessages
|
|
3251
|
+
});
|
|
3252
|
+
stream = persistedRun.events;
|
|
3253
|
+
}
|
|
3254
|
+
return streamAgentRunEvents(c, stream, {
|
|
3255
|
+
onCancel: async () => {
|
|
3256
|
+
props.approvalRuntime.cancelRun(run.runId);
|
|
3257
|
+
props.questionRuntime.cancelRun(run.runId);
|
|
3258
|
+
const persistence = [];
|
|
3259
|
+
if (persistedRun !== void 0) {
|
|
3260
|
+
persistence.push(persistedRun.cancel());
|
|
3261
|
+
}
|
|
3262
|
+
if (run.session !== void 0 && run.sessionStore !== void 0) {
|
|
3263
|
+
persistence.push(
|
|
3264
|
+
appendSessionLog(
|
|
3265
|
+
run.sessionStore,
|
|
3266
|
+
runCancelledLog(run.session.id, run.runId, run.runStartedAt)
|
|
3267
|
+
)
|
|
3268
|
+
);
|
|
3269
|
+
}
|
|
3270
|
+
await Promise.all(persistence);
|
|
3271
|
+
}
|
|
3071
3272
|
});
|
|
3072
|
-
return streamAgentRunEvents(c, stream);
|
|
3073
3273
|
}
|
|
3074
3274
|
async function handleBufferedAgentRun(c, run, props) {
|
|
3075
3275
|
try {
|
|
@@ -3123,11 +3323,14 @@ async function completeBufferedSessionRun(run, response) {
|
|
|
3123
3323
|
});
|
|
3124
3324
|
}
|
|
3125
3325
|
}
|
|
3326
|
+
const durationMs2 = Date.now() - run.runStartedAt;
|
|
3327
|
+
const transcript = transcriptFromMessages(response.messages);
|
|
3328
|
+
assignTranscriptRunDuration(transcript, durationMs2);
|
|
3126
3329
|
await run.sessionStore.saveSessionRunTranscript({
|
|
3127
3330
|
id: run.session.id,
|
|
3128
3331
|
runId: run.runId,
|
|
3129
3332
|
...optionalTitle(run.body.message),
|
|
3130
|
-
transcript
|
|
3333
|
+
transcript,
|
|
3131
3334
|
status: "success"
|
|
3132
3335
|
});
|
|
3133
3336
|
await appendSessionLog(
|
|
@@ -3135,7 +3338,7 @@ async function completeBufferedSessionRun(run, response) {
|
|
|
3135
3338
|
runCompletedLog({
|
|
3136
3339
|
sessionId: run.session.id,
|
|
3137
3340
|
runId: run.runId,
|
|
3138
|
-
durationMs:
|
|
3341
|
+
durationMs: durationMs2,
|
|
3139
3342
|
usage: response.usage,
|
|
3140
3343
|
output: response.output,
|
|
3141
3344
|
messageCount: response.messages.length
|
|
@@ -3158,11 +3361,14 @@ async function failBufferedSessionRun(run, error) {
|
|
|
3158
3361
|
sessionId: run.session.id,
|
|
3159
3362
|
metadata: run.memoryMetadata
|
|
3160
3363
|
});
|
|
3364
|
+
const durationMs2 = Date.now() - run.runStartedAt;
|
|
3365
|
+
const transcript = transcriptFromMessages(messages.slice(run.session.messageCount));
|
|
3366
|
+
assignTranscriptRunDuration(transcript, durationMs2);
|
|
3161
3367
|
await run.sessionStore.saveSessionRunTranscript({
|
|
3162
3368
|
id: run.session.id,
|
|
3163
3369
|
runId: run.runId,
|
|
3164
3370
|
...optionalTitle(run.body.message),
|
|
3165
|
-
transcript
|
|
3371
|
+
transcript,
|
|
3166
3372
|
status: "error",
|
|
3167
3373
|
error: serializeError(error)
|
|
3168
3374
|
});
|
|
@@ -3322,6 +3528,22 @@ function createApprovalRuntime() {
|
|
|
3322
3528
|
return true;
|
|
3323
3529
|
}).map(publicApproval);
|
|
3324
3530
|
},
|
|
3531
|
+
cancelRun(runId) {
|
|
3532
|
+
const cancelled = [];
|
|
3533
|
+
for (const approval of approvals.values()) {
|
|
3534
|
+
if (!isPendingApproval(approval) || approval.runId !== runId) {
|
|
3535
|
+
continue;
|
|
3536
|
+
}
|
|
3537
|
+
const resolved = resolveApproval(approval, "cancelled", {
|
|
3538
|
+
reason: "Run cancelled in Anvia Studio."
|
|
3539
|
+
});
|
|
3540
|
+
approvals.set(approval.id, resolved);
|
|
3541
|
+
approval.emit?.({ type: "tool_approval_result", approval: resolved });
|
|
3542
|
+
approval.resolve(approvalDecision(false, resolved.reason));
|
|
3543
|
+
cancelled.push(resolved);
|
|
3544
|
+
}
|
|
3545
|
+
return cancelled;
|
|
3546
|
+
},
|
|
3325
3547
|
decide(id, decision) {
|
|
3326
3548
|
const approval = approvals.get(id);
|
|
3327
3549
|
if (approval === void 0) {
|
|
@@ -3581,7 +3803,7 @@ function pipelineConfig(pipeline) {
|
|
|
3581
3803
|
if (pipeline.metadata !== void 0) config.metadata = pipeline.metadata;
|
|
3582
3804
|
return config;
|
|
3583
3805
|
}
|
|
3584
|
-
function buildConfig(options, agents, pipelines, stores) {
|
|
3806
|
+
function buildConfig(options, agents, pipelines, stores, sandboxCount = 0) {
|
|
3585
3807
|
const models = options.models === void 0 ? void 0 : studioModelsConfig(createStudioModelRegistry(options.models), agents);
|
|
3586
3808
|
const config = {
|
|
3587
3809
|
id: runnerId(options),
|
|
@@ -3591,7 +3813,7 @@ function buildConfig(options, agents, pipelines, stores) {
|
|
|
3591
3813
|
chat: {
|
|
3592
3814
|
quickPrompts: Object.fromEntries(agents.map((agent) => [agent.id, agent.quickPrompts ?? []]))
|
|
3593
3815
|
},
|
|
3594
|
-
capabilities: capabilityConfig(options, agents, pipelines, stores),
|
|
3816
|
+
capabilities: capabilityConfig(options, agents, pipelines, stores, sandboxCount),
|
|
3595
3817
|
unsupportedCapabilities: unsupportedCapabilities(stores)
|
|
3596
3818
|
};
|
|
3597
3819
|
if (options.name !== void 0) config.name = options.name;
|
|
@@ -3600,7 +3822,7 @@ function buildConfig(options, agents, pipelines, stores) {
|
|
|
3600
3822
|
if (models !== void 0) config.models = models;
|
|
3601
3823
|
return config;
|
|
3602
3824
|
}
|
|
3603
|
-
function capabilityConfig(_options, agents, pipelines, stores) {
|
|
3825
|
+
function capabilityConfig(_options, agents, pipelines, stores, sandboxCount = 0) {
|
|
3604
3826
|
const capabilities = {
|
|
3605
3827
|
agents: { enabled: true },
|
|
3606
3828
|
observability: { enabled: true },
|
|
@@ -3608,6 +3830,8 @@ function capabilityConfig(_options, agents, pipelines, stores) {
|
|
|
3608
3830
|
};
|
|
3609
3831
|
if (stores.sessions !== void 0) {
|
|
3610
3832
|
capabilities.sessions = { enabled: true };
|
|
3833
|
+
}
|
|
3834
|
+
if (stores.sessions !== void 0 || agents.some((agent) => agent.agent.memory !== void 0)) {
|
|
3611
3835
|
capabilities.memory = { enabled: true };
|
|
3612
3836
|
}
|
|
3613
3837
|
if (stores.traces !== void 0) {
|
|
@@ -3627,6 +3851,9 @@ function capabilityConfig(_options, agents, pipelines, stores) {
|
|
|
3627
3851
|
if (agents.some(agentHasMcpTools)) {
|
|
3628
3852
|
capabilities.mcps = { enabled: true };
|
|
3629
3853
|
}
|
|
3854
|
+
if (sandboxCount > 0) {
|
|
3855
|
+
capabilities.sandboxes = { enabled: true };
|
|
3856
|
+
}
|
|
3630
3857
|
if (agents.some(
|
|
3631
3858
|
(agent) => agent.agent.hook !== void 0 || agent.agent.toolSet.values().some((tool) => tool.approval)
|
|
3632
3859
|
)) {
|
|
@@ -4159,13 +4386,137 @@ async function agentMcpMetadata(agent) {
|
|
|
4159
4386
|
|
|
4160
4387
|
// src/runtime/memory.ts
|
|
4161
4388
|
var DEFAULT_USER_ID = "default";
|
|
4389
|
+
var USER_DISCOVERY_LIMIT = 1e3;
|
|
4390
|
+
function createStudioMemorySourceRegistry(agents, sessionStore) {
|
|
4391
|
+
const sources = [];
|
|
4392
|
+
const byStore = /* @__PURE__ */ new Map();
|
|
4393
|
+
const agentsWithoutMemory = [];
|
|
4394
|
+
for (const agent of agents) {
|
|
4395
|
+
const store = agent.agent.memory?.store;
|
|
4396
|
+
if (store === void 0) {
|
|
4397
|
+
agentsWithoutMemory.push(agent);
|
|
4398
|
+
continue;
|
|
4399
|
+
}
|
|
4400
|
+
const existing = byStore.get(store);
|
|
4401
|
+
if (existing !== void 0) {
|
|
4402
|
+
existing.summary.agentIds.push(agent.id);
|
|
4403
|
+
existing.summary.label = "Shared agent memory";
|
|
4404
|
+
continue;
|
|
4405
|
+
}
|
|
4406
|
+
const ref = `agent-memory-${sources.length + 1}`;
|
|
4407
|
+
const label = `${agent.name ?? agent.agent.name ?? agent.id} memory`;
|
|
4408
|
+
const kind = storeKind(store);
|
|
4409
|
+
const source = store.inspector === void 0 ? unavailableAgentSource(ref, label, agent.id, kind) : agentMemorySource(ref, label, agent.id, kind, store.inspector);
|
|
4410
|
+
sources.push(source);
|
|
4411
|
+
byStore.set(store, source);
|
|
4412
|
+
}
|
|
4413
|
+
if (sessionStore !== void 0 && (agentsWithoutMemory.length > 0 || agents.length === 0)) {
|
|
4414
|
+
sources.push(
|
|
4415
|
+
studioSessionSource(
|
|
4416
|
+
sessionStore,
|
|
4417
|
+
agentsWithoutMemory.map((agent) => agent.id)
|
|
4418
|
+
)
|
|
4419
|
+
);
|
|
4420
|
+
}
|
|
4421
|
+
const byRef = new Map(sources.map((source) => [source.summary.ref, source]));
|
|
4422
|
+
return {
|
|
4423
|
+
size: sources.length,
|
|
4424
|
+
list: () => sources.map((source) => ({ ...source.summary, agentIds: [...source.summary.agentIds] })),
|
|
4425
|
+
get: (ref) => byRef.get(ref)
|
|
4426
|
+
};
|
|
4427
|
+
}
|
|
4162
4428
|
function registerMemoryRoutes(app, props) {
|
|
4429
|
+
registerMemorySourceRoutes(app, props.sources);
|
|
4430
|
+
if (props.sessionStore !== void 0) {
|
|
4431
|
+
registerLegacyMemoryRoutes(app, props.sessionStore);
|
|
4432
|
+
}
|
|
4433
|
+
}
|
|
4434
|
+
function registerMemorySourceRoutes(app, registry) {
|
|
4435
|
+
app.get(
|
|
4436
|
+
"/memory/sources",
|
|
4437
|
+
(c) => c.json({ sources: registry.list() })
|
|
4438
|
+
);
|
|
4439
|
+
app.get("/memory/sources/:sourceRef/users", async (c) => {
|
|
4440
|
+
const limit = parseLimit(c.req.query("limit"));
|
|
4441
|
+
if (limit === void 0) {
|
|
4442
|
+
return errorResponse(c, 400, "bad_request", "limit must be a positive integer");
|
|
4443
|
+
}
|
|
4444
|
+
const source = registry.get(c.req.param("sourceRef"));
|
|
4445
|
+
if (source === void 0) {
|
|
4446
|
+
return errorResponse(c, 404, "not_found", "Memory source not found");
|
|
4447
|
+
}
|
|
4448
|
+
if (!source.summary.available) return unavailableSourceResponse(c, source.summary);
|
|
4449
|
+
const conversations = await source.listConversations({ limit: USER_DISCOVERY_LIMIT });
|
|
4450
|
+
const userPage = memoryUsers(conversations, limit);
|
|
4451
|
+
return c.json({
|
|
4452
|
+
source: source.summary,
|
|
4453
|
+
users: userPage.users,
|
|
4454
|
+
total: userPage.total
|
|
4455
|
+
});
|
|
4456
|
+
});
|
|
4457
|
+
app.get("/memory/sources/:sourceRef/conversations", async (c) => {
|
|
4458
|
+
const limit = parseLimit(c.req.query("limit"));
|
|
4459
|
+
if (limit === void 0) {
|
|
4460
|
+
return errorResponse(c, 400, "bad_request", "limit must be a positive integer");
|
|
4461
|
+
}
|
|
4462
|
+
const source = registry.get(c.req.param("sourceRef"));
|
|
4463
|
+
if (source === void 0) {
|
|
4464
|
+
return errorResponse(c, 404, "not_found", "Memory source not found");
|
|
4465
|
+
}
|
|
4466
|
+
if (!source.summary.available) return unavailableSourceResponse(c, source.summary);
|
|
4467
|
+
const userId = optionalQueryString(c.req.query("userId"));
|
|
4468
|
+
const listOptions = { limit };
|
|
4469
|
+
if (userId !== void 0) listOptions.userId = userId;
|
|
4470
|
+
const conversations = await source.listConversations(listOptions);
|
|
4471
|
+
return c.json({
|
|
4472
|
+
source: source.summary,
|
|
4473
|
+
conversations,
|
|
4474
|
+
total: conversations.length
|
|
4475
|
+
});
|
|
4476
|
+
});
|
|
4477
|
+
app.get("/memory/sources/:sourceRef/conversations/:conversationRef/messages", async (c) => {
|
|
4478
|
+
const source = registry.get(c.req.param("sourceRef"));
|
|
4479
|
+
if (source === void 0) {
|
|
4480
|
+
return errorResponse(c, 404, "not_found", "Memory source not found");
|
|
4481
|
+
}
|
|
4482
|
+
if (!source.summary.available) return unavailableSourceResponse(c, source.summary);
|
|
4483
|
+
const detail = await source.getConversation(c.req.param("conversationRef"));
|
|
4484
|
+
if (detail === void 0) {
|
|
4485
|
+
return errorResponse(c, 404, "not_found", "Conversation not found");
|
|
4486
|
+
}
|
|
4487
|
+
const messages = detail.records.map((record) => record.message);
|
|
4488
|
+
return c.json({
|
|
4489
|
+
source: source.summary,
|
|
4490
|
+
conversation: detail.conversation,
|
|
4491
|
+
messages,
|
|
4492
|
+
records: detail.records,
|
|
4493
|
+
transcript: transcriptFromMessages(messages)
|
|
4494
|
+
});
|
|
4495
|
+
});
|
|
4496
|
+
app.get("/memory/sources/:sourceRef/conversations/:conversationRef/steps", async (c) => {
|
|
4497
|
+
const source = registry.get(c.req.param("sourceRef"));
|
|
4498
|
+
if (source === void 0) {
|
|
4499
|
+
return errorResponse(c, 404, "not_found", "Memory source not found");
|
|
4500
|
+
}
|
|
4501
|
+
if (!source.summary.available) return unavailableSourceResponse(c, source.summary);
|
|
4502
|
+
const detail = await source.getConversation(c.req.param("conversationRef"));
|
|
4503
|
+
if (detail === void 0) {
|
|
4504
|
+
return errorResponse(c, 404, "not_found", "Conversation not found");
|
|
4505
|
+
}
|
|
4506
|
+
return c.json({
|
|
4507
|
+
source: source.summary,
|
|
4508
|
+
conversation: detail.conversation,
|
|
4509
|
+
steps: transcriptFromMessages(detail.records.map((record) => record.message))
|
|
4510
|
+
});
|
|
4511
|
+
});
|
|
4512
|
+
}
|
|
4513
|
+
function registerLegacyMemoryRoutes(app, sessionStore) {
|
|
4163
4514
|
app.get("/memory/users", async (c) => {
|
|
4164
4515
|
const limit = parseLimit(c.req.query("limit"));
|
|
4165
4516
|
if (limit === void 0) {
|
|
4166
4517
|
return errorResponse(c, 400, "bad_request", "limit must be a positive integer");
|
|
4167
4518
|
}
|
|
4168
|
-
const sessions = await
|
|
4519
|
+
const sessions = await sessionStore.listSessions({ limit: 100 });
|
|
4169
4520
|
const users = /* @__PURE__ */ new Map();
|
|
4170
4521
|
for (const session of sessions) {
|
|
4171
4522
|
const userId = sessionUserId(session);
|
|
@@ -4180,9 +4531,7 @@ function registerMemoryRoutes(app, props) {
|
|
|
4180
4531
|
continue;
|
|
4181
4532
|
}
|
|
4182
4533
|
existing.conversationCount += 1;
|
|
4183
|
-
if (!existing.agentIds.includes(session.agentId))
|
|
4184
|
-
existing.agentIds.push(session.agentId);
|
|
4185
|
-
}
|
|
4534
|
+
if (!existing.agentIds.includes(session.agentId)) existing.agentIds.push(session.agentId);
|
|
4186
4535
|
if (new Date(session.updatedAt).getTime() > new Date(existing.lastInteractionAt).getTime()) {
|
|
4187
4536
|
existing.lastInteractionAt = session.updatedAt;
|
|
4188
4537
|
}
|
|
@@ -4201,7 +4550,7 @@ function registerMemoryRoutes(app, props) {
|
|
|
4201
4550
|
const userId = optionalQueryString(c.req.query("userId"));
|
|
4202
4551
|
const listInput = { limit: 100 };
|
|
4203
4552
|
if (agentId !== void 0) listInput.agentId = agentId;
|
|
4204
|
-
const sessions = await
|
|
4553
|
+
const sessions = await sessionStore.listSessions(listInput);
|
|
4205
4554
|
const conversations = sessions.map(memoryConversationSummary).filter((session) => userId === void 0 || session.userId === userId).slice(0, limit);
|
|
4206
4555
|
return c.json({
|
|
4207
4556
|
conversations,
|
|
@@ -4209,7 +4558,7 @@ function registerMemoryRoutes(app, props) {
|
|
|
4209
4558
|
});
|
|
4210
4559
|
});
|
|
4211
4560
|
app.get("/memory/conversations/:conversationId/messages", async (c) => {
|
|
4212
|
-
const session = await
|
|
4561
|
+
const session = await sessionStore.getSession(c.req.param("conversationId"));
|
|
4213
4562
|
if (session === void 0) {
|
|
4214
4563
|
return errorResponse(c, 404, "not_found", "Conversation not found");
|
|
4215
4564
|
}
|
|
@@ -4220,7 +4569,7 @@ function registerMemoryRoutes(app, props) {
|
|
|
4220
4569
|
});
|
|
4221
4570
|
});
|
|
4222
4571
|
app.get("/memory/conversations/:conversationId/steps", async (c) => {
|
|
4223
|
-
const session = await
|
|
4572
|
+
const session = await sessionStore.getSession(c.req.param("conversationId"));
|
|
4224
4573
|
if (session === void 0) {
|
|
4225
4574
|
return errorResponse(c, 404, "not_found", "Conversation not found");
|
|
4226
4575
|
}
|
|
@@ -4230,6 +4579,149 @@ function registerMemoryRoutes(app, props) {
|
|
|
4230
4579
|
});
|
|
4231
4580
|
});
|
|
4232
4581
|
}
|
|
4582
|
+
function agentMemorySource(ref, label, agentId, storeKindValue, inspector) {
|
|
4583
|
+
const summary = {
|
|
4584
|
+
ref,
|
|
4585
|
+
kind: "agent",
|
|
4586
|
+
label,
|
|
4587
|
+
agentIds: [agentId],
|
|
4588
|
+
available: true
|
|
4589
|
+
};
|
|
4590
|
+
if (storeKindValue !== void 0) summary.storeKind = storeKindValue;
|
|
4591
|
+
return {
|
|
4592
|
+
summary,
|
|
4593
|
+
async listConversations(options) {
|
|
4594
|
+
const inspectorOptions = {
|
|
4595
|
+
limit: options.userId === DEFAULT_USER_ID ? USER_DISCOVERY_LIMIT : options.limit
|
|
4596
|
+
};
|
|
4597
|
+
if (options.userId !== void 0 && options.userId !== DEFAULT_USER_ID) {
|
|
4598
|
+
inspectorOptions.userId = options.userId;
|
|
4599
|
+
}
|
|
4600
|
+
const conversations = await inspector.listConversations(inspectorOptions);
|
|
4601
|
+
return conversations.map((conversation) => sourceConversationSummary(conversation, summary.agentIds)).filter(
|
|
4602
|
+
(conversation) => options.userId === void 0 || conversation.userId === options.userId
|
|
4603
|
+
).slice(0, options.limit);
|
|
4604
|
+
},
|
|
4605
|
+
async getConversation(conversationRef) {
|
|
4606
|
+
const conversation = await inspector.getConversation(conversationRef);
|
|
4607
|
+
return conversation === void 0 ? void 0 : inspectorConversationDetail(conversation, summary.agentIds);
|
|
4608
|
+
}
|
|
4609
|
+
};
|
|
4610
|
+
}
|
|
4611
|
+
function unavailableAgentSource(ref, label, agentId, storeKindValue) {
|
|
4612
|
+
const summary = {
|
|
4613
|
+
ref,
|
|
4614
|
+
kind: "agent",
|
|
4615
|
+
label,
|
|
4616
|
+
agentIds: [agentId],
|
|
4617
|
+
available: false,
|
|
4618
|
+
reason: "This memory store does not expose the optional inspector capability."
|
|
4619
|
+
};
|
|
4620
|
+
if (storeKindValue !== void 0) summary.storeKind = storeKindValue;
|
|
4621
|
+
return {
|
|
4622
|
+
summary,
|
|
4623
|
+
listConversations: async () => [],
|
|
4624
|
+
getConversation: async () => void 0
|
|
4625
|
+
};
|
|
4626
|
+
}
|
|
4627
|
+
function studioSessionSource(sessionStore, agentIds) {
|
|
4628
|
+
const summary = {
|
|
4629
|
+
ref: "studio-sessions",
|
|
4630
|
+
kind: "studio",
|
|
4631
|
+
label: "Studio sessions",
|
|
4632
|
+
agentIds: [...agentIds],
|
|
4633
|
+
available: true,
|
|
4634
|
+
storeKind: sessionStore.kind ?? "studio"
|
|
4635
|
+
};
|
|
4636
|
+
const allowedAgents = new Set(agentIds);
|
|
4637
|
+
const restrictAgents = allowedAgents.size > 0;
|
|
4638
|
+
return {
|
|
4639
|
+
summary,
|
|
4640
|
+
async listConversations(options) {
|
|
4641
|
+
const sessions = await sessionStore.listSessions({ limit: USER_DISCOVERY_LIMIT });
|
|
4642
|
+
return sessions.filter((session) => !restrictAgents || allowedAgents.has(session.agentId)).map(studioSourceConversationSummary).filter(
|
|
4643
|
+
(conversation) => options.userId === void 0 || conversation.userId === options.userId
|
|
4644
|
+
).slice(0, options.limit);
|
|
4645
|
+
},
|
|
4646
|
+
async getConversation(conversationRef) {
|
|
4647
|
+
const session = await sessionStore.getSession(conversationRef);
|
|
4648
|
+
if (session === void 0 || restrictAgents && !allowedAgents.has(session.agentId)) {
|
|
4649
|
+
return void 0;
|
|
4650
|
+
}
|
|
4651
|
+
return {
|
|
4652
|
+
conversation: studioSourceConversationSummary(session),
|
|
4653
|
+
records: session.messages.map((message, position) => ({
|
|
4654
|
+
position,
|
|
4655
|
+
runId: "studio-session",
|
|
4656
|
+
turn: 0,
|
|
4657
|
+
createdAt: session.updatedAt,
|
|
4658
|
+
message
|
|
4659
|
+
}))
|
|
4660
|
+
};
|
|
4661
|
+
}
|
|
4662
|
+
};
|
|
4663
|
+
}
|
|
4664
|
+
function sourceConversationSummary(conversation, agentIds) {
|
|
4665
|
+
const summary = {
|
|
4666
|
+
ref: conversation.ref,
|
|
4667
|
+
sessionId: conversation.sessionId,
|
|
4668
|
+
userId: conversation.userId ?? DEFAULT_USER_ID,
|
|
4669
|
+
agentIds: [...agentIds],
|
|
4670
|
+
createdAt: conversation.createdAt,
|
|
4671
|
+
updatedAt: conversation.updatedAt,
|
|
4672
|
+
messageCount: conversation.messageCount
|
|
4673
|
+
};
|
|
4674
|
+
if (conversation.metadata !== void 0) summary.metadata = conversation.metadata;
|
|
4675
|
+
return summary;
|
|
4676
|
+
}
|
|
4677
|
+
function inspectorConversationDetail(conversation, agentIds) {
|
|
4678
|
+
return {
|
|
4679
|
+
conversation: sourceConversationSummary(conversation, agentIds),
|
|
4680
|
+
records: conversation.messages
|
|
4681
|
+
};
|
|
4682
|
+
}
|
|
4683
|
+
function studioSourceConversationSummary(session) {
|
|
4684
|
+
const summary = {
|
|
4685
|
+
ref: session.id,
|
|
4686
|
+
sessionId: session.id,
|
|
4687
|
+
userId: sessionUserId(session),
|
|
4688
|
+
agentIds: [session.agentId],
|
|
4689
|
+
createdAt: session.createdAt,
|
|
4690
|
+
updatedAt: session.updatedAt,
|
|
4691
|
+
messageCount: session.messageCount
|
|
4692
|
+
};
|
|
4693
|
+
if (session.title !== void 0) summary.title = session.title;
|
|
4694
|
+
if (session.metadata !== void 0) summary.metadata = session.metadata;
|
|
4695
|
+
return summary;
|
|
4696
|
+
}
|
|
4697
|
+
function memoryUsers(conversations, limit) {
|
|
4698
|
+
const users = /* @__PURE__ */ new Map();
|
|
4699
|
+
for (const conversation of conversations) {
|
|
4700
|
+
const existing = users.get(conversation.userId);
|
|
4701
|
+
if (existing === void 0) {
|
|
4702
|
+
users.set(conversation.userId, {
|
|
4703
|
+
userId: conversation.userId,
|
|
4704
|
+
conversationCount: 1,
|
|
4705
|
+
agentIds: [...conversation.agentIds],
|
|
4706
|
+
lastInteractionAt: conversation.updatedAt
|
|
4707
|
+
});
|
|
4708
|
+
continue;
|
|
4709
|
+
}
|
|
4710
|
+
existing.conversationCount += 1;
|
|
4711
|
+
for (const agentId of conversation.agentIds) {
|
|
4712
|
+
if (!existing.agentIds.includes(agentId)) existing.agentIds.push(agentId);
|
|
4713
|
+
}
|
|
4714
|
+
if (new Date(conversation.updatedAt).getTime() > new Date(existing.lastInteractionAt).getTime()) {
|
|
4715
|
+
existing.lastInteractionAt = conversation.updatedAt;
|
|
4716
|
+
}
|
|
4717
|
+
}
|
|
4718
|
+
return {
|
|
4719
|
+
users: [...users.values()].sort(
|
|
4720
|
+
(left, right) => new Date(right.lastInteractionAt).getTime() - new Date(left.lastInteractionAt).getTime()
|
|
4721
|
+
).slice(0, limit),
|
|
4722
|
+
total: users.size
|
|
4723
|
+
};
|
|
4724
|
+
}
|
|
4233
4725
|
function memoryConversationSummary(session) {
|
|
4234
4726
|
const summary = {
|
|
4235
4727
|
id: session.id,
|
|
@@ -4247,6 +4739,19 @@ function sessionUserId(session) {
|
|
|
4247
4739
|
const userId = session.metadata?.userId;
|
|
4248
4740
|
return typeof userId === "string" && userId.trim().length > 0 ? userId : DEFAULT_USER_ID;
|
|
4249
4741
|
}
|
|
4742
|
+
function storeKind(store) {
|
|
4743
|
+
const kind = store.kind;
|
|
4744
|
+
return typeof kind === "string" && kind.length > 0 ? kind : void 0;
|
|
4745
|
+
}
|
|
4746
|
+
function unavailableSourceResponse(c, source) {
|
|
4747
|
+
return errorResponse(
|
|
4748
|
+
c,
|
|
4749
|
+
501,
|
|
4750
|
+
"unsupported_capability",
|
|
4751
|
+
source.reason ?? "Memory source is not inspectable",
|
|
4752
|
+
{ sourceRef: source.ref }
|
|
4753
|
+
);
|
|
4754
|
+
}
|
|
4250
4755
|
|
|
4251
4756
|
// src/runtime/pipeline-logs.ts
|
|
4252
4757
|
async function appendPipelineLog(store, input) {
|
|
@@ -4910,6 +5415,20 @@ function createQuestionRuntime() {
|
|
|
4910
5415
|
return true;
|
|
4911
5416
|
}).map(publicQuestion);
|
|
4912
5417
|
},
|
|
5418
|
+
cancelRun(runId) {
|
|
5419
|
+
const cancelled = [];
|
|
5420
|
+
for (const question of questions.values()) {
|
|
5421
|
+
if (!isPendingQuestion(question) || question.runId !== runId) {
|
|
5422
|
+
continue;
|
|
5423
|
+
}
|
|
5424
|
+
const resolved = cancelQuestion(question);
|
|
5425
|
+
questions.set(question.id, resolved);
|
|
5426
|
+
question.emit?.({ type: "tool_question_result", question: resolved });
|
|
5427
|
+
question.resolve([]);
|
|
5428
|
+
cancelled.push(resolved);
|
|
5429
|
+
}
|
|
5430
|
+
return cancelled;
|
|
5431
|
+
},
|
|
4913
5432
|
answer(id, answers) {
|
|
4914
5433
|
const question = questions.get(id);
|
|
4915
5434
|
if (question === void 0) {
|
|
@@ -5010,6 +5529,13 @@ function resolveQuestion(question, answers) {
|
|
|
5010
5529
|
answers
|
|
5011
5530
|
});
|
|
5012
5531
|
}
|
|
5532
|
+
function cancelQuestion(question) {
|
|
5533
|
+
return publicQuestion({
|
|
5534
|
+
...question,
|
|
5535
|
+
status: "cancelled",
|
|
5536
|
+
cancelledAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5537
|
+
});
|
|
5538
|
+
}
|
|
5013
5539
|
function publicQuestion(question) {
|
|
5014
5540
|
const { emit, resolve: resolve2, ...rest } = question;
|
|
5015
5541
|
void emit;
|
|
@@ -5017,6 +5543,446 @@ function publicQuestion(question) {
|
|
|
5017
5543
|
return rest;
|
|
5018
5544
|
}
|
|
5019
5545
|
|
|
5546
|
+
// src/runtime/sandboxes.ts
|
|
5547
|
+
import path from "path";
|
|
5548
|
+
var sandboxToolMetadataKey = /* @__PURE__ */ Symbol.for("anvia.sandbox.tool.metadata");
|
|
5549
|
+
var maxFileResponseBytes = 10 * 1024 * 1024;
|
|
5550
|
+
var defaultProcessLogBytes = 64 * 1024;
|
|
5551
|
+
var maxProcessLogBytes = 1024 * 1024;
|
|
5552
|
+
var StudioSandboxRegistry = class {
|
|
5553
|
+
entries;
|
|
5554
|
+
constructor(entries) {
|
|
5555
|
+
this.entries = new Map(entries.map((entry) => [entry.summary.ref, entry]));
|
|
5556
|
+
}
|
|
5557
|
+
get size() {
|
|
5558
|
+
return this.entries.size;
|
|
5559
|
+
}
|
|
5560
|
+
list() {
|
|
5561
|
+
return [...this.entries.values()].map((entry) => copySummary(entry.summary));
|
|
5562
|
+
}
|
|
5563
|
+
get(ref) {
|
|
5564
|
+
return this.entries.get(ref);
|
|
5565
|
+
}
|
|
5566
|
+
};
|
|
5567
|
+
function createStudioSandboxRegistry(agents) {
|
|
5568
|
+
const drafts = /* @__PURE__ */ new Map();
|
|
5569
|
+
for (const agent of agents) {
|
|
5570
|
+
for (const { tool } of agentToolItems(agent)) {
|
|
5571
|
+
const session = sandboxSessionFromTool(tool);
|
|
5572
|
+
if (session === void 0) {
|
|
5573
|
+
continue;
|
|
5574
|
+
}
|
|
5575
|
+
const draft = drafts.get(session) ?? {
|
|
5576
|
+
session,
|
|
5577
|
+
agentIds: /* @__PURE__ */ new Set(),
|
|
5578
|
+
toolNames: /* @__PURE__ */ new Set()
|
|
5579
|
+
};
|
|
5580
|
+
draft.agentIds.add(agent.id);
|
|
5581
|
+
draft.toolNames.add(tool.name);
|
|
5582
|
+
drafts.set(session, draft);
|
|
5583
|
+
}
|
|
5584
|
+
}
|
|
5585
|
+
const refCounts = /* @__PURE__ */ new Map();
|
|
5586
|
+
const entries = [...drafts.values()].sort(
|
|
5587
|
+
(left, right) => `${left.session.provider}\0${left.session.id}`.localeCompare(
|
|
5588
|
+
`${right.session.provider}\0${right.session.id}`
|
|
5589
|
+
)
|
|
5590
|
+
).map((draft) => {
|
|
5591
|
+
const baseRef = sandboxRef(draft.session.provider, draft.session.id);
|
|
5592
|
+
const occurrence = (refCounts.get(baseRef) ?? 0) + 1;
|
|
5593
|
+
refCounts.set(baseRef, occurrence);
|
|
5594
|
+
const ref = occurrence === 1 ? baseRef : `${baseRef}.${occurrence}`;
|
|
5595
|
+
return {
|
|
5596
|
+
session: draft.session,
|
|
5597
|
+
summary: {
|
|
5598
|
+
ref,
|
|
5599
|
+
id: draft.session.id,
|
|
5600
|
+
provider: draft.session.provider,
|
|
5601
|
+
workdir: draft.session.workdir,
|
|
5602
|
+
agentIds: [...draft.agentIds].sort(),
|
|
5603
|
+
toolNames: [...draft.toolNames].sort(),
|
|
5604
|
+
capabilities: {
|
|
5605
|
+
files: true,
|
|
5606
|
+
ports: supportsPorts(draft.session),
|
|
5607
|
+
processes: supportsProcesses(draft.session)
|
|
5608
|
+
}
|
|
5609
|
+
}
|
|
5610
|
+
};
|
|
5611
|
+
});
|
|
5612
|
+
return new StudioSandboxRegistry(entries);
|
|
5613
|
+
}
|
|
5614
|
+
function registerSandboxRoutes(app, registry) {
|
|
5615
|
+
app.use("/sandboxes", sandboxNoStore);
|
|
5616
|
+
app.use("/sandboxes/*", sandboxNoStore);
|
|
5617
|
+
app.get("/sandboxes", (c) => c.json({ sandboxes: registry.list() }));
|
|
5618
|
+
app.get("/sandboxes/:sandboxRef", (c) => {
|
|
5619
|
+
const entry = registry.get(c.req.param("sandboxRef"));
|
|
5620
|
+
if (entry === void 0) {
|
|
5621
|
+
return errorResponse(c, 404, "not_found", "Sandbox not found");
|
|
5622
|
+
}
|
|
5623
|
+
return c.json(copySummary(entry.summary));
|
|
5624
|
+
});
|
|
5625
|
+
app.get("/sandboxes/:sandboxRef/files", async (c) => {
|
|
5626
|
+
const entry = registry.get(c.req.param("sandboxRef"));
|
|
5627
|
+
if (entry === void 0) {
|
|
5628
|
+
return errorResponse(c, 404, "not_found", "Sandbox not found");
|
|
5629
|
+
}
|
|
5630
|
+
try {
|
|
5631
|
+
const requestedPath = c.req.query("path") ?? ".";
|
|
5632
|
+
const normalizedPath = normalizeSandboxPath(requestedPath, true);
|
|
5633
|
+
const entries = await listSandboxFiles(entry.session, normalizedPath);
|
|
5634
|
+
return c.json({
|
|
5635
|
+
sandboxRef: entry.summary.ref,
|
|
5636
|
+
path: normalizedPath,
|
|
5637
|
+
entries
|
|
5638
|
+
});
|
|
5639
|
+
} catch (error) {
|
|
5640
|
+
return sandboxErrorResponse(c, error);
|
|
5641
|
+
}
|
|
5642
|
+
});
|
|
5643
|
+
app.get("/sandboxes/:sandboxRef/files/content", async (c) => {
|
|
5644
|
+
const entry = registry.get(c.req.param("sandboxRef"));
|
|
5645
|
+
if (entry === void 0) {
|
|
5646
|
+
return errorResponse(c, 404, "not_found", "Sandbox not found");
|
|
5647
|
+
}
|
|
5648
|
+
try {
|
|
5649
|
+
const requestedPath = c.req.query("path");
|
|
5650
|
+
if (requestedPath === void 0) {
|
|
5651
|
+
throw new SandboxRouteError(400, "bad_request", "path query parameter is required");
|
|
5652
|
+
}
|
|
5653
|
+
const download = c.req.query("download");
|
|
5654
|
+
if (download !== void 0 && download !== "1") {
|
|
5655
|
+
throw new SandboxRouteError(400, "bad_request", "download must be 1 when provided");
|
|
5656
|
+
}
|
|
5657
|
+
const normalizedPath = normalizeSandboxPath(requestedPath, false);
|
|
5658
|
+
const parentPath = path.posix.dirname(normalizedPath);
|
|
5659
|
+
const siblings = await listSandboxFiles(entry.session, parentPath);
|
|
5660
|
+
const file = siblings.find((candidate) => candidate.path === normalizedPath);
|
|
5661
|
+
if (file === void 0) {
|
|
5662
|
+
throw new SandboxRouteError(404, "not_found", "Sandbox file not found");
|
|
5663
|
+
}
|
|
5664
|
+
if (file.type !== "file") {
|
|
5665
|
+
throw new SandboxRouteError(
|
|
5666
|
+
400,
|
|
5667
|
+
"bad_request",
|
|
5668
|
+
"Sandbox content path must refer to a regular file"
|
|
5669
|
+
);
|
|
5670
|
+
}
|
|
5671
|
+
if (file.size !== void 0 && file.size > maxFileResponseBytes) {
|
|
5672
|
+
throw fileTooLarge(file.size);
|
|
5673
|
+
}
|
|
5674
|
+
const rawBytes = await entry.session.readFile(normalizedPath);
|
|
5675
|
+
if (!(rawBytes instanceof Uint8Array)) {
|
|
5676
|
+
throw new TypeError("Sandbox readFile returned an invalid byte payload");
|
|
5677
|
+
}
|
|
5678
|
+
if (rawBytes.byteLength > maxFileResponseBytes) {
|
|
5679
|
+
throw fileTooLarge(rawBytes.byteLength);
|
|
5680
|
+
}
|
|
5681
|
+
const bytes = new Uint8Array(rawBytes.byteLength);
|
|
5682
|
+
bytes.set(rawBytes);
|
|
5683
|
+
const disposition = download === "1" ? "attachment" : "inline";
|
|
5684
|
+
const filename = encodeURIComponent(path.posix.basename(normalizedPath));
|
|
5685
|
+
return new Response(bytes, {
|
|
5686
|
+
headers: {
|
|
5687
|
+
"cache-control": "no-store",
|
|
5688
|
+
"content-disposition": `${disposition}; filename*=UTF-8''${filename}`,
|
|
5689
|
+
"content-length": String(bytes.byteLength),
|
|
5690
|
+
"content-type": "application/octet-stream",
|
|
5691
|
+
"x-content-type-options": "nosniff"
|
|
5692
|
+
}
|
|
5693
|
+
});
|
|
5694
|
+
} catch (error) {
|
|
5695
|
+
return sandboxErrorResponse(c, error);
|
|
5696
|
+
}
|
|
5697
|
+
});
|
|
5698
|
+
app.get("/sandboxes/:sandboxRef/ports", (c) => {
|
|
5699
|
+
const entry = registry.get(c.req.param("sandboxRef"));
|
|
5700
|
+
if (entry === void 0) {
|
|
5701
|
+
return errorResponse(c, 404, "not_found", "Sandbox not found");
|
|
5702
|
+
}
|
|
5703
|
+
if (!supportsPorts(entry.session)) {
|
|
5704
|
+
return unsupportedSandboxOperation(c, "ports");
|
|
5705
|
+
}
|
|
5706
|
+
try {
|
|
5707
|
+
return c.json({
|
|
5708
|
+
sandboxRef: entry.summary.ref,
|
|
5709
|
+
ports: normalizePorts(entry.session.publishedPorts)
|
|
5710
|
+
});
|
|
5711
|
+
} catch (error) {
|
|
5712
|
+
return sandboxErrorResponse(c, error);
|
|
5713
|
+
}
|
|
5714
|
+
});
|
|
5715
|
+
app.get("/sandboxes/:sandboxRef/processes", async (c) => {
|
|
5716
|
+
const entry = registry.get(c.req.param("sandboxRef"));
|
|
5717
|
+
if (entry === void 0) {
|
|
5718
|
+
return errorResponse(c, 404, "not_found", "Sandbox not found");
|
|
5719
|
+
}
|
|
5720
|
+
if (!supportsProcesses(entry.session)) {
|
|
5721
|
+
return unsupportedSandboxOperation(c, "processes");
|
|
5722
|
+
}
|
|
5723
|
+
try {
|
|
5724
|
+
return c.json({
|
|
5725
|
+
sandboxRef: entry.summary.ref,
|
|
5726
|
+
processes: normalizeProcesses(await entry.session.listProcesses())
|
|
5727
|
+
});
|
|
5728
|
+
} catch (error) {
|
|
5729
|
+
return sandboxErrorResponse(c, error);
|
|
5730
|
+
}
|
|
5731
|
+
});
|
|
5732
|
+
app.get("/sandboxes/:sandboxRef/processes/:processId/logs", async (c) => {
|
|
5733
|
+
const entry = registry.get(c.req.param("sandboxRef"));
|
|
5734
|
+
if (entry === void 0) {
|
|
5735
|
+
return errorResponse(c, 404, "not_found", "Sandbox not found");
|
|
5736
|
+
}
|
|
5737
|
+
if (!supportsProcesses(entry.session)) {
|
|
5738
|
+
return unsupportedSandboxOperation(c, "processes.logs");
|
|
5739
|
+
}
|
|
5740
|
+
try {
|
|
5741
|
+
const tailBytes = parseTailBytes(c.req.query("tailBytes"));
|
|
5742
|
+
const processId = c.req.param("processId");
|
|
5743
|
+
const processes = normalizeProcesses(await entry.session.listProcesses());
|
|
5744
|
+
if (!processes.some((process2) => process2.id === processId)) {
|
|
5745
|
+
throw new SandboxRouteError(404, "not_found", "Sandbox process not found");
|
|
5746
|
+
}
|
|
5747
|
+
const logs = normalizeProcessLogs(
|
|
5748
|
+
await entry.session.readProcessLogs(processId, { tailBytes }),
|
|
5749
|
+
tailBytes
|
|
5750
|
+
);
|
|
5751
|
+
return c.json({
|
|
5752
|
+
sandboxRef: entry.summary.ref,
|
|
5753
|
+
processId,
|
|
5754
|
+
...logs
|
|
5755
|
+
});
|
|
5756
|
+
} catch (error) {
|
|
5757
|
+
return sandboxErrorResponse(c, error);
|
|
5758
|
+
}
|
|
5759
|
+
});
|
|
5760
|
+
}
|
|
5761
|
+
async function sandboxNoStore(c, next) {
|
|
5762
|
+
c.header("cache-control", "no-store");
|
|
5763
|
+
await next();
|
|
5764
|
+
}
|
|
5765
|
+
function sandboxSessionFromTool(tool) {
|
|
5766
|
+
const metadata = tool[sandboxToolMetadataKey];
|
|
5767
|
+
if (!isRecord4(metadata)) {
|
|
5768
|
+
return void 0;
|
|
5769
|
+
}
|
|
5770
|
+
return isSandboxSession(metadata.session) ? metadata.session : void 0;
|
|
5771
|
+
}
|
|
5772
|
+
function isSandboxSession(value) {
|
|
5773
|
+
return isRecord4(value) && typeof value.id === "string" && value.id.length > 0 && typeof value.provider === "string" && value.provider.length > 0 && typeof value.workdir === "string" && value.workdir.length > 0 && typeof value.listFiles === "function" && typeof value.readFile === "function";
|
|
5774
|
+
}
|
|
5775
|
+
function supportsPorts(session) {
|
|
5776
|
+
return Array.isArray(session.publishedPorts);
|
|
5777
|
+
}
|
|
5778
|
+
function supportsProcesses(session) {
|
|
5779
|
+
return typeof session.listProcesses === "function" && typeof session.readProcessLogs === "function";
|
|
5780
|
+
}
|
|
5781
|
+
function sandboxRef(provider, id) {
|
|
5782
|
+
return Buffer.from(JSON.stringify([provider, id]), "utf8").toString("base64url");
|
|
5783
|
+
}
|
|
5784
|
+
function copySummary(summary) {
|
|
5785
|
+
return {
|
|
5786
|
+
...summary,
|
|
5787
|
+
agentIds: [...summary.agentIds],
|
|
5788
|
+
toolNames: [...summary.toolNames],
|
|
5789
|
+
capabilities: { ...summary.capabilities }
|
|
5790
|
+
};
|
|
5791
|
+
}
|
|
5792
|
+
async function listSandboxFiles(session, filePath) {
|
|
5793
|
+
return normalizeFileEntries(await session.listFiles(filePath)).sort((left, right) => {
|
|
5794
|
+
if (left.type === "directory" && right.type !== "directory") return -1;
|
|
5795
|
+
if (left.type !== "directory" && right.type === "directory") return 1;
|
|
5796
|
+
return left.path.localeCompare(right.path);
|
|
5797
|
+
});
|
|
5798
|
+
}
|
|
5799
|
+
function normalizeFileEntries(value) {
|
|
5800
|
+
if (!Array.isArray(value)) {
|
|
5801
|
+
throw new TypeError("Sandbox listFiles returned an invalid payload");
|
|
5802
|
+
}
|
|
5803
|
+
return value.map((item) => {
|
|
5804
|
+
if (!isRecord4(item) || typeof item.path !== "string" || !isFileType(item.type)) {
|
|
5805
|
+
throw new TypeError("Sandbox listFiles returned an invalid entry");
|
|
5806
|
+
}
|
|
5807
|
+
let entryPath;
|
|
5808
|
+
try {
|
|
5809
|
+
entryPath = normalizeSandboxPath(item.path, false);
|
|
5810
|
+
} catch {
|
|
5811
|
+
throw new TypeError("Sandbox listFiles returned an invalid entry path");
|
|
5812
|
+
}
|
|
5813
|
+
const entry = { path: entryPath, type: item.type };
|
|
5814
|
+
if (item.size !== void 0) {
|
|
5815
|
+
if (typeof item.size !== "number" || !Number.isFinite(item.size) || item.size < 0) {
|
|
5816
|
+
throw new TypeError("Sandbox listFiles returned an invalid file size");
|
|
5817
|
+
}
|
|
5818
|
+
entry.size = item.size;
|
|
5819
|
+
}
|
|
5820
|
+
return entry;
|
|
5821
|
+
});
|
|
5822
|
+
}
|
|
5823
|
+
function normalizePorts(value) {
|
|
5824
|
+
if (!Array.isArray(value)) {
|
|
5825
|
+
throw new TypeError("Sandbox publishedPorts returned an invalid payload");
|
|
5826
|
+
}
|
|
5827
|
+
return value.map((item) => {
|
|
5828
|
+
if (!isRecord4(item) || !isPort(item.containerPort) || typeof item.host !== "string" || !isPort(item.hostPort) || typeof item.protocol !== "string") {
|
|
5829
|
+
throw new TypeError("Sandbox publishedPorts returned an invalid entry");
|
|
5830
|
+
}
|
|
5831
|
+
return {
|
|
5832
|
+
containerPort: item.containerPort,
|
|
5833
|
+
host: item.host,
|
|
5834
|
+
hostPort: item.hostPort,
|
|
5835
|
+
protocol: item.protocol
|
|
5836
|
+
};
|
|
5837
|
+
});
|
|
5838
|
+
}
|
|
5839
|
+
function normalizeProcesses(value) {
|
|
5840
|
+
if (!Array.isArray(value)) {
|
|
5841
|
+
throw new TypeError("Sandbox listProcesses returned an invalid payload");
|
|
5842
|
+
}
|
|
5843
|
+
return value.map((item) => {
|
|
5844
|
+
if (!isRecord4(item) || typeof item.id !== "string" || typeof item.command !== "string" || !Array.isArray(item.args) || !item.args.every((arg) => typeof arg === "string") || !isProcessStatus(item.status) || typeof item.startedAt !== "string") {
|
|
5845
|
+
throw new TypeError("Sandbox listProcesses returned an invalid entry");
|
|
5846
|
+
}
|
|
5847
|
+
const process2 = {
|
|
5848
|
+
id: item.id,
|
|
5849
|
+
command: item.command,
|
|
5850
|
+
args: item.args,
|
|
5851
|
+
status: item.status,
|
|
5852
|
+
startedAt: item.startedAt
|
|
5853
|
+
};
|
|
5854
|
+
if (typeof item.cwd === "string") process2.cwd = item.cwd;
|
|
5855
|
+
if (typeof item.exitCode === "number") process2.exitCode = item.exitCode;
|
|
5856
|
+
if (typeof item.endedAt === "string") process2.endedAt = item.endedAt;
|
|
5857
|
+
return process2;
|
|
5858
|
+
});
|
|
5859
|
+
}
|
|
5860
|
+
function normalizeProcessLogs(value, maxBytes) {
|
|
5861
|
+
if (!isRecord4(value) || typeof value.stdout !== "string" || typeof value.stderr !== "string" || typeof value.stdoutTruncated !== "boolean" || typeof value.stderrTruncated !== "boolean") {
|
|
5862
|
+
throw new TypeError("Sandbox readProcessLogs returned an invalid payload");
|
|
5863
|
+
}
|
|
5864
|
+
const stdout = boundedLogText(value.stdout, maxBytes);
|
|
5865
|
+
const stderr = boundedLogText(value.stderr, maxBytes);
|
|
5866
|
+
return {
|
|
5867
|
+
stdout: stdout.text,
|
|
5868
|
+
stderr: stderr.text,
|
|
5869
|
+
stdoutTruncated: value.stdoutTruncated || stdout.truncated,
|
|
5870
|
+
stderrTruncated: value.stderrTruncated || stderr.truncated
|
|
5871
|
+
};
|
|
5872
|
+
}
|
|
5873
|
+
function boundedLogText(value, maxBytes) {
|
|
5874
|
+
const bytes = new TextEncoder().encode(value);
|
|
5875
|
+
if (bytes.byteLength <= maxBytes) {
|
|
5876
|
+
return { text: value, truncated: false };
|
|
5877
|
+
}
|
|
5878
|
+
if (maxBytes === 0) {
|
|
5879
|
+
return { text: "", truncated: true };
|
|
5880
|
+
}
|
|
5881
|
+
let start = bytes.byteLength - maxBytes;
|
|
5882
|
+
while (start < bytes.byteLength && (bytes[start] ?? 0) >> 6 === 2) {
|
|
5883
|
+
start += 1;
|
|
5884
|
+
}
|
|
5885
|
+
return {
|
|
5886
|
+
text: new TextDecoder().decode(bytes.subarray(start)),
|
|
5887
|
+
truncated: true
|
|
5888
|
+
};
|
|
5889
|
+
}
|
|
5890
|
+
function normalizeSandboxPath(input, allowRoot) {
|
|
5891
|
+
if (input.length === 0) {
|
|
5892
|
+
throw new SandboxRouteError(400, "bad_request", "Sandbox path cannot be empty");
|
|
5893
|
+
}
|
|
5894
|
+
if (input.includes("\0")) {
|
|
5895
|
+
throw new SandboxRouteError(400, "bad_request", "Sandbox path cannot contain null bytes");
|
|
5896
|
+
}
|
|
5897
|
+
const normalized = path.posix.normalize(input.replaceAll("\\", "/"));
|
|
5898
|
+
if (path.posix.isAbsolute(normalized)) {
|
|
5899
|
+
throw new SandboxRouteError(400, "bad_request", "Sandbox path must be relative");
|
|
5900
|
+
}
|
|
5901
|
+
if (normalized === ".." || normalized.startsWith("../")) {
|
|
5902
|
+
throw new SandboxRouteError(400, "bad_request", "Sandbox path cannot leave the workspace");
|
|
5903
|
+
}
|
|
5904
|
+
if (normalized === "." && !allowRoot) {
|
|
5905
|
+
throw new SandboxRouteError(400, "bad_request", "Sandbox path must refer to a file");
|
|
5906
|
+
}
|
|
5907
|
+
return normalized;
|
|
5908
|
+
}
|
|
5909
|
+
function parseTailBytes(value) {
|
|
5910
|
+
if (value === void 0) {
|
|
5911
|
+
return defaultProcessLogBytes;
|
|
5912
|
+
}
|
|
5913
|
+
const parsed = Number(value);
|
|
5914
|
+
if (!Number.isInteger(parsed) || parsed < 0 || parsed > maxProcessLogBytes) {
|
|
5915
|
+
throw new SandboxRouteError(
|
|
5916
|
+
400,
|
|
5917
|
+
"bad_request",
|
|
5918
|
+
`tailBytes must be an integer from 0 through ${maxProcessLogBytes}`
|
|
5919
|
+
);
|
|
5920
|
+
}
|
|
5921
|
+
return parsed;
|
|
5922
|
+
}
|
|
5923
|
+
function unsupportedSandboxOperation(c, operation) {
|
|
5924
|
+
return errorResponse(c, 501, "unsupported_capability", `Sandbox does not support ${operation}`, {
|
|
5925
|
+
capability: "sandboxes",
|
|
5926
|
+
operation
|
|
5927
|
+
});
|
|
5928
|
+
}
|
|
5929
|
+
function sandboxErrorResponse(c, error) {
|
|
5930
|
+
if (error instanceof SandboxRouteError) {
|
|
5931
|
+
return errorResponse(c, error.status, error.code, error.message);
|
|
5932
|
+
}
|
|
5933
|
+
const name = errorName(error);
|
|
5934
|
+
if (name === "SandboxPathError") {
|
|
5935
|
+
return errorResponse(c, 400, "bad_request", errorMessage(error));
|
|
5936
|
+
}
|
|
5937
|
+
if (name === "SandboxFileSizeError") {
|
|
5938
|
+
return errorResponse(c, 413, "payload_too_large", errorMessage(error));
|
|
5939
|
+
}
|
|
5940
|
+
if (name === "SandboxSessionDestroyedError") {
|
|
5941
|
+
return errorResponse(c, 409, "conflict", "Sandbox session is no longer available");
|
|
5942
|
+
}
|
|
5943
|
+
return errorResponse(
|
|
5944
|
+
c,
|
|
5945
|
+
500,
|
|
5946
|
+
"internal_error",
|
|
5947
|
+
"Sandbox inspection failed",
|
|
5948
|
+
serializeError(error)
|
|
5949
|
+
);
|
|
5950
|
+
}
|
|
5951
|
+
function fileTooLarge(size) {
|
|
5952
|
+
return new SandboxRouteError(
|
|
5953
|
+
413,
|
|
5954
|
+
"payload_too_large",
|
|
5955
|
+
`Sandbox file exceeds the ${maxFileResponseBytes}-byte response limit (${size} bytes)`
|
|
5956
|
+
);
|
|
5957
|
+
}
|
|
5958
|
+
var SandboxRouteError = class extends Error {
|
|
5959
|
+
constructor(status, code, message) {
|
|
5960
|
+
super(message);
|
|
5961
|
+
this.status = status;
|
|
5962
|
+
this.code = code;
|
|
5963
|
+
}
|
|
5964
|
+
status;
|
|
5965
|
+
code;
|
|
5966
|
+
};
|
|
5967
|
+
function isFileType(value) {
|
|
5968
|
+
return value === "file" || value === "directory" || value === "symlink" || value === "other";
|
|
5969
|
+
}
|
|
5970
|
+
function isProcessStatus(value) {
|
|
5971
|
+
return value === "running" || value === "exited" || value === "stopped";
|
|
5972
|
+
}
|
|
5973
|
+
function isPort(value) {
|
|
5974
|
+
return Number.isInteger(value) && Number(value) >= 1 && Number(value) <= 65535;
|
|
5975
|
+
}
|
|
5976
|
+
function isRecord4(value) {
|
|
5977
|
+
return typeof value === "object" && value !== null;
|
|
5978
|
+
}
|
|
5979
|
+
function errorName(error) {
|
|
5980
|
+
return isRecord4(error) && typeof error.name === "string" ? error.name : "";
|
|
5981
|
+
}
|
|
5982
|
+
function errorMessage(error) {
|
|
5983
|
+
return isRecord4(error) && typeof error.message === "string" ? error.message : String(error);
|
|
5984
|
+
}
|
|
5985
|
+
|
|
5020
5986
|
// src/runtime/sessions.ts
|
|
5021
5987
|
function registerSessionRoutes(app, props) {
|
|
5022
5988
|
app.get("/sessions", async (c) => {
|
|
@@ -5505,6 +6471,7 @@ function registerStatusRoutes(app, props) {
|
|
|
5505
6471
|
agents: props.agents.length,
|
|
5506
6472
|
pipelines: props.pipelines.length
|
|
5507
6473
|
};
|
|
6474
|
+
if (props.sandboxCount > 0) counts.sandboxes = props.sandboxCount;
|
|
5508
6475
|
if (props.stores.sessions !== void 0) {
|
|
5509
6476
|
counts.sessions = (await props.stores.sessions.listSessions({ limit: 100 })).length;
|
|
5510
6477
|
}
|
|
@@ -5525,7 +6492,13 @@ function registerStatusRoutes(app, props) {
|
|
|
5525
6492
|
runner,
|
|
5526
6493
|
storage,
|
|
5527
6494
|
counts,
|
|
5528
|
-
capabilities: capabilityConfig(
|
|
6495
|
+
capabilities: capabilityConfig(
|
|
6496
|
+
props.options,
|
|
6497
|
+
props.agents,
|
|
6498
|
+
props.pipelines,
|
|
6499
|
+
props.stores,
|
|
6500
|
+
props.sandboxCount
|
|
6501
|
+
),
|
|
5529
6502
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5530
6503
|
};
|
|
5531
6504
|
return c.json(summary);
|
|
@@ -5734,20 +6707,38 @@ var Studio = class {
|
|
|
5734
6707
|
const log = serveOptions.log ?? true;
|
|
5735
6708
|
if (log) {
|
|
5736
6709
|
const host = serveOptions.hostname ?? "localhost";
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
6710
|
+
this.logAddress(host, port);
|
|
6711
|
+
}
|
|
6712
|
+
if (serveOptions.handleSignals ?? true) {
|
|
6713
|
+
this.sigintHandler = () => {
|
|
6714
|
+
this.close();
|
|
6715
|
+
process.exit(0);
|
|
6716
|
+
};
|
|
6717
|
+
process.once("SIGINT", this.sigintHandler);
|
|
5743
6718
|
}
|
|
5744
|
-
this.sigintHandler = () => {
|
|
5745
|
-
this.close();
|
|
5746
|
-
process.exit(0);
|
|
5747
|
-
};
|
|
5748
|
-
process.once("SIGINT", this.sigintHandler);
|
|
5749
6719
|
return this;
|
|
5750
6720
|
}
|
|
6721
|
+
async serve(serveOptions = {}) {
|
|
6722
|
+
const { onShutdown, signal, ...startOptions } = serveOptions;
|
|
6723
|
+
try {
|
|
6724
|
+
this.start({ ...startOptions, log: false, handleSignals: false });
|
|
6725
|
+
const server = this.server;
|
|
6726
|
+
if (server === void 0) {
|
|
6727
|
+
throw new Error("Studio server did not start");
|
|
6728
|
+
}
|
|
6729
|
+
await waitForServerListening(server);
|
|
6730
|
+
if (startOptions.log ?? true) {
|
|
6731
|
+
const requestedPort = startOptions.port ?? Number(process.env.RUNNER_PORT ?? 4021);
|
|
6732
|
+
const address = server.address();
|
|
6733
|
+
const port = typeof address === "object" && address !== null ? address.port : requestedPort;
|
|
6734
|
+
this.logAddress(startOptions.hostname ?? "localhost", port);
|
|
6735
|
+
}
|
|
6736
|
+
await waitForShutdown(signal);
|
|
6737
|
+
} finally {
|
|
6738
|
+
this.close();
|
|
6739
|
+
await onShutdown?.();
|
|
6740
|
+
}
|
|
6741
|
+
}
|
|
5751
6742
|
close() {
|
|
5752
6743
|
if (this.sigintHandler !== void 0) {
|
|
5753
6744
|
process.off("SIGINT", this.sigintHandler);
|
|
@@ -5757,7 +6748,68 @@ var Studio = class {
|
|
|
5757
6748
|
this.server = void 0;
|
|
5758
6749
|
this.studio.close();
|
|
5759
6750
|
}
|
|
6751
|
+
logAddress(host, port) {
|
|
6752
|
+
if (isStudioUiEnabled(this.options.ui)) {
|
|
6753
|
+
const uiPath = studioUiEntryPath(resolveStudioUiOptions(this.options.ui));
|
|
6754
|
+
console.log(`Studio UI: http://${host}:${port}${uiPath}`);
|
|
6755
|
+
} else {
|
|
6756
|
+
console.log(`Studio API: http://${host}:${port}`);
|
|
6757
|
+
}
|
|
6758
|
+
}
|
|
5760
6759
|
};
|
|
6760
|
+
function waitForServerListening(server) {
|
|
6761
|
+
if (server.listening) return Promise.resolve();
|
|
6762
|
+
return new Promise((resolve2, reject) => {
|
|
6763
|
+
const cleanup = () => {
|
|
6764
|
+
server.off("listening", onListening);
|
|
6765
|
+
server.off("error", onError);
|
|
6766
|
+
};
|
|
6767
|
+
const onListening = () => {
|
|
6768
|
+
cleanup();
|
|
6769
|
+
resolve2();
|
|
6770
|
+
};
|
|
6771
|
+
const onError = (error) => {
|
|
6772
|
+
cleanup();
|
|
6773
|
+
reject(error);
|
|
6774
|
+
};
|
|
6775
|
+
server.once("listening", onListening);
|
|
6776
|
+
server.once("error", onError);
|
|
6777
|
+
});
|
|
6778
|
+
}
|
|
6779
|
+
function waitForShutdown(signal) {
|
|
6780
|
+
if (signal?.aborted) return Promise.resolve();
|
|
6781
|
+
return new Promise((resolve2) => {
|
|
6782
|
+
const useRawInput = process.stdin.isTTY === true;
|
|
6783
|
+
const wasRaw = process.stdin.isRaw;
|
|
6784
|
+
const wasFlowing = process.stdin.readableFlowing;
|
|
6785
|
+
const cleanup = () => {
|
|
6786
|
+
process.off("SIGINT", finish);
|
|
6787
|
+
process.off("SIGTERM", finish);
|
|
6788
|
+
signal?.removeEventListener("abort", finish);
|
|
6789
|
+
if (useRawInput) {
|
|
6790
|
+
process.stdin.off("data", onInput);
|
|
6791
|
+
if (!wasRaw && process.stdin.isRaw) process.stdin.setRawMode(false);
|
|
6792
|
+
if (wasFlowing !== true) process.stdin.pause();
|
|
6793
|
+
}
|
|
6794
|
+
};
|
|
6795
|
+
const finish = () => {
|
|
6796
|
+
cleanup();
|
|
6797
|
+
resolve2();
|
|
6798
|
+
};
|
|
6799
|
+
const onInput = (chunk) => {
|
|
6800
|
+
const interrupted = typeof chunk === "string" ? chunk.includes("") : chunk.includes(3);
|
|
6801
|
+
if (interrupted) finish();
|
|
6802
|
+
};
|
|
6803
|
+
process.once("SIGINT", finish);
|
|
6804
|
+
process.once("SIGTERM", finish);
|
|
6805
|
+
signal?.addEventListener("abort", finish, { once: true });
|
|
6806
|
+
if (useRawInput) {
|
|
6807
|
+
if (!wasRaw) process.stdin.setRawMode(true);
|
|
6808
|
+
process.stdin.resume();
|
|
6809
|
+
process.stdin.on("data", onInput);
|
|
6810
|
+
}
|
|
6811
|
+
});
|
|
6812
|
+
}
|
|
5761
6813
|
function studioOptionsFromTargets(targets, options) {
|
|
5762
6814
|
const agents = targets.filter((target) => target instanceof Agent2);
|
|
5763
6815
|
const pipelines = targets.filter(
|
|
@@ -5836,6 +6888,8 @@ function createStudioApp(options) {
|
|
|
5836
6888
|
const evalMap = new Map(options.evals.map((suite) => [suite.id ?? suite.name, suite]));
|
|
5837
6889
|
const approvalRuntime = createApprovalRuntime();
|
|
5838
6890
|
const questionRuntime = createQuestionRuntime();
|
|
6891
|
+
const sandboxRegistry = createStudioSandboxRegistry(agents);
|
|
6892
|
+
const memorySources = createStudioMemorySourceRegistry(agents, stores.sessions);
|
|
5839
6893
|
const app = new HonoApp();
|
|
5840
6894
|
const uiOptions = isStudioUiEnabled(options.ui) ? resolveStudioUiOptions(options.ui) : void 0;
|
|
5841
6895
|
if (uiOptions !== void 0 && !uiOptions.protectShell) {
|
|
@@ -5855,8 +6909,17 @@ function createStudioApp(options) {
|
|
|
5855
6909
|
}
|
|
5856
6910
|
})
|
|
5857
6911
|
);
|
|
5858
|
-
app.get(
|
|
5859
|
-
|
|
6912
|
+
app.get(
|
|
6913
|
+
"/config",
|
|
6914
|
+
(c) => c.json(buildConfig(options, agents, pipelines, stores, sandboxRegistry.size))
|
|
6915
|
+
);
|
|
6916
|
+
registerStatusRoutes(app, {
|
|
6917
|
+
options,
|
|
6918
|
+
agents,
|
|
6919
|
+
pipelines,
|
|
6920
|
+
stores,
|
|
6921
|
+
sandboxCount: sandboxRegistry.size
|
|
6922
|
+
});
|
|
5860
6923
|
app.get("/agents", (c) => c.json({ agents: agents.map(agentConfig) }));
|
|
5861
6924
|
app.get("/agents/:agentId", (c) => {
|
|
5862
6925
|
const agent = agentMap.get(c.req.param("agentId"));
|
|
@@ -5875,6 +6938,7 @@ function createStudioApp(options) {
|
|
|
5875
6938
|
registerModelRoutes(app, { registry: modelRegistry, agentMap });
|
|
5876
6939
|
registerMcpRoutes(app, { agentMap });
|
|
5877
6940
|
registerToolRoutes(app, { agentMap });
|
|
6941
|
+
registerSandboxRoutes(app, sandboxRegistry);
|
|
5878
6942
|
registerApprovalRoutes(app, approvalRuntime);
|
|
5879
6943
|
registerQuestionRoutes(app, questionRuntime);
|
|
5880
6944
|
registerObservabilityRoutes(app, observabilityHub);
|
|
@@ -5899,10 +6963,13 @@ function createStudioApp(options) {
|
|
|
5899
6963
|
approvalRuntime,
|
|
5900
6964
|
questionRuntime
|
|
5901
6965
|
});
|
|
5902
|
-
if (stores.sessions !== void 0) {
|
|
6966
|
+
if (memorySources.size > 0 || stores.sessions !== void 0) {
|
|
5903
6967
|
registerMemoryRoutes(app, {
|
|
6968
|
+
sources: memorySources,
|
|
5904
6969
|
sessionStore: stores.sessions
|
|
5905
6970
|
});
|
|
6971
|
+
}
|
|
6972
|
+
if (stores.sessions !== void 0) {
|
|
5906
6973
|
const sessionOptions = {
|
|
5907
6974
|
agentMap,
|
|
5908
6975
|
sessionStore: stores.sessions
|
|
@@ -5923,7 +6990,7 @@ function createStudioApp(options) {
|
|
|
5923
6990
|
return app.fetch(request);
|
|
5924
6991
|
},
|
|
5925
6992
|
config() {
|
|
5926
|
-
return buildConfig(options, agents, pipelines, stores);
|
|
6993
|
+
return buildConfig(options, agents, pipelines, stores, sandboxRegistry.size);
|
|
5927
6994
|
},
|
|
5928
6995
|
close() {
|
|
5929
6996
|
}
|
|
@@ -5962,8 +7029,8 @@ function createSqliteSessionStore(options = {}) {
|
|
|
5962
7029
|
return new SqliteSessionStore(options.path ?? ":memory:");
|
|
5963
7030
|
}
|
|
5964
7031
|
var SqliteSessionStore = class {
|
|
5965
|
-
constructor(
|
|
5966
|
-
this.path =
|
|
7032
|
+
constructor(path2) {
|
|
7033
|
+
this.path = path2;
|
|
5967
7034
|
}
|
|
5968
7035
|
path;
|
|
5969
7036
|
kind = "sqlite";
|