Skip to content
Open
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
133 changes: 133 additions & 0 deletions packages/core/execution/src/attachment-delivery.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { describe, expect, it } from "@effect/vitest";
import { Effect } from "effect";
import { makeQuickJsExecutor } from "@executor-js/runtime-quickjs";
import { withAttachmentDelivery } from "./attachment-delivery";

const image = { type: "image", mimeType: "image/gif", data: "R0lGODlh" };
const video = {
type: "resource",
resource: { uri: "https://slides.example/slides.mp4", mimeType: "video/mp4", blob: "AAAA" },
};

const run = (value: unknown, code: string) => {
const delivery = withAttachmentDelivery({ invoke: () => Effect.succeed(value) });
return makeQuickJsExecutor().execute(code, delivery.invoker).pipe(Effect.map(delivery.finish));
};

describe("native attachment delivery", () => {
it.effect("removes serialized attachment bytes from returns, logs, and emitted text", () =>
Effect.gen(function* () {
const media = { ...image, data: "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" };
const result = yield* run(
{ ok: true, data: { content: [media] } },
'const r = await tools.slides.org.main.render({}); const text = JSON.stringify(r); console.log(text); emit({type:"text",text}); return text;',
);
expect(JSON.stringify({ result: result.result, logs: result.logs })).not.toContain(
media.data,
);
expect(result.output?.[0]).toEqual({ type: "content", content: media });
expect(JSON.stringify(result.output?.[1])).not.toContain(media.data);
}),
);
it.effect("delivers every MCP attachment without emit or a download", () =>
Effect.gen(function* () {
const result = yield* run(
{ ok: true, data: { content: [image, video], structuredContent: { render_id: "abc" } } },
"return await tools.slides.org.main.render({});",
);
expect(result.output).toEqual([
{ type: "content", content: image },
{ type: "content", content: video },
]);
expect(JSON.stringify(result.result)).not.toContain(image.data);
expect(JSON.stringify(result.result)).not.toContain(video.resource.blob);
expect(result.result).toMatchObject({
ok: true,
data: { structuredContent: { render_id: "abc" } },
});
}),
);

it.effect("delivers files even when the script returns only metadata", () =>
Effect.gen(function* () {
const file = {
_tag: "ToolFile",
encoding: "base64",
name: "report.pdf",
mimeType: "application/pdf",
data: "JVBERg==",
byteLength: 4,
};
const result = yield* run(
{ ok: true, data: file },
"await tools.reports.org.main.get({}); return {done:true};",
);
expect(result.output).toEqual([{ type: "file", file }]);
expect(result.result).toEqual({ done: true });
}),
);

it.effect("preserves explicit output order without duplicate attachments", () =>
Effect.gen(function* () {
const result = yield* run(
{ ok: true, data: { content: [image] } },
'const r = await tools.slides.org.main.render({}); emit({type:"text",text:"caption"}); emit(r.data.content[0]); return r;',
);
expect(result.output).toEqual([
{ type: "content", content: { type: "text", text: "caption" } },
{ type: "content", content: image },
]);
}),
);

it.effect("keeps attachment bytes available for tool-to-tool uploads", () =>
Effect.gen(function* () {
const result = yield* run(
{ ok: true, data: { content: [image] } },
"const r = await tools.slides.org.main.render({}); return {bytesAvailable: r.data.content[0].data === 'R0lGODlh'};",
);
expect(result.result).toEqual({ bytesAvailable: true });
}),
);

it.effect("does not deliver failed tool results", () =>
Effect.gen(function* () {
for (const value of [
{ ok: false, error: { content: [image] } },
{ ok: true, data: { isError: true, content: [image] } },
]) {
const result = yield* run(value, "return await tools.slides.org.main.render({});");
expect(result.output ?? []).toEqual([]);
expect(JSON.stringify(result.result)).not.toContain(image.data);
}
}),
);

it.effect("keeps successful attachments when a later script step fails", () =>
Effect.gen(function* () {
const result = yield* run(
{ ok: true, data: { content: [image] } },
"await tools.slides.org.main.render({}); throw new Error('later');",
);
expect(result.error).toContain("later");
expect(result.output).toEqual([{ type: "content", content: image }]);
}),
);

it.effect("preserves ordinary text and resource links", () =>
Effect.gen(function* () {
const value = {
ok: true,
data: {
content: [
{ type: "text", text: "hello" },
{ type: "resource_link", uri: "https://example.com", name: "report" },
],
},
};
const result = yield* run(value, "return await tools.slides.org.main.render({});");
expect(result.result).toEqual(value);
expect(result.output).toBeUndefined();
}),
);
});
135 changes: 135 additions & 0 deletions packages/core/execution/src/attachment-delivery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { Effect } from "effect";
import { isToolFile } from "@executor-js/sdk";
import type { ExecuteResult, SandboxToolInvoker } from "@executor-js/codemode-core";

type Output = NonNullable<ExecuteResult["output"]>[number];
const isRecord = (value: unknown): value is Record<string, unknown> =>
value !== null && typeof value === "object" && !Array.isArray(value);

/** Keep attachment bytes on the host and expose metadata in the result preview. */
export const withAttachmentDelivery = (invoker: SandboxToolInvoker) => {
const attachments: Output[] = [];
const seen = new Set<string>();
const bytes = new Set<string>();

const redact = (text: string): string => {
for (const encoded of bytes) {
if (encoded.length >= 16) text = text.replaceAll(encoded, "[attachment bytes omitted]");
}
return text;
};

const capture = (value: unknown, deliver: boolean): unknown => {
if (isToolFile(value)) {
bytes.add(value.data);
const key = `${value.mimeType}:${value.data}`;
if (deliver && !seen.has(key)) {
seen.add(key);
attachments.push({ type: "file", file: value });
}
return {
name: value.name,
mimeType: value.mimeType,
byteLength: value.byteLength,
delivery: deliver ? "attachment" : "omitted",
};
}
if (Array.isArray(value)) return value.map((item) => capture(item, deliver));
if (!isRecord(value)) return typeof value === "string" ? redact(value) : value;
// Failed calls must never deliver attachments, including nested MCP errors.
deliver = deliver && value.ok !== false && value.isError !== true;

const binary =
(value.type === "image" || value.type === "audio") &&
typeof value.data === "string" &&
typeof value.mimeType === "string"
? { mimeType: value.mimeType, data: value.data }
: value.type === "resource" &&
isRecord(value.resource) &&
typeof value.resource.blob === "string"
? {
mimeType: value.resource.mimeType,
data: value.resource.blob,
uri: value.resource.uri,
}
: undefined;
if (binary) {
bytes.add(binary.data);
const key = `${binary.mimeType}:${binary.data}`;
if (deliver && !seen.has(key)) {
seen.add(key);
attachments.push({ type: "content", content: value });
}
return {
type: "text",
text: JSON.stringify({
delivery: deliver ? "attachment" : "omitted",
mimeType: binary.mimeType,
uri: binary.uri,
}),
};
}
return Object.fromEntries(
Object.entries(value).map(([key, item]) => [key, capture(item, deliver)]),
);
};

return {
invoker: {
invoke: (input) =>
invoker.invoke(input).pipe(
Effect.tap((value) =>
Effect.sync(() => {
capture(value, true);
}),
),
),
} satisfies SandboxToolInvoker,
finish: (result: ExecuteResult): ExecuteResult => {
// Returning a file or native block directly also delivers it. Explicit emit
// remains supported; match its bytes to avoid sending the attachment twice.
const compactResult = capture(result.result, true);
const key = (output: Output): string | undefined => {
const value = output.type === "file" ? output.file : output.content;
if (isToolFile(value)) return `${value.mimeType}:${value.data}`;
if (!isRecord(value)) return undefined;
if ((value.type === "image" || value.type === "audio") && typeof value.data === "string")
return `${value.mimeType}:${value.data}`;
if (
value.type === "resource" &&
isRecord(value.resource) &&
typeof value.resource.blob === "string"
)
return `${value.resource.mimeType}:${value.resource.blob}`;
return undefined;
};
const explicitKeys = new Set<string>();
const explicit = (result.output ?? [])
.map((item) => {
if (
item.type === "content" &&
isRecord(item.content) &&
item.content.type === "text" &&
typeof item.content.text === "string"
) {
return { ...item, content: { ...item.content, text: redact(item.content.text) } };
}
return item;
})
.filter((item) => {
const identity = key(item);
if (identity === undefined) return true;
if (explicitKeys.has(identity)) return false;
explicitKeys.add(identity);
return true;
});
const output = [...attachments.filter((item) => !explicitKeys.has(key(item)!)), ...explicit];
return {
...result,
result: compactResult,
...(result.logs ? { logs: result.logs.map(redact) } : {}),
...(output.length ? { output } : {}),
};
},
};
};
58 changes: 47 additions & 11 deletions packages/core/execution/src/description.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ const SLACK = IntegrationSlug.make("slack");
const TEMPLATE = AuthTemplateSlug.make("apiKey");

// The execute description lists the top-level integrations the user has
// connected: one bare line per integration slug, deduped across connections,
// names only (no per-integration descriptions).
// connected: one line per integration slug, deduped across connections, with
// the integration's capability description when the catalog carries a real one
// (legacy slug/name-only descriptions are suppressed).
const githubPlugin = definePlugin(() => ({
id: "github-plugin" as const,
credentialProviders: [memoryProvider()],
Expand Down Expand Up @@ -118,7 +119,7 @@ describe("buildExecuteDescription", () => {
}),
);

it.effect("lists integration names only, with no descriptions", () =>
it.effect("renders capability descriptions, suppressing slug/name repeats", () =>
Effect.gen(function* () {
const executor = yield* createExecutor(
makeTestConfig({ plugins: [slackPlugin, githubPlugin] as const }),
Expand All @@ -142,12 +143,10 @@ describe("buildExecuteDescription", () => {

const description = yield* buildExecuteDescription(executor);

// Bare slugs, sorted, with no per-integration description riding the line
// (Slack's "Send and read workspace messages." is dropped).
expect(description).toContain("- `github`");
expect(description).toContain("- `slack`");
expect(description).not.toContain("Send and read workspace messages");
expect(description).not.toContain("- `slack` —");
// Slack's real capability description rides its line; github's legacy
// description ("GitHub", a name repeat) is suppressed.
expect(description).toContain("- `slack` — Send and read workspace messages.");
expect(description).toContain("- `github`\n");
expect(description).not.toContain("- `github` —");
}),
);
Expand Down Expand Up @@ -179,6 +178,43 @@ describe("buildExecuteDescription", () => {
}),
);

it.effect("truncates long descriptions to one scannable line", () =>
Effect.gen(function* () {
const verbosePlugin = definePlugin(() => ({
id: "verbose-plugin" as const,
credentialProviders: [memoryProvider()],
storage: () => ({}),
extension: (ctx) => ({
seed: () =>
ctx.core.integrations.register({
slug: IntegrationSlug.make("verbose"),
name: "Verbose",
description: `${"word ".repeat(60).trim()} trailing\nsecond line ignored`,
config: {},
}),
}),
}))();
const executor = yield* createExecutor(makeTestConfig({ plugins: [verbosePlugin] as const }));
yield* executor["verbose-plugin"].seed();
yield* executor.connections.create({
owner: "org",
name: ConnectionName.make("main"),
integration: IntegrationSlug.make("verbose"),
template: TEMPLATE,
value: "verbose-token",
});

const description = yield* buildExecuteDescription(executor);
const line = description.split("\n").find((l) => l.startsWith("- `verbose`")) ?? "";

expect(line.startsWith("- `verbose` — word")).toBe(true);
expect(line.endsWith("…")).toBe(true);
expect(line.length).toBeLessThan(140);
expect(line).not.toContain("trailing");
expect(line).not.toContain("second line");
}),
);

it.effect("omits the Available integrations section when no connections exist", () =>
Effect.gen(function* () {
const executor = yield* createExecutor(makeTestConfig({ plugins: [] as const }));
Expand Down Expand Up @@ -224,15 +260,15 @@ describe("parseIntegrationInventory", () => {
expect(parseIntegrationInventory("Execute TypeScript in a sandboxed runtime.")).toEqual([]);
});

it("reads item lines only, not the overflow marker or prose", () => {
it("reads item lines only, not the overflow marker, prose, or descriptions", () => {
const description = [
"Execute TypeScript in a sandboxed runtime.",
"",
"## Available integrations",
"",
"Integrations you have connected. Their tools live under `tools.<integration>.…`.",
"- `github`",
"- `google_gmail`",
"- `google_gmail` — search, read, and send mail",
"- ... 3 more",
].join("\n");

Expand Down
Loading
Loading