@anvia/studio 0.7.33 → 0.7.34
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 +78 -6
- package/dist/index.js +860 -77
- package/dist/index.js.map +1 -1
- package/dist/ui/assets/evals-page-CHxft1PA.js +2 -0
- package/dist/ui/assets/{evals-page-DN6aZ9dT.js.map → evals-page-CHxft1PA.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-DMB9ju7U.css +1 -0
- package/dist/ui/assets/index-DgQb2APn.js +55 -0
- package/dist/ui/assets/index-DgQb2APn.js.map +1 -0
- package/dist/ui/assets/{knowledge-page-DYwhR8Ly.js → knowledge-page-BIphUowG.js} +2 -2
- package/dist/ui/assets/{knowledge-page-DYwhR8Ly.js.map → knowledge-page-BIphUowG.js.map} +1 -1
- package/dist/ui/assets/{mcps-page-B3e3YF6D.js → mcps-page-CEyvZfpe.js} +2 -2
- package/dist/ui/assets/{mcps-page-B3e3YF6D.js.map → mcps-page-CEyvZfpe.js.map} +1 -1
- package/dist/ui/assets/{memory-page-BeR1NNHY.js → memory-page-C2Nph26O.js} +2 -2
- package/dist/ui/assets/{memory-page-BeR1NNHY.js.map → memory-page-C2Nph26O.js.map} +1 -1
- package/dist/ui/assets/{pipelines-page-DdYD59h4.js → pipelines-page-BllEnKpu.js} +3 -3
- package/dist/ui/assets/{pipelines-page-DdYD59h4.js.map → pipelines-page-BllEnKpu.js.map} +1 -1
- package/dist/ui/assets/{renderers-m-gqGocU.js → renderers-Dzjh-nGh.js} +2 -2
- package/dist/ui/assets/{renderers-m-gqGocU.js.map → renderers-Dzjh-nGh.js.map} +1 -1
- package/dist/ui/assets/sandboxes-page-B8GcUS5f.js +2 -0
- package/dist/ui/assets/sandboxes-page-B8GcUS5f.js.map +1 -0
- package/dist/ui/assets/{sessions-page-SAmGnFSU.js → sessions-page-BY2ysr2u.js} +2 -2
- package/dist/ui/assets/{sessions-page-SAmGnFSU.js.map → sessions-page-BY2ysr2u.js.map} +1 -1
- package/dist/ui/assets/{status-page-g__90tnX.js → status-page-CZ0HBJY5.js} +2 -2
- package/dist/ui/assets/{status-page-g__90tnX.js.map → status-page-CZ0HBJY5.js.map} +1 -1
- package/dist/ui/assets/tool-runner-CkQ3WBw4.js +2 -0
- package/dist/ui/assets/{tool-runner-CcqIP2r3.js.map → tool-runner-CkQ3WBw4.js.map} +1 -1
- package/dist/ui/assets/{tools-page-By0mx0ua.js → tools-page-CmVAorUe.js} +2 -2
- package/dist/ui/assets/{tools-page-By0mx0ua.js.map → tools-page-CmVAorUe.js.map} +1 -1
- package/dist/ui/assets/trace-browser-DekV4BNP.js +10 -0
- package/dist/ui/assets/{trace-browser-BDet5qes.js.map → trace-browser-DekV4BNP.js.map} +1 -1
- package/dist/ui/assets/transcript-item-CQCD2CY3.js +3 -0
- package/dist/ui/assets/transcript-item-CQCD2CY3.js.map +1 -0
- package/dist/ui/index.html +3 -3
- package/package.json +2 -2
- 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/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 },
|
|
@@ -3627,6 +3849,9 @@ function capabilityConfig(_options, agents, pipelines, stores) {
|
|
|
3627
3849
|
if (agents.some(agentHasMcpTools)) {
|
|
3628
3850
|
capabilities.mcps = { enabled: true };
|
|
3629
3851
|
}
|
|
3852
|
+
if (sandboxCount > 0) {
|
|
3853
|
+
capabilities.sandboxes = { enabled: true };
|
|
3854
|
+
}
|
|
3630
3855
|
if (agents.some(
|
|
3631
3856
|
(agent) => agent.agent.hook !== void 0 || agent.agent.toolSet.values().some((tool) => tool.approval)
|
|
3632
3857
|
)) {
|
|
@@ -4910,6 +5135,20 @@ function createQuestionRuntime() {
|
|
|
4910
5135
|
return true;
|
|
4911
5136
|
}).map(publicQuestion);
|
|
4912
5137
|
},
|
|
5138
|
+
cancelRun(runId) {
|
|
5139
|
+
const cancelled = [];
|
|
5140
|
+
for (const question of questions.values()) {
|
|
5141
|
+
if (!isPendingQuestion(question) || question.runId !== runId) {
|
|
5142
|
+
continue;
|
|
5143
|
+
}
|
|
5144
|
+
const resolved = cancelQuestion(question);
|
|
5145
|
+
questions.set(question.id, resolved);
|
|
5146
|
+
question.emit?.({ type: "tool_question_result", question: resolved });
|
|
5147
|
+
question.resolve([]);
|
|
5148
|
+
cancelled.push(resolved);
|
|
5149
|
+
}
|
|
5150
|
+
return cancelled;
|
|
5151
|
+
},
|
|
4913
5152
|
answer(id, answers) {
|
|
4914
5153
|
const question = questions.get(id);
|
|
4915
5154
|
if (question === void 0) {
|
|
@@ -5010,6 +5249,13 @@ function resolveQuestion(question, answers) {
|
|
|
5010
5249
|
answers
|
|
5011
5250
|
});
|
|
5012
5251
|
}
|
|
5252
|
+
function cancelQuestion(question) {
|
|
5253
|
+
return publicQuestion({
|
|
5254
|
+
...question,
|
|
5255
|
+
status: "cancelled",
|
|
5256
|
+
cancelledAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5257
|
+
});
|
|
5258
|
+
}
|
|
5013
5259
|
function publicQuestion(question) {
|
|
5014
5260
|
const { emit, resolve: resolve2, ...rest } = question;
|
|
5015
5261
|
void emit;
|
|
@@ -5017,6 +5263,446 @@ function publicQuestion(question) {
|
|
|
5017
5263
|
return rest;
|
|
5018
5264
|
}
|
|
5019
5265
|
|
|
5266
|
+
// src/runtime/sandboxes.ts
|
|
5267
|
+
import path from "path";
|
|
5268
|
+
var sandboxToolMetadataKey = /* @__PURE__ */ Symbol.for("anvia.sandbox.tool.metadata");
|
|
5269
|
+
var maxFileResponseBytes = 10 * 1024 * 1024;
|
|
5270
|
+
var defaultProcessLogBytes = 64 * 1024;
|
|
5271
|
+
var maxProcessLogBytes = 1024 * 1024;
|
|
5272
|
+
var StudioSandboxRegistry = class {
|
|
5273
|
+
entries;
|
|
5274
|
+
constructor(entries) {
|
|
5275
|
+
this.entries = new Map(entries.map((entry) => [entry.summary.ref, entry]));
|
|
5276
|
+
}
|
|
5277
|
+
get size() {
|
|
5278
|
+
return this.entries.size;
|
|
5279
|
+
}
|
|
5280
|
+
list() {
|
|
5281
|
+
return [...this.entries.values()].map((entry) => copySummary(entry.summary));
|
|
5282
|
+
}
|
|
5283
|
+
get(ref) {
|
|
5284
|
+
return this.entries.get(ref);
|
|
5285
|
+
}
|
|
5286
|
+
};
|
|
5287
|
+
function createStudioSandboxRegistry(agents) {
|
|
5288
|
+
const drafts = /* @__PURE__ */ new Map();
|
|
5289
|
+
for (const agent of agents) {
|
|
5290
|
+
for (const { tool } of agentToolItems(agent)) {
|
|
5291
|
+
const session = sandboxSessionFromTool(tool);
|
|
5292
|
+
if (session === void 0) {
|
|
5293
|
+
continue;
|
|
5294
|
+
}
|
|
5295
|
+
const draft = drafts.get(session) ?? {
|
|
5296
|
+
session,
|
|
5297
|
+
agentIds: /* @__PURE__ */ new Set(),
|
|
5298
|
+
toolNames: /* @__PURE__ */ new Set()
|
|
5299
|
+
};
|
|
5300
|
+
draft.agentIds.add(agent.id);
|
|
5301
|
+
draft.toolNames.add(tool.name);
|
|
5302
|
+
drafts.set(session, draft);
|
|
5303
|
+
}
|
|
5304
|
+
}
|
|
5305
|
+
const refCounts = /* @__PURE__ */ new Map();
|
|
5306
|
+
const entries = [...drafts.values()].sort(
|
|
5307
|
+
(left, right) => `${left.session.provider}\0${left.session.id}`.localeCompare(
|
|
5308
|
+
`${right.session.provider}\0${right.session.id}`
|
|
5309
|
+
)
|
|
5310
|
+
).map((draft) => {
|
|
5311
|
+
const baseRef = sandboxRef(draft.session.provider, draft.session.id);
|
|
5312
|
+
const occurrence = (refCounts.get(baseRef) ?? 0) + 1;
|
|
5313
|
+
refCounts.set(baseRef, occurrence);
|
|
5314
|
+
const ref = occurrence === 1 ? baseRef : `${baseRef}.${occurrence}`;
|
|
5315
|
+
return {
|
|
5316
|
+
session: draft.session,
|
|
5317
|
+
summary: {
|
|
5318
|
+
ref,
|
|
5319
|
+
id: draft.session.id,
|
|
5320
|
+
provider: draft.session.provider,
|
|
5321
|
+
workdir: draft.session.workdir,
|
|
5322
|
+
agentIds: [...draft.agentIds].sort(),
|
|
5323
|
+
toolNames: [...draft.toolNames].sort(),
|
|
5324
|
+
capabilities: {
|
|
5325
|
+
files: true,
|
|
5326
|
+
ports: supportsPorts(draft.session),
|
|
5327
|
+
processes: supportsProcesses(draft.session)
|
|
5328
|
+
}
|
|
5329
|
+
}
|
|
5330
|
+
};
|
|
5331
|
+
});
|
|
5332
|
+
return new StudioSandboxRegistry(entries);
|
|
5333
|
+
}
|
|
5334
|
+
function registerSandboxRoutes(app, registry) {
|
|
5335
|
+
app.use("/sandboxes", sandboxNoStore);
|
|
5336
|
+
app.use("/sandboxes/*", sandboxNoStore);
|
|
5337
|
+
app.get("/sandboxes", (c) => c.json({ sandboxes: registry.list() }));
|
|
5338
|
+
app.get("/sandboxes/:sandboxRef", (c) => {
|
|
5339
|
+
const entry = registry.get(c.req.param("sandboxRef"));
|
|
5340
|
+
if (entry === void 0) {
|
|
5341
|
+
return errorResponse(c, 404, "not_found", "Sandbox not found");
|
|
5342
|
+
}
|
|
5343
|
+
return c.json(copySummary(entry.summary));
|
|
5344
|
+
});
|
|
5345
|
+
app.get("/sandboxes/:sandboxRef/files", async (c) => {
|
|
5346
|
+
const entry = registry.get(c.req.param("sandboxRef"));
|
|
5347
|
+
if (entry === void 0) {
|
|
5348
|
+
return errorResponse(c, 404, "not_found", "Sandbox not found");
|
|
5349
|
+
}
|
|
5350
|
+
try {
|
|
5351
|
+
const requestedPath = c.req.query("path") ?? ".";
|
|
5352
|
+
const normalizedPath = normalizeSandboxPath(requestedPath, true);
|
|
5353
|
+
const entries = await listSandboxFiles(entry.session, normalizedPath);
|
|
5354
|
+
return c.json({
|
|
5355
|
+
sandboxRef: entry.summary.ref,
|
|
5356
|
+
path: normalizedPath,
|
|
5357
|
+
entries
|
|
5358
|
+
});
|
|
5359
|
+
} catch (error) {
|
|
5360
|
+
return sandboxErrorResponse(c, error);
|
|
5361
|
+
}
|
|
5362
|
+
});
|
|
5363
|
+
app.get("/sandboxes/:sandboxRef/files/content", async (c) => {
|
|
5364
|
+
const entry = registry.get(c.req.param("sandboxRef"));
|
|
5365
|
+
if (entry === void 0) {
|
|
5366
|
+
return errorResponse(c, 404, "not_found", "Sandbox not found");
|
|
5367
|
+
}
|
|
5368
|
+
try {
|
|
5369
|
+
const requestedPath = c.req.query("path");
|
|
5370
|
+
if (requestedPath === void 0) {
|
|
5371
|
+
throw new SandboxRouteError(400, "bad_request", "path query parameter is required");
|
|
5372
|
+
}
|
|
5373
|
+
const download = c.req.query("download");
|
|
5374
|
+
if (download !== void 0 && download !== "1") {
|
|
5375
|
+
throw new SandboxRouteError(400, "bad_request", "download must be 1 when provided");
|
|
5376
|
+
}
|
|
5377
|
+
const normalizedPath = normalizeSandboxPath(requestedPath, false);
|
|
5378
|
+
const parentPath = path.posix.dirname(normalizedPath);
|
|
5379
|
+
const siblings = await listSandboxFiles(entry.session, parentPath);
|
|
5380
|
+
const file = siblings.find((candidate) => candidate.path === normalizedPath);
|
|
5381
|
+
if (file === void 0) {
|
|
5382
|
+
throw new SandboxRouteError(404, "not_found", "Sandbox file not found");
|
|
5383
|
+
}
|
|
5384
|
+
if (file.type !== "file") {
|
|
5385
|
+
throw new SandboxRouteError(
|
|
5386
|
+
400,
|
|
5387
|
+
"bad_request",
|
|
5388
|
+
"Sandbox content path must refer to a regular file"
|
|
5389
|
+
);
|
|
5390
|
+
}
|
|
5391
|
+
if (file.size !== void 0 && file.size > maxFileResponseBytes) {
|
|
5392
|
+
throw fileTooLarge(file.size);
|
|
5393
|
+
}
|
|
5394
|
+
const rawBytes = await entry.session.readFile(normalizedPath);
|
|
5395
|
+
if (!(rawBytes instanceof Uint8Array)) {
|
|
5396
|
+
throw new TypeError("Sandbox readFile returned an invalid byte payload");
|
|
5397
|
+
}
|
|
5398
|
+
if (rawBytes.byteLength > maxFileResponseBytes) {
|
|
5399
|
+
throw fileTooLarge(rawBytes.byteLength);
|
|
5400
|
+
}
|
|
5401
|
+
const bytes = new Uint8Array(rawBytes.byteLength);
|
|
5402
|
+
bytes.set(rawBytes);
|
|
5403
|
+
const disposition = download === "1" ? "attachment" : "inline";
|
|
5404
|
+
const filename = encodeURIComponent(path.posix.basename(normalizedPath));
|
|
5405
|
+
return new Response(bytes, {
|
|
5406
|
+
headers: {
|
|
5407
|
+
"cache-control": "no-store",
|
|
5408
|
+
"content-disposition": `${disposition}; filename*=UTF-8''${filename}`,
|
|
5409
|
+
"content-length": String(bytes.byteLength),
|
|
5410
|
+
"content-type": "application/octet-stream",
|
|
5411
|
+
"x-content-type-options": "nosniff"
|
|
5412
|
+
}
|
|
5413
|
+
});
|
|
5414
|
+
} catch (error) {
|
|
5415
|
+
return sandboxErrorResponse(c, error);
|
|
5416
|
+
}
|
|
5417
|
+
});
|
|
5418
|
+
app.get("/sandboxes/:sandboxRef/ports", (c) => {
|
|
5419
|
+
const entry = registry.get(c.req.param("sandboxRef"));
|
|
5420
|
+
if (entry === void 0) {
|
|
5421
|
+
return errorResponse(c, 404, "not_found", "Sandbox not found");
|
|
5422
|
+
}
|
|
5423
|
+
if (!supportsPorts(entry.session)) {
|
|
5424
|
+
return unsupportedSandboxOperation(c, "ports");
|
|
5425
|
+
}
|
|
5426
|
+
try {
|
|
5427
|
+
return c.json({
|
|
5428
|
+
sandboxRef: entry.summary.ref,
|
|
5429
|
+
ports: normalizePorts(entry.session.publishedPorts)
|
|
5430
|
+
});
|
|
5431
|
+
} catch (error) {
|
|
5432
|
+
return sandboxErrorResponse(c, error);
|
|
5433
|
+
}
|
|
5434
|
+
});
|
|
5435
|
+
app.get("/sandboxes/:sandboxRef/processes", async (c) => {
|
|
5436
|
+
const entry = registry.get(c.req.param("sandboxRef"));
|
|
5437
|
+
if (entry === void 0) {
|
|
5438
|
+
return errorResponse(c, 404, "not_found", "Sandbox not found");
|
|
5439
|
+
}
|
|
5440
|
+
if (!supportsProcesses(entry.session)) {
|
|
5441
|
+
return unsupportedSandboxOperation(c, "processes");
|
|
5442
|
+
}
|
|
5443
|
+
try {
|
|
5444
|
+
return c.json({
|
|
5445
|
+
sandboxRef: entry.summary.ref,
|
|
5446
|
+
processes: normalizeProcesses(await entry.session.listProcesses())
|
|
5447
|
+
});
|
|
5448
|
+
} catch (error) {
|
|
5449
|
+
return sandboxErrorResponse(c, error);
|
|
5450
|
+
}
|
|
5451
|
+
});
|
|
5452
|
+
app.get("/sandboxes/:sandboxRef/processes/:processId/logs", async (c) => {
|
|
5453
|
+
const entry = registry.get(c.req.param("sandboxRef"));
|
|
5454
|
+
if (entry === void 0) {
|
|
5455
|
+
return errorResponse(c, 404, "not_found", "Sandbox not found");
|
|
5456
|
+
}
|
|
5457
|
+
if (!supportsProcesses(entry.session)) {
|
|
5458
|
+
return unsupportedSandboxOperation(c, "processes.logs");
|
|
5459
|
+
}
|
|
5460
|
+
try {
|
|
5461
|
+
const tailBytes = parseTailBytes(c.req.query("tailBytes"));
|
|
5462
|
+
const processId = c.req.param("processId");
|
|
5463
|
+
const processes = normalizeProcesses(await entry.session.listProcesses());
|
|
5464
|
+
if (!processes.some((process2) => process2.id === processId)) {
|
|
5465
|
+
throw new SandboxRouteError(404, "not_found", "Sandbox process not found");
|
|
5466
|
+
}
|
|
5467
|
+
const logs = normalizeProcessLogs(
|
|
5468
|
+
await entry.session.readProcessLogs(processId, { tailBytes }),
|
|
5469
|
+
tailBytes
|
|
5470
|
+
);
|
|
5471
|
+
return c.json({
|
|
5472
|
+
sandboxRef: entry.summary.ref,
|
|
5473
|
+
processId,
|
|
5474
|
+
...logs
|
|
5475
|
+
});
|
|
5476
|
+
} catch (error) {
|
|
5477
|
+
return sandboxErrorResponse(c, error);
|
|
5478
|
+
}
|
|
5479
|
+
});
|
|
5480
|
+
}
|
|
5481
|
+
async function sandboxNoStore(c, next) {
|
|
5482
|
+
c.header("cache-control", "no-store");
|
|
5483
|
+
await next();
|
|
5484
|
+
}
|
|
5485
|
+
function sandboxSessionFromTool(tool) {
|
|
5486
|
+
const metadata = tool[sandboxToolMetadataKey];
|
|
5487
|
+
if (!isRecord4(metadata)) {
|
|
5488
|
+
return void 0;
|
|
5489
|
+
}
|
|
5490
|
+
return isSandboxSession(metadata.session) ? metadata.session : void 0;
|
|
5491
|
+
}
|
|
5492
|
+
function isSandboxSession(value) {
|
|
5493
|
+
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";
|
|
5494
|
+
}
|
|
5495
|
+
function supportsPorts(session) {
|
|
5496
|
+
return Array.isArray(session.publishedPorts);
|
|
5497
|
+
}
|
|
5498
|
+
function supportsProcesses(session) {
|
|
5499
|
+
return typeof session.listProcesses === "function" && typeof session.readProcessLogs === "function";
|
|
5500
|
+
}
|
|
5501
|
+
function sandboxRef(provider, id) {
|
|
5502
|
+
return Buffer.from(JSON.stringify([provider, id]), "utf8").toString("base64url");
|
|
5503
|
+
}
|
|
5504
|
+
function copySummary(summary) {
|
|
5505
|
+
return {
|
|
5506
|
+
...summary,
|
|
5507
|
+
agentIds: [...summary.agentIds],
|
|
5508
|
+
toolNames: [...summary.toolNames],
|
|
5509
|
+
capabilities: { ...summary.capabilities }
|
|
5510
|
+
};
|
|
5511
|
+
}
|
|
5512
|
+
async function listSandboxFiles(session, filePath) {
|
|
5513
|
+
return normalizeFileEntries(await session.listFiles(filePath)).sort((left, right) => {
|
|
5514
|
+
if (left.type === "directory" && right.type !== "directory") return -1;
|
|
5515
|
+
if (left.type !== "directory" && right.type === "directory") return 1;
|
|
5516
|
+
return left.path.localeCompare(right.path);
|
|
5517
|
+
});
|
|
5518
|
+
}
|
|
5519
|
+
function normalizeFileEntries(value) {
|
|
5520
|
+
if (!Array.isArray(value)) {
|
|
5521
|
+
throw new TypeError("Sandbox listFiles returned an invalid payload");
|
|
5522
|
+
}
|
|
5523
|
+
return value.map((item) => {
|
|
5524
|
+
if (!isRecord4(item) || typeof item.path !== "string" || !isFileType(item.type)) {
|
|
5525
|
+
throw new TypeError("Sandbox listFiles returned an invalid entry");
|
|
5526
|
+
}
|
|
5527
|
+
let entryPath;
|
|
5528
|
+
try {
|
|
5529
|
+
entryPath = normalizeSandboxPath(item.path, false);
|
|
5530
|
+
} catch {
|
|
5531
|
+
throw new TypeError("Sandbox listFiles returned an invalid entry path");
|
|
5532
|
+
}
|
|
5533
|
+
const entry = { path: entryPath, type: item.type };
|
|
5534
|
+
if (item.size !== void 0) {
|
|
5535
|
+
if (typeof item.size !== "number" || !Number.isFinite(item.size) || item.size < 0) {
|
|
5536
|
+
throw new TypeError("Sandbox listFiles returned an invalid file size");
|
|
5537
|
+
}
|
|
5538
|
+
entry.size = item.size;
|
|
5539
|
+
}
|
|
5540
|
+
return entry;
|
|
5541
|
+
});
|
|
5542
|
+
}
|
|
5543
|
+
function normalizePorts(value) {
|
|
5544
|
+
if (!Array.isArray(value)) {
|
|
5545
|
+
throw new TypeError("Sandbox publishedPorts returned an invalid payload");
|
|
5546
|
+
}
|
|
5547
|
+
return value.map((item) => {
|
|
5548
|
+
if (!isRecord4(item) || !isPort(item.containerPort) || typeof item.host !== "string" || !isPort(item.hostPort) || typeof item.protocol !== "string") {
|
|
5549
|
+
throw new TypeError("Sandbox publishedPorts returned an invalid entry");
|
|
5550
|
+
}
|
|
5551
|
+
return {
|
|
5552
|
+
containerPort: item.containerPort,
|
|
5553
|
+
host: item.host,
|
|
5554
|
+
hostPort: item.hostPort,
|
|
5555
|
+
protocol: item.protocol
|
|
5556
|
+
};
|
|
5557
|
+
});
|
|
5558
|
+
}
|
|
5559
|
+
function normalizeProcesses(value) {
|
|
5560
|
+
if (!Array.isArray(value)) {
|
|
5561
|
+
throw new TypeError("Sandbox listProcesses returned an invalid payload");
|
|
5562
|
+
}
|
|
5563
|
+
return value.map((item) => {
|
|
5564
|
+
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") {
|
|
5565
|
+
throw new TypeError("Sandbox listProcesses returned an invalid entry");
|
|
5566
|
+
}
|
|
5567
|
+
const process2 = {
|
|
5568
|
+
id: item.id,
|
|
5569
|
+
command: item.command,
|
|
5570
|
+
args: item.args,
|
|
5571
|
+
status: item.status,
|
|
5572
|
+
startedAt: item.startedAt
|
|
5573
|
+
};
|
|
5574
|
+
if (typeof item.cwd === "string") process2.cwd = item.cwd;
|
|
5575
|
+
if (typeof item.exitCode === "number") process2.exitCode = item.exitCode;
|
|
5576
|
+
if (typeof item.endedAt === "string") process2.endedAt = item.endedAt;
|
|
5577
|
+
return process2;
|
|
5578
|
+
});
|
|
5579
|
+
}
|
|
5580
|
+
function normalizeProcessLogs(value, maxBytes) {
|
|
5581
|
+
if (!isRecord4(value) || typeof value.stdout !== "string" || typeof value.stderr !== "string" || typeof value.stdoutTruncated !== "boolean" || typeof value.stderrTruncated !== "boolean") {
|
|
5582
|
+
throw new TypeError("Sandbox readProcessLogs returned an invalid payload");
|
|
5583
|
+
}
|
|
5584
|
+
const stdout = boundedLogText(value.stdout, maxBytes);
|
|
5585
|
+
const stderr = boundedLogText(value.stderr, maxBytes);
|
|
5586
|
+
return {
|
|
5587
|
+
stdout: stdout.text,
|
|
5588
|
+
stderr: stderr.text,
|
|
5589
|
+
stdoutTruncated: value.stdoutTruncated || stdout.truncated,
|
|
5590
|
+
stderrTruncated: value.stderrTruncated || stderr.truncated
|
|
5591
|
+
};
|
|
5592
|
+
}
|
|
5593
|
+
function boundedLogText(value, maxBytes) {
|
|
5594
|
+
const bytes = new TextEncoder().encode(value);
|
|
5595
|
+
if (bytes.byteLength <= maxBytes) {
|
|
5596
|
+
return { text: value, truncated: false };
|
|
5597
|
+
}
|
|
5598
|
+
if (maxBytes === 0) {
|
|
5599
|
+
return { text: "", truncated: true };
|
|
5600
|
+
}
|
|
5601
|
+
let start = bytes.byteLength - maxBytes;
|
|
5602
|
+
while (start < bytes.byteLength && (bytes[start] ?? 0) >> 6 === 2) {
|
|
5603
|
+
start += 1;
|
|
5604
|
+
}
|
|
5605
|
+
return {
|
|
5606
|
+
text: new TextDecoder().decode(bytes.subarray(start)),
|
|
5607
|
+
truncated: true
|
|
5608
|
+
};
|
|
5609
|
+
}
|
|
5610
|
+
function normalizeSandboxPath(input, allowRoot) {
|
|
5611
|
+
if (input.length === 0) {
|
|
5612
|
+
throw new SandboxRouteError(400, "bad_request", "Sandbox path cannot be empty");
|
|
5613
|
+
}
|
|
5614
|
+
if (input.includes("\0")) {
|
|
5615
|
+
throw new SandboxRouteError(400, "bad_request", "Sandbox path cannot contain null bytes");
|
|
5616
|
+
}
|
|
5617
|
+
const normalized = path.posix.normalize(input.replaceAll("\\", "/"));
|
|
5618
|
+
if (path.posix.isAbsolute(normalized)) {
|
|
5619
|
+
throw new SandboxRouteError(400, "bad_request", "Sandbox path must be relative");
|
|
5620
|
+
}
|
|
5621
|
+
if (normalized === ".." || normalized.startsWith("../")) {
|
|
5622
|
+
throw new SandboxRouteError(400, "bad_request", "Sandbox path cannot leave the workspace");
|
|
5623
|
+
}
|
|
5624
|
+
if (normalized === "." && !allowRoot) {
|
|
5625
|
+
throw new SandboxRouteError(400, "bad_request", "Sandbox path must refer to a file");
|
|
5626
|
+
}
|
|
5627
|
+
return normalized;
|
|
5628
|
+
}
|
|
5629
|
+
function parseTailBytes(value) {
|
|
5630
|
+
if (value === void 0) {
|
|
5631
|
+
return defaultProcessLogBytes;
|
|
5632
|
+
}
|
|
5633
|
+
const parsed = Number(value);
|
|
5634
|
+
if (!Number.isInteger(parsed) || parsed < 0 || parsed > maxProcessLogBytes) {
|
|
5635
|
+
throw new SandboxRouteError(
|
|
5636
|
+
400,
|
|
5637
|
+
"bad_request",
|
|
5638
|
+
`tailBytes must be an integer from 0 through ${maxProcessLogBytes}`
|
|
5639
|
+
);
|
|
5640
|
+
}
|
|
5641
|
+
return parsed;
|
|
5642
|
+
}
|
|
5643
|
+
function unsupportedSandboxOperation(c, operation) {
|
|
5644
|
+
return errorResponse(c, 501, "unsupported_capability", `Sandbox does not support ${operation}`, {
|
|
5645
|
+
capability: "sandboxes",
|
|
5646
|
+
operation
|
|
5647
|
+
});
|
|
5648
|
+
}
|
|
5649
|
+
function sandboxErrorResponse(c, error) {
|
|
5650
|
+
if (error instanceof SandboxRouteError) {
|
|
5651
|
+
return errorResponse(c, error.status, error.code, error.message);
|
|
5652
|
+
}
|
|
5653
|
+
const name = errorName(error);
|
|
5654
|
+
if (name === "SandboxPathError") {
|
|
5655
|
+
return errorResponse(c, 400, "bad_request", errorMessage(error));
|
|
5656
|
+
}
|
|
5657
|
+
if (name === "SandboxFileSizeError") {
|
|
5658
|
+
return errorResponse(c, 413, "payload_too_large", errorMessage(error));
|
|
5659
|
+
}
|
|
5660
|
+
if (name === "SandboxSessionDestroyedError") {
|
|
5661
|
+
return errorResponse(c, 409, "conflict", "Sandbox session is no longer available");
|
|
5662
|
+
}
|
|
5663
|
+
return errorResponse(
|
|
5664
|
+
c,
|
|
5665
|
+
500,
|
|
5666
|
+
"internal_error",
|
|
5667
|
+
"Sandbox inspection failed",
|
|
5668
|
+
serializeError(error)
|
|
5669
|
+
);
|
|
5670
|
+
}
|
|
5671
|
+
function fileTooLarge(size) {
|
|
5672
|
+
return new SandboxRouteError(
|
|
5673
|
+
413,
|
|
5674
|
+
"payload_too_large",
|
|
5675
|
+
`Sandbox file exceeds the ${maxFileResponseBytes}-byte response limit (${size} bytes)`
|
|
5676
|
+
);
|
|
5677
|
+
}
|
|
5678
|
+
var SandboxRouteError = class extends Error {
|
|
5679
|
+
constructor(status, code, message) {
|
|
5680
|
+
super(message);
|
|
5681
|
+
this.status = status;
|
|
5682
|
+
this.code = code;
|
|
5683
|
+
}
|
|
5684
|
+
status;
|
|
5685
|
+
code;
|
|
5686
|
+
};
|
|
5687
|
+
function isFileType(value) {
|
|
5688
|
+
return value === "file" || value === "directory" || value === "symlink" || value === "other";
|
|
5689
|
+
}
|
|
5690
|
+
function isProcessStatus(value) {
|
|
5691
|
+
return value === "running" || value === "exited" || value === "stopped";
|
|
5692
|
+
}
|
|
5693
|
+
function isPort(value) {
|
|
5694
|
+
return Number.isInteger(value) && Number(value) >= 1 && Number(value) <= 65535;
|
|
5695
|
+
}
|
|
5696
|
+
function isRecord4(value) {
|
|
5697
|
+
return typeof value === "object" && value !== null;
|
|
5698
|
+
}
|
|
5699
|
+
function errorName(error) {
|
|
5700
|
+
return isRecord4(error) && typeof error.name === "string" ? error.name : "";
|
|
5701
|
+
}
|
|
5702
|
+
function errorMessage(error) {
|
|
5703
|
+
return isRecord4(error) && typeof error.message === "string" ? error.message : String(error);
|
|
5704
|
+
}
|
|
5705
|
+
|
|
5020
5706
|
// src/runtime/sessions.ts
|
|
5021
5707
|
function registerSessionRoutes(app, props) {
|
|
5022
5708
|
app.get("/sessions", async (c) => {
|
|
@@ -5505,6 +6191,7 @@ function registerStatusRoutes(app, props) {
|
|
|
5505
6191
|
agents: props.agents.length,
|
|
5506
6192
|
pipelines: props.pipelines.length
|
|
5507
6193
|
};
|
|
6194
|
+
if (props.sandboxCount > 0) counts.sandboxes = props.sandboxCount;
|
|
5508
6195
|
if (props.stores.sessions !== void 0) {
|
|
5509
6196
|
counts.sessions = (await props.stores.sessions.listSessions({ limit: 100 })).length;
|
|
5510
6197
|
}
|
|
@@ -5525,7 +6212,13 @@ function registerStatusRoutes(app, props) {
|
|
|
5525
6212
|
runner,
|
|
5526
6213
|
storage,
|
|
5527
6214
|
counts,
|
|
5528
|
-
capabilities: capabilityConfig(
|
|
6215
|
+
capabilities: capabilityConfig(
|
|
6216
|
+
props.options,
|
|
6217
|
+
props.agents,
|
|
6218
|
+
props.pipelines,
|
|
6219
|
+
props.stores,
|
|
6220
|
+
props.sandboxCount
|
|
6221
|
+
),
|
|
5529
6222
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5530
6223
|
};
|
|
5531
6224
|
return c.json(summary);
|
|
@@ -5734,20 +6427,38 @@ var Studio = class {
|
|
|
5734
6427
|
const log = serveOptions.log ?? true;
|
|
5735
6428
|
if (log) {
|
|
5736
6429
|
const host = serveOptions.hostname ?? "localhost";
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
6430
|
+
this.logAddress(host, port);
|
|
6431
|
+
}
|
|
6432
|
+
if (serveOptions.handleSignals ?? true) {
|
|
6433
|
+
this.sigintHandler = () => {
|
|
6434
|
+
this.close();
|
|
6435
|
+
process.exit(0);
|
|
6436
|
+
};
|
|
6437
|
+
process.once("SIGINT", this.sigintHandler);
|
|
5743
6438
|
}
|
|
5744
|
-
this.sigintHandler = () => {
|
|
5745
|
-
this.close();
|
|
5746
|
-
process.exit(0);
|
|
5747
|
-
};
|
|
5748
|
-
process.once("SIGINT", this.sigintHandler);
|
|
5749
6439
|
return this;
|
|
5750
6440
|
}
|
|
6441
|
+
async serve(serveOptions = {}) {
|
|
6442
|
+
const { onShutdown, signal, ...startOptions } = serveOptions;
|
|
6443
|
+
try {
|
|
6444
|
+
this.start({ ...startOptions, log: false, handleSignals: false });
|
|
6445
|
+
const server = this.server;
|
|
6446
|
+
if (server === void 0) {
|
|
6447
|
+
throw new Error("Studio server did not start");
|
|
6448
|
+
}
|
|
6449
|
+
await waitForServerListening(server);
|
|
6450
|
+
if (startOptions.log ?? true) {
|
|
6451
|
+
const requestedPort = startOptions.port ?? Number(process.env.RUNNER_PORT ?? 4021);
|
|
6452
|
+
const address = server.address();
|
|
6453
|
+
const port = typeof address === "object" && address !== null ? address.port : requestedPort;
|
|
6454
|
+
this.logAddress(startOptions.hostname ?? "localhost", port);
|
|
6455
|
+
}
|
|
6456
|
+
await waitForShutdown(signal);
|
|
6457
|
+
} finally {
|
|
6458
|
+
this.close();
|
|
6459
|
+
await onShutdown?.();
|
|
6460
|
+
}
|
|
6461
|
+
}
|
|
5751
6462
|
close() {
|
|
5752
6463
|
if (this.sigintHandler !== void 0) {
|
|
5753
6464
|
process.off("SIGINT", this.sigintHandler);
|
|
@@ -5757,7 +6468,68 @@ var Studio = class {
|
|
|
5757
6468
|
this.server = void 0;
|
|
5758
6469
|
this.studio.close();
|
|
5759
6470
|
}
|
|
6471
|
+
logAddress(host, port) {
|
|
6472
|
+
if (isStudioUiEnabled(this.options.ui)) {
|
|
6473
|
+
const uiPath = studioUiEntryPath(resolveStudioUiOptions(this.options.ui));
|
|
6474
|
+
console.log(`Studio UI: http://${host}:${port}${uiPath}`);
|
|
6475
|
+
} else {
|
|
6476
|
+
console.log(`Studio API: http://${host}:${port}`);
|
|
6477
|
+
}
|
|
6478
|
+
}
|
|
5760
6479
|
};
|
|
6480
|
+
function waitForServerListening(server) {
|
|
6481
|
+
if (server.listening) return Promise.resolve();
|
|
6482
|
+
return new Promise((resolve2, reject) => {
|
|
6483
|
+
const cleanup = () => {
|
|
6484
|
+
server.off("listening", onListening);
|
|
6485
|
+
server.off("error", onError);
|
|
6486
|
+
};
|
|
6487
|
+
const onListening = () => {
|
|
6488
|
+
cleanup();
|
|
6489
|
+
resolve2();
|
|
6490
|
+
};
|
|
6491
|
+
const onError = (error) => {
|
|
6492
|
+
cleanup();
|
|
6493
|
+
reject(error);
|
|
6494
|
+
};
|
|
6495
|
+
server.once("listening", onListening);
|
|
6496
|
+
server.once("error", onError);
|
|
6497
|
+
});
|
|
6498
|
+
}
|
|
6499
|
+
function waitForShutdown(signal) {
|
|
6500
|
+
if (signal?.aborted) return Promise.resolve();
|
|
6501
|
+
return new Promise((resolve2) => {
|
|
6502
|
+
const useRawInput = process.stdin.isTTY === true;
|
|
6503
|
+
const wasRaw = process.stdin.isRaw;
|
|
6504
|
+
const wasFlowing = process.stdin.readableFlowing;
|
|
6505
|
+
const cleanup = () => {
|
|
6506
|
+
process.off("SIGINT", finish);
|
|
6507
|
+
process.off("SIGTERM", finish);
|
|
6508
|
+
signal?.removeEventListener("abort", finish);
|
|
6509
|
+
if (useRawInput) {
|
|
6510
|
+
process.stdin.off("data", onInput);
|
|
6511
|
+
if (!wasRaw && process.stdin.isRaw) process.stdin.setRawMode(false);
|
|
6512
|
+
if (wasFlowing !== true) process.stdin.pause();
|
|
6513
|
+
}
|
|
6514
|
+
};
|
|
6515
|
+
const finish = () => {
|
|
6516
|
+
cleanup();
|
|
6517
|
+
resolve2();
|
|
6518
|
+
};
|
|
6519
|
+
const onInput = (chunk) => {
|
|
6520
|
+
const interrupted = typeof chunk === "string" ? chunk.includes("") : chunk.includes(3);
|
|
6521
|
+
if (interrupted) finish();
|
|
6522
|
+
};
|
|
6523
|
+
process.once("SIGINT", finish);
|
|
6524
|
+
process.once("SIGTERM", finish);
|
|
6525
|
+
signal?.addEventListener("abort", finish, { once: true });
|
|
6526
|
+
if (useRawInput) {
|
|
6527
|
+
if (!wasRaw) process.stdin.setRawMode(true);
|
|
6528
|
+
process.stdin.resume();
|
|
6529
|
+
process.stdin.on("data", onInput);
|
|
6530
|
+
}
|
|
6531
|
+
});
|
|
6532
|
+
}
|
|
5761
6533
|
function studioOptionsFromTargets(targets, options) {
|
|
5762
6534
|
const agents = targets.filter((target) => target instanceof Agent2);
|
|
5763
6535
|
const pipelines = targets.filter(
|
|
@@ -5836,6 +6608,7 @@ function createStudioApp(options) {
|
|
|
5836
6608
|
const evalMap = new Map(options.evals.map((suite) => [suite.id ?? suite.name, suite]));
|
|
5837
6609
|
const approvalRuntime = createApprovalRuntime();
|
|
5838
6610
|
const questionRuntime = createQuestionRuntime();
|
|
6611
|
+
const sandboxRegistry = createStudioSandboxRegistry(agents);
|
|
5839
6612
|
const app = new HonoApp();
|
|
5840
6613
|
const uiOptions = isStudioUiEnabled(options.ui) ? resolveStudioUiOptions(options.ui) : void 0;
|
|
5841
6614
|
if (uiOptions !== void 0 && !uiOptions.protectShell) {
|
|
@@ -5855,8 +6628,17 @@ function createStudioApp(options) {
|
|
|
5855
6628
|
}
|
|
5856
6629
|
})
|
|
5857
6630
|
);
|
|
5858
|
-
app.get(
|
|
5859
|
-
|
|
6631
|
+
app.get(
|
|
6632
|
+
"/config",
|
|
6633
|
+
(c) => c.json(buildConfig(options, agents, pipelines, stores, sandboxRegistry.size))
|
|
6634
|
+
);
|
|
6635
|
+
registerStatusRoutes(app, {
|
|
6636
|
+
options,
|
|
6637
|
+
agents,
|
|
6638
|
+
pipelines,
|
|
6639
|
+
stores,
|
|
6640
|
+
sandboxCount: sandboxRegistry.size
|
|
6641
|
+
});
|
|
5860
6642
|
app.get("/agents", (c) => c.json({ agents: agents.map(agentConfig) }));
|
|
5861
6643
|
app.get("/agents/:agentId", (c) => {
|
|
5862
6644
|
const agent = agentMap.get(c.req.param("agentId"));
|
|
@@ -5875,6 +6657,7 @@ function createStudioApp(options) {
|
|
|
5875
6657
|
registerModelRoutes(app, { registry: modelRegistry, agentMap });
|
|
5876
6658
|
registerMcpRoutes(app, { agentMap });
|
|
5877
6659
|
registerToolRoutes(app, { agentMap });
|
|
6660
|
+
registerSandboxRoutes(app, sandboxRegistry);
|
|
5878
6661
|
registerApprovalRoutes(app, approvalRuntime);
|
|
5879
6662
|
registerQuestionRoutes(app, questionRuntime);
|
|
5880
6663
|
registerObservabilityRoutes(app, observabilityHub);
|
|
@@ -5923,7 +6706,7 @@ function createStudioApp(options) {
|
|
|
5923
6706
|
return app.fetch(request);
|
|
5924
6707
|
},
|
|
5925
6708
|
config() {
|
|
5926
|
-
return buildConfig(options, agents, pipelines, stores);
|
|
6709
|
+
return buildConfig(options, agents, pipelines, stores, sandboxRegistry.size);
|
|
5927
6710
|
},
|
|
5928
6711
|
close() {
|
|
5929
6712
|
}
|
|
@@ -5962,8 +6745,8 @@ function createSqliteSessionStore(options = {}) {
|
|
|
5962
6745
|
return new SqliteSessionStore(options.path ?? ":memory:");
|
|
5963
6746
|
}
|
|
5964
6747
|
var SqliteSessionStore = class {
|
|
5965
|
-
constructor(
|
|
5966
|
-
this.path =
|
|
6748
|
+
constructor(path2) {
|
|
6749
|
+
this.path = path2;
|
|
5967
6750
|
}
|
|
5968
6751
|
path;
|
|
5969
6752
|
kind = "sqlite";
|