/** * Runtime output-shape inference — the "muscle memory" half of code mode. * * Most tools declare no output schema, so `tools.describe.tool()` renders * `data: unknown` and the model guesses response shapes. This module infers a * lightweight shape from real tool results at dispatch time: field names and * broad types only, never values. Shapes merge across calls, so unions and * optional fields converge instead of thrashing. * * The output is a small JSON Schema subset (`type`, `properties`, `required`, * `items`, `additionalProperties`, `anyOf`) so it can be stored as plain JSON * and rendered through the same schema → TypeScript compiler that declared * schemas use. * * Every dimension is bounded — depth, object width, array sampling, union * width, serialized size — because inference runs on the hot dispatch path * against arbitrary upstream payloads. */ export type InferredShape = { readonly type?: "null" | "boolean" | "number" | "string" | "object" | "array"; readonly properties?: Readonly<Record<string, InferredShape>>; readonly required?: readonly string[]; readonly items?: InferredShape; readonly additionalProperties?: InferredShape; readonly anyOf?: readonly InferredShape[];}; /** `{}` — matches anything; the "we know nothing beyond this point" shape. */const UNKNOWN: InferredShape = {}; const MAX_DEPTH = 6;/** Array elements sampled per call; later calls keep widening the merge. */const MAX_ARRAY_SAMPLE = 5;/** * An object with more own keys than this is treated as a map keyed by data * (ids, emails, dates) rather than a struct. Collapsing to * `additionalProperties` keeps data-bearing keys out of the shape — field * names of a struct are API surface, but map keys are values. */const MAX_OBJECT_KEYS = 24;/** Union width cap; beyond this the shape degrades to unknown. */const MAX_ANYOF = 4; const isUnknown = (shape: InferredShape): boolean => shape.type === undefined && shape.anyOf === undefined; /** Infer the shape of one observed value. Reads structure only, never values. */export const inferShape = (value: unknown, depth = 0): InferredShape => { if (value === null || value === undefined) return { type: "null" }; if (typeof value === "boolean") return { type: "boolean" }; if (typeof value === "number") return { type: "number" }; if (typeof value === "string") return { type: "string" }; if (depth >= MAX_DEPTH) return UNKNOWN; if (Array.isArray(value)) { if (value.length === 0) return { type: "array" }; const sampled = value .slice(0, MAX_ARRAY_SAMPLE) .map((item) => inferShape(item, depth + 1)) .reduce((left, right) => mergeShapes(left, right)); return { type: "array", items: sampled }; } if (typeof value === "object") { const entries = Object.entries(value as Record<string, unknown>); if (entries.length > MAX_OBJECT_KEYS) { const merged = entries .slice(0, MAX_ARRAY_SAMPLE) .map(([, item]) => inferShape(item, depth + 1)) .reduce((left, right) => mergeShapes(left, right)); return { type: "object", additionalProperties: merged }; } const properties: Record<string, InferredShape> = {}; for (const [key, item] of entries) { properties[key] = inferShape(item, depth + 1); } return { type: "object", properties, required: entries.map(([key]) => key).sort() }; } // function / symbol / bigint — nothing useful to say structurally. return UNKNOWN;}; const mergeObjectShapes = (left: InferredShape, right: InferredShape): InferredShape => { // A map-shaped observation absorbs struct-shaped ones: once keys look like // data, later struct keys are data too. if (left.additionalProperties !== undefined || right.additionalProperties !== undefined) { const values = [ left.additionalProperties, right.additionalProperties, ...Object.values(left.properties ?? {}), ...Object.values(right.properties ?? {}), ].filter((shape): shape is InferredShape => shape !== undefined); return { type: "object", additionalProperties: values.length === 0 ? UNKNOWN : values.reduce((a, b) => mergeShapes(a, b)), }; } const leftProps = left.properties ?? {}; const rightProps = right.properties ?? {}; const keys = [...new Set([...Object.keys(leftProps), ...Object.keys(rightProps)])].sort(); if (keys.length > MAX_OBJECT_KEYS) { const values = keys .slice(0, MAX_ARRAY_SAMPLE) .map((key) => leftProps[key] ?? rightProps[key]) .filter((shape): shape is InferredShape => shape !== undefined); return { type: "object", additionalProperties: values.length === 0 ? UNKNOWN : values.reduce((a, b) => mergeShapes(a, b)), }; } const properties: Record<string, InferredShape> = {}; for (const key of keys) { const a = leftProps[key]; const b = rightProps[key]; properties[key] = a !== undefined && b !== undefined ? mergeShapes(a, b) : (a ?? b ?? UNKNOWN); } const leftRequired = new Set(left.required ?? []); const required = (right.required ?? []).filter((key) => leftRequired.has(key)).sort(); return { type: "object", properties, required };}; /** * Merge two observed shapes into the narrowest shape matching both. * Same-typed shapes merge structurally; differently-typed shapes union into * `anyOf`, degrading to unknown past `MAX_ANYOF` branches. */export const mergeShapes = (left: InferredShape, right: InferredShape): InferredShape => { if (isUnknown(left) || isUnknown(right)) return UNKNOWN; if (left.anyOf !== undefined || right.anyOf !== undefined) { const branches = [...(left.anyOf ?? [left]), ...(right.anyOf ?? [right])]; return branches.reduce((merged, branch) => addUnionBranch(merged, branch), { anyOf: [], } as InferredShape); } if (left.type !== right.type) return addUnionBranch({ anyOf: [left] }, right); if (left.type === "object") return mergeObjectShapes(left, right); if (left.type === "array") { if (left.items === undefined) return right; if (right.items === undefined) return left; return { type: "array", items: mergeShapes(left.items, right.items) }; } return left;}; const addUnionBranch = (union: InferredShape, branch: InferredShape): InferredShape => { if (isUnknown(union) || isUnknown(branch)) return UNKNOWN; const branches = [...(union.anyOf ?? [union])]; const index = branches.findIndex((existing) => existing.type === branch.type); const next = index === -1 ? [...branches, branch] : branches.map((existing, i) => (i === index ? mergeShapes(existing, branch) : existing)); if (next.length > MAX_ANYOF) return UNKNOWN; return next.length === 1 ? (next[0] ?? UNKNOWN) : { anyOf: next };}; /** * One tool's accumulated muscle memory: the merged shape plus enough * bookkeeping to judge freshness. Stored as plain JSON. */export type ObservedShape = { readonly schema: InferredShape; readonly observations: number; readonly updatedAt: number;}; /** Serialized-size ceiling per tool; a shape past this degrades to unknown * children rather than growing without bound. */const MAX_SHAPE_JSON_CHARS = 16_000; const shrink = (shape: InferredShape, depth: number): InferredShape => { if (depth <= 0) return UNKNOWN; if (shape.anyOf) return { anyOf: shape.anyOf.map((branch) => shrink(branch, depth - 1)) }; if (shape.type === "array" && shape.items) { return { type: "array", items: shrink(shape.items, depth - 1) }; } if (shape.type === "object" && shape.additionalProperties) { return { type: "object", additionalProperties: shrink(shape.additionalProperties, depth - 1) }; } if (shape.type === "object" && shape.properties) { const properties: Record<string, InferredShape> = {}; for (const [key, child] of Object.entries(shape.properties)) { properties[key] = shrink(child, depth - 1); } return { ...shape, properties }; } return shape;}; /** Fold a new observation into an existing record, keeping the result bounded. */export const observeShape = ( previous: ObservedShape | null, value: unknown, now: number,): ObservedShape => { const observed = inferShape(value); let schema = previous === null ? observed : mergeShapes(previous.schema, observed); for ( let depth = MAX_DEPTH; depth > 0 && JSON.stringify(schema).length > MAX_SHAPE_JSON_CHARS; depth-- ) { schema = shrink(schema, depth); } return { schema, observations: (previous?.observations ?? 0) + 1, updatedAt: now, };};
/**
* Runtime output-shape inference — the "muscle memory" half of code mode.
*
* Most tools declare no output schema, so `tools.describe.tool()` renders
* `data: unknown` and the model guesses response shapes. This module infers a
* lightweight shape from real tool results at dispatch time: field names and
* broad types only, never values. Shapes merge across calls, so unions and
* optional fields converge instead of thrashing.
*
* The output is a small JSON Schema subset (`type`, `properties`, `required`,
* `items`, `additionalProperties`, `anyOf`) so it can be stored as plain JSON
* and rendered through the same schema → TypeScript compiler that declared
* schemas use.
*
* Every dimension is bounded — depth, object width, array sampling, union
* width, serialized size — because inference runs on the hot dispatch path
* against arbitrary upstream payloads.
*/
export type InferredShape = {
readonly type?: "null" | "boolean" | "number" | "string" | "object" | "array";
readonly properties?: Readonly<Record<string, InferredShape>>;
readonly required?: readonly string[];
readonly items?: InferredShape;
readonly additionalProperties?: InferredShape;
readonly anyOf?: readonly InferredShape[];
};
/** `{}` — matches anything; the "we know nothing beyond this point" shape. */
const UNKNOWN: InferredShape = {};
const MAX_DEPTH = 6;
/** Array elements sampled per call; later calls keep widening the merge. */
const MAX_ARRAY_SAMPLE = 5;
/**
* An object with more own keys than this is treated as a map keyed by data
* (ids, emails, dates) rather than a struct. Collapsing to
* `additionalProperties` keeps data-bearing keys out of the shape — field
* names of a struct are API surface, but map keys are values.
*/
const MAX_OBJECT_KEYS = 24;
/** Union width cap; beyond this the shape degrades to unknown. */
const MAX_ANYOF = 4;
const isUnknown = (shape: InferredShape): boolean =>
shape.type === undefined && shape.anyOf === undefined;
/** Infer the shape of one observed value. Reads structure only, never values. */
export const inferShape = (value: unknown, depth = 0): InferredShape => {
if (value === null || value === undefined) return { type: "null" };
if (typeof value === "boolean") return { type: "boolean" };
if (typeof value === "number") return { type: "number" };
if (typeof value === "string") return { type: "string" };
if (depth >= MAX_DEPTH) return UNKNOWN;
if (Array.isArray(value)) {
if (value.length === 0) return { type: "array" };
const sampled = value
.slice(0, MAX_ARRAY_SAMPLE)
.map((item) => inferShape(item, depth + 1))
.reduce((left, right) => mergeShapes(left, right));
return { type: "array", items: sampled };
}
if (typeof value === "object") {
const entries = Object.entries(value as Record<string, unknown>);
if (entries.length > MAX_OBJECT_KEYS) {
const merged = entries
.slice(0, MAX_ARRAY_SAMPLE)
.map(([, item]) => inferShape(item, depth + 1))
.reduce((left, right) => mergeShapes(left, right));
return { type: "object", additionalProperties: merged };
}
const properties: Record<string, InferredShape> = {};
for (const [key, item] of entries) {
properties[key] = inferShape(item, depth + 1);
}
return { type: "object", properties, required: entries.map(([key]) => key).sort() };
}
// function / symbol / bigint — nothing useful to say structurally.
return UNKNOWN;
};
const mergeObjectShapes = (left: InferredShape, right: InferredShape): InferredShape => {
// A map-shaped observation absorbs struct-shaped ones: once keys look like
// data, later struct keys are data too.
if (left.additionalProperties !== undefined || right.additionalProperties !== undefined) {
const values = [
left.additionalProperties,
right.additionalProperties,
...Object.values(left.properties ?? {}),
...Object.values(right.properties ?? {}),
].filter((shape): shape is InferredShape => shape !== undefined);
return {
type: "object",
additionalProperties:
values.length === 0 ? UNKNOWN : values.reduce((a, b) => mergeShapes(a, b)),
};
}
const leftProps = left.properties ?? {};
const rightProps = right.properties ?? {};
const keys = [...new Set([...Object.keys(leftProps), ...Object.keys(rightProps)])].sort();
if (keys.length > MAX_OBJECT_KEYS) {
const values = keys
.slice(0, MAX_ARRAY_SAMPLE)
.map((key) => leftProps[key] ?? rightProps[key])
.filter((shape): shape is InferredShape => shape !== undefined);
return {
type: "object",
additionalProperties:
values.length === 0 ? UNKNOWN : values.reduce((a, b) => mergeShapes(a, b)),
};
}
const properties: Record<string, InferredShape> = {};
for (const key of keys) {
const a = leftProps[key];
const b = rightProps[key];
properties[key] = a !== undefined && b !== undefined ? mergeShapes(a, b) : (a ?? b ?? UNKNOWN);
}
const leftRequired = new Set(left.required ?? []);
const required = (right.required ?? []).filter((key) => leftRequired.has(key)).sort();
return { type: "object", properties, required };
};
/**
* Merge two observed shapes into the narrowest shape matching both.
* Same-typed shapes merge structurally; differently-typed shapes union into
* `anyOf`, degrading to unknown past `MAX_ANYOF` branches.
*/
export const mergeShapes = (left: InferredShape, right: InferredShape): InferredShape => {
if (isUnknown(left) || isUnknown(right)) return UNKNOWN;
if (left.anyOf !== undefined || right.anyOf !== undefined) {
const branches = [...(left.anyOf ?? [left]), ...(right.anyOf ?? [right])];
return branches.reduce((merged, branch) => addUnionBranch(merged, branch), {
anyOf: [],
} as InferredShape);
}
if (left.type !== right.type) return addUnionBranch({ anyOf: [left] }, right);
if (left.type === "object") return mergeObjectShapes(left, right);
if (left.type === "array") {
if (left.items === undefined) return right;
if (right.items === undefined) return left;
return { type: "array", items: mergeShapes(left.items, right.items) };
}
return left;
};
const addUnionBranch = (union: InferredShape, branch: InferredShape): InferredShape => {
if (isUnknown(union) || isUnknown(branch)) return UNKNOWN;
const branches = [...(union.anyOf ?? [union])];
const index = branches.findIndex((existing) => existing.type === branch.type);
const next =
index === -1
? [...branches, branch]
: branches.map((existing, i) => (i === index ? mergeShapes(existing, branch) : existing));
if (next.length > MAX_ANYOF) return UNKNOWN;
return next.length === 1 ? (next[0] ?? UNKNOWN) : { anyOf: next };
};
/**
* One tool's accumulated muscle memory: the merged shape plus enough
* bookkeeping to judge freshness. Stored as plain JSON.
*/
export type ObservedShape = {
readonly schema: InferredShape;
readonly observations: number;
readonly updatedAt: number;
};
/** Serialized-size ceiling per tool; a shape past this degrades to unknown
* children rather than growing without bound. */
const MAX_SHAPE_JSON_CHARS = 16_000;
const shrink = (shape: InferredShape, depth: number): InferredShape => {
if (depth <= 0) return UNKNOWN;
if (shape.anyOf) return { anyOf: shape.anyOf.map((branch) => shrink(branch, depth - 1)) };
if (shape.type === "array" && shape.items) {
return { type: "array", items: shrink(shape.items, depth - 1) };
}
if (shape.type === "object" && shape.additionalProperties) {
return { type: "object", additionalProperties: shrink(shape.additionalProperties, depth - 1) };
}
if (shape.type === "object" && shape.properties) {
const properties: Record<string, InferredShape> = {};
for (const [key, child] of Object.entries(shape.properties)) {
properties[key] = shrink(child, depth - 1);
}
return { ...shape, properties };
}
return shape;
};
/** Fold a new observation into an existing record, keeping the result bounded. */
export const observeShape = (
previous: ObservedShape | null,
value: unknown,
now: number,
): ObservedShape => {
const observed = inferShape(value);
let schema = previous === null ? observed : mergeShapes(previous.schema, observed);
for (
let depth = MAX_DEPTH;
depth > 0 && JSON.stringify(schema).length > MAX_SHAPE_JSON_CHARS;
depth--
) {
schema = shrink(schema, depth);
}
return {
schema,
observations: (previous?.observations ?? 0) + 1,
updatedAt: now,
};
};