@giselles-ai/agent 0.1.26 → 0.1.27
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.js +11 -1
- package/dist/server/index.js +209 -0
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -4,9 +4,19 @@ import {
|
|
|
4
4
|
} from "./chunk-JGH5DCA7.js";
|
|
5
5
|
|
|
6
6
|
// src/define-agent.ts
|
|
7
|
+
function createInternalArtifactPrompt() {
|
|
8
|
+
return `
|
|
9
|
+
## Artifact Convention
|
|
10
|
+
- Files intended for user review or download must be written under ./artifacts/.
|
|
11
|
+
- Temporary files, logs, caches, and intermediate data should stay outside ./artifacts/.
|
|
12
|
+
- Before finishing, inspect ./artifacts/ and mention only files that actually exist there.
|
|
13
|
+
- Mention created artifact paths in the assistant response for fallback visibility.
|
|
14
|
+
`.trim();
|
|
15
|
+
}
|
|
7
16
|
function defineAgent(config) {
|
|
8
17
|
const catalogPrompt = config.catalog?.prompt({ mode: "inline" });
|
|
9
|
-
const
|
|
18
|
+
const internalPrompt = createInternalArtifactPrompt();
|
|
19
|
+
const agentMd = [config.agentMd, internalPrompt, catalogPrompt].filter(Boolean).join("\n\n");
|
|
10
20
|
return {
|
|
11
21
|
agentType: config.agentType ?? "gemini",
|
|
12
22
|
agentMd: agentMd || void 0,
|
package/dist/server/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
// src/agent-api.ts
|
|
2
|
+
import { basename } from "path";
|
|
2
3
|
import {
|
|
3
4
|
createRelayHandler,
|
|
4
5
|
createRelaySession
|
|
5
6
|
} from "@giselles-ai/browser-tool/relay";
|
|
7
|
+
import { Sandbox as Sandbox3 } from "@vercel/sandbox";
|
|
6
8
|
|
|
7
9
|
// src/build.ts
|
|
8
10
|
import { Sandbox } from "@vercel/sandbox";
|
|
@@ -606,6 +608,96 @@ function createAgent(options) {
|
|
|
606
608
|
import { Writable } from "stream";
|
|
607
609
|
import { Sandbox as Sandbox2 } from "@vercel/sandbox";
|
|
608
610
|
var COMMAND_TIMEOUT_EXTENSION_MS = 5 * 60 * 1e3;
|
|
611
|
+
var ARTIFACT_DIRS = ["./artifacts", "/vercel/sandbox/artifacts"];
|
|
612
|
+
function getArtifactMimeType(path) {
|
|
613
|
+
const extension = path.split(".").pop()?.toLowerCase();
|
|
614
|
+
switch (extension) {
|
|
615
|
+
case "md":
|
|
616
|
+
return "text/markdown; charset=utf-8";
|
|
617
|
+
case "json":
|
|
618
|
+
return "application/json; charset=utf-8";
|
|
619
|
+
case "csv":
|
|
620
|
+
return "text/csv; charset=utf-8";
|
|
621
|
+
case "txt":
|
|
622
|
+
return "text/plain; charset=utf-8";
|
|
623
|
+
default:
|
|
624
|
+
return "application/octet-stream";
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
function getArtifactLabel(path) {
|
|
628
|
+
const pieces = path.split("/");
|
|
629
|
+
return pieces[pieces.length - 1] ?? path;
|
|
630
|
+
}
|
|
631
|
+
async function collectArtifacts(sandbox) {
|
|
632
|
+
const artifacts = [];
|
|
633
|
+
const seen = /* @__PURE__ */ new Set();
|
|
634
|
+
let discoveryFailed = false;
|
|
635
|
+
for (const artifactDir of ARTIFACT_DIRS) {
|
|
636
|
+
let output = "";
|
|
637
|
+
let discoveryStderr = "";
|
|
638
|
+
let exitCode = 0;
|
|
639
|
+
try {
|
|
640
|
+
const result = await sandbox.runCommand({
|
|
641
|
+
cmd: "find",
|
|
642
|
+
args: [artifactDir, "-type", "f", "-print"]
|
|
643
|
+
});
|
|
644
|
+
output = await result.stdout();
|
|
645
|
+
discoveryStderr = await result.stderr();
|
|
646
|
+
exitCode = typeof result.exitCode === "number" ? result.exitCode : 0;
|
|
647
|
+
} catch (error) {
|
|
648
|
+
discoveryFailed = true;
|
|
649
|
+
console.warn("[chat-run] artifact discovery failed for directory", {
|
|
650
|
+
directory: artifactDir,
|
|
651
|
+
error: error instanceof Error ? error.message : String(error),
|
|
652
|
+
stderr: discoveryStderr
|
|
653
|
+
});
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
if (exitCode !== 0 && !output.trim()) {
|
|
657
|
+
discoveryFailed = true;
|
|
658
|
+
console.warn(
|
|
659
|
+
"[chat-run] artifact discovery returned non-zero exit code",
|
|
660
|
+
{
|
|
661
|
+
directory: artifactDir,
|
|
662
|
+
exitCode,
|
|
663
|
+
stderr: discoveryStderr
|
|
664
|
+
}
|
|
665
|
+
);
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
if (!output.trim()) {
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
671
|
+
for (const line of output.split("\n")) {
|
|
672
|
+
const trimmed = line.trim();
|
|
673
|
+
if (!trimmed) {
|
|
674
|
+
continue;
|
|
675
|
+
}
|
|
676
|
+
const [path, rawSize] = trimmed.split(" ");
|
|
677
|
+
if (!path) {
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
const normalizedPath = path.startsWith("/vercel/sandbox/") ? `./${path.slice("/vercel/sandbox/".length)}` : path;
|
|
681
|
+
if (!normalizedPath.startsWith("./artifacts/") || seen.has(normalizedPath)) {
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
seen.add(normalizedPath);
|
|
685
|
+
const parsedSize = Number.parseInt(rawSize ?? "0", 10);
|
|
686
|
+
const size_bytes = Number.isNaN(parsedSize) ? 0 : parsedSize;
|
|
687
|
+
artifacts.push({
|
|
688
|
+
type: "artifact",
|
|
689
|
+
path: normalizedPath,
|
|
690
|
+
size_bytes,
|
|
691
|
+
mime_type: getArtifactMimeType(normalizedPath),
|
|
692
|
+
label: getArtifactLabel(normalizedPath)
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
if (discoveryFailed && artifacts.length === 0) {
|
|
697
|
+
return [];
|
|
698
|
+
}
|
|
699
|
+
return artifacts;
|
|
700
|
+
}
|
|
609
701
|
function emitText(controller, text, encoder) {
|
|
610
702
|
if (text.length === 0) {
|
|
611
703
|
return;
|
|
@@ -756,6 +848,10 @@ function runChat(input) {
|
|
|
756
848
|
}),
|
|
757
849
|
signal: abortController.signal
|
|
758
850
|
});
|
|
851
|
+
const discoveredArtifacts = await collectArtifacts(sandbox);
|
|
852
|
+
for (const artifact of discoveredArtifacts) {
|
|
853
|
+
enqueueEvent(artifact);
|
|
854
|
+
}
|
|
759
855
|
const snapshot = await sandbox.snapshot();
|
|
760
856
|
enqueueEvent({
|
|
761
857
|
type: "snapshot",
|
|
@@ -1480,6 +1576,70 @@ function resolveRelayUrl(basePath, request) {
|
|
|
1480
1576
|
}
|
|
1481
1577
|
return new URL(`${basePath}/relay`, request.url).toString();
|
|
1482
1578
|
}
|
|
1579
|
+
function createArtifactError(status, path, message) {
|
|
1580
|
+
return errorResponse(status, "FILE_ERROR", `${path}: ${message}`);
|
|
1581
|
+
}
|
|
1582
|
+
function getArtifactMimeType2(path) {
|
|
1583
|
+
const extension = path.split(".").pop()?.toLowerCase();
|
|
1584
|
+
switch (extension) {
|
|
1585
|
+
case "md":
|
|
1586
|
+
return "text/markdown; charset=utf-8";
|
|
1587
|
+
case "json":
|
|
1588
|
+
return "application/json; charset=utf-8";
|
|
1589
|
+
case "csv":
|
|
1590
|
+
return "text/csv; charset=utf-8";
|
|
1591
|
+
case "txt":
|
|
1592
|
+
return "text/plain; charset=utf-8";
|
|
1593
|
+
default:
|
|
1594
|
+
return "application/octet-stream";
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
async function resolveReadableSandbox(input) {
|
|
1598
|
+
const createFromSnapshot = async (snapshotId) => Sandbox3.create({
|
|
1599
|
+
source: {
|
|
1600
|
+
type: "snapshot",
|
|
1601
|
+
snapshotId
|
|
1602
|
+
}
|
|
1603
|
+
});
|
|
1604
|
+
if (input.sandboxId) {
|
|
1605
|
+
try {
|
|
1606
|
+
const existing = await Sandbox3.get({ sandboxId: input.sandboxId });
|
|
1607
|
+
if (existing.status === "running") {
|
|
1608
|
+
return existing;
|
|
1609
|
+
}
|
|
1610
|
+
if (!input.snapshotId) {
|
|
1611
|
+
throw new Error(
|
|
1612
|
+
`Sandbox ${input.sandboxId} is ${existing.status}, not running`
|
|
1613
|
+
);
|
|
1614
|
+
}
|
|
1615
|
+
console.log(
|
|
1616
|
+
`[agent-api] sandbox=${input.sandboxId} status=${existing.status}, recreating from snapshot=${input.snapshotId}`
|
|
1617
|
+
);
|
|
1618
|
+
return createFromSnapshot(input.snapshotId);
|
|
1619
|
+
} catch (error) {
|
|
1620
|
+
if (!input.snapshotId) {
|
|
1621
|
+
throw error;
|
|
1622
|
+
}
|
|
1623
|
+
console.log(
|
|
1624
|
+
`[agent-api] sandbox=${input.sandboxId} expired, recreating from snapshot=${input.snapshotId}`
|
|
1625
|
+
);
|
|
1626
|
+
return createFromSnapshot(input.snapshotId);
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
if (!input.snapshotId) {
|
|
1630
|
+
throw new Error("No sandbox_id or snapshot_id available for file read");
|
|
1631
|
+
}
|
|
1632
|
+
return createFromSnapshot(input.snapshotId);
|
|
1633
|
+
}
|
|
1634
|
+
function createDownloadHeaders(path, options) {
|
|
1635
|
+
const filename = basename(path);
|
|
1636
|
+
const mode = options.download === "1" ? "attachment" : "inline";
|
|
1637
|
+
return {
|
|
1638
|
+
"Content-Type": getArtifactMimeType2(path),
|
|
1639
|
+
"Content-Disposition": `${mode}; filename="${filename}"`,
|
|
1640
|
+
"Cache-Control": "private, no-store"
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1483
1643
|
function createAgentApi(options) {
|
|
1484
1644
|
const relay = createRelayHandler();
|
|
1485
1645
|
const { basePath, agent: agentOptions } = options;
|
|
@@ -1521,6 +1681,45 @@ function createAgentApi(options) {
|
|
|
1521
1681
|
return errorResponse(500, "INTERNAL_ERROR", message);
|
|
1522
1682
|
}
|
|
1523
1683
|
}
|
|
1684
|
+
async function handleFiles(request) {
|
|
1685
|
+
const url = new URL(request.url);
|
|
1686
|
+
const chatId = url.searchParams.get("chat_id")?.trim();
|
|
1687
|
+
const path = url.searchParams.get("path")?.trim();
|
|
1688
|
+
const download = url.searchParams.get("download")?.trim();
|
|
1689
|
+
if (!chatId || !path) {
|
|
1690
|
+
return createArtifactError(400, "query", "chat_id and path are required");
|
|
1691
|
+
}
|
|
1692
|
+
if (!path.startsWith("./artifacts/") || path.includes("..")) {
|
|
1693
|
+
return createArtifactError(400, "path", "path must be under ./artifacts");
|
|
1694
|
+
}
|
|
1695
|
+
const store = await getStore();
|
|
1696
|
+
const chatState = await store.load(chatId);
|
|
1697
|
+
if (!chatState) {
|
|
1698
|
+
return createArtifactError(404, "chat_id", "chat session not found");
|
|
1699
|
+
}
|
|
1700
|
+
let sandbox;
|
|
1701
|
+
try {
|
|
1702
|
+
sandbox = await resolveReadableSandbox({
|
|
1703
|
+
sandboxId: chatState.sandboxId,
|
|
1704
|
+
snapshotId: chatState.snapshotId
|
|
1705
|
+
});
|
|
1706
|
+
} catch (error) {
|
|
1707
|
+
return createArtifactError(
|
|
1708
|
+
400,
|
|
1709
|
+
"sandbox",
|
|
1710
|
+
error instanceof Error ? error.message : "failed to resolve sandbox"
|
|
1711
|
+
);
|
|
1712
|
+
}
|
|
1713
|
+
const fileBuffer = await sandbox.readFileToBuffer({
|
|
1714
|
+
path
|
|
1715
|
+
});
|
|
1716
|
+
if (!fileBuffer) {
|
|
1717
|
+
return createArtifactError(404, "path", "file not found");
|
|
1718
|
+
}
|
|
1719
|
+
return new Response(new Uint8Array(fileBuffer), {
|
|
1720
|
+
headers: createDownloadHeaders(path, { download })
|
|
1721
|
+
});
|
|
1722
|
+
}
|
|
1524
1723
|
async function handleRun(request) {
|
|
1525
1724
|
try {
|
|
1526
1725
|
const hookResult = await options.hooks?.chat?.before?.(request);
|
|
@@ -1579,6 +1778,7 @@ function createAgentApi(options) {
|
|
|
1579
1778
|
function matchSubPath(request) {
|
|
1580
1779
|
const url = new URL(request.url);
|
|
1581
1780
|
const pathname = url.pathname;
|
|
1781
|
+
const filesPath = `${basePath}/files`;
|
|
1582
1782
|
if (pathname === authPath || pathname === `${authPath}/`) {
|
|
1583
1783
|
return "auth";
|
|
1584
1784
|
}
|
|
@@ -1588,6 +1788,9 @@ function createAgentApi(options) {
|
|
|
1588
1788
|
if (pathname === buildPath || pathname === `${buildPath}/`) {
|
|
1589
1789
|
return "build";
|
|
1590
1790
|
}
|
|
1791
|
+
if (pathname === filesPath || pathname === `${filesPath}/`) {
|
|
1792
|
+
return "files";
|
|
1793
|
+
}
|
|
1591
1794
|
if (pathname === relayPrefix || pathname.startsWith(`${relayPrefix}/`)) {
|
|
1592
1795
|
return "relay";
|
|
1593
1796
|
}
|
|
@@ -1599,6 +1802,9 @@ function createAgentApi(options) {
|
|
|
1599
1802
|
if (sub === "relay") {
|
|
1600
1803
|
return relay.GET(request);
|
|
1601
1804
|
}
|
|
1805
|
+
if (sub === "files") {
|
|
1806
|
+
return handleFiles(request);
|
|
1807
|
+
}
|
|
1602
1808
|
return errorResponse(404, "NOT_FOUND", "Not found.");
|
|
1603
1809
|
},
|
|
1604
1810
|
POST: async (request) => {
|
|
@@ -1622,6 +1828,9 @@ function createAgentApi(options) {
|
|
|
1622
1828
|
if (sub === "relay") {
|
|
1623
1829
|
return relay.OPTIONS(request);
|
|
1624
1830
|
}
|
|
1831
|
+
if (sub === "files") {
|
|
1832
|
+
return new Response(null, { status: 204 });
|
|
1833
|
+
}
|
|
1625
1834
|
return new Response(null, { status: 204 });
|
|
1626
1835
|
}
|
|
1627
1836
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@giselles-ai/agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.27",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -37,10 +37,11 @@
|
|
|
37
37
|
"format": "pnpm exec biome check --write ."
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@giselles-ai/browser-tool": "0.1.
|
|
40
|
+
"@giselles-ai/browser-tool": "0.1.27",
|
|
41
|
+
"@iarna/toml": "3.0.0",
|
|
41
42
|
"@json-render/core": "0.13.0",
|
|
42
43
|
"@vercel/sandbox": "1.8.1",
|
|
43
|
-
"
|
|
44
|
+
"sandbox": "2.5.5",
|
|
44
45
|
"zod": "4.3.6"
|
|
45
46
|
},
|
|
46
47
|
"devDependencies": {
|