@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 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 agentMd = [config.agentMd, catalogPrompt].filter(Boolean).join("\n\n");
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,
@@ -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.26",
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.26",
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
- "@iarna/toml": "3.0.0",
44
+ "sandbox": "2.5.5",
44
45
  "zod": "4.3.6"
45
46
  },
46
47
  "devDependencies": {