@giselles-ai/agent 0.1.22 → 0.1.23

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.
@@ -0,0 +1,75 @@
1
+ // src/hash.ts
2
+ import { createHash } from "crypto";
3
+ function computeConfigHash(config) {
4
+ const payload = JSON.stringify({
5
+ agentType: config.agentType ?? "gemini",
6
+ agentMd: config.agentMd ?? null,
7
+ files: (config.files ?? []).map((f) => ({
8
+ path: f.path,
9
+ content: f.content
10
+ }))
11
+ });
12
+ return createHash("sha256").update(payload).digest("hex").slice(0, 16);
13
+ }
14
+
15
+ // src/request-build.ts
16
+ function trimTrailingSlash(url) {
17
+ return url.replace(/\/+$/, "");
18
+ }
19
+ function resolveFiles(agent) {
20
+ const files = [
21
+ ...agent.files ?? []
22
+ ];
23
+ if (agent.agentMd !== void 0) {
24
+ files.push(
25
+ {
26
+ path: "/home/vercel-sandbox/.codex/AGENTS.md",
27
+ content: agent.agentMd
28
+ },
29
+ {
30
+ path: "/home/vercel-sandbox/.gemini/GEMINI.md",
31
+ content: agent.agentMd
32
+ }
33
+ );
34
+ }
35
+ return files;
36
+ }
37
+ async function requestBuild(agent, options) {
38
+ const baseUrl = trimTrailingSlash(
39
+ options?.baseUrl ?? process.env.GISELLE_AGENT_BASE_URL ?? "https://studio.giselles.ai/agent-api"
40
+ );
41
+ const apiUrl = `${baseUrl}/build`;
42
+ const token = options?.token ?? process.env.GISELLE_AGENT_API_KEY;
43
+ if (!token) {
44
+ throw new Error(
45
+ "Missing API token. Set GISELLE_AGENT_API_KEY or pass options.token."
46
+ );
47
+ }
48
+ const configHash = computeConfigHash(agent);
49
+ const files = resolveFiles(agent);
50
+ const requestBody = {
51
+ config_hash: configHash,
52
+ agent_type: agent.agentType ?? "gemini",
53
+ files
54
+ };
55
+ const requestHeaders = {
56
+ "content-type": "application/json",
57
+ authorization: `Bearer ${token}`,
58
+ ...options?.headers
59
+ };
60
+ const response = await fetch(apiUrl, {
61
+ method: "POST",
62
+ headers: requestHeaders,
63
+ body: JSON.stringify(requestBody)
64
+ });
65
+ if (!response.ok) {
66
+ const body = await response.text().catch(() => "");
67
+ throw new Error(`Build failed (${response.status}): ${body}`);
68
+ }
69
+ return await response.json();
70
+ }
71
+
72
+ export {
73
+ computeConfigHash,
74
+ requestBuild
75
+ };
package/dist/index.d.ts CHANGED
@@ -5,4 +5,18 @@ declare function defineAgent(config: AgentConfig): DefinedAgent;
5
5
 
6
6
  declare function computeConfigHash(config: AgentConfig): string;
7
7
 
8
- export { AgentConfig, DefinedAgent, computeConfigHash, defineAgent };
8
+ type RequestBuildOptions = {
9
+ /** Base URL for the agent API. Default: process.env.GISELLE_AGENT_BASE_URL ?? "https://studio.giselles.ai/agent-api" */
10
+ baseUrl?: string;
11
+ /** Bearer token. Default: process.env.GISELLE_AGENT_API_KEY */
12
+ token?: string;
13
+ /** Additional headers to include in the build API request */
14
+ headers?: Record<string, string | undefined>;
15
+ };
16
+ type BuildResult = {
17
+ snapshot_id: string;
18
+ cached: boolean;
19
+ };
20
+ declare function requestBuild(agent: AgentConfig, options?: RequestBuildOptions): Promise<BuildResult>;
21
+
22
+ export { AgentConfig, type BuildResult, DefinedAgent, type RequestBuildOptions, computeConfigHash, defineAgent, requestBuild };
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import {
2
- computeConfigHash
3
- } from "./chunk-K2DPJIHU.js";
2
+ computeConfigHash,
3
+ requestBuild
4
+ } from "./chunk-TAQV6ZOB.js";
4
5
 
5
6
  // src/define-agent.ts
6
7
  function defineAgent(config) {
@@ -21,5 +22,6 @@ function defineAgent(config) {
21
22
  }
22
23
  export {
23
24
  computeConfigHash,
24
- defineAgent
25
+ defineAgent,
26
+ requestBuild
25
27
  };
@@ -10,6 +10,6 @@ type GiselleAgentPluginOptions = {
10
10
  headers?: Record<string, string | undefined>;
11
11
  };
12
12
 
13
- declare function withGiselleAgent(nextConfig: NextConfig, agent: AgentConfig, options?: GiselleAgentPluginOptions): () => Promise<NextConfig>;
13
+ declare function withGiselleAgent(nextConfig: NextConfig, agent: AgentConfig, options?: GiselleAgentPluginOptions): (phase: string) => Promise<NextConfig>;
14
14
 
15
15
  export { type GiselleAgentPluginOptions, withGiselleAgent };
@@ -1,73 +1,112 @@
1
1
  import {
2
- computeConfigHash
3
- } from "../chunk-K2DPJIHU.js";
2
+ requestBuild
3
+ } from "../chunk-TAQV6ZOB.js";
4
4
 
5
5
  // src/next/with-giselle-agent.ts
6
- function trimTrailingSlash(url) {
7
- return url.replace(/\/+$/, "");
6
+ import crypto from "crypto";
7
+ import fs from "fs";
8
+ import { createRequire } from "module";
9
+ import path from "path";
10
+ var require2 = createRequire(import.meta.url);
11
+ var { version: PKG_VERSION } = require2("../../package.json");
12
+ var bold = (s) => `\x1B[1m${s}\x1B[22m`;
13
+ var green = (s) => `\x1B[32m${s}\x1B[39m`;
14
+ var red = (s) => `\x1B[31m${s}\x1B[39m`;
15
+ var magenta = (s) => `\x1B[35m${s}\x1B[39m`;
16
+ var dim = (s) => `\x1B[2m${s}\x1B[22m`;
17
+ function resolveBaseUrl(options) {
18
+ return (options?.baseUrl ?? process.env.GISELLE_AGENT_BASE_URL ?? "https://studio.giselles.ai/agent-api").replace(/\/+$/, "");
19
+ }
20
+ function getSnapshotFile(agent) {
21
+ const key = {
22
+ agentType: agent.agentType,
23
+ agentMd: agent.agentMd,
24
+ files: agent.files
25
+ };
26
+ const hash = crypto.createHash("sha256").update(JSON.stringify(key)).digest("hex").slice(0, 16);
27
+ return path.join(process.cwd(), ".next", "giselle", hash);
28
+ }
29
+ function hasRunRecently() {
30
+ const lockFile = path.join(process.cwd(), ".next", "giselle", ".lock");
31
+ try {
32
+ const ts = Number(fs.readFileSync(lockFile, "utf-8").trim());
33
+ if (Date.now() - ts < 2e3) {
34
+ return true;
35
+ }
36
+ } catch {
37
+ }
38
+ fs.mkdirSync(path.dirname(lockFile), { recursive: true });
39
+ fs.writeFileSync(lockFile, String(Date.now()));
40
+ return false;
8
41
  }
9
42
  function withGiselleAgent(nextConfig, agent, options) {
10
43
  return async () => {
11
- const baseUrl = trimTrailingSlash(
12
- options?.baseUrl ?? process.env.GISELLE_AGENT_BASE_URL ?? "https://studio.giselles.ai/agent-api"
13
- );
14
- const apiUrl = `${baseUrl}/build`;
15
- const token = options?.token ?? process.env.GISELLE_AGENT_API_KEY;
16
- if (!token) {
17
- console.warn("[withGiselleAgent] Skipped snapshot build: missing token.");
44
+ if (hasRunRecently()) {
18
45
  return nextConfig;
19
46
  }
20
- const configHash = computeConfigHash(agent);
21
- const files = [
22
- ...agent.files ?? []
23
- ];
24
- if (agent.agentMd !== void 0) {
25
- files.push(
26
- {
27
- path: "/home/vercel-sandbox/.codex/AGENTS.md",
28
- content: agent.agentMd
29
- },
30
- {
31
- path: "/home/vercel-sandbox/.gemini/GEMINI.md",
32
- content: agent.agentMd
47
+ const baseUrl = resolveBaseUrl(options);
48
+ const snapshotFile = getSnapshotFile(agent);
49
+ const cached = fs.existsSync(snapshotFile) ? fs.readFileSync(snapshotFile, "utf-8").trim() : void 0;
50
+ if (cached) {
51
+ return {
52
+ ...nextConfig,
53
+ env: {
54
+ ...nextConfig.env,
55
+ GISELLE_AGENT_SNAPSHOT_ID: cached
33
56
  }
57
+ };
58
+ }
59
+ console.log("");
60
+ console.log(`${magenta(bold(`\u2726 Giselle Agent ${PKG_VERSION}`))}`);
61
+ console.log(`${dim("- Base URL:")} ${baseUrl}`);
62
+ console.log("");
63
+ const token = options?.token ?? process.env.GISELLE_AGENT_API_KEY;
64
+ if (!token) {
65
+ console.log(`${red("\u2717")} Missing API key`);
66
+ console.log("");
67
+ console.log(
68
+ " Create an account at https://studio.giselles.ai and generate"
69
+ );
70
+ console.log(
71
+ " an API key, then set it as GISELLE_AGENT_API_KEY in your environment."
34
72
  );
73
+ console.log("");
74
+ throw new Error("Missing GISELLE_AGENT_API_KEY");
35
75
  }
36
- const requestBody = {
37
- config_hash: configHash,
38
- agent_type: agent.agentType ?? "gemini",
39
- files
40
- };
41
- const requestHeaders = {
42
- "content-type": "application/json",
43
- authorization: `Bearer ${token}`,
44
- ...options?.headers
45
- };
46
- console.debug("[withGiselleAgent] POST %s", apiUrl);
47
- console.debug(
48
- "[withGiselleAgent] headers:",
49
- JSON.stringify(requestHeaders, null, 2)
50
- );
51
- console.debug(
52
- "[withGiselleAgent] body:",
53
- JSON.stringify(requestBody, null, 2)
54
- );
55
- const response = await fetch(apiUrl, {
76
+ const authResponse = await fetch(`${baseUrl}/auth`, {
56
77
  method: "POST",
57
- headers: requestHeaders,
58
- body: JSON.stringify(requestBody)
78
+ headers: {
79
+ authorization: `Bearer ${token}`,
80
+ ...options?.headers
81
+ }
59
82
  });
60
- if (!response.ok) {
61
- const body = await response.text().catch(() => "");
62
- throw new Error(
63
- `[withGiselleAgent] Build failed (${response.status}): ${body}`
83
+ if (!authResponse.ok) {
84
+ console.log(`${red("\u2717")} Authentication failed`);
85
+ console.log("");
86
+ console.log(
87
+ " Create an account at https://studio.giselles.ai and generate"
88
+ );
89
+ console.log(
90
+ " an API key, then set it as GISELLE_AGENT_API_KEY in your environment."
64
91
  );
92
+ console.log("");
93
+ throw new Error("Authentication failed");
65
94
  }
66
- const result = await response.json();
67
- console.debug(
68
- "[withGiselleAgent] result:",
69
- JSON.stringify(result, null, 2)
70
- );
95
+ console.log(`${green("\u2713")} Authenticated`);
96
+ const start = performance.now();
97
+ const result = await requestBuild(agent, {
98
+ baseUrl: options?.baseUrl,
99
+ token,
100
+ headers: options?.headers
101
+ });
102
+ fs.mkdirSync(path.dirname(snapshotFile), { recursive: true });
103
+ fs.writeFileSync(snapshotFile, result.snapshot_id);
104
+ const elapsed = Math.round(performance.now() - start);
105
+ const elapsedStr = elapsed < 1e3 ? `${elapsed}ms` : `${(elapsed / 1e3).toFixed(1)}s`;
106
+ console.log(`${green("\u2713")} Building...`);
107
+ console.log(`${green("\u2713")} Ready in ${elapsedStr}`);
108
+ console.log("");
109
+ console.log("");
71
110
  return {
72
111
  ...nextConfig,
73
112
  env: {
@@ -90,7 +90,36 @@ import {
90
90
 
91
91
  // src/build.ts
92
92
  import { Sandbox as Sandbox2 } from "@vercel/sandbox";
93
- var snapshotCache = /* @__PURE__ */ new Map();
93
+ var CACHE_KEY_PREFIX = "agent-build:snapshot:";
94
+ var CACHE_TTL_SEC = 60 * 60 * 24;
95
+ var memoryCache = /* @__PURE__ */ new Map();
96
+ var MemorySnapshotCache = class {
97
+ async get(key2) {
98
+ return memoryCache.get(key2) ?? null;
99
+ }
100
+ async set(key2, value) {
101
+ memoryCache.set(key2, value);
102
+ }
103
+ };
104
+ var RedisSnapshotCache = class {
105
+ constructor(redis) {
106
+ this.redis = redis;
107
+ }
108
+ async get(key2) {
109
+ return this.redis.get(`${CACHE_KEY_PREFIX}${key2}`);
110
+ }
111
+ async set(key2, value) {
112
+ await this.redis.set(
113
+ `${CACHE_KEY_PREFIX}${key2}`,
114
+ value,
115
+ "EX",
116
+ CACHE_TTL_SEC
117
+ );
118
+ }
119
+ };
120
+ function createSnapshotCache(redis) {
121
+ return redis ? new RedisSnapshotCache(redis) : new MemorySnapshotCache();
122
+ }
94
123
  function resolveBaseSnapshotId(configured) {
95
124
  const envValue = process.env.GISELLE_AGENT_SANDBOX_BASE_SNAPSHOT_ID?.trim();
96
125
  if (envValue) {
@@ -136,6 +165,7 @@ function parseBuildRequest(body) {
136
165
  };
137
166
  }
138
167
  async function buildAgent(input) {
168
+ const cache = input.cache ?? new MemorySnapshotCache();
139
169
  const body = await input.request.json().catch(() => null);
140
170
  const parsed = parseBuildRequest(body);
141
171
  if (!parsed) {
@@ -155,7 +185,7 @@ async function buildAgent(input) {
155
185
  );
156
186
  }
157
187
  const cacheKey = `${baseSnapshotId}:${parsed.config_hash}`;
158
- const cached = snapshotCache.get(cacheKey);
188
+ const cached = await cache.get(cacheKey);
159
189
  if (cached) {
160
190
  console.log(
161
191
  `[agent-build] cache hit: hash=${cacheKey} -> snapshot=${cached}`
@@ -179,7 +209,7 @@ async function buildAgent(input) {
179
209
  }
180
210
  const snapshot = await sandbox.snapshot();
181
211
  console.log(`[agent-build] snapshot created: ${snapshot.snapshotId}`);
182
- snapshotCache.set(cacheKey, snapshot.snapshotId);
212
+ await cache.set(cacheKey, snapshot.snapshotId);
183
213
  const response = {
184
214
  snapshot_id: snapshot.snapshotId,
185
215
  cached: false
@@ -1328,14 +1358,10 @@ function continueManagedCloudResponseFromLiveConnection(input) {
1328
1358
  return managed.response;
1329
1359
  }
1330
1360
 
1331
- // src/cloud-chat-store.ts
1332
- var CHAT_STATE_TTL_SEC = 60 * 60;
1361
+ // src/redis.ts
1333
1362
  var REDIS_URL_ENV_NAME = "REDIS_URL";
1334
- function key(chatId) {
1335
- return `cloud-chat:${chatId}`;
1336
- }
1337
1363
  function resolveRedisUrl(url) {
1338
- const resolved = url?.trim() || process.env.REDIS_URL?.trim();
1364
+ const resolved = url?.trim() || process.env[REDIS_URL_ENV_NAME]?.trim();
1339
1365
  if (resolved) {
1340
1366
  return resolved;
1341
1367
  }
@@ -1343,6 +1369,26 @@ function resolveRedisUrl(url) {
1343
1369
  `Missing Redis URL. Set ${REDIS_URL_ENV_NAME} or pass store.url.`
1344
1370
  );
1345
1371
  }
1372
+ async function createRedisClient(url) {
1373
+ let RedisCtor;
1374
+ try {
1375
+ const module = await import("ioredis");
1376
+ RedisCtor = module.default;
1377
+ } catch {
1378
+ throw new Error(
1379
+ "Redis store adapter requires `ioredis` to be installed as a peer dependency."
1380
+ );
1381
+ }
1382
+ return new RedisCtor(resolveRedisUrl(url), {
1383
+ maxRetriesPerRequest: 2
1384
+ });
1385
+ }
1386
+
1387
+ // src/cloud-chat-store.ts
1388
+ var CHAT_STATE_TTL_SEC = 60 * 60;
1389
+ function key(chatId) {
1390
+ return `cloud-chat:${chatId}`;
1391
+ }
1346
1392
  var RedisCloudChatStateStore = class {
1347
1393
  constructor(redis) {
1348
1394
  this.redis = redis;
@@ -1363,25 +1409,12 @@ var RedisCloudChatStateStore = class {
1363
1409
  await this.redis.del(key(chatId));
1364
1410
  }
1365
1411
  };
1366
- async function createRedisStore(url) {
1367
- let RedisCtor;
1368
- try {
1369
- const module = await import("ioredis");
1370
- RedisCtor = module.default;
1371
- } catch {
1372
- throw new Error(
1373
- "Redis store adapter requires `ioredis` to be installed as a peer dependency."
1374
- );
1375
- }
1376
- const redis = new RedisCtor(resolveRedisUrl(url), {
1377
- maxRetriesPerRequest: 2
1378
- });
1379
- return new RedisCloudChatStateStore(redis);
1380
- }
1381
1412
  async function resolveCloudChatStateStore(store) {
1382
1413
  switch (store.adapter) {
1383
- case "redis":
1384
- return createRedisStore(store.url);
1414
+ case "redis": {
1415
+ const redis = await createRedisClient(store.url);
1416
+ return new RedisCloudChatStateStore(redis);
1417
+ }
1385
1418
  }
1386
1419
  }
1387
1420
 
@@ -1413,6 +1446,8 @@ function createAgentApi(options) {
1413
1446
  const relay = createRelayHandler();
1414
1447
  const { basePath, agent: agentOptions } = options;
1415
1448
  let storePromise = null;
1449
+ let snapshotCachePromise = null;
1450
+ const authPath = `${basePath}/auth`;
1416
1451
  const runPath = `${basePath}/run`;
1417
1452
  const buildPath = `${basePath}/build`;
1418
1453
  const relayPrefix = `${basePath}/relay`;
@@ -1420,6 +1455,13 @@ function createAgentApi(options) {
1420
1455
  storePromise ??= resolveCloudChatStateStore(options.store);
1421
1456
  return storePromise;
1422
1457
  }
1458
+ function getSnapshotCache() {
1459
+ snapshotCachePromise ??= (async () => {
1460
+ const redis = await createRedisClient(options.store.url);
1461
+ return createSnapshotCache(redis);
1462
+ })();
1463
+ return snapshotCachePromise;
1464
+ }
1423
1465
  async function handleBuild(request) {
1424
1466
  const baseSnapshotId = process.env.GISELLE_AGENT_SANDBOX_BASE_SNAPSHOT_ID?.trim() || options.build?.baseSnapshotId;
1425
1467
  if (!options.build && !baseSnapshotId) {
@@ -1432,7 +1474,8 @@ function createAgentApi(options) {
1432
1474
  }
1433
1475
  return await buildAgent({
1434
1476
  request,
1435
- baseSnapshotId
1477
+ baseSnapshotId,
1478
+ cache: await getSnapshotCache()
1436
1479
  });
1437
1480
  } catch (error) {
1438
1481
  const message = error instanceof Error ? error.message : "Failed to process build request.";
@@ -1478,9 +1521,24 @@ function createAgentApi(options) {
1478
1521
  return errorResponse(500, "INTERNAL_ERROR", message);
1479
1522
  }
1480
1523
  }
1524
+ async function handleAuth(request) {
1525
+ try {
1526
+ const hookResult = await options.hooks?.build?.before?.(request);
1527
+ if (hookResult instanceof Response) {
1528
+ return hookResult;
1529
+ }
1530
+ return Response.json({ ok: true });
1531
+ } catch (error) {
1532
+ const message = error instanceof Error ? error.message : "Authentication failed.";
1533
+ return errorResponse(401, "UNAUTHORIZED", message);
1534
+ }
1535
+ }
1481
1536
  function matchSubPath(request) {
1482
1537
  const url = new URL(request.url);
1483
1538
  const pathname = url.pathname;
1539
+ if (pathname === authPath || pathname === `${authPath}/`) {
1540
+ return "auth";
1541
+ }
1484
1542
  if (pathname === runPath || pathname === `${runPath}/`) {
1485
1543
  return "run";
1486
1544
  }
@@ -1502,6 +1560,9 @@ function createAgentApi(options) {
1502
1560
  },
1503
1561
  POST: async (request) => {
1504
1562
  const sub = matchSubPath(request);
1563
+ if (sub === "auth") {
1564
+ return handleAuth(request);
1565
+ }
1505
1566
  if (sub === "run") {
1506
1567
  return handleRun(request);
1507
1568
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@giselles-ai/agent",
3
- "version": "0.1.22",
3
+ "version": "0.1.23",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "license": "Apache-2.0",
@@ -37,7 +37,7 @@
37
37
  "format": "pnpm exec biome check --write ."
38
38
  },
39
39
  "dependencies": {
40
- "@giselles-ai/browser-tool": "0.1.22",
40
+ "@giselles-ai/browser-tool": "0.1.23",
41
41
  "@vercel/sandbox": "1.6.0",
42
42
  "@iarna/toml": "3.0.0",
43
43
  "zod": "4.3.6"
@@ -1,17 +0,0 @@
1
- // src/hash.ts
2
- import { createHash } from "crypto";
3
- function computeConfigHash(config) {
4
- const payload = JSON.stringify({
5
- agentType: config.agentType ?? "gemini",
6
- agentMd: config.agentMd ?? null,
7
- files: (config.files ?? []).map((f) => ({
8
- path: f.path,
9
- content: f.content
10
- }))
11
- });
12
- return createHash("sha256").update(payload).digest("hex").slice(0, 16);
13
- }
14
-
15
- export {
16
- computeConfigHash
17
- };