Skip to content

Commit f726e12

Browse files
committed
Expand code execution helpers and cells
1 parent cb13484 commit f726e12

19 files changed

Lines changed: 1883 additions & 46 deletions

File tree

apps/cloud/src/api/protected.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,16 @@ const makeBaseEngine = (): ExecutionEngine =>
1111
status: "completed",
1212
result: { result: "ok", logs: [] },
1313
}),
14+
startCell: () =>
15+
Effect.succeed({
16+
status: "completed",
17+
cellId: "cell_test",
18+
cursor: 1,
19+
events: [],
20+
result: { result: "ok", logs: [] },
21+
}),
22+
waitCell: () => Effect.succeed(null),
23+
terminateCell: () => Effect.succeed(null),
1424
resume: () =>
1525
Effect.succeed({
1626
status: "completed",

apps/cloud/src/engine/execution-usage.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ export const withExecutionUsageTracking = <E extends Cause.YieldableError>(
1616
engine
1717
.executeWithPause(code)
1818
.pipe(Effect.tap(() => Effect.sync(() => trackUsage(organizationId)))),
19+
startCell: (code, options) =>
20+
engine
21+
.startCell(code, options)
22+
.pipe(Effect.tap(() => Effect.sync(() => trackUsage(organizationId)))),
23+
waitCell: (cellId, options) => engine.waitCell(cellId, options),
24+
terminateCell: (cellId) => engine.terminateCell(cellId),
1925
// resume doesn't count as usage
2026
resume: (executionId, response) => engine.resume(executionId, response),
2127
getPausedExecution: (executionId) => engine.getPausedExecution(executionId),
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import { randomBytes } from "node:crypto";
2+
import { createServer } from "node:http";
3+
4+
import { expect } from "@effect/vitest";
5+
import { Effect } from "effect";
6+
import { composePluginApi } from "@executor-js/api/server";
7+
import { openApiHttpPlugin } from "@executor-js/plugin-openapi/api";
8+
import { AuthTemplateSlug, ConnectionName, IntegrationSlug } from "@executor-js/sdk/shared";
9+
10+
import { scenario } from "../src/scenario";
11+
import { Api, Target, Telemetry } from "../src/services";
12+
13+
const api = composePluginApi([openApiHttpPlugin()] as const);
14+
15+
const upstreamSpec = (baseUrl: string): string =>
16+
JSON.stringify({
17+
openapi: "3.0.3",
18+
info: { title: "Cell Telemetry Upstream", version: "1.0.0" },
19+
servers: [{ url: baseUrl }],
20+
paths: {
21+
"/ok": {
22+
get: {
23+
operationId: "ok",
24+
summary: "Succeeds",
25+
tags: ["probe"],
26+
responses: { "200": { description: "" } },
27+
},
28+
},
29+
},
30+
});
31+
32+
const serveUpstream = Effect.acquireRelease(
33+
Effect.callback<{ readonly baseUrl: string; readonly close: () => void }>((resume) => {
34+
const server = createServer((_request, response) => {
35+
response.writeHead(200, { "content-type": "application/json" });
36+
response.end('{"fine":true}');
37+
});
38+
server.listen(0, "127.0.0.1", () => {
39+
const address = server.address();
40+
const port = typeof address === "object" && address ? address.port : 0;
41+
resume(
42+
Effect.succeed({
43+
baseUrl: `http://127.0.0.1:${port}`,
44+
close: () => {
45+
server.close();
46+
server.closeAllConnections();
47+
},
48+
}),
49+
);
50+
});
51+
}),
52+
(server) => Effect.sync(server.close),
53+
);
54+
55+
scenario(
56+
"Codemode · cell tool calls carry trace correlation metadata",
57+
{ timeout: 180_000 },
58+
Effect.scoped(
59+
Effect.gen(function* () {
60+
const target = yield* Target;
61+
const { client: apiClient } = yield* Api;
62+
const telemetry = yield* Telemetry;
63+
const identity = yield* target.newIdentity();
64+
const client = yield* apiClient(api, identity);
65+
const upstream = yield* serveUpstream;
66+
67+
const slug = IntegrationSlug.make(`celltrace${randomBytes(4).toString("hex")}`);
68+
yield* client.openapi.addSpec({
69+
payload: {
70+
spec: { kind: "blob", value: upstreamSpec(upstream.baseUrl) },
71+
slug,
72+
baseUrl: upstream.baseUrl,
73+
authenticationTemplate: [
74+
{
75+
slug: "apiKey",
76+
type: "apiKey",
77+
headers: { Authorization: ["Bearer ", { type: "variable", name: "token" }] },
78+
},
79+
],
80+
},
81+
});
82+
yield* client.connections.create({
83+
payload: {
84+
owner: "org",
85+
name: ConnectionName.make("main"),
86+
integration: slug,
87+
template: AuthTemplateSlug.make("apiKey"),
88+
value: "cell-telemetry-token",
89+
},
90+
});
91+
92+
const tools = yield* client.tools.list({ query: {} });
93+
const tool = tools.find(
94+
(entry) =>
95+
String(entry.integration) === String(slug) && String(entry.address).endsWith(".ok"),
96+
);
97+
expect(tool, "the OpenAPI operation is in the tool catalog").toBeDefined();
98+
const address = String(tool!.address);
99+
const path = address.startsWith("tools.") ? address.slice("tools.".length) : address;
100+
101+
const specResponse = yield* Effect.promise(() =>
102+
fetch(new URL("/api/openapi.json", target.baseUrl)),
103+
);
104+
const spec = (yield* Effect.promise(() => specResponse.json())) as {
105+
readonly paths?: Record<string, unknown>;
106+
};
107+
expect(Object.keys(spec.paths ?? {}), "the cell API is documented").toContain(
108+
"/api/execution-cells",
109+
);
110+
111+
const cellResponse = yield* Effect.promise(() =>
112+
fetch(new URL("/api/execution-cells", target.baseUrl), {
113+
method: "POST",
114+
headers: {
115+
...(identity.headers ?? {}),
116+
"content-type": "application/json",
117+
origin: new URL(target.baseUrl).origin,
118+
},
119+
body: JSON.stringify({
120+
yieldAfterMs: 5_000,
121+
code: `return await tools.${path}({});`,
122+
}),
123+
}),
124+
);
125+
const cellBody = yield* Effect.promise(() => cellResponse.text());
126+
expect(cellResponse.status, `startCell response body: ${cellBody.slice(0, 500)}`).toBe(200);
127+
const cell = JSON.parse(cellBody) as {
128+
readonly status?: unknown;
129+
readonly cellId?: unknown;
130+
};
131+
expect(cell.status, "a one-shot tool call cell completes").toBe("completed");
132+
expect(cell.cellId, "the completed cell response includes a cell id").toEqual(
133+
expect.any(String),
134+
);
135+
136+
const span = yield* telemetry.expectSpan({
137+
operation: "executor.code.cell.tool",
138+
attributes: {
139+
"executor.tool.source": "code_cell",
140+
"executor.tool.path": path,
141+
},
142+
});
143+
expect(
144+
span.span.tags["executor.code.cell_id"],
145+
"the cell tool span carries a cell id",
146+
).toBeTruthy();
147+
}),
148+
),
149+
);
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { expect } from "@effect/vitest";
2+
import { Effect } from "effect";
3+
4+
import { scenario } from "../src/scenario";
5+
import { Mcp, Target } from "../src/services";
6+
7+
const PNG_DATA =
8+
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg==";
9+
const PNG_DATA_URL = `data:image/png;base64,${PNG_DATA}`;
10+
11+
scenario(
12+
"Codemode · output helpers emit images, detail metadata, and notifications",
13+
{},
14+
Effect.gen(function* () {
15+
const target = yield* Target;
16+
const mcp = yield* Mcp;
17+
const identity = yield* target.newIdentity();
18+
const session = mcp.session(identity);
19+
20+
const result = yield* session.call("execute", {
21+
code: [
22+
'text("helper caption");',
23+
`image(${JSON.stringify(PNG_DATA_URL)}, "original");`,
24+
`image({ image_url: ${JSON.stringify(PNG_DATA_URL)}, detail: "low" });`,
25+
'notify({ message: "rendered previews", data: { count: 2 } });',
26+
"return {",
27+
' value: "structured return",',
28+
" toolKeys: {",
29+
" root: Object.keys(tools),",
30+
" describe: Object.keys(tools.describe),",
31+
" executor: Object.keys(tools.executor),",
32+
" sources: Object.keys(tools.executor.sources),",
33+
" },",
34+
"};",
35+
].join("\n"),
36+
});
37+
38+
expect(result.ok, `execute completed (got: ${result.text.slice(0, 300)})`).toBe(true);
39+
40+
const raw = result.raw as {
41+
content?: ReadonlyArray<Record<string, unknown>>;
42+
structuredContent?: Record<string, unknown>;
43+
};
44+
const content = raw.content ?? [];
45+
expect(result.text, "text() reaches the user-visible MCP text stream").toContain(
46+
"helper caption",
47+
);
48+
expect(result.text, "notify() renders as a distinct visible notification").toContain(
49+
"Notification: rendered previews",
50+
);
51+
52+
const images = content.filter((block) => block.type === "image");
53+
expect(images, "image() emits two MCP image blocks").toHaveLength(2);
54+
expect(images[0], "data URI image is converted to MCP image content").toMatchObject({
55+
type: "image",
56+
data: PNG_DATA,
57+
mimeType: "image/png",
58+
_meta: { "codex/imageDetail": "original" },
59+
});
60+
expect(images[1], "image_url object detail is preserved").toMatchObject({
61+
type: "image",
62+
data: PNG_DATA,
63+
mimeType: "image/png",
64+
_meta: { "codex/imageDetail": "low" },
65+
});
66+
67+
expect(raw.structuredContent?.result, "return value stays model-visible").toEqual({
68+
value: "structured return",
69+
toolKeys: {
70+
root: ["search", "describe", "executor"],
71+
describe: ["tool"],
72+
executor: ["sources"],
73+
sources: ["list"],
74+
},
75+
});
76+
expect(raw.structuredContent?.notifications, "notifications are structured too").toEqual([
77+
{ message: "rendered previews", data: { count: 2 } },
78+
]);
79+
}),
80+
);

0 commit comments

Comments
 (0)