@giselles-ai/agent 0.1.25 → 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.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { A as AgentConfig, D as DefinedAgent } from './types-cRejduhB.js';
2
- export { a as AgentFile, b as AgentSetup } from './types-cRejduhB.js';
1
+ import { A as AgentConfig, D as DefinedAgent } from './types-Co_KfALw.js';
2
+ export { a as AgentFile, b as AgentSetup, U as UICatalog } from './types-Co_KfALw.js';
3
3
 
4
4
  declare function defineAgent(config: AgentConfig): DefinedAgent;
5
5
 
package/dist/index.js CHANGED
@@ -4,12 +4,29 @@ 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) {
17
+ const catalogPrompt = config.catalog?.prompt({ mode: "inline" });
18
+ const internalPrompt = createInternalArtifactPrompt();
19
+ const agentMd = [config.agentMd, internalPrompt, catalogPrompt].filter(Boolean).join("\n\n");
8
20
  return {
9
21
  agentType: config.agentType ?? "gemini",
10
- agentMd: config.agentMd,
22
+ agentMd: agentMd || void 0,
23
+ catalog: config.catalog,
11
24
  files: config.files ?? [],
12
- env: config.env ?? {},
25
+ env: config.env ? Object.fromEntries(
26
+ Object.entries(config.env).filter(
27
+ (entry) => entry[1] != null
28
+ )
29
+ ) : {},
13
30
  setup: config.setup,
14
31
  get snapshotId() {
15
32
  const id = process.env?.GISELLE_AGENT_SNAPSHOT_ID;
@@ -1,5 +1,5 @@
1
1
  import { NextConfig } from 'next';
2
- import { A as AgentConfig } from '../types-cRejduhB.js';
2
+ import { A as AgentConfig } from '../types-Co_KfALw.js';
3
3
 
4
4
  type GiselleAgentPluginOptions = {
5
5
  /** Base URL for the agent API. Default: process.env.GISELLE_AGENT_BASE_URL ?? "https://studio.giselles.ai/agent-api" */
@@ -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";
@@ -537,11 +539,19 @@ function createGeminiAgent(options = {}) {
537
539
  "--output-format",
538
540
  "stream-json",
539
541
  "--approval-mode",
540
- "yolo"
542
+ "yolo",
543
+ "--sandbox",
544
+ "false"
541
545
  ];
542
546
  if (input.session_id) {
543
547
  args.push("--resume", input.session_id);
544
548
  }
549
+ console.info("[gemini-agent] createCommand", {
550
+ hasSessionId: Boolean(input.session_id),
551
+ hasSandboxId: Boolean(input.sandbox_id),
552
+ hasSnapshotId: Boolean(input.snapshot_id),
553
+ argCount: args.length
554
+ });
545
555
  return {
546
556
  cmd: "gemini",
547
557
  args,
@@ -597,6 +607,97 @@ function createAgent(options) {
597
607
  // src/chat-run.ts
598
608
  import { Writable } from "stream";
599
609
  import { Sandbox as Sandbox2 } from "@vercel/sandbox";
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
+ }
600
701
  function emitText(controller, text, encoder) {
601
702
  if (text.length === 0) {
602
703
  return;
@@ -666,7 +767,21 @@ function runChat(input) {
666
767
  const sandbox = await (async () => {
667
768
  if (parsed.sandbox_id) {
668
769
  try {
669
- return await Sandbox2.get({ sandboxId: parsed.sandbox_id });
770
+ const existing = await Sandbox2.get({
771
+ sandboxId: parsed.sandbox_id
772
+ });
773
+ if (existing.status !== "running") {
774
+ if (!snapshotId) {
775
+ throw new Error(
776
+ `Sandbox ${parsed.sandbox_id} is ${existing.status}, not running`
777
+ );
778
+ }
779
+ console.log(
780
+ `[sandbox] sandbox=${parsed.sandbox_id} status=${existing.status}, recreating from snapshot=${snapshotId}`
781
+ );
782
+ return createFromSnapshot(snapshotId);
783
+ }
784
+ return existing;
670
785
  } catch (error) {
671
786
  if (!snapshotId) {
672
787
  throw error;
@@ -692,6 +807,16 @@ function runChat(input) {
692
807
  const command = input.agent.createCommand({
693
808
  input: parsed
694
809
  });
810
+ console.info("[chat-run] starting sandbox command", {
811
+ sandboxId: sandbox.sandboxId,
812
+ cmd: command.cmd,
813
+ args: command.args,
814
+ envKeys: Object.keys(command.env ?? {}).sort(),
815
+ hasSessionId: typeof parsed.session_id === "string" && parsed.session_id.length > 0,
816
+ hasSandboxId: typeof parsed.sandbox_id === "string" && parsed.sandbox_id.length > 0,
817
+ hasSnapshotId: typeof parsed.snapshot_id === "string" && parsed.snapshot_id.length > 0
818
+ });
819
+ await sandbox.extendTimeout(COMMAND_TIMEOUT_EXTENSION_MS);
695
820
  await sandbox.runCommand({
696
821
  cmd: command.cmd,
697
822
  args: command.args,
@@ -713,12 +838,20 @@ function runChat(input) {
713
838
  stderr: new Writable({
714
839
  write(chunk, _encoding, callback) {
715
840
  const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
841
+ console.error("[chat-run] sandbox stderr", {
842
+ sandboxId: sandbox.sandboxId,
843
+ content: text
844
+ });
716
845
  enqueueEvent({ type: "stderr", content: text });
717
846
  callback();
718
847
  }
719
848
  }),
720
849
  signal: abortController.signal
721
850
  });
851
+ const discoveredArtifacts = await collectArtifacts(sandbox);
852
+ for (const artifact of discoveredArtifacts) {
853
+ enqueueEvent(artifact);
854
+ }
722
855
  const snapshot = await sandbox.snapshot();
723
856
  enqueueEvent({
724
857
  type: "snapshot",
@@ -969,8 +1102,12 @@ function injectRelayUrl(agent, relayUrl) {
969
1102
  }
970
1103
  };
971
1104
  }
1105
+ function usesBrowserTool(agent) {
1106
+ return isCreateAgentOptions2(agent) && agent.tools?.browser !== void 0;
1107
+ }
972
1108
  async function runCloudChat(input) {
973
- const agentParam = injectRelayUrl(input.agent, input.relayUrl);
1109
+ const browserToolEnabled = usesBrowserTool(input.agent);
1110
+ const agentParam = browserToolEnabled ? injectRelayUrl(input.agent, input.relayUrl) : input.agent;
974
1111
  const agent = resolveAgent(agentParam);
975
1112
  const now = input.now?.() ?? Date.now();
976
1113
  const createRelaySub = input.createRelayRequestSubscription ?? createRelayRequestSubscription;
@@ -992,23 +1129,29 @@ async function runCloudChat(input) {
992
1129
  sendResponse
993
1130
  });
994
1131
  }
995
- const relaySession = await input.createRelaySession();
996
- const relaySubscription = await createRelaySub({
997
- sessionId: relaySession.sessionId,
998
- token: relaySession.token
999
- });
1000
- console.info("[cloud-chat] relay subscription ready", {
1001
- chatId: input.chatId,
1002
- relaySessionId: relaySession.sessionId
1003
- });
1004
1132
  const runtimeInput = {
1005
1133
  ...input.request,
1006
1134
  ...existing?.agentSessionId ? { session_id: existing.agentSessionId } : {},
1007
1135
  ...existing?.sandboxId ? { sandbox_id: existing.sandboxId } : {},
1008
- ...existing?.snapshotId ? { snapshot_id: existing.snapshotId } : {},
1009
- relay_session_id: relaySession.sessionId,
1010
- relay_token: relaySession.token
1136
+ ...existing?.snapshotId ? { snapshot_id: existing.snapshotId } : {}
1011
1137
  };
1138
+ let relaySession;
1139
+ let relaySubscription = null;
1140
+ if (browserToolEnabled) {
1141
+ relaySession = await input.createRelaySession();
1142
+ relaySubscription = await createRelaySub({
1143
+ sessionId: relaySession.sessionId,
1144
+ token: relaySession.token
1145
+ });
1146
+ console.info("[cloud-chat] relay subscription ready", {
1147
+ chatId: input.chatId,
1148
+ relaySessionId: relaySession.sessionId
1149
+ });
1150
+ Object.assign(runtimeInput, {
1151
+ relay_session_id: relaySession.sessionId,
1152
+ relay_token: relaySession.token
1153
+ });
1154
+ }
1012
1155
  let response;
1013
1156
  try {
1014
1157
  response = await (input.runChatImpl ?? runChat)({
@@ -1017,7 +1160,7 @@ async function runCloudChat(input) {
1017
1160
  input: runtimeInput
1018
1161
  });
1019
1162
  } catch (error) {
1020
- await relaySubscription.close().catch(() => void 0);
1163
+ await relaySubscription?.close().catch(() => void 0);
1021
1164
  throw error;
1022
1165
  }
1023
1166
  const managed = createManagedCloudResponseFromReader({
@@ -1032,7 +1175,7 @@ async function runCloudChat(input) {
1032
1175
  relaySubscription,
1033
1176
  relaySession,
1034
1177
  relayUrl: input.relayUrl,
1035
- includeRelaySessionPrelude: true,
1178
+ includeRelaySessionPrelude: browserToolEnabled,
1036
1179
  initialBuffer: "",
1037
1180
  initialTextBlockOpen: false
1038
1181
  });
@@ -1433,6 +1576,70 @@ function resolveRelayUrl(basePath, request) {
1433
1576
  }
1434
1577
  return new URL(`${basePath}/relay`, request.url).toString();
1435
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
+ }
1436
1643
  function createAgentApi(options) {
1437
1644
  const relay = createRelayHandler();
1438
1645
  const { basePath, agent: agentOptions } = options;
@@ -1474,6 +1681,45 @@ function createAgentApi(options) {
1474
1681
  return errorResponse(500, "INTERNAL_ERROR", message);
1475
1682
  }
1476
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
+ }
1477
1723
  async function handleRun(request) {
1478
1724
  try {
1479
1725
  const hookResult = await options.hooks?.chat?.before?.(request);
@@ -1532,6 +1778,7 @@ function createAgentApi(options) {
1532
1778
  function matchSubPath(request) {
1533
1779
  const url = new URL(request.url);
1534
1780
  const pathname = url.pathname;
1781
+ const filesPath = `${basePath}/files`;
1535
1782
  if (pathname === authPath || pathname === `${authPath}/`) {
1536
1783
  return "auth";
1537
1784
  }
@@ -1541,6 +1788,9 @@ function createAgentApi(options) {
1541
1788
  if (pathname === buildPath || pathname === `${buildPath}/`) {
1542
1789
  return "build";
1543
1790
  }
1791
+ if (pathname === filesPath || pathname === `${filesPath}/`) {
1792
+ return "files";
1793
+ }
1544
1794
  if (pathname === relayPrefix || pathname.startsWith(`${relayPrefix}/`)) {
1545
1795
  return "relay";
1546
1796
  }
@@ -1552,6 +1802,9 @@ function createAgentApi(options) {
1552
1802
  if (sub === "relay") {
1553
1803
  return relay.GET(request);
1554
1804
  }
1805
+ if (sub === "files") {
1806
+ return handleFiles(request);
1807
+ }
1555
1808
  return errorResponse(404, "NOT_FOUND", "Not found.");
1556
1809
  },
1557
1810
  POST: async (request) => {
@@ -1575,6 +1828,9 @@ function createAgentApi(options) {
1575
1828
  if (sub === "relay") {
1576
1829
  return relay.OPTIONS(request);
1577
1830
  }
1831
+ if (sub === "files") {
1832
+ return new Response(null, { status: 204 });
1833
+ }
1578
1834
  return new Response(null, { status: 204 });
1579
1835
  }
1580
1836
  };
@@ -1,3 +1,10 @@
1
+ /** Minimal interface for a json-render catalog. */
2
+ type UICatalog = {
3
+ prompt(options?: {
4
+ mode?: "inline" | "standalone";
5
+ customRules?: string[];
6
+ }): string;
7
+ };
1
8
  type AgentFile = {
2
9
  path: string;
3
10
  content: string;
@@ -11,8 +18,10 @@ type AgentConfig = {
11
18
  agentType?: "gemini" | "codex";
12
19
  /** Content for AGENTS.md in the sandbox. */
13
20
  agentMd?: string;
21
+ /** Optional json-render catalog for generative UI. */
22
+ catalog?: UICatalog;
14
23
  /** Environment variables passed to the sandbox at build and run time. */
15
- env?: Record<string, string>;
24
+ env?: Record<string, string | undefined>;
16
25
  /** Additional files to write into the sandbox. */
17
26
  files?: AgentFile[];
18
27
  /** Setup configuration for the sandbox build phase. */
@@ -21,6 +30,7 @@ type AgentConfig = {
21
30
  type DefinedAgent = {
22
31
  readonly agentType: "gemini" | "codex";
23
32
  readonly agentMd?: string;
33
+ readonly catalog?: UICatalog;
24
34
  readonly files: AgentFile[];
25
35
  /** Setup configuration. Undefined when no setup is configured. */
26
36
  readonly setup?: AgentSetup;
@@ -30,4 +40,4 @@ type DefinedAgent = {
30
40
  readonly snapshotId: string;
31
41
  };
32
42
 
33
- export type { AgentConfig as A, DefinedAgent as D, AgentFile as a, AgentSetup as b };
43
+ export type { AgentConfig as A, DefinedAgent as D, UICatalog as U, AgentFile as a, AgentSetup as b };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@giselles-ai/agent",
3
- "version": "0.1.25",
3
+ "version": "0.1.27",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "license": "Apache-2.0",
@@ -37,9 +37,11 @@
37
37
  "format": "pnpm exec biome check --write ."
38
38
  },
39
39
  "dependencies": {
40
- "@giselles-ai/browser-tool": "0.1.25",
41
- "@vercel/sandbox": "1.8.1",
40
+ "@giselles-ai/browser-tool": "0.1.27",
42
41
  "@iarna/toml": "3.0.0",
42
+ "@json-render/core": "0.13.0",
43
+ "@vercel/sandbox": "1.8.1",
44
+ "sandbox": "2.5.5",
43
45
  "zod": "4.3.6"
44
46
  },
45
47
  "devDependencies": {