mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-15 09:01:17 +00:00
chore: simplify cloudflare compatibility (#191)
This commit is contained in:
parent
03e06e956d
commit
4201bd204b
10 changed files with 418 additions and 249 deletions
|
|
@ -48,22 +48,36 @@ import {
|
|||
|
||||
const API_PREFIX = "/v1";
|
||||
const FS_PATH = `${API_PREFIX}/fs`;
|
||||
const DEFAULT_BASE_URL = "http://sandbox-agent";
|
||||
|
||||
const DEFAULT_REPLAY_MAX_EVENTS = 50;
|
||||
const DEFAULT_REPLAY_MAX_CHARS = 12_000;
|
||||
const EVENT_INDEX_SCAN_EVENTS_LIMIT = 500;
|
||||
|
||||
export interface SandboxAgentConnectOptions {
|
||||
baseUrl: string;
|
||||
interface SandboxAgentConnectCommonOptions {
|
||||
headers?: HeadersInit;
|
||||
persist?: SessionPersistDriver;
|
||||
replayMaxEvents?: number;
|
||||
replayMaxChars?: number;
|
||||
token?: string;
|
||||
}
|
||||
|
||||
export type SandboxAgentConnectOptions =
|
||||
| (SandboxAgentConnectCommonOptions & {
|
||||
baseUrl: string;
|
||||
fetch?: typeof fetch;
|
||||
})
|
||||
| (SandboxAgentConnectCommonOptions & {
|
||||
fetch: typeof fetch;
|
||||
baseUrl?: string;
|
||||
});
|
||||
|
||||
export interface SandboxAgentStartOptions {
|
||||
fetch?: typeof fetch;
|
||||
headers?: HeadersInit;
|
||||
persist?: SessionPersistDriver;
|
||||
replayMaxEvents?: number;
|
||||
replayMaxChars?: number;
|
||||
}
|
||||
|
||||
export interface SandboxAgentStartOptions extends Omit<SandboxAgentConnectOptions, "baseUrl" | "token"> {
|
||||
spawn?: SandboxAgentSpawnOptions | boolean;
|
||||
}
|
||||
|
||||
|
|
@ -443,18 +457,22 @@ export class SandboxAgent {
|
|||
private readonly seedSessionEventIndexBySession = new Map<string, Promise<void>>();
|
||||
|
||||
constructor(options: SandboxAgentConnectOptions) {
|
||||
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
||||
const baseUrl = options.baseUrl?.trim();
|
||||
if (!baseUrl && !options.fetch) {
|
||||
throw new Error("baseUrl is required unless fetch is provided.");
|
||||
}
|
||||
this.baseUrl = (baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
|
||||
this.token = options.token;
|
||||
this.fetcher = options.fetch ?? globalThis.fetch.bind(globalThis);
|
||||
const resolvedFetch = options.fetch ?? globalThis.fetch?.bind(globalThis);
|
||||
if (!resolvedFetch) {
|
||||
throw new Error("Fetch API is not available; provide a fetch implementation.");
|
||||
}
|
||||
this.fetcher = resolvedFetch;
|
||||
this.defaultHeaders = options.headers;
|
||||
this.persist = options.persist ?? new InMemorySessionPersistDriver();
|
||||
|
||||
this.replayMaxEvents = normalizePositiveInt(options.replayMaxEvents, DEFAULT_REPLAY_MAX_EVENTS);
|
||||
this.replayMaxChars = normalizePositiveInt(options.replayMaxChars, DEFAULT_REPLAY_MAX_CHARS);
|
||||
|
||||
if (!this.fetcher) {
|
||||
throw new Error("Fetch API is not available; provide a fetch implementation.");
|
||||
}
|
||||
}
|
||||
|
||||
static async connect(options: SandboxAgentConnectOptions): Promise<SandboxAgent> {
|
||||
|
|
@ -468,7 +486,8 @@ export class SandboxAgent {
|
|||
}
|
||||
|
||||
const { spawnSandboxAgent } = await import("./spawn.js");
|
||||
const handle = await spawnSandboxAgent(spawnOptions, options.fetch ?? globalThis.fetch);
|
||||
const resolvedFetch = options.fetch ?? globalThis.fetch?.bind(globalThis);
|
||||
const handle = await spawnSandboxAgent(spawnOptions, resolvedFetch);
|
||||
|
||||
const client = new SandboxAgent({
|
||||
baseUrl: handle.baseUrl,
|
||||
|
|
|
|||
|
|
@ -137,6 +137,45 @@ describe("Integration: TypeScript SDK flat session API", () => {
|
|||
await sdk.dispose();
|
||||
});
|
||||
|
||||
it("uses custom fetch for both HTTP helpers and ACP session traffic", async () => {
|
||||
const defaultFetch = globalThis.fetch;
|
||||
if (!defaultFetch) {
|
||||
throw new Error("Global fetch is not available in this runtime.");
|
||||
}
|
||||
|
||||
const seenPaths: string[] = [];
|
||||
const customFetch: typeof fetch = async (input, init) => {
|
||||
const outgoing = new Request(input, init);
|
||||
const parsed = new URL(outgoing.url);
|
||||
seenPaths.push(parsed.pathname);
|
||||
|
||||
const forwardedUrl = new URL(`${parsed.pathname}${parsed.search}`, baseUrl);
|
||||
const forwarded = new Request(forwardedUrl.toString(), outgoing);
|
||||
return defaultFetch(forwarded);
|
||||
};
|
||||
|
||||
const sdk = await SandboxAgent.connect({
|
||||
token,
|
||||
fetch: customFetch,
|
||||
});
|
||||
|
||||
await sdk.getHealth();
|
||||
const session = await sdk.createSession({ agent: "mock" });
|
||||
const prompt = await session.prompt([{ type: "text", text: "custom fetch integration test" }]);
|
||||
expect(prompt.stopReason).toBe("end_turn");
|
||||
|
||||
expect(seenPaths).toContain("/v1/health");
|
||||
expect(seenPaths.some((path) => path.startsWith("/v1/acp/"))).toBe(true);
|
||||
|
||||
await sdk.dispose();
|
||||
});
|
||||
|
||||
it("requires baseUrl when fetch is not provided", async () => {
|
||||
await expect(SandboxAgent.connect({ token } as any)).rejects.toThrow(
|
||||
"baseUrl is required unless fetch is provided.",
|
||||
);
|
||||
});
|
||||
|
||||
it("restores a session on stale connection by recreating and replaying history on first prompt", async () => {
|
||||
const persist = new InMemorySessionPersistDriver({
|
||||
maxEventsPerSession: 200,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue