feat: add capability playground to discover

This commit is contained in:
lda
2026-08-11 20:30:30 +07:00 Verified
parent 13f56c4c95
commit 0fd4b561c7
12 changed files with 1136 additions and 48 deletions
@@ -3,7 +3,7 @@ import type { DraftDiagnostic, DraftWorkspace } from "../domain/draft-workspace-
import type { CapabilityDetail, CapabilitySummary } from "../domain/capability-models.js";
import { projectAuthoringGraph, type WorkbenchSelection } from "./authoring-graph.js";
import { withDiagnosticKeys } from "./diagnostic-key.js";
import { formatBoundedJson } from "./format-bounded-json.js";
import { formatBoundedJson } from "../domain/format-bounded-json.js";
import { CapabilityNodeForm } from "./CapabilityNodeForm.js";
import { SelectedCapabilityInspector } from "./SelectedCapabilityInspector.js";
import { RouteForm } from "./RouteForm.js";
@@ -11,7 +11,7 @@ import {
type SchemaField,
} from "../schema-form/schema-field.js";
import { serializeSchemaValues, type FieldSources } from "../schema-form/schema-values.js";
import { formatBoundedJson } from "./format-bounded-json.js";
import { formatBoundedJson } from "../domain/format-bounded-json.js";
import { displayGraphInputPath, displayLocalInputPath } from "./input-binding-paths.js";
import {
capabilityLocalPathSuggestions,
@@ -5,7 +5,7 @@ import type {
OutputBinding,
StatePath,
} from "../domain/draft-workspace-models.js";
import { formatBoundedJson } from "./format-bounded-json.js";
import { formatBoundedJson } from "../domain/format-bounded-json.js";
import {
capabilityLocalPathSuggestions,
inferredStateSchemaPreview,
@@ -1,33 +0,0 @@
import { describe, expect, it } from "vitest";
import { formatBoundedJson } from "./format-bounded-json.js";
describe("formatBoundedJson", () => {
it("bounds traversal without reading remote fields after the budget", () => {
const draft = {
first: "x".repeat(1_000),
get later() {
throw new Error("later field should not be read");
},
};
expect(() => formatBoundedJson(draft, 80)).not.toThrow();
expect(formatBoundedJson(draft, 80)).toHaveLength(80);
expect(formatBoundedJson(draft, 80)).toContain("truncated");
});
it("keeps an exact-fit JSON document complete and valid", () => {
const draft = { step: "collect", count: 2 };
const completeJson = JSON.stringify(draft, null, 2);
expect(formatBoundedJson(draft, completeJson.length)).toBe(completeJson);
expect(JSON.parse(formatBoundedJson(draft, completeJson.length))).toEqual(draft);
});
it("does not truncate a complete document just below the boundary", () => {
const draft = { step: "collect", count: 2 };
const completeJson = JSON.stringify(draft, null, 2);
expect(formatBoundedJson(draft, completeJson.length + 1)).toBe(completeJson);
expect(formatBoundedJson(draft, completeJson.length - 1)).toContain("truncated");
});
});
@@ -1,101 +0,0 @@
const MAX_RAW_DRAFT_CHARS = 12_000;
const TRUNCATION_MARKER = "... truncated ...";
const isRecord = (value: unknown): value is Record<string, unknown> =>
typeof value === "object" && value !== null && !Array.isArray(value);
// Traverse until the display budget is exhausted so a large remote object is
// never fully materialized just to produce a clipped escape-hatch preview.
export const formatBoundedJson = (
value: unknown,
maxChars = MAX_RAW_DRAFT_CHARS,
): string => {
const truncationMarker = TRUNCATION_MARKER.slice(0, Math.max(0, maxChars));
const contentLimit = Math.max(0, maxChars);
let output = "";
let truncated = false;
const activeObjects = new WeakSet<object>();
const append = (chunk: string): void => {
if (truncated) return;
if (output.length + chunk.length > contentLimit) {
output += chunk.slice(0, Math.max(0, contentLimit - output.length));
truncated = true;
return;
}
output += chunk;
};
const appendJsonString = (text: string): void => {
append('"');
for (let index = 0; index < text.length; index++) {
if (truncated) return;
const code = text.charCodeAt(index);
if (code === 0x22) append('\\"');
else if (code === 0x5c) append("\\\\");
else if (code < 0x20) append(`\\u${code.toString(16).padStart(4, "0")}`);
else if (code >= 0xd800 && code <= 0xdbff) {
const nextCode = text.charCodeAt(index + 1);
if (nextCode >= 0xdc00 && nextCode <= 0xdfff) {
append(text.slice(index, index + 2));
index++;
} else append(`\\u${code.toString(16).padStart(4, "0")}`);
} else if (code >= 0xdc00 && code <= 0xdfff) {
append(`\\u${code.toString(16).padStart(4, "0")}`);
} else append(text.charAt(index));
}
if (!truncated) append('"');
};
const visit = (current: unknown, depth: number): void => {
if (truncated) return;
if (current === null || typeof current !== "object") {
if (typeof current === "string") appendJsonString(current);
else if (typeof current === "number") append(Number.isFinite(current) ? String(current) : "null");
else if (typeof current === "boolean") append(current ? "true" : "false");
else append("null");
return;
}
if (activeObjects.has(current)) {
append('"[Circular]"');
return;
}
activeObjects.add(current);
const indent = " ".repeat(depth);
const childIndent = " ".repeat(depth + 1);
if (Array.isArray(current)) {
append("[");
let first = true;
for (const item of current) {
if (truncated) break;
append(first ? `\n${childIndent}` : `,\n${childIndent}`);
visit(item, depth + 1);
first = false;
}
if (!truncated) append(first ? "]" : `\n${indent}]`);
} else {
if (!isRecord(current)) {
activeObjects.delete(current);
return;
}
const record = current;
append("{");
let first = true;
for (const key in record) {
if (!Object.prototype.hasOwnProperty.call(record, key) || truncated) continue;
append(first ? `\n${childIndent}` : `,\n${childIndent}`);
appendJsonString(key);
append(": ");
visit(record[key], depth + 1);
first = false;
}
if (!truncated) append(first ? "}" : `\n${indent}}`);
}
activeObjects.delete(current);
};
visit(value, 0);
if (!truncated) return output;
const markerStart = Math.max(0, contentLimit - truncationMarker.length);
return `${output.slice(0, markerStart)}${truncationMarker}`;
};