// ---------------------------------------------------------------------------// ToolResult — typed value-based discriminated union returned by tool// handlers and `invokeTool`. Domain success and expected failure both// resolve through Effect's success channel; only true infra defects use// the Effect failure channel.// --------------------------------------------------------------------------- import { Effect, Schema } from "effect"; export const ToolErrorSchema = Schema.Struct({ code: Schema.String, message: Schema.String, status: Schema.optional(Schema.Number), details: Schema.optional(Schema.Unknown), retryable: Schema.optional(Schema.Boolean),}); export type ToolError = typeof ToolErrorSchema.Type; export const ToolHttpMetaSchema = Schema.Struct({ status: Schema.Number, headers: Schema.Record(Schema.String, Schema.String),}); /** * Transport metadata for HTTP-backed tools (OpenAPI). Kept beside `data` * rather than wrapped around it: `data` stays the upstream payload, while * cross-cutting transport facts (pagination Link headers, rate-limit * headers) remain reachable for callers that need them. */export type ToolHttpMeta = typeof ToolHttpMetaSchema.Type; export const ToolFileSchema = Schema.TaggedStruct("ToolFile", { name: Schema.optional(Schema.String), mimeType: Schema.String, encoding: Schema.Literal("base64"), data: Schema.String.annotate({ description: "Base64-encoded file bytes.", contentEncoding: "base64", }), byteLength: Schema.Int.annotate({ description: "Raw file size in bytes before base64 encoding.", }),}); export type ToolFile = typeof ToolFileSchema.Type; export const ToolFileJsonSchema = Schema.toJsonSchemaDocument(ToolFileSchema).schema; const matchesToolFileSchema = Schema.is(ToolFileSchema); export const isToolFile = (value: unknown): value is ToolFile => matchesToolFileSchema(value); export type ToolResult<T> = | { readonly ok: true; readonly data: T; readonly http?: ToolHttpMeta } | { readonly ok: false; readonly error: ToolError }; export const ToolResult = { ok: <T>(data: T, meta?: { readonly http?: ToolHttpMeta }): ToolResult<T> => ({ ok: true, data, ...(meta?.http ? { http: meta.http } : {}), }), fail: <T = never>(error: ToolError): ToolResult<T> => ({ ok: false, error }),} as const; const ToolResultSchema = Schema.Union([ Schema.Struct({ ok: Schema.Literal(true), data: Schema.Unknown, http: Schema.optional(ToolHttpMetaSchema), }), Schema.Struct({ ok: Schema.Literal(false), error: ToolErrorSchema }),]); const isUnknownToolResult = Schema.is(ToolResultSchema); export const isToolResult = (value: unknown): value is ToolResult<unknown> => isUnknownToolResult(value); /** * Annotate the current span with the outcome of a tool invocation. * * `ToolResult.fail` rides the Effect *success* channel by design (expected * failures are values, not defects), which means the tracer records those * spans as healthy. Without this, "user keeps hitting 4xx walls" is invisible * to telemetry — the exact class of signal that lets us catch product issues * before they're reported. Stamped attributes: * * - `executor.tool.outcome` — "ok" | "fail" (always, on ToolResults) * - `executor.tool.error_code` — ToolError.code (fail only) * - `executor.tool.error_status` — upstream HTTP status (fail, when present) * * Codes/statuses are enumerable identifiers, never user content — safe span * attributes. Non-ToolResult values (raw success payloads) annotate "ok". */export const annotateToolResultOutcome = (value: unknown): Effect.Effect<void> => { if (isToolResult(value) && !value.ok) { return Effect.annotateCurrentSpan({ "executor.tool.outcome": "fail", "executor.tool.error_code": value.error.code, ...(value.error.status != null ? { "executor.tool.error_status": value.error.status } : {}), }); } return Effect.annotateCurrentSpan({ "executor.tool.outcome": "ok" });};
// ---------------------------------------------------------------------------
// ToolResult — typed value-based discriminated union returned by tool
// handlers and `invokeTool`. Domain success and expected failure both
// resolve through Effect's success channel; only true infra defects use
// the Effect failure channel.
// ---------------------------------------------------------------------------
import { Effect, Schema } from "effect";
export const ToolErrorSchema = Schema.Struct({
code: Schema.String,
message: Schema.String,
status: Schema.optional(Schema.Number),
details: Schema.optional(Schema.Unknown),
retryable: Schema.optional(Schema.Boolean),
});
export type ToolError = typeof ToolErrorSchema.Type;
export const ToolHttpMetaSchema = Schema.Struct({
status: Schema.Number,
headers: Schema.Record(Schema.String, Schema.String),
});
/**
* Transport metadata for HTTP-backed tools (OpenAPI). Kept beside `data`
* rather than wrapped around it: `data` stays the upstream payload, while
* cross-cutting transport facts (pagination Link headers, rate-limit
* headers) remain reachable for callers that need them.
*/
export type ToolHttpMeta = typeof ToolHttpMetaSchema.Type;
export const ToolFileSchema = Schema.TaggedStruct("ToolFile", {
name: Schema.optional(Schema.String),
mimeType: Schema.String,
encoding: Schema.Literal("base64"),
data: Schema.String.annotate({
description: "Base64-encoded file bytes.",
contentEncoding: "base64",
}),
byteLength: Schema.Int.annotate({
description: "Raw file size in bytes before base64 encoding.",
}),
});
export type ToolFile = typeof ToolFileSchema.Type;
export const ToolFileJsonSchema = Schema.toJsonSchemaDocument(ToolFileSchema).schema;
const matchesToolFileSchema = Schema.is(ToolFileSchema);
export const isToolFile = (value: unknown): value is ToolFile => matchesToolFileSchema(value);
export type ToolResult<T> =
| { readonly ok: true; readonly data: T; readonly http?: ToolHttpMeta }
| { readonly ok: false; readonly error: ToolError };
export const ToolResult = {
ok: <T>(data: T, meta?: { readonly http?: ToolHttpMeta }): ToolResult<T> => ({
ok: true,
data,
...(meta?.http ? { http: meta.http } : {}),
}),
fail: <T = never>(error: ToolError): ToolResult<T> => ({ ok: false, error }),
} as const;
const ToolResultSchema = Schema.Union([
Schema.Struct({
ok: Schema.Literal(true),
data: Schema.Unknown,
http: Schema.optional(ToolHttpMetaSchema),
}),
Schema.Struct({ ok: Schema.Literal(false), error: ToolErrorSchema }),
]);
const isUnknownToolResult = Schema.is(ToolResultSchema);
export const isToolResult = (value: unknown): value is ToolResult<unknown> =>
isUnknownToolResult(value);
/**
* Annotate the current span with the outcome of a tool invocation.
*
* `ToolResult.fail` rides the Effect *success* channel by design (expected
* failures are values, not defects), which means the tracer records those
* spans as healthy. Without this, "user keeps hitting 4xx walls" is invisible
* to telemetry — the exact class of signal that lets us catch product issues
* before they're reported. Stamped attributes:
*
* - `executor.tool.outcome` — "ok" | "fail" (always, on ToolResults)
* - `executor.tool.error_code` — ToolError.code (fail only)
* - `executor.tool.error_status` — upstream HTTP status (fail, when present)
*
* Codes/statuses are enumerable identifiers, never user content — safe span
* attributes. Non-ToolResult values (raw success payloads) annotate "ok".
*/
export const annotateToolResultOutcome = (value: unknown): Effect.Effect<void> => {
if (isToolResult(value) && !value.ok) {
return Effect.annotateCurrentSpan({
"executor.tool.outcome": "fail",
"executor.tool.error_code": value.error.code,
...(value.error.status != null ? { "executor.tool.error_status": value.error.status } : {}),
});
}
return Effect.annotateCurrentSpan({ "executor.tool.outcome": "ok" });
};