Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
abe3576
feat(frontend): preserve native JSON in evaluator execution inputs
ardaerzin May 25, 2026
fe7b85f
feat(frontend): promote V2 view-mode primitives to @agenta/entity-ui
ardaerzin May 25, 2026
fbcb9e2
feat(frontend): add PlaygroundInputsBody for V2-aligned input UX
ardaerzin May 25, 2026
5144cce
feat(frontend): add inputs visibility rule for the playground inputs …
ardaerzin May 25, 2026
cace2ec
feat(frontend): add TemplateFormatPicker for the playground
ardaerzin May 25, 2026
42047fd
fix(frontend): wire @agenta/entity-ui/template-format subpath export
ardaerzin May 25, 2026
b0a1dda
feat(frontend): wire PlaygroundInputsBody into SingleLayout (feature-…
ardaerzin May 25, 2026
5483ec0
Merge remote-tracking branch 'origin/feat/add-mustache-rendering' int…
ardaerzin May 25, 2026
ba5fa4c
chore(frontend): reconcile post-merge with #4393's canonical helpers
ardaerzin May 25, 2026
6a84b7a
chore(frontend): fix import order in SingleLayout (move workspace imp…
ardaerzin May 25, 2026
df52f5f
feat(frontend): default the new playground inputs body ON
ardaerzin May 26, 2026
1a5b70e
fix(frontend): editor accepts mustache section close tags `{{/name}}`
ardaerzin May 26, 2026
030f438
docs(testing): add mustache + native-JSON QA testset fixture
ardaerzin May 26, 2026
e742a03
fix(frontend): validator accepts JSONPath rooted at testcase columns
ardaerzin May 26, 2026
56bbed7
fix(frontend): extractor skips mustache block markers; parser handles…
ardaerzin May 26, 2026
ac0c65c
fix(frontend): visibility atom dereferences entity.data
ardaerzin May 26, 2026
2b20b95
fix(frontend): native JSON survives the completion+chat request body …
ardaerzin May 26, 2026
699cdd3
feat(frontend): wire PlaygroundInputsBody into ComparisonLayout
ardaerzin May 26, 2026
d9424b7
feat(frontend): grouped evaluator layout uses PlaygroundInputsBody
ardaerzin May 26, 2026
2854349
feat(frontend): surface TemplateFormatPicker in playground section he…
ardaerzin May 26, 2026
faa41f4
fix(frontend): mustache section openers surface as input ports
ardaerzin May 27, 2026
cb10e2e
feat(frontend): schema-aware default view + chip for draft inputs
ardaerzin May 27, 2026
e4194a2
feat(frontend): seed object drafts with the expected sub-fields
ardaerzin May 27, 2026
83693af
fix(frontend): match prompt-config dropdown styling on ViewTypeSelect
ardaerzin May 27, 2026
d8db84d
feat(frontend): infer `array` type for mustache section-opener-only p…
ardaerzin May 27, 2026
ef6aef1
feat(frontend): array drafts default to JSON view (not Form)
ardaerzin May 27, 2026
2ad40f2
fix(frontend): restore "View as ▾" trigger label, keep simplified list
ardaerzin May 27, 2026
89f73b3
fix(frontend): right-align per-field "View as ▾" with card-level drop…
ardaerzin May 27, 2026
c76295e
fix(frontend): nested FormView labels match parent VariableCard style
ardaerzin May 27, 2026
37c99ff
fix(frontend): drop FormView right padding so leaf cards align with l…
ardaerzin May 27, 2026
7150093
feat(frontend): align playground input cards with input-ux mockup
ardaerzin May 27, 2026
52967ae
fix(frontend): label/chip/controls live INSIDE the variable card border
ardaerzin May 27, 2026
aefed44
fix(frontend): no inner border on hover/focus in VariableCard editors
ardaerzin May 27, 2026
88f5911
fix(frontend): don't seed YAML buffer with flow-style `[]` / `{}`
ardaerzin May 27, 2026
0bdfc93
fix(frontend): JSON / YAML always at the bottom of the view-mode list
ardaerzin May 27, 2026
acf98ab
feat(frontend): typeahead suggests field names after `{{#` / `{{^`
ardaerzin May 27, 2026
9ca1d8f
fix(frontend): JSON / YAML modes never auto-parse strings (gap-04 at …
ardaerzin May 27, 2026
8d088ad
fix(frontend): chat-mode variable cards are editable (match MessagesF…
ardaerzin May 27, 2026
d43c52e
fix(frontend): unreferenced columns are editable when expanded
ardaerzin May 27, 2026
2eb1f94
fix(frontend): gate mustache-specific affordances by templateFormat
ardaerzin May 27, 2026
b83dee2
Merge remote-tracking branch 'origin/feat/add-mustache-rendering' int…
ardaerzin May 27, 2026
f69a76e
fix(frontend): CodeRabbit review pass — formatting + targeted bugs
ardaerzin May 27, 2026
4ba4738
fix(frontend): thread active templateFormat into chat-mode variable i…
ardaerzin May 27, 2026
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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
[
{
"country": "Vanuatu",
"population_thousands": 320,
"is_island_nation": true,
"geo": {
"region": "Pacific Islands",
"subregion": "Western Melanesia",
"coordinates": {
"lat": -15.376,
"lng": 166.959
}
},
"languages": ["en", "bi", "fr"],
"metadata": "{\"source\":\"trace\",\"trace_id\":\"vu-001\",\"latency_ms\":520,\"confidence\":\"high\"}",
"messages": [
{
"role": "system",
"content": "You are a geography research assistant with access to a country lookup tool. Cite ISO codes when available."
},
{
"role": "user",
"content": "What is the capital of Vanuatu and its ISO 3166-1 alpha-2 code?"
},
{
"role": "assistant",
"content": "Port Vila is the capital of Vanuatu. Its ISO 3166-1 alpha-2 code is VU."
}
],
"correct_answer": "Port Vila",
"notes": "Vanuatu became independent in 1980. Unused-by-default to exercise the unreferenced-columns footer."
},
{
"country": "Kiribati",
"population_thousands": 120,
"is_island_nation": true,
"geo": {
"region": "Pacific Islands",
"subregion": "Micronesia",
"coordinates": {
"lat": 1.451,
"lng": 172.971
}
},
"languages": ["en", "gil"],
"metadata": "{\"source\":\"trace\",\"trace_id\":\"ki-001\",\"latency_ms\":480,\"confidence\":\"high\"}",
"messages": [
{
"role": "system",
"content": "You are a geography research assistant with access to a country lookup tool. Cite ISO codes when available."
},
{
"role": "user",
"content": "What is the capital of Kiribati?"
}
],
"correct_answer": "South Tarawa",
"notes": "Kiribati straddles the equator and the international date line."
},
{
"country": "Switzerland",
"population_thousands": 8700,
"is_island_nation": false,
"geo": {
"region": "Europe",
"subregion": "Western Europe",
"coordinates": {
"lat": 46.818,
"lng": 8.227
}
},
"languages": ["de", "fr", "it", "rm"],
"metadata": "{\"source\":\"trace\",\"trace_id\":\"ch-001\",\"latency_ms\":410,\"confidence\":\"high\"}",
"messages": [
{
"role": "user",
"content": "What is the capital of Switzerland?"
}
],
"correct_answer": "Bern",
"notes": "Switzerland is famous for its political neutrality and four national languages."
}
]
2 changes: 2 additions & 0 deletions web/packages/agenta-entities/src/runnable/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,12 @@ export {
buildEvaluatorExecutionInputs,
validateEvaluatorInputs,
// Template variable extraction
extractMustacheSectionOpeners,
extractTemplateVariables,
extractTemplateVariablesFromJson,
extractVariablesFromPrompts,
extractVariablesFromConfig,
extractSectionOpenersFromConfig,
extractVariablesFromEnhancedPrompts,
} from "./utils"
export type {ExecuteRunnableOptions} from "./utils"
Expand Down
80 changes: 70 additions & 10 deletions web/packages/agenta-entities/src/runnable/portHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,14 @@ export interface GroupedTemplateVariable {
/** Display label. Same as `key` under this model. */
name: string
/**
* Declared shape. `"object"` when any placeholder references a sub-path
* of this group (signals the UI to render a JSON editor); otherwise
* `"string"`.
* Declared shape.
* - `"object"` when any placeholder references a sub-path of this
* group (`{{geo.region}}` → object with `region` sub-path).
* - `"array"` when the name appears ONLY as a mustache section opener
* (`{{#languages}}{{.}}{{/languages}}` → array, no sub-paths).
* - `"string"` otherwise.
*/
type: "string" | "object"
type: "string" | "object" | "array"
/**
* Known sub-paths beneath the group (populated only when `type === "object"`).
* Used to seed a shape-hint default in the JSON editor so users see
Expand Down Expand Up @@ -191,11 +194,35 @@ function parseTemplateExpression(expr: string): ParsedTemplateExpression {

const parseSegments = (segments: string[]): ParsedTemplateExpression => {
if (segments.length === 0) return {envelope: "inputs", key: ""}
if (segments.length === 1) return {envelope: segments[0], key: ""}

const first = segments[0]
const firstIsEnvelope = KNOWN_ENVELOPE_SLOTS.has(first)

if (segments.length === 1) {
// `$.inputs` / `$.outputs` — envelope-only reference.
if (firstIsEnvelope) return {envelope: first, key: ""}
// `$.profile` — testcase-spread key. Per the RFC, testcase
// top-level columns are spread into the render context, so
// they live implicitly under the `inputs` envelope. Treating
// the first segment as the key under `inputs` keeps port
// discovery consistent with envelope-rooted writes.
return {envelope: "inputs", key: first}
}

if (firstIsEnvelope) {
return {
envelope: first,
key: segments[1],
subPath: segments.length > 2 ? segments.slice(2).join(".") : undefined,
}
}
// Testcase-spread key with a sub-path: `$.profile.name` →
// `{envelope: "inputs", key: "profile", subPath: "name"}`. The
// testcase spread makes this equivalent to `$.inputs.profile.name`.
return {
envelope: segments[0],
key: segments[1],
subPath: segments.length > 2 ? segments.slice(2).join(".") : undefined,
envelope: "inputs",
key: first,
subPath: segments.length > 1 ? segments.slice(1).join(".") : undefined,
}
}

Expand Down Expand Up @@ -261,9 +288,33 @@ function parseTemplateExpression(expr: string): ParsedTemplateExpression {
* `envelope === "inputs"`; other slots are runtime-resolved (backend
* populates them from trace / workflow config / etc.).
*/
export function groupTemplateVariables(placeholders: string[]): GroupedTemplateVariable[] {
export function groupTemplateVariables(
placeholders: string[],
options?: {
/** Set of names that appeared as mustache section openers
* (`{{#name}}` / `{{^name}}`) in the source template. Used to
* refine type inference: a name referenced ONLY as a section
* opener (no sub-paths) gets `type: "array"` — the iteration
* intent is the strongest signal we have without parsing the
* block body. Names with sub-paths stay `"object"` regardless. */
sectionOpeners?: Set<string>
},
): GroupedTemplateVariable[] {
const groups = new Map<string, {envelope: string; key: string; subPaths: Set<string>}>()

// Resolve section opener names through `parseTemplateExpression` and
// key them by envelope-scoped `${envelope}.${key}` ids — same identity
// the `groups` map uses below. Otherwise a section opener written as
// `{{#languages}}` would coerce BOTH `inputs.languages` AND
// `outputs.languages` (if both existed in the same prompt) to `array`.
const sectionOpenerIds = new Set<string>()
if (options?.sectionOpeners) {
for (const opener of options.sectionOpeners) {
const parsed = parseTemplateExpression(opener)
if (parsed.key) sectionOpenerIds.add(`${parsed.envelope}.${parsed.key}`)
}
}

for (const placeholder of placeholders) {
// Invalid envelope references (e.g. `$.input.xx.abc` — `input` is not
// a known envelope slot) don't get an input control. The prompt
Expand All @@ -289,11 +340,20 @@ export function groupTemplateVariables(placeholders: string[]): GroupedTemplateV

return Array.from(groups.values()).map(({envelope, key, subPaths}) => {
const subPathList = Array.from(subPaths)
// Type inference priority:
// 1. Sub-paths present → `"object"` (the strongest signal — the
// template addresses specific fields).
// 2. Section opener AND no sub-paths → `"array"` (iteration intent).
// 3. Otherwise → `"string"`.
const groupId = `${envelope}.${key}`
const isSectionOpener = sectionOpenerIds.has(groupId)
const type: GroupedTemplateVariable["type"] =
subPathList.length > 0 ? "object" : isSectionOpener ? "array" : "string"
return {
envelope,
key,
name: key,
type: subPathList.length > 0 ? ("object" as const) : ("string" as const),
type,
...(subPathList.length > 0 ? {subPaths: subPathList} : {}),
}
})
Expand Down
Loading
Loading