426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
257 lines
7.2 KiB
TypeScript
257 lines
7.2 KiB
TypeScript
#!/usr/bin/env bun
|
|
/**
|
|
* Local Hono server for the mock cloud E2E harness.
|
|
*
|
|
* The production Cloud API still runs as a Cloudflare Worker. This launcher is
|
|
* intentionally scoped to local/mock tests where we need deterministic process
|
|
* startup and the same Hono route graph, but not Wrangler's dev proxy/runtime.
|
|
*/
|
|
|
|
import { createApp } from "../../../../cloud/api/src/bootstrap-app";
|
|
|
|
type StoredObject = {
|
|
bytes: Uint8Array;
|
|
httpMetadata?: { contentType?: string };
|
|
customMetadata?: Record<string, string>;
|
|
uploaded: Date;
|
|
etag: string;
|
|
};
|
|
|
|
const encoder = new TextEncoder();
|
|
const store = new Map<string, StoredObject>();
|
|
const multipartUploads = new Map<
|
|
string,
|
|
{
|
|
key: string;
|
|
httpMetadata?: { contentType?: string };
|
|
customMetadata?: Record<string, string>;
|
|
parts: Map<number, Uint8Array>;
|
|
}
|
|
>();
|
|
|
|
async function toBytes(
|
|
value: string | ArrayBuffer | ArrayBufferView | Blob | null,
|
|
): Promise<Uint8Array> {
|
|
if (value === null) return new Uint8Array();
|
|
if (typeof value === "string") return encoder.encode(value);
|
|
if (value instanceof ArrayBuffer) return new Uint8Array(value);
|
|
if (ArrayBuffer.isView(value)) {
|
|
return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
}
|
|
return new Uint8Array(await value.arrayBuffer());
|
|
}
|
|
|
|
function createEtag(bytes: Uint8Array): string {
|
|
return `"local-${bytes.byteLength}-${Bun.hash(bytes)}"`;
|
|
}
|
|
|
|
function objectHead(key: string, object: StoredObject) {
|
|
return {
|
|
key,
|
|
version: null,
|
|
size: object.bytes.byteLength,
|
|
etag: object.etag,
|
|
httpEtag: object.etag,
|
|
uploaded: object.uploaded,
|
|
httpMetadata: object.httpMetadata,
|
|
customMetadata: object.customMetadata,
|
|
checksums: {},
|
|
};
|
|
}
|
|
|
|
const blobBinding = {
|
|
async get(key: string) {
|
|
const object = store.get(key);
|
|
if (!object) return null;
|
|
return {
|
|
...objectHead(key, object),
|
|
httpMetadata: object.httpMetadata,
|
|
customMetadata: object.customMetadata,
|
|
async text() {
|
|
return new TextDecoder().decode(object.bytes);
|
|
},
|
|
async arrayBuffer() {
|
|
return object.bytes.buffer.slice(
|
|
object.bytes.byteOffset,
|
|
object.bytes.byteOffset + object.bytes.byteLength,
|
|
);
|
|
},
|
|
};
|
|
},
|
|
async put(
|
|
key: string,
|
|
value: string | ArrayBuffer | ArrayBufferView | Blob | null,
|
|
options?: {
|
|
httpMetadata?: { contentType?: string };
|
|
customMetadata?: Record<string, string>;
|
|
},
|
|
) {
|
|
const bytes = await toBytes(value);
|
|
store.set(key, {
|
|
bytes,
|
|
httpMetadata: options?.httpMetadata,
|
|
customMetadata: options?.customMetadata,
|
|
uploaded: new Date(),
|
|
etag: createEtag(bytes),
|
|
});
|
|
},
|
|
async delete(key: string) {
|
|
store.delete(key);
|
|
},
|
|
async list(options?: { prefix?: string; limit?: number; cursor?: string }) {
|
|
const prefix = options?.prefix ?? "";
|
|
const start = options?.cursor ? Number.parseInt(options.cursor, 10) : 0;
|
|
const limit = Math.max(1, options?.limit ?? 1000);
|
|
const matchingKeys = Array.from(store.keys())
|
|
.filter((key) => key.startsWith(prefix))
|
|
.sort();
|
|
const page = matchingKeys.slice(start, start + limit);
|
|
const next = start + page.length;
|
|
const objects = page.flatMap((key) => {
|
|
const object = store.get(key);
|
|
return object ? [objectHead(key, object)] : [];
|
|
});
|
|
|
|
return {
|
|
objects,
|
|
truncated: next < matchingKeys.length,
|
|
cursor: next < matchingKeys.length ? String(next) : undefined,
|
|
delimitedPrefixes: [],
|
|
};
|
|
},
|
|
async head(key: string) {
|
|
const object = store.get(key);
|
|
return object ? objectHead(key, object) : null;
|
|
},
|
|
async createMultipartUpload(
|
|
key: string,
|
|
options?: {
|
|
httpMetadata?: { contentType?: string };
|
|
customMetadata?: Record<string, string>;
|
|
},
|
|
) {
|
|
const uploadId = crypto.randomUUID();
|
|
multipartUploads.set(uploadId, {
|
|
key,
|
|
httpMetadata: options?.httpMetadata,
|
|
customMetadata: options?.customMetadata,
|
|
parts: new Map(),
|
|
});
|
|
return this.resumeMultipartUpload(key, uploadId);
|
|
},
|
|
resumeMultipartUpload(key: string, uploadId: string) {
|
|
const upload = multipartUploads.get(uploadId);
|
|
if (!upload || upload.key !== key) {
|
|
throw new Error(
|
|
`[cloud-api-hono-dev] multipart upload not found: ${key} ${uploadId}`,
|
|
);
|
|
}
|
|
|
|
return {
|
|
key,
|
|
uploadId,
|
|
async uploadPart(
|
|
partNumber: number,
|
|
value: string | ArrayBuffer | ArrayBufferView | Blob,
|
|
) {
|
|
const bytes = await toBytes(value);
|
|
upload.parts.set(partNumber, bytes);
|
|
return {
|
|
partNumber,
|
|
etag: createEtag(bytes),
|
|
};
|
|
},
|
|
async complete(uploadedParts: Array<{ partNumber: number }>) {
|
|
const orderedParts = uploadedParts
|
|
.map((part) => upload.parts.get(part.partNumber))
|
|
.filter((part): part is Uint8Array => part !== undefined);
|
|
const totalBytes = orderedParts.reduce(
|
|
(total, part) => total + part.byteLength,
|
|
0,
|
|
);
|
|
const bytes = new Uint8Array(totalBytes);
|
|
let offset = 0;
|
|
for (const part of orderedParts) {
|
|
bytes.set(part, offset);
|
|
offset += part.byteLength;
|
|
}
|
|
store.set(key, {
|
|
bytes,
|
|
httpMetadata: upload.httpMetadata,
|
|
customMetadata: upload.customMetadata,
|
|
uploaded: new Date(),
|
|
etag: createEtag(bytes),
|
|
});
|
|
multipartUploads.delete(uploadId);
|
|
const completedObject = store.get(key);
|
|
if (!completedObject) {
|
|
throw new Error(
|
|
`[cloud-api-hono-dev] multipart completion failed: ${key}`,
|
|
);
|
|
}
|
|
return objectHead(key, completedObject);
|
|
},
|
|
async abort() {
|
|
multipartUploads.delete(uploadId);
|
|
},
|
|
};
|
|
},
|
|
};
|
|
|
|
function executionContext(): ExecutionContext {
|
|
return {
|
|
waitUntil(promise) {
|
|
Promise.resolve(promise).catch((error) => {
|
|
console.error("[cloud-api-hono-dev] waitUntil failed", error);
|
|
});
|
|
},
|
|
passThroughOnException() {},
|
|
} as ExecutionContext;
|
|
}
|
|
|
|
const port = Number.parseInt(process.env.API_DEV_PORT || "8787", 10);
|
|
const hostname = process.env.API_DEV_HOST || "127.0.0.1";
|
|
const app = createApp();
|
|
const env = {
|
|
...process.env,
|
|
BLOB: blobBinding,
|
|
};
|
|
|
|
const server = Bun.serve({
|
|
hostname,
|
|
port,
|
|
async fetch(request) {
|
|
try {
|
|
const url = new URL(request.url);
|
|
if (url.pathname === "/api/health") {
|
|
return Response.json(
|
|
{
|
|
status: "ok",
|
|
timestamp: Date.now(),
|
|
region: "local-hono",
|
|
},
|
|
{ headers: { "Cache-Control": "no-store, max-age=0" } },
|
|
);
|
|
}
|
|
return await app.fetch(request, env, executionContext());
|
|
} catch (error) {
|
|
console.error("[cloud-api-hono-dev] unhandled request error", error);
|
|
return Response.json(
|
|
{ success: false, error: "internal_error" },
|
|
{ status: 500 },
|
|
);
|
|
}
|
|
},
|
|
});
|
|
|
|
console.log(
|
|
`[cloud-api-hono-dev] listening on http://${hostname}:${server.port}`,
|
|
);
|
|
|
|
const shutdown = () => {
|
|
server.stop(true);
|
|
process.exit(0);
|
|
};
|
|
process.once("SIGINT", shutdown);
|
|
process.once("SIGTERM", shutdown);
|