@giselles-ai/agent 0.1.22 → 0.1.24
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/chunk-TAQV6ZOB.js +75 -0
- package/dist/index.d.ts +15 -1
- package/dist/index.js +5 -3
- package/dist/next/index.d.ts +1 -1
- package/dist/next/index.js +94 -55
- package/dist/server/index.d.ts +3 -25
- package/dist/server/index.js +127 -124
- package/package.json +2 -2
- package/dist/chunk-K2DPJIHU.js +0 -17
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
};
|
package/dist/next/index.d.ts
CHANGED
|
@@ -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 };
|
package/dist/next/index.js
CHANGED
|
@@ -1,73 +1,112 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
} from "../chunk-
|
|
2
|
+
requestBuild
|
|
3
|
+
} from "../chunk-TAQV6ZOB.js";
|
|
4
4
|
|
|
5
5
|
// src/next/with-giselle-agent.ts
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
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
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
{
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
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:
|
|
58
|
-
|
|
78
|
+
headers: {
|
|
79
|
+
authorization: `Bearer ${token}`,
|
|
80
|
+
...options?.headers
|
|
81
|
+
}
|
|
59
82
|
});
|
|
60
|
-
if (!
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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: {
|
package/dist/server/index.d.ts
CHANGED
|
@@ -3,30 +3,6 @@ import { z } from 'zod';
|
|
|
3
3
|
import { RelayRequest, RelayResponse } from '@giselles-ai/browser-tool';
|
|
4
4
|
import { RelayRequestSubscription } from '@giselles-ai/browser-tool/relay';
|
|
5
5
|
|
|
6
|
-
type AgentType$1 = "codex" | "gemini";
|
|
7
|
-
declare class Agent {
|
|
8
|
-
private _type;
|
|
9
|
-
private _snapshotId;
|
|
10
|
-
private _pendingOps;
|
|
11
|
-
private constructor();
|
|
12
|
-
static create(type: AgentType$1, options: {
|
|
13
|
-
snapshotId: string;
|
|
14
|
-
}): Agent;
|
|
15
|
-
get type(): AgentType$1;
|
|
16
|
-
get snapshotId(): string;
|
|
17
|
-
get dirty(): boolean;
|
|
18
|
-
addFiles(files: Array<{
|
|
19
|
-
path: string;
|
|
20
|
-
content: Buffer;
|
|
21
|
-
}>): this;
|
|
22
|
-
setAgentMd(content: string | Buffer): this;
|
|
23
|
-
runCommands(commands: Array<{
|
|
24
|
-
cmd: string;
|
|
25
|
-
args?: string[];
|
|
26
|
-
}>): this;
|
|
27
|
-
prepare(): Promise<void>;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
6
|
type BaseChatRequest = {
|
|
31
7
|
message: string;
|
|
32
8
|
session_id?: string;
|
|
@@ -143,6 +119,7 @@ type CloudChatSessionState = {
|
|
|
143
119
|
chatId: string;
|
|
144
120
|
agentSessionId?: string;
|
|
145
121
|
sandboxId?: string;
|
|
122
|
+
snapshotId?: string;
|
|
146
123
|
relay?: CloudRelaySession;
|
|
147
124
|
pendingTool?: PendingToolState | null;
|
|
148
125
|
updatedAt: number;
|
|
@@ -150,6 +127,7 @@ type CloudChatSessionState = {
|
|
|
150
127
|
type CloudChatSessionPatch = {
|
|
151
128
|
agentSessionId?: string;
|
|
152
129
|
sandboxId?: string;
|
|
130
|
+
snapshotId?: string;
|
|
153
131
|
relay?: CloudRelaySession;
|
|
154
132
|
pendingTool?: PendingToolState | null;
|
|
155
133
|
};
|
|
@@ -251,4 +229,4 @@ declare function runCloudChat<TRequest extends CloudChatRequest & {
|
|
|
251
229
|
}) => Promise<void>;
|
|
252
230
|
}): Promise<Response>;
|
|
253
231
|
|
|
254
|
-
export {
|
|
232
|
+
export { type AgentApiOptions, type AgentApiStoreConfig, type AgentParam, type AgentRequest, type AgentType, type BaseChatRequest, type ChatAgent, type ChatCommand, type CloudChatRequest, type CloudChatRunRequest, type CloudChatSessionPatch, type CloudChatSessionState, type CloudChatStateStore, type CloudRelaySession, type CloudToolName, type CloudToolResult, type CreateAgentOptions, type PendingToolState, type RelaySessionFactoryResult, type RunChatImpl, type RunChatInput, type StdoutMapper, applyCloudChatPatch, cloudChatRunRequestSchema, createAgent, createAgentApi, createCodexAgent, createCodexStdoutMapper, createGeminiAgent, reduceCloudChatEvent, runChat, runCloudChat, toolNameFromRelayRequest };
|
package/dist/server/index.js
CHANGED
|
@@ -1,96 +1,41 @@
|
|
|
1
|
-
// src/agent.ts
|
|
1
|
+
// src/agent-api.ts
|
|
2
|
+
import {
|
|
3
|
+
createRelayHandler,
|
|
4
|
+
createRelaySession
|
|
5
|
+
} from "@giselles-ai/browser-tool/relay";
|
|
6
|
+
|
|
7
|
+
// src/build.ts
|
|
2
8
|
import { Sandbox } from "@vercel/sandbox";
|
|
3
|
-
var
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
this._snapshotId = snapshotId;
|
|
10
|
-
}
|
|
11
|
-
static create(type, options) {
|
|
12
|
-
const trimmed = options.snapshotId.trim();
|
|
13
|
-
if (!trimmed) {
|
|
14
|
-
throw new Error("snapshotId is required.");
|
|
15
|
-
}
|
|
16
|
-
return new _Agent(type, trimmed);
|
|
9
|
+
var CACHE_KEY_PREFIX = "agent-build:snapshot:";
|
|
10
|
+
var CACHE_TTL_SEC = 60 * 60 * 24;
|
|
11
|
+
var memoryCache = /* @__PURE__ */ new Map();
|
|
12
|
+
var MemorySnapshotCache = class {
|
|
13
|
+
async get(key2) {
|
|
14
|
+
return memoryCache.get(key2) ?? null;
|
|
17
15
|
}
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
async set(key2, value) {
|
|
17
|
+
memoryCache.set(key2, value);
|
|
20
18
|
}
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
};
|
|
20
|
+
var RedisSnapshotCache = class {
|
|
21
|
+
constructor(redis) {
|
|
22
|
+
this.redis = redis;
|
|
23
23
|
}
|
|
24
|
-
get
|
|
25
|
-
return this.
|
|
24
|
+
async get(key2) {
|
|
25
|
+
return this.redis.get(`${CACHE_KEY_PREFIX}${key2}`);
|
|
26
26
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
files
|
|
34
|
-
});
|
|
35
|
-
return this;
|
|
36
|
-
}
|
|
37
|
-
setAgentMd(content) {
|
|
38
|
-
const buffer = typeof content === "string" ? Buffer.from(content) : content;
|
|
39
|
-
console.log(`[agent] setAgentMd called, content length=${buffer.length}`);
|
|
40
|
-
return this.addFiles([
|
|
41
|
-
{ path: "/home/vercel-sandbox/.codex/AGENTS.md", content: buffer }
|
|
42
|
-
]);
|
|
43
|
-
}
|
|
44
|
-
runCommands(commands) {
|
|
45
|
-
for (const command of commands) {
|
|
46
|
-
this._pendingOps.push({
|
|
47
|
-
kind: "runCommand",
|
|
48
|
-
cmd: command.cmd,
|
|
49
|
-
args: command.args ?? []
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
return this;
|
|
53
|
-
}
|
|
54
|
-
async prepare() {
|
|
55
|
-
console.log(
|
|
56
|
-
`[agent] prepare called, dirty=${this.dirty}, pendingOps=${this._pendingOps.length}`
|
|
57
|
-
);
|
|
58
|
-
if (!this.dirty) {
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
const ops = this._pendingOps;
|
|
62
|
-
const sandbox = await Sandbox.create({
|
|
63
|
-
source: { type: "snapshot", snapshotId: this._snapshotId }
|
|
64
|
-
});
|
|
65
|
-
console.log(
|
|
66
|
-
`[sandbox] created sandbox=${sandbox.sandboxId} from snapshot=${this._snapshotId}`
|
|
27
|
+
async set(key2, value) {
|
|
28
|
+
await this.redis.set(
|
|
29
|
+
`${CACHE_KEY_PREFIX}${key2}`,
|
|
30
|
+
value,
|
|
31
|
+
"EX",
|
|
32
|
+
CACHE_TTL_SEC
|
|
67
33
|
);
|
|
68
|
-
for (const op of ops) {
|
|
69
|
-
switch (op.kind) {
|
|
70
|
-
case "writeFiles":
|
|
71
|
-
await sandbox.writeFiles(op.files);
|
|
72
|
-
break;
|
|
73
|
-
case "runCommand":
|
|
74
|
-
await sandbox.runCommand(op.cmd, op.args);
|
|
75
|
-
break;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
const snapshot = await sandbox.snapshot();
|
|
79
|
-
console.log(`[agent] prepare done, new snapshotId=${snapshot.snapshotId}`);
|
|
80
|
-
this._snapshotId = snapshot.snapshotId;
|
|
81
|
-
this._pendingOps = [];
|
|
82
34
|
}
|
|
83
35
|
};
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
createRelayHandler,
|
|
88
|
-
createRelaySession
|
|
89
|
-
} from "@giselles-ai/browser-tool/relay";
|
|
90
|
-
|
|
91
|
-
// src/build.ts
|
|
92
|
-
import { Sandbox as Sandbox2 } from "@vercel/sandbox";
|
|
93
|
-
var snapshotCache = /* @__PURE__ */ new Map();
|
|
36
|
+
function createSnapshotCache(redis) {
|
|
37
|
+
return redis ? new RedisSnapshotCache(redis) : new MemorySnapshotCache();
|
|
38
|
+
}
|
|
94
39
|
function resolveBaseSnapshotId(configured) {
|
|
95
40
|
const envValue = process.env.GISELLE_AGENT_SANDBOX_BASE_SNAPSHOT_ID?.trim();
|
|
96
41
|
if (envValue) {
|
|
@@ -136,6 +81,7 @@ function parseBuildRequest(body) {
|
|
|
136
81
|
};
|
|
137
82
|
}
|
|
138
83
|
async function buildAgent(input) {
|
|
84
|
+
const cache = input.cache ?? new MemorySnapshotCache();
|
|
139
85
|
const body = await input.request.json().catch(() => null);
|
|
140
86
|
const parsed = parseBuildRequest(body);
|
|
141
87
|
if (!parsed) {
|
|
@@ -155,7 +101,7 @@ async function buildAgent(input) {
|
|
|
155
101
|
);
|
|
156
102
|
}
|
|
157
103
|
const cacheKey = `${baseSnapshotId}:${parsed.config_hash}`;
|
|
158
|
-
const cached =
|
|
104
|
+
const cached = await cache.get(cacheKey);
|
|
159
105
|
if (cached) {
|
|
160
106
|
console.log(
|
|
161
107
|
`[agent-build] cache hit: hash=${cacheKey} -> snapshot=${cached}`
|
|
@@ -163,7 +109,7 @@ async function buildAgent(input) {
|
|
|
163
109
|
const response2 = { snapshot_id: cached, cached: true };
|
|
164
110
|
return Response.json(response2);
|
|
165
111
|
}
|
|
166
|
-
const sandbox = await
|
|
112
|
+
const sandbox = await Sandbox.create({
|
|
167
113
|
source: { type: "snapshot", snapshotId: baseSnapshotId }
|
|
168
114
|
});
|
|
169
115
|
console.log(
|
|
@@ -179,7 +125,7 @@ async function buildAgent(input) {
|
|
|
179
125
|
}
|
|
180
126
|
const snapshot = await sandbox.snapshot();
|
|
181
127
|
console.log(`[agent-build] snapshot created: ${snapshot.snapshotId}`);
|
|
182
|
-
|
|
128
|
+
await cache.set(cacheKey, snapshot.snapshotId);
|
|
183
129
|
const response = {
|
|
184
130
|
snapshot_id: snapshot.snapshotId,
|
|
185
131
|
cached: false
|
|
@@ -603,7 +549,7 @@ function createAgent(options) {
|
|
|
603
549
|
|
|
604
550
|
// src/chat-run.ts
|
|
605
551
|
import { Writable } from "stream";
|
|
606
|
-
import { Sandbox as
|
|
552
|
+
import { Sandbox as Sandbox2 } from "@vercel/sandbox";
|
|
607
553
|
function emitText(controller, text, encoder) {
|
|
608
554
|
if (text.length === 0) {
|
|
609
555
|
return;
|
|
@@ -657,23 +603,39 @@ function runChat(input) {
|
|
|
657
603
|
const mapper = input.agent.createStdoutMapper?.();
|
|
658
604
|
void (async () => {
|
|
659
605
|
try {
|
|
660
|
-
const
|
|
661
|
-
const
|
|
662
|
-
if (!snapshotId) {
|
|
663
|
-
throw new Error(
|
|
664
|
-
"Agent must provide snapshotId when sandbox_id is not provided."
|
|
665
|
-
);
|
|
666
|
-
}
|
|
667
|
-
const created = await Sandbox3.create({
|
|
606
|
+
const createFromSnapshot = async (snapshotId2) => {
|
|
607
|
+
const created = await Sandbox2.create({
|
|
668
608
|
source: {
|
|
669
609
|
type: "snapshot",
|
|
670
|
-
snapshotId
|
|
610
|
+
snapshotId: snapshotId2
|
|
671
611
|
}
|
|
672
612
|
});
|
|
673
613
|
console.log(
|
|
674
|
-
`[sandbox] created sandbox=${created.sandboxId} from snapshot=${
|
|
614
|
+
`[sandbox] created sandbox=${created.sandboxId} from snapshot=${snapshotId2}`
|
|
675
615
|
);
|
|
676
616
|
return created;
|
|
617
|
+
};
|
|
618
|
+
const snapshotId = parsed.snapshot_id?.trim() || input.agent.snapshotId?.trim();
|
|
619
|
+
const sandbox = await (async () => {
|
|
620
|
+
if (parsed.sandbox_id) {
|
|
621
|
+
try {
|
|
622
|
+
return await Sandbox2.get({ sandboxId: parsed.sandbox_id });
|
|
623
|
+
} catch (error) {
|
|
624
|
+
if (!snapshotId) {
|
|
625
|
+
throw error;
|
|
626
|
+
}
|
|
627
|
+
console.log(
|
|
628
|
+
`[sandbox] sandbox=${parsed.sandbox_id} expired, recreating from snapshot=${snapshotId}`
|
|
629
|
+
);
|
|
630
|
+
return createFromSnapshot(snapshotId);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
if (!snapshotId) {
|
|
634
|
+
throw new Error(
|
|
635
|
+
"Agent must provide snapshotId when sandbox_id is not provided."
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
return createFromSnapshot(snapshotId);
|
|
677
639
|
})();
|
|
678
640
|
enqueueEvent({ type: "sandbox", sandbox_id: sandbox.sandboxId });
|
|
679
641
|
await input.agent.prepareSandbox({
|
|
@@ -710,6 +672,11 @@ function runChat(input) {
|
|
|
710
672
|
}),
|
|
711
673
|
signal: abortController.signal
|
|
712
674
|
});
|
|
675
|
+
const snapshot = await sandbox.snapshot();
|
|
676
|
+
enqueueEvent({
|
|
677
|
+
type: "snapshot",
|
|
678
|
+
snapshot_id: snapshot.snapshotId
|
|
679
|
+
});
|
|
713
680
|
} catch (error) {
|
|
714
681
|
if (abortController.signal.aborted) {
|
|
715
682
|
return;
|
|
@@ -825,6 +792,9 @@ function reduceCloudChatEvent(event) {
|
|
|
825
792
|
if (event.type === "sandbox" && typeof event.sandbox_id === "string") {
|
|
826
793
|
return { sandboxId: event.sandbox_id };
|
|
827
794
|
}
|
|
795
|
+
if (event.type === "snapshot" && typeof event.snapshot_id === "string") {
|
|
796
|
+
return { snapshotId: event.snapshot_id };
|
|
797
|
+
}
|
|
828
798
|
if (event.type === "relay.session" && typeof event.sessionId === "string" && typeof event.token === "string" && typeof event.relayUrl === "string" && typeof event.expiresAt === "number") {
|
|
829
799
|
return {
|
|
830
800
|
relay: {
|
|
@@ -865,6 +835,7 @@ function applyCloudChatPatch(input) {
|
|
|
865
835
|
chatId: input.chatId,
|
|
866
836
|
agentSessionId: input.patch?.agentSessionId ?? input.base?.agentSessionId,
|
|
867
837
|
sandboxId: input.patch?.sandboxId ?? input.base?.sandboxId,
|
|
838
|
+
snapshotId: input.patch?.snapshotId ?? input.base?.snapshotId,
|
|
868
839
|
relay: input.patch?.relay ?? input.base?.relay,
|
|
869
840
|
pendingTool: input.patch?.pendingTool !== void 0 ? input.patch.pendingTool : input.base?.pendingTool,
|
|
870
841
|
updatedAt: input.now
|
|
@@ -986,6 +957,7 @@ async function runCloudChat(input) {
|
|
|
986
957
|
...input.request,
|
|
987
958
|
...existing?.agentSessionId ? { session_id: existing.agentSessionId } : {},
|
|
988
959
|
...existing?.sandboxId ? { sandbox_id: existing.sandboxId } : {},
|
|
960
|
+
...existing?.snapshotId ? { snapshot_id: existing.snapshotId } : {},
|
|
989
961
|
relay_session_id: relaySession.sessionId,
|
|
990
962
|
relay_token: relaySession.token
|
|
991
963
|
};
|
|
@@ -1086,6 +1058,7 @@ async function resumeCloudChat(input) {
|
|
|
1086
1058
|
...input.request,
|
|
1087
1059
|
...input.existing.agentSessionId ? { session_id: input.existing.agentSessionId } : {},
|
|
1088
1060
|
...input.existing.sandboxId ? { sandbox_id: input.existing.sandboxId } : {},
|
|
1061
|
+
...input.existing.snapshotId ? { snapshot_id: input.existing.snapshotId } : {},
|
|
1089
1062
|
relay_session_id: relaySession.sessionId,
|
|
1090
1063
|
relay_token: relaySession.token
|
|
1091
1064
|
};
|
|
@@ -1328,14 +1301,10 @@ function continueManagedCloudResponseFromLiveConnection(input) {
|
|
|
1328
1301
|
return managed.response;
|
|
1329
1302
|
}
|
|
1330
1303
|
|
|
1331
|
-
// src/
|
|
1332
|
-
var CHAT_STATE_TTL_SEC = 60 * 60;
|
|
1304
|
+
// src/redis.ts
|
|
1333
1305
|
var REDIS_URL_ENV_NAME = "REDIS_URL";
|
|
1334
|
-
function key(chatId) {
|
|
1335
|
-
return `cloud-chat:${chatId}`;
|
|
1336
|
-
}
|
|
1337
1306
|
function resolveRedisUrl(url) {
|
|
1338
|
-
const resolved = url?.trim() || process.env
|
|
1307
|
+
const resolved = url?.trim() || process.env[REDIS_URL_ENV_NAME]?.trim();
|
|
1339
1308
|
if (resolved) {
|
|
1340
1309
|
return resolved;
|
|
1341
1310
|
}
|
|
@@ -1343,6 +1312,26 @@ function resolveRedisUrl(url) {
|
|
|
1343
1312
|
`Missing Redis URL. Set ${REDIS_URL_ENV_NAME} or pass store.url.`
|
|
1344
1313
|
);
|
|
1345
1314
|
}
|
|
1315
|
+
async function createRedisClient(url) {
|
|
1316
|
+
let RedisCtor;
|
|
1317
|
+
try {
|
|
1318
|
+
const module = await import("ioredis");
|
|
1319
|
+
RedisCtor = module.default;
|
|
1320
|
+
} catch {
|
|
1321
|
+
throw new Error(
|
|
1322
|
+
"Redis store adapter requires `ioredis` to be installed as a peer dependency."
|
|
1323
|
+
);
|
|
1324
|
+
}
|
|
1325
|
+
return new RedisCtor(resolveRedisUrl(url), {
|
|
1326
|
+
maxRetriesPerRequest: 2
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
// src/cloud-chat-store.ts
|
|
1331
|
+
var CHAT_STATE_TTL_SEC = 60 * 60;
|
|
1332
|
+
function key(chatId) {
|
|
1333
|
+
return `cloud-chat:${chatId}`;
|
|
1334
|
+
}
|
|
1346
1335
|
var RedisCloudChatStateStore = class {
|
|
1347
1336
|
constructor(redis) {
|
|
1348
1337
|
this.redis = redis;
|
|
@@ -1363,25 +1352,12 @@ var RedisCloudChatStateStore = class {
|
|
|
1363
1352
|
await this.redis.del(key(chatId));
|
|
1364
1353
|
}
|
|
1365
1354
|
};
|
|
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
1355
|
async function resolveCloudChatStateStore(store) {
|
|
1382
1356
|
switch (store.adapter) {
|
|
1383
|
-
case "redis":
|
|
1384
|
-
|
|
1357
|
+
case "redis": {
|
|
1358
|
+
const redis = await createRedisClient(store.url);
|
|
1359
|
+
return new RedisCloudChatStateStore(redis);
|
|
1360
|
+
}
|
|
1385
1361
|
}
|
|
1386
1362
|
}
|
|
1387
1363
|
|
|
@@ -1413,6 +1389,8 @@ function createAgentApi(options) {
|
|
|
1413
1389
|
const relay = createRelayHandler();
|
|
1414
1390
|
const { basePath, agent: agentOptions } = options;
|
|
1415
1391
|
let storePromise = null;
|
|
1392
|
+
let snapshotCachePromise = null;
|
|
1393
|
+
const authPath = `${basePath}/auth`;
|
|
1416
1394
|
const runPath = `${basePath}/run`;
|
|
1417
1395
|
const buildPath = `${basePath}/build`;
|
|
1418
1396
|
const relayPrefix = `${basePath}/relay`;
|
|
@@ -1420,6 +1398,13 @@ function createAgentApi(options) {
|
|
|
1420
1398
|
storePromise ??= resolveCloudChatStateStore(options.store);
|
|
1421
1399
|
return storePromise;
|
|
1422
1400
|
}
|
|
1401
|
+
function getSnapshotCache() {
|
|
1402
|
+
snapshotCachePromise ??= (async () => {
|
|
1403
|
+
const redis = await createRedisClient(options.store.url);
|
|
1404
|
+
return createSnapshotCache(redis);
|
|
1405
|
+
})();
|
|
1406
|
+
return snapshotCachePromise;
|
|
1407
|
+
}
|
|
1423
1408
|
async function handleBuild(request) {
|
|
1424
1409
|
const baseSnapshotId = process.env.GISELLE_AGENT_SANDBOX_BASE_SNAPSHOT_ID?.trim() || options.build?.baseSnapshotId;
|
|
1425
1410
|
if (!options.build && !baseSnapshotId) {
|
|
@@ -1432,7 +1417,8 @@ function createAgentApi(options) {
|
|
|
1432
1417
|
}
|
|
1433
1418
|
return await buildAgent({
|
|
1434
1419
|
request,
|
|
1435
|
-
baseSnapshotId
|
|
1420
|
+
baseSnapshotId,
|
|
1421
|
+
cache: await getSnapshotCache()
|
|
1436
1422
|
});
|
|
1437
1423
|
} catch (error) {
|
|
1438
1424
|
const message = error instanceof Error ? error.message : "Failed to process build request.";
|
|
@@ -1478,9 +1464,24 @@ function createAgentApi(options) {
|
|
|
1478
1464
|
return errorResponse(500, "INTERNAL_ERROR", message);
|
|
1479
1465
|
}
|
|
1480
1466
|
}
|
|
1467
|
+
async function handleAuth(request) {
|
|
1468
|
+
try {
|
|
1469
|
+
const hookResult = await options.hooks?.build?.before?.(request);
|
|
1470
|
+
if (hookResult instanceof Response) {
|
|
1471
|
+
return hookResult;
|
|
1472
|
+
}
|
|
1473
|
+
return Response.json({ ok: true });
|
|
1474
|
+
} catch (error) {
|
|
1475
|
+
const message = error instanceof Error ? error.message : "Authentication failed.";
|
|
1476
|
+
return errorResponse(401, "UNAUTHORIZED", message);
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1481
1479
|
function matchSubPath(request) {
|
|
1482
1480
|
const url = new URL(request.url);
|
|
1483
1481
|
const pathname = url.pathname;
|
|
1482
|
+
if (pathname === authPath || pathname === `${authPath}/`) {
|
|
1483
|
+
return "auth";
|
|
1484
|
+
}
|
|
1484
1485
|
if (pathname === runPath || pathname === `${runPath}/`) {
|
|
1485
1486
|
return "run";
|
|
1486
1487
|
}
|
|
@@ -1502,6 +1503,9 @@ function createAgentApi(options) {
|
|
|
1502
1503
|
},
|
|
1503
1504
|
POST: async (request) => {
|
|
1504
1505
|
const sub = matchSubPath(request);
|
|
1506
|
+
if (sub === "auth") {
|
|
1507
|
+
return handleAuth(request);
|
|
1508
|
+
}
|
|
1505
1509
|
if (sub === "run") {
|
|
1506
1510
|
return handleRun(request);
|
|
1507
1511
|
}
|
|
@@ -1523,7 +1527,6 @@ function createAgentApi(options) {
|
|
|
1523
1527
|
};
|
|
1524
1528
|
}
|
|
1525
1529
|
export {
|
|
1526
|
-
Agent,
|
|
1527
1530
|
applyCloudChatPatch,
|
|
1528
1531
|
cloudChatRunRequestSchema,
|
|
1529
1532
|
createAgent,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@giselles-ai/agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.24",
|
|
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.
|
|
40
|
+
"@giselles-ai/browser-tool": "0.1.24",
|
|
41
41
|
"@vercel/sandbox": "1.6.0",
|
|
42
42
|
"@iarna/toml": "3.0.0",
|
|
43
43
|
"zod": "4.3.6"
|
package/dist/chunk-K2DPJIHU.js
DELETED
|
@@ -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
|
-
};
|