Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/public-demo.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ browser / tress attach / tress connect
for the model calls. This is separate from the
Harness key: Harness stores and shares the thread; the Tress host runs the
agent and pays for inference. The current host expects `ANTHROPIC_API_KEY`.
If the key is not scoped to a workspace, also configure
`ANTHROPIC_WORKSPACE_ID` with the `wrkspc_...` ID from Anthropic's Workspaces
settings. This is separate from `HARNESS_WORKSPACE`.
3. Provision a PostgreSQL database that the site can reach. The site uses it
for visitor/session ownership, access-token hashes, usage limits, hosted
virtual files, and connected-device jobs. Harness—not PostgreSQL—stores the
Expand Down
6 changes: 6 additions & 0 deletions site/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ a model; the default is `claude-sonnet-5`. Keys stay on the server. Without a ke
the playground explains the setup and disables sending; it does not simulate a
live agent.

For an Anthropic personal or service-account key that is not scoped to a
workspace, also set `ANTHROPIC_WORKSPACE_ID=wrkspc_...` on the host. Use the ID
from the Anthropic Console's Workspaces page. The host sends it on model and
diagnostic requests. Workspace-scoped keys can omit it. For Vercel, configure
both variables in the intended environment and redeploy to activate them.

Install the Rust `wasm32-unknown-unknown` target and `wasm-bindgen-cli` matching
the `wasm-bindgen` version in `Cargo.lock`. Starting the site automatically builds
the Rust engine and generates its bundler bindings:
Expand Down
48 changes: 46 additions & 2 deletions site/scripts/health-policy.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const {
} = await import(output.href);
const keys = [
"ANTHROPIC_API_KEY",
"ANTHROPIC_WORKSPACE_ID",
"TRESS_MODEL",
"TRESS_API_URL",
"HARNESS_API_KEY",
Expand Down Expand Up @@ -80,6 +81,7 @@ const store = (name) => createFileThreadStore(join(root, name));
const request = () => new Request("https://host.example/api/health");

test("health uses host credentials, closes streams, and never generates a completion", async () => {
process.env.ANTHROPIC_WORKSPACE_ID = "wrkspc_health_fixture";
const calls = [];
let closed = 0;
const result = await checkHost(
Expand All @@ -91,13 +93,16 @@ test("health uses host credentials, closes streams, and never generates a comple
assert.equal(options.method, undefined);
assert.equal(options.redirect, "error");
assert(options.signal);
if (url.hostname === "provider.example")
if (url.hostname === "provider.example") {
assert.equal(options.headers["x-api-key"], "fake-model-secret");
else
assert.equal(options.headers["anthropic-workspace-id"], "wrkspc_health_fixture");
} else {
assert.equal(options.headers["anthropic-workspace-id"], undefined);
assert.equal(
options.headers.Authorization,
"Bearer fake-harness-secret",
);
}
return new Response(
new ReadableStream({
cancel() {
Expand All @@ -112,6 +117,45 @@ test("health uses host credentials, closes streams, and never generates a comple
assert(calls.some((url) => url.endsWith("/v1/models/fixture-model")));
assert(calls.some((url) => url.includes("/threads/fixture~")));
assert(!JSON.stringify(result).includes("secret"));
assert(!JSON.stringify(result).includes("wrkspc_health_fixture"));
});

test("browser model requests and probes use only the host's optional Anthropic workspace", async (t) => {
const storeCache = globalThis[Symbol.for("tress.demo.thread-store.v4")];
const previousStore = storeCache.store;
t.after(() => { storeCache.store = previousStore; });
const routeOutput = new URL(`../.tress/anthropic-route-${process.pid}.mjs`, import.meta.url);
await build({
entryPoints: [new URL("../src/app/api/messages/route.ts", import.meta.url).pathname],
outfile: routeOutput.pathname, bundle: true, packages: "external", platform: "node", format: "esm",
});
t.after(() => rm(routeOutput, { force: true }));
const { POST } = await import(routeOutput.href);
process.env.TRESS_DEMO_SHARED = "1";
const calls = [];
t.mock.method(globalThis, "fetch", async (url, options) => {
calls.push({ url, headers: options.headers });
return new Response("data: fixture\n\n", { headers: { "content-type": "text/event-stream" } });
});
for (const workspace of [undefined, " wrkspc_server_fixture ", " "]) {
if (workspace === undefined) delete process.env.ANTHROPIC_WORKSPACE_ID;
else process.env.ANTHROPIC_WORKSPACE_ID = workspace;
for (const probe of [false, true]) {
const response = await POST(new Request("https://host.example/api/messages", {
method: "POST",
headers: { "content-type": "application/json", "anthropic-workspace-id": "wrkspc_client_must_not_override" },
body: JSON.stringify({ probe, messages: [{ role: "user", content: "fixture" }] }),
}));
assert.equal(response.status, 200);
const call = calls.at(-1);
assert.equal(call.url, "https://api.anthropic.com/v1/messages");
assert.equal(call.headers["x-api-key"], "fake-model-secret");
assert.equal(call.headers["anthropic-workspace-id"], workspace?.trim() || undefined);
assert.equal(response.headers.get("anthropic-workspace-id"), null);
assert(!((await response.text()).includes("fake-model-secret")));
}
}
assert.equal(calls.length, 6);
});

test("health distinguishes database failure, rejected Harness access, and unsupported model metadata", async () => {
Expand Down
2 changes: 2 additions & 0 deletions site/scripts/verify-health-policy.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ let generations = 0;
let metadataChecks = 0;
const model = createServer(async (request, response) => {
assert.equal(request.headers["x-api-key"], "mock-host-key");
assert.equal(request.headers["anthropic-workspace-id"], "wrkspc_host_fixture");
if (request.method === "GET" && request.url === "/v1/models/test-model") {
metadataChecks++;
response.writeHead(200, { "Content-Type": "application/json" });
Expand Down Expand Up @@ -73,6 +74,7 @@ try {
PORT: String(port),
HOST: "127.0.0.1",
ANTHROPIC_API_KEY: "mock-host-key",
ANTHROPIC_WORKSPACE_ID: "wrkspc_host_fixture",
TRESS_API_URL: `http://127.0.0.1:${model.address().port}/v1/messages`,
TRESS_MODEL: "test-model",
TRESS_THREAD_MODE: "local",
Expand Down
7 changes: 3 additions & 4 deletions site/src/app/api/messages/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
sessionResponse,
} from "../../../server/demo-session";
import { admitRun } from "../../../server/demo-policy";
import { anthropicHeaders } from "../../../server/anthropic";

type Block =
| { kind: "text"; text: string }
Expand Down Expand Up @@ -93,8 +94,7 @@ async function keyState(key: string): Promise<"live" | "rejected"> {
method: "POST",
headers: {
"content-type": "application/json",
"x-api-key": key,
"anthropic-version": "2023-06-01",
...anthropicHeaders(key),
},
body: JSON.stringify({
model: "claude-sonnet-5",
Expand Down Expand Up @@ -204,8 +204,7 @@ export async function POST(request: Request) {
method: "POST",
headers: {
"content-type": "application/json",
"x-api-key": key,
"anthropic-version": "2023-06-01",
...anthropicHeaders(key),
},
body,
});
Expand Down
3 changes: 2 additions & 1 deletion site/src/server/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
type WorkspaceToolsOptions,
} from "@tress/workspaces";
import { workspaceConfig } from "./workspace";
import { anthropicHeaders } from "./anthropic";

export const openSession = async (
workspace: Workspace,
Expand Down Expand Up @@ -37,7 +38,7 @@ export const openSession = async (
workspace,
url: process.env.TRESS_API_URL ?? "https://api.anthropic.com/v1/messages",
model: process.env.TRESS_MODEL ?? "claude-sonnet-5",
headers: { "x-api-key": key, "anthropic-version": "2023-06-01" },
headers: anthropicHeaders(key),
system: `You are tress, a coding agent working in a shared workspace. Browser and terminal clients share this conversation and its files.
Answer questions directly. Only inspect or change files when needed for the user's request. ${workspace.filesystemRoot ? `Paths may be absolute under filesystem root ${JSON.stringify(workspace.filesystemRoot)} or relative to that root.` : "Paths are relative to the workspace root."}
${connected ? "Files are served by a user-approved native folder. No shell or project test runner is available in this connection." : nativeSandboxShell ? "Commands run inside a sandbox, with a timeout. Installed runtimes and dependencies belong to that sandbox." : nativeDeviceShell ? "Commands run on the host device under its configured tool policy. Only claim a command ran when the tool confirms it." : "The shell is just-bash: simulated file and text commands, not a native operating system. Node, npm, git, and project test runners are not available. Never claim tests ran unless a tool actually ran them."}
Expand Down
9 changes: 9 additions & 0 deletions site/src/server/anthropic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/** Server-owned authentication for both workspace keys and personal keys. */
export const anthropicHeaders = (key: string): Record<string, string> => {
const workspace = process.env.ANTHROPIC_WORKSPACE_ID?.trim();
return {
"x-api-key": key,
"anthropic-version": "2023-06-01",
...(workspace ? { "anthropic-workspace-id": workspace } : {}),
};
};
3 changes: 2 additions & 1 deletion site/src/server/health.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import { threadStore, type ThreadStore } from "./thread-store";
import { managedBackendUrl, managedStreamHeaders } from "./managed-backend";
import { demoLimits } from "./demo-policy";
import { anthropicHeaders } from "./anthropic";

type Check = {
name: string;
Expand Down Expand Up @@ -112,7 +113,7 @@ export const checkHost = async (
return await probe(
"Model",
url,
{ "x-api-key": key, "anthropic-version": "2023-06-01" },
anthropicHeaders(key),
fetcher,
);
} catch {
Expand Down
Loading