Skip to content
Closed
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
8 changes: 8 additions & 0 deletions .changeset/computed-output-dispatch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@stackables/bridge": patch
"@stackables/bridge-core": patch
"@stackables/bridge-parser": patch
"@stackables/bridge-compiler": patch
---

Bridge output array mappings now support computed root dispatch indices such as `o[item.index] <- source[] as item { ... }`. Runtime execution, streaming patches, serialization, and compiled execution all preserve the computed slot so mapped items land at the intended output positions.
7 changes: 7 additions & 0 deletions .changeset/trace-tool-metadata-fixes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@stackables/bridge": patch
"@stackables/bridge-core": patch
"@stackables/bridge-compiler": patch
---

Tool-level tracing metadata is now applied consistently across runtime and compiled execution. Internal helpers such as string-interpolation `concat` no longer emit trace entries when their metadata sets `trace: false`, and stream tools such as `httpSSE` now produce trace entries when tracing is enabled in normal `executeBridge()` execution.
180 changes: 159 additions & 21 deletions packages/bridge-compiler/src/codegen.ts

Large diffs are not rendered by default.

80 changes: 80 additions & 0 deletions packages/bridge-compiler/src/execute-bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

import type {
BridgeDocument,
Bridge,
ToolDef,
ToolMap,
Logger,
ToolTrace,
Expand Down Expand Up @@ -225,6 +227,54 @@ function flattenTools(

// ── Public API ──────────────────────────────────────────────────────────────

/**
* Collect the tool function names (flat-key form, e.g. "std.httpCall") that
* a specific bridge operation actually references. This walks the bridge's
* handle bindings → ToolDef → extends chain to resolve the `fn` field.
*/
function getUsedToolFnNames(
document: BridgeDocument,
operation: string,
): string[] {
const [type, field] = operation.split(".");
const bridge = document.instructions.find(
(i): i is Bridge =>
i.kind === "bridge" && i.type === type && i.field === field,
);
if (!bridge) return [];

const toolDefs = document.instructions.filter(
(i): i is ToolDef => i.kind === "tool",
);

const fnNames: string[] = [];
for (const h of bridge.handles) {
if (h.kind !== "tool") continue;
const resolved = resolveToolFn(h.name, toolDefs);
fnNames.push(resolved ?? h.name);
}
return fnNames;
}

/** Walk the ToolDef extends chain to find the actual `fn` name. */
function resolveToolFn(
name: string,
toolDefs: ToolDef[],
seen?: Set<string>,
): string | undefined {
const def = toolDefs.find((t) => t.name === name);
if (!def) return undefined;
if (def.fn) return def.fn;
if (def.extends) {
// Guard against circular extends
const visited = seen ?? new Set<string>();
if (visited.has(name)) return undefined;
visited.add(name);
return resolveToolFn(def.extends, toolDefs, visited);
}
return undefined;
}

/**
* Execute a bridge operation using AOT-compiled code.
*
Expand Down Expand Up @@ -289,6 +339,36 @@ export async function executeBridge<T = unknown>(
const allTools: ToolMap = { std: bundledStd, ...userTools };
const flatTools = flattenTools(allTools as Record<string, any>);

// Stream tools (async generators with `.bridge.stream`) are unsupported by
// the compiled code path. Fall back to the core interpreter which wraps
// them in StreamHandle / eagerly consumes them.
// Only check tools actually referenced by this bridge operation — not every
// tool in the namespace — so unrelated stream tools don't force a fallback.
const usedFnNames = getUsedToolFnNames(document, operation);
for (const fnName of usedFnNames) {
const val = flatTools[fnName];
if (
typeof val === "function" &&
val.bridge &&
typeof val.bridge === "object" &&
(val as { bridge: { stream?: boolean } }).bridge.stream === true
) {
return executeCoreBridge<T>({
document,
operation,
input,
tools: userTools,
context,
signal,
toolTimeoutMs,
logger,
trace: options.trace,
requestedFields: options.requestedFields,
...(maxDepth !== undefined ? { maxDepth } : {}),
});
}
}

// Set up tracing if requested
const traceLevel = options.trace ?? "off";
let tracer: TraceCollector | undefined;
Expand Down
Loading
Loading