markform 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -42
- package/dist/ai-sdk.d.mts +2 -2
- package/dist/ai-sdk.mjs +5 -5
- package/dist/{apply-BfAGTHMh.mjs → apply-C54EMAJ1.mjs} +383 -26
- package/dist/bin.mjs +6 -6
- package/dist/{cli-B3NVm6zL.mjs → cli-BhWhn6L9.mjs} +456 -141
- package/dist/cli.mjs +6 -6
- package/dist/{coreTypes-BXhhz9Iq.d.mts → coreTypes-cbNTYAcb.d.mts} +1878 -325
- package/dist/{coreTypes-Dful87E0.mjs → coreTypes-pyctKRgc.mjs} +79 -5
- package/dist/index.d.mts +146 -9
- package/dist/index.mjs +5 -5
- package/dist/session-B_stoXQn.mjs +4 -0
- package/dist/{session-Bqnwi9wp.mjs → session-uF0e6m6k.mjs} +9 -5
- package/dist/{shared-N_s1M-_K.mjs → shared-BqPnYXrn.mjs} +82 -1
- package/dist/shared-CZsyShck.mjs +3 -0
- package/dist/{src-BXRkGFpG.mjs → src-BNh7Cx9P.mjs} +801 -121
- package/docs/markform-apis.md +194 -0
- package/{DOCS.md → docs/markform-reference.md} +111 -50
- package/{SPEC.md → docs/markform-spec.md} +342 -91
- package/examples/celebrity-deep-research/celebrity-deep-research.form.md +196 -141
- package/examples/earnings-analysis/earnings-analysis.form.md +236 -226
- package/examples/movie-research/movie-research-basic.form.md +25 -21
- package/examples/movie-research/movie-research-deep.form.md +74 -62
- package/examples/movie-research/movie-research-minimal.form.md +29 -34
- package/examples/simple/simple-mock-filled.form.md +93 -29
- package/examples/simple/simple-skipped-filled.form.md +91 -29
- package/examples/simple/simple-with-skips.session.yaml +93 -25
- package/examples/simple/simple.form.md +74 -20
- package/examples/simple/simple.session.yaml +98 -25
- package/examples/startup-deep-research/startup-deep-research.form.md +108 -81
- package/examples/startup-research/startup-research-mock-filled.form.md +43 -43
- package/examples/startup-research/startup-research.form.md +24 -24
- package/package.json +18 -27
- package/dist/session-DdAtY2Ni.mjs +0 -4
- package/dist/shared-D7gf27Tr.mjs +0 -3
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { C as DEFAULT_ROLE_INSTRUCTIONS,
|
|
1
|
+
import { L as PatchSchema } from "./coreTypes-pyctKRgc.mjs";
|
|
2
|
+
import { C as DEFAULT_ROLE_INSTRUCTIONS, P as getWebSearchConfig, S as DEFAULT_ROLES, _ as DEFAULT_MAX_TURNS, b as DEFAULT_RESEARCH_MAX_ISSUES_PER_TURN, g as DEFAULT_MAX_PATCHES_PER_TURN, h as DEFAULT_MAX_ISSUES_PER_TURN, n as getFieldsForRoles, p as AGENT_ROLE, r as inspect, t as applyPatches, u as serialize, x as DEFAULT_RESEARCH_MAX_PATCHES_PER_TURN, y as DEFAULT_PRIORITY } from "./apply-C54EMAJ1.mjs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
3
4
|
import { z } from "zod";
|
|
4
5
|
import Markdoc from "@markdoc/markdoc";
|
|
5
6
|
import YAML from "yaml";
|
|
@@ -12,6 +13,27 @@ import { ZodFirstPartyTypeKind } from "zod/v3";
|
|
|
12
13
|
import { google } from "@ai-sdk/google";
|
|
13
14
|
import { xai } from "@ai-sdk/xai";
|
|
14
15
|
|
|
16
|
+
//#region src/engine/fieldRegistry.ts
|
|
17
|
+
/**
|
|
18
|
+
* All field kinds as a const tuple - the single source of truth.
|
|
19
|
+
* Adding a new kind here will cause TypeScript errors until all
|
|
20
|
+
* corresponding types and handlers are added.
|
|
21
|
+
*/
|
|
22
|
+
const FIELD_KINDS = [
|
|
23
|
+
"string",
|
|
24
|
+
"number",
|
|
25
|
+
"string_list",
|
|
26
|
+
"checkboxes",
|
|
27
|
+
"single_select",
|
|
28
|
+
"multi_select",
|
|
29
|
+
"url",
|
|
30
|
+
"url_list",
|
|
31
|
+
"date",
|
|
32
|
+
"year",
|
|
33
|
+
"table"
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
//#endregion
|
|
15
37
|
//#region src/engine/parseHelpers.ts
|
|
16
38
|
/** Parse error with source location info */
|
|
17
39
|
var ParseError = class extends Error {
|
|
@@ -90,6 +112,19 @@ function getValidateAttr(node) {
|
|
|
90
112
|
if (typeof value === "object") return [value];
|
|
91
113
|
}
|
|
92
114
|
/**
|
|
115
|
+
* Get string array attribute value or undefined.
|
|
116
|
+
* Handles both single string (converts to array) and array formats.
|
|
117
|
+
*/
|
|
118
|
+
function getStringArrayAttr(node, name$2) {
|
|
119
|
+
const value = node.attributes?.[name$2];
|
|
120
|
+
if (value === void 0 || value === null) return;
|
|
121
|
+
if (Array.isArray(value)) {
|
|
122
|
+
const strings = value.filter((v) => typeof v === "string");
|
|
123
|
+
return strings.length > 0 ? strings : void 0;
|
|
124
|
+
}
|
|
125
|
+
if (typeof value === "string") return [value];
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
93
128
|
* Extract option items from node children (for option lists).
|
|
94
129
|
* Works with raw AST nodes. Collects text and ID from list items.
|
|
95
130
|
*/
|
|
@@ -146,6 +181,55 @@ function extractFenceValue(node) {
|
|
|
146
181
|
}
|
|
147
182
|
return null;
|
|
148
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* Extract table content from node children.
|
|
186
|
+
* Handles both raw text and Markdoc-parsed table nodes.
|
|
187
|
+
* Reconstructs markdown table format from the AST.
|
|
188
|
+
*/
|
|
189
|
+
function extractTableContent(node) {
|
|
190
|
+
const lines = [];
|
|
191
|
+
function extractTextFromNode(n) {
|
|
192
|
+
if (!n || typeof n !== "object") return "";
|
|
193
|
+
if (n.type === "text" && typeof n.attributes?.content === "string") return n.attributes.content;
|
|
194
|
+
if (n.children && Array.isArray(n.children)) return n.children.map(extractTextFromNode).join("");
|
|
195
|
+
return "";
|
|
196
|
+
}
|
|
197
|
+
function extractTableRow(trNode) {
|
|
198
|
+
if (!trNode.children || !Array.isArray(trNode.children)) return "";
|
|
199
|
+
return `| ${trNode.children.filter((c) => c.type === "th" || c.type === "td").map((c) => extractTextFromNode(c).trim()).join(" | ")} |`;
|
|
200
|
+
}
|
|
201
|
+
function processNode(child) {
|
|
202
|
+
if (!child || typeof child !== "object") return;
|
|
203
|
+
if (child.type === "paragraph" || child.type === "inline") {
|
|
204
|
+
const text = extractTextFromNode(child).trim();
|
|
205
|
+
if (text) lines.push(text);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if (child.type === "text" && typeof child.attributes?.content === "string") {
|
|
209
|
+
const text = child.attributes.content.trim();
|
|
210
|
+
if (text) lines.push(text);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (child.type === "table") {
|
|
214
|
+
const thead = child.children?.find((c) => c.type === "thead");
|
|
215
|
+
if (thead?.children) for (const tr of thead.children.filter((c) => c.type === "tr")) lines.push(extractTableRow(tr));
|
|
216
|
+
if (thead?.children?.length) {
|
|
217
|
+
const firstTr = thead.children.find((c) => c.type === "tr");
|
|
218
|
+
if (firstTr?.children) {
|
|
219
|
+
const colCount = firstTr.children.filter((c) => c.type === "th" || c.type === "td").length;
|
|
220
|
+
const separatorCells = Array(colCount).fill("----");
|
|
221
|
+
lines.push(`| ${separatorCells.join(" | ")} |`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const tbody = child.children?.find((c) => c.type === "tbody");
|
|
225
|
+
if (tbody?.children) for (const tr of tbody.children.filter((c) => c.type === "tr")) lines.push(extractTableRow(tr));
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (child.children && Array.isArray(child.children)) for (const c of child.children) processNode(c);
|
|
229
|
+
}
|
|
230
|
+
if (node.children && Array.isArray(node.children)) for (const child of node.children) processNode(child);
|
|
231
|
+
return lines.join("\n").trim() || null;
|
|
232
|
+
}
|
|
149
233
|
|
|
150
234
|
//#endregion
|
|
151
235
|
//#region src/engine/parseSentinels.ts
|
|
@@ -157,7 +241,7 @@ const SENTINEL_ABORT = "%ABORT%";
|
|
|
157
241
|
* Formats: `%SKIP%`, `%SKIP% (reason text)`, `%ABORT%`, `%ABORT% (reason text)`
|
|
158
242
|
* Returns null if the content is not a sentinel.
|
|
159
243
|
*/
|
|
160
|
-
function parseSentinel(content) {
|
|
244
|
+
function parseSentinel$1(content) {
|
|
161
245
|
if (!content) return null;
|
|
162
246
|
const trimmed = content.trim();
|
|
163
247
|
const reasonPattern = /^\((.+)\)$/s;
|
|
@@ -195,7 +279,7 @@ function parseSentinel(content) {
|
|
|
195
279
|
function tryParseSentinelResponse(node, fieldId, required) {
|
|
196
280
|
const fenceContent = extractFenceValue(node);
|
|
197
281
|
const stateAttr = getStringAttr(node, "state");
|
|
198
|
-
const sentinel = parseSentinel(fenceContent);
|
|
282
|
+
const sentinel = parseSentinel$1(fenceContent);
|
|
199
283
|
if (!sentinel) return null;
|
|
200
284
|
if (sentinel.type === "skip") {
|
|
201
285
|
if (stateAttr !== void 0 && stateAttr !== "skipped") throw new ParseError(`Field '${fieldId}' has conflicting state='${stateAttr}' with %SKIP% sentinel`);
|
|
@@ -215,6 +299,216 @@ function tryParseSentinelResponse(node, fieldId, required) {
|
|
|
215
299
|
return null;
|
|
216
300
|
}
|
|
217
301
|
|
|
302
|
+
//#endregion
|
|
303
|
+
//#region src/engine/table/parseTable.ts
|
|
304
|
+
/** Sentinel pattern: %SKIP% or %SKIP:reason% or %SKIP(reason)% */
|
|
305
|
+
const SKIP_PATTERN = /^%SKIP(?:[:(](.*))?[)]?%$/i;
|
|
306
|
+
/** Sentinel pattern: %ABORT% or %ABORT:reason% or %ABORT(reason)% */
|
|
307
|
+
const ABORT_PATTERN = /^%ABORT(?:[:(](.*))?[)]?%$/i;
|
|
308
|
+
/**
|
|
309
|
+
* Detect if a cell value is a sentinel.
|
|
310
|
+
*/
|
|
311
|
+
function parseSentinel(value) {
|
|
312
|
+
const trimmed = value.trim();
|
|
313
|
+
const skipMatch = SKIP_PATTERN.exec(trimmed);
|
|
314
|
+
if (skipMatch) return {
|
|
315
|
+
type: "skip",
|
|
316
|
+
reason: skipMatch[1]
|
|
317
|
+
};
|
|
318
|
+
const abortMatch = ABORT_PATTERN.exec(trimmed);
|
|
319
|
+
if (abortMatch) return {
|
|
320
|
+
type: "abort",
|
|
321
|
+
reason: abortMatch[1]
|
|
322
|
+
};
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Parse a cell value according to its column type.
|
|
327
|
+
* Returns a CellResponse with appropriate state.
|
|
328
|
+
*/
|
|
329
|
+
function parseCellValue(rawValue, columnType) {
|
|
330
|
+
const trimmed = rawValue.trim();
|
|
331
|
+
if (!trimmed) return { state: "skipped" };
|
|
332
|
+
const sentinel = parseSentinel(trimmed);
|
|
333
|
+
if (sentinel) return {
|
|
334
|
+
state: sentinel.type === "skip" ? "skipped" : "aborted",
|
|
335
|
+
reason: sentinel.reason
|
|
336
|
+
};
|
|
337
|
+
switch (columnType) {
|
|
338
|
+
case "string": return {
|
|
339
|
+
state: "answered",
|
|
340
|
+
value: trimmed
|
|
341
|
+
};
|
|
342
|
+
case "number": {
|
|
343
|
+
const num = parseFloat(trimmed);
|
|
344
|
+
if (isNaN(num)) return {
|
|
345
|
+
state: "answered",
|
|
346
|
+
value: trimmed
|
|
347
|
+
};
|
|
348
|
+
return {
|
|
349
|
+
state: "answered",
|
|
350
|
+
value: num
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
case "url": return {
|
|
354
|
+
state: "answered",
|
|
355
|
+
value: trimmed
|
|
356
|
+
};
|
|
357
|
+
case "date": return {
|
|
358
|
+
state: "answered",
|
|
359
|
+
value: trimmed
|
|
360
|
+
};
|
|
361
|
+
case "year": {
|
|
362
|
+
const year = parseInt(trimmed, 10);
|
|
363
|
+
if (isNaN(year) || !Number.isInteger(year)) return {
|
|
364
|
+
state: "answered",
|
|
365
|
+
value: trimmed
|
|
366
|
+
};
|
|
367
|
+
return {
|
|
368
|
+
state: "answered",
|
|
369
|
+
value: year
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Parse a table row into cell values.
|
|
376
|
+
* Handles leading/trailing pipes and cell trimming.
|
|
377
|
+
*/
|
|
378
|
+
function parseTableRow(line) {
|
|
379
|
+
let trimmed = line.trim();
|
|
380
|
+
if (trimmed.startsWith("|")) trimmed = trimmed.slice(1);
|
|
381
|
+
if (trimmed.endsWith("|")) trimmed = trimmed.slice(0, -1);
|
|
382
|
+
return trimmed.split("|").map((cell) => cell.trim());
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Extract header labels from table content.
|
|
386
|
+
* Returns array of header labels from the first row, or empty array if no valid header.
|
|
387
|
+
*/
|
|
388
|
+
function extractTableHeaderLabels(content) {
|
|
389
|
+
if (!content || content.trim() === "") return [];
|
|
390
|
+
const lines = content.trim().split("\n").filter((line) => line.trim());
|
|
391
|
+
if (lines.length === 0) return [];
|
|
392
|
+
return parseTableRow(lines[0]);
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Check if a line is a valid table separator row.
|
|
396
|
+
* Each cell should contain only dashes and optional colons for alignment.
|
|
397
|
+
*/
|
|
398
|
+
function isValidSeparator(line, expectedCols) {
|
|
399
|
+
const cells = parseTableRow(line);
|
|
400
|
+
if (cells.length !== expectedCols) return false;
|
|
401
|
+
const separatorPattern = /^:?-+:?$/;
|
|
402
|
+
return cells.every((cell) => separatorPattern.test(cell.trim()));
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Parse a markdown table with column schema for type coercion.
|
|
406
|
+
*
|
|
407
|
+
* @param content - The markdown table content
|
|
408
|
+
* @param columns - Column definitions from the table field schema
|
|
409
|
+
* @param dataStartLine - Optional line index where data rows start (skips header validation)
|
|
410
|
+
* @returns Parsed table value with typed cells
|
|
411
|
+
*/
|
|
412
|
+
function parseMarkdownTable(content, columns, dataStartLine) {
|
|
413
|
+
const lines = content.trim().split("\n").filter((line) => line.trim());
|
|
414
|
+
if (lines.length === 0) return {
|
|
415
|
+
ok: true,
|
|
416
|
+
value: {
|
|
417
|
+
kind: "table",
|
|
418
|
+
rows: []
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
if (lines.length < 2) return {
|
|
422
|
+
ok: false,
|
|
423
|
+
error: "Table must have at least a header and separator row"
|
|
424
|
+
};
|
|
425
|
+
const headerLine = lines[0];
|
|
426
|
+
const headers = parseTableRow(headerLine);
|
|
427
|
+
const separatorLine = lines[1];
|
|
428
|
+
if (!isValidSeparator(separatorLine, headers.length)) return {
|
|
429
|
+
ok: false,
|
|
430
|
+
error: "Invalid table separator row"
|
|
431
|
+
};
|
|
432
|
+
if (dataStartLine !== void 0) {
|
|
433
|
+
const rows$1 = [];
|
|
434
|
+
for (let i = dataStartLine; i < lines.length; i++) {
|
|
435
|
+
const rawCells = parseTableRow(lines[i]);
|
|
436
|
+
const row = {};
|
|
437
|
+
for (let j = 0; j < columns.length; j++) {
|
|
438
|
+
const column = columns[j];
|
|
439
|
+
const rawValue = rawCells[j] ?? "";
|
|
440
|
+
row[column.id] = parseCellValue(rawValue, column.type);
|
|
441
|
+
}
|
|
442
|
+
rows$1.push(row);
|
|
443
|
+
}
|
|
444
|
+
return {
|
|
445
|
+
ok: true,
|
|
446
|
+
value: {
|
|
447
|
+
kind: "table",
|
|
448
|
+
rows: rows$1
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
const columnIdToIndex = /* @__PURE__ */ new Map();
|
|
453
|
+
for (let i = 0; i < headers.length; i++) {
|
|
454
|
+
const header = headers[i];
|
|
455
|
+
const column = columns.find((c) => c.id === header || c.label === header);
|
|
456
|
+
if (column) columnIdToIndex.set(column.id, i);
|
|
457
|
+
}
|
|
458
|
+
const rows = [];
|
|
459
|
+
for (let i = 2; i < lines.length; i++) {
|
|
460
|
+
const rawCells = parseTableRow(lines[i]);
|
|
461
|
+
const row = {};
|
|
462
|
+
for (const column of columns) {
|
|
463
|
+
const cellIndex = columnIdToIndex.get(column.id);
|
|
464
|
+
const rawValue = cellIndex !== void 0 ? rawCells[cellIndex] ?? "" : "";
|
|
465
|
+
row[column.id] = parseCellValue(rawValue, column.type);
|
|
466
|
+
}
|
|
467
|
+
rows.push(row);
|
|
468
|
+
}
|
|
469
|
+
return {
|
|
470
|
+
ok: true,
|
|
471
|
+
value: {
|
|
472
|
+
kind: "table",
|
|
473
|
+
rows
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Parse just the raw table structure without schema.
|
|
479
|
+
* Useful for validation and error reporting.
|
|
480
|
+
*/
|
|
481
|
+
function parseRawTable(content) {
|
|
482
|
+
const lines = content.trim().split("\n").filter((line) => line.trim());
|
|
483
|
+
if (lines.length === 0) return {
|
|
484
|
+
ok: true,
|
|
485
|
+
headers: [],
|
|
486
|
+
rows: []
|
|
487
|
+
};
|
|
488
|
+
if (lines.length < 2) return {
|
|
489
|
+
ok: false,
|
|
490
|
+
error: "Table must have at least a header and separator row"
|
|
491
|
+
};
|
|
492
|
+
const headers = parseTableRow(lines[0]);
|
|
493
|
+
const separatorLine = lines[1];
|
|
494
|
+
if (!isValidSeparator(separatorLine, headers.length)) return {
|
|
495
|
+
ok: false,
|
|
496
|
+
error: "Invalid table separator row"
|
|
497
|
+
};
|
|
498
|
+
const rows = [];
|
|
499
|
+
for (let i = 2; i < lines.length; i++) {
|
|
500
|
+
const row = parseTableRow(lines[i]);
|
|
501
|
+
while (row.length < headers.length) row.push("");
|
|
502
|
+
if (row.length > headers.length) row.length = headers.length;
|
|
503
|
+
rows.push(row);
|
|
504
|
+
}
|
|
505
|
+
return {
|
|
506
|
+
ok: true,
|
|
507
|
+
headers,
|
|
508
|
+
rows
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
|
|
218
512
|
//#endregion
|
|
219
513
|
//#region src/engine/parseFields.ts
|
|
220
514
|
/**
|
|
@@ -233,6 +527,7 @@ function isValueEmpty(value) {
|
|
|
233
527
|
case "single_select": return value.selected === null;
|
|
234
528
|
case "multi_select": return value.selected.length === 0;
|
|
235
529
|
case "checkboxes": return Object.values(value.values).every((v) => v === "todo" || v === "unfilled");
|
|
530
|
+
case "table": return value.rows.length === 0;
|
|
236
531
|
}
|
|
237
532
|
}
|
|
238
533
|
/**
|
|
@@ -281,27 +576,86 @@ function getPriorityAttr(node) {
|
|
|
281
576
|
return DEFAULT_PRIORITY;
|
|
282
577
|
}
|
|
283
578
|
/**
|
|
284
|
-
* Parse
|
|
579
|
+
* Parse and validate base field attributes (id, label, required).
|
|
580
|
+
* Throws ParseError if id or label is missing.
|
|
285
581
|
*/
|
|
286
|
-
function
|
|
582
|
+
function parseBaseFieldAttrs(node, kind) {
|
|
287
583
|
const id = getStringAttr(node, "id");
|
|
288
584
|
const label = getStringAttr(node, "label");
|
|
289
|
-
if (!id) throw new ParseError(
|
|
290
|
-
if (!label) throw new ParseError(`
|
|
291
|
-
|
|
585
|
+
if (!id) throw new ParseError(`field kind="${kind}" missing required 'id' attribute`);
|
|
586
|
+
if (!label) throw new ParseError(`field '${id}' missing required 'label' attribute`);
|
|
587
|
+
return {
|
|
588
|
+
id,
|
|
589
|
+
label,
|
|
590
|
+
required: getBooleanAttr(node, "required") ?? false
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Get common field attributes (priority, role, validate, report).
|
|
595
|
+
*/
|
|
596
|
+
function getCommonFieldAttrs(node) {
|
|
597
|
+
return {
|
|
598
|
+
priority: getPriorityAttr(node),
|
|
599
|
+
role: getStringAttr(node, "role") ?? AGENT_ROLE,
|
|
600
|
+
validate: getValidateAttr(node),
|
|
601
|
+
report: getBooleanAttr(node, "report")
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Validate that placeholder/examples are not used on chooser fields.
|
|
606
|
+
* Throws ParseError if either attribute is present.
|
|
607
|
+
*/
|
|
608
|
+
function validateNoPlaceholderExamples(node, fieldType, fieldId) {
|
|
609
|
+
const placeholder = getStringAttr(node, "placeholder");
|
|
610
|
+
const examples = getStringArrayAttr(node, "examples");
|
|
611
|
+
if (placeholder !== void 0) throw new ParseError(`${fieldType} '${fieldId}' has 'placeholder' attribute, but placeholder is only valid on text-entry fields (string, number, string-list, url, url-list)`);
|
|
612
|
+
if (examples !== void 0) throw new ParseError(`${fieldType} '${fieldId}' has 'examples' attribute, but examples is only valid on text-entry fields (string, number, string-list, url, url-list)`);
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Check if a string is a valid URL.
|
|
616
|
+
*/
|
|
617
|
+
function isValidUrl(str) {
|
|
618
|
+
try {
|
|
619
|
+
new URL(str);
|
|
620
|
+
return true;
|
|
621
|
+
} catch {
|
|
622
|
+
return false;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Validate examples for number fields - all must parse as numbers.
|
|
627
|
+
*/
|
|
628
|
+
function validateNumberExamples(examples, fieldId) {
|
|
629
|
+
if (!examples) return;
|
|
630
|
+
for (const example of examples) {
|
|
631
|
+
const parsed = Number(example);
|
|
632
|
+
if (Number.isNaN(parsed)) throw new ParseError(`number-field '${fieldId}' has invalid example '${example}' - must be a valid number`);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Validate examples for URL fields - all must be valid URLs.
|
|
637
|
+
*/
|
|
638
|
+
function validateUrlExamples(examples, fieldId) {
|
|
639
|
+
if (!examples) return;
|
|
640
|
+
for (const example of examples) if (!isValidUrl(example)) throw new ParseError(`url-field '${fieldId}' has invalid example '${example}' - must be a valid URL`);
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Parse a string-field tag.
|
|
644
|
+
*/
|
|
645
|
+
function parseStringField(node) {
|
|
646
|
+
const { id, label, required } = parseBaseFieldAttrs(node, "string");
|
|
292
647
|
const field = {
|
|
293
648
|
kind: "string",
|
|
294
649
|
id,
|
|
295
650
|
label,
|
|
296
651
|
required,
|
|
297
|
-
|
|
298
|
-
role: getStringAttr(node, "role") ?? AGENT_ROLE,
|
|
652
|
+
...getCommonFieldAttrs(node),
|
|
299
653
|
multiline: getBooleanAttr(node, "multiline"),
|
|
300
654
|
pattern: getStringAttr(node, "pattern"),
|
|
301
655
|
minLength: getNumberAttr(node, "minLength"),
|
|
302
656
|
maxLength: getNumberAttr(node, "maxLength"),
|
|
303
|
-
|
|
304
|
-
|
|
657
|
+
placeholder: getStringAttr(node, "placeholder"),
|
|
658
|
+
examples: getStringArrayAttr(node, "examples")
|
|
305
659
|
};
|
|
306
660
|
const sentinelResponse = tryParseSentinelResponse(node, id, required);
|
|
307
661
|
if (sentinelResponse) return {
|
|
@@ -321,23 +675,21 @@ function parseStringField(node) {
|
|
|
321
675
|
* Parse a number-field tag.
|
|
322
676
|
*/
|
|
323
677
|
function parseNumberField(node) {
|
|
324
|
-
const id =
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
const required = getBooleanAttr(node, "required") ?? false;
|
|
678
|
+
const { id, label, required } = parseBaseFieldAttrs(node, "number");
|
|
679
|
+
const placeholder = getStringAttr(node, "placeholder");
|
|
680
|
+
const examples = getStringArrayAttr(node, "examples");
|
|
681
|
+
validateNumberExamples(examples, id);
|
|
329
682
|
const field = {
|
|
330
683
|
kind: "number",
|
|
331
684
|
id,
|
|
332
685
|
label,
|
|
333
686
|
required,
|
|
334
|
-
|
|
335
|
-
role: getStringAttr(node, "role") ?? AGENT_ROLE,
|
|
687
|
+
...getCommonFieldAttrs(node),
|
|
336
688
|
min: getNumberAttr(node, "min"),
|
|
337
689
|
max: getNumberAttr(node, "max"),
|
|
338
690
|
integer: getBooleanAttr(node, "integer"),
|
|
339
|
-
|
|
340
|
-
|
|
691
|
+
placeholder,
|
|
692
|
+
examples
|
|
341
693
|
};
|
|
342
694
|
const sentinelResponse = tryParseSentinelResponse(node, id, required);
|
|
343
695
|
if (sentinelResponse) return {
|
|
@@ -363,25 +715,20 @@ function parseNumberField(node) {
|
|
|
363
715
|
* Parse a string-list tag.
|
|
364
716
|
*/
|
|
365
717
|
function parseStringListField(node) {
|
|
366
|
-
const id =
|
|
367
|
-
const label = getStringAttr(node, "label");
|
|
368
|
-
if (!id) throw new ParseError("string-list missing required 'id' attribute");
|
|
369
|
-
if (!label) throw new ParseError(`string-list '${id}' missing required 'label' attribute`);
|
|
370
|
-
const required = getBooleanAttr(node, "required") ?? false;
|
|
718
|
+
const { id, label, required } = parseBaseFieldAttrs(node, "string_list");
|
|
371
719
|
const field = {
|
|
372
720
|
kind: "string_list",
|
|
373
721
|
id,
|
|
374
722
|
label,
|
|
375
723
|
required,
|
|
376
|
-
|
|
377
|
-
role: getStringAttr(node, "role") ?? AGENT_ROLE,
|
|
724
|
+
...getCommonFieldAttrs(node),
|
|
378
725
|
minItems: getNumberAttr(node, "minItems"),
|
|
379
726
|
maxItems: getNumberAttr(node, "maxItems"),
|
|
380
727
|
itemMinLength: getNumberAttr(node, "itemMinLength"),
|
|
381
728
|
itemMaxLength: getNumberAttr(node, "itemMaxLength"),
|
|
382
729
|
uniqueItems: getBooleanAttr(node, "uniqueItems"),
|
|
383
|
-
|
|
384
|
-
|
|
730
|
+
placeholder: getStringAttr(node, "placeholder"),
|
|
731
|
+
examples: getStringArrayAttr(node, "examples")
|
|
385
732
|
};
|
|
386
733
|
const sentinelResponse = tryParseSentinelResponse(node, id, required);
|
|
387
734
|
if (sentinelResponse) return {
|
|
@@ -435,22 +782,16 @@ function parseOptions(node, fieldId) {
|
|
|
435
782
|
* Parse a single-select tag.
|
|
436
783
|
*/
|
|
437
784
|
function parseSingleSelectField(node) {
|
|
438
|
-
const id =
|
|
439
|
-
|
|
440
|
-
if (!id) throw new ParseError("single-select missing required 'id' attribute");
|
|
441
|
-
if (!label) throw new ParseError(`single-select '${id}' missing required 'label' attribute`);
|
|
442
|
-
const required = getBooleanAttr(node, "required") ?? false;
|
|
785
|
+
const { id, label, required } = parseBaseFieldAttrs(node, "single_select");
|
|
786
|
+
validateNoPlaceholderExamples(node, "single-select", id);
|
|
443
787
|
const { options, selected } = parseOptions(node, id);
|
|
444
788
|
const field = {
|
|
445
789
|
kind: "single_select",
|
|
446
790
|
id,
|
|
447
791
|
label,
|
|
448
792
|
required,
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
options,
|
|
452
|
-
validate: getValidateAttr(node),
|
|
453
|
-
report: getBooleanAttr(node, "report")
|
|
793
|
+
...getCommonFieldAttrs(node),
|
|
794
|
+
options
|
|
454
795
|
};
|
|
455
796
|
let selectedOption = null;
|
|
456
797
|
for (const [optId, state] of Object.entries(selected)) if (state === "done") {
|
|
@@ -469,24 +810,18 @@ function parseSingleSelectField(node) {
|
|
|
469
810
|
* Parse a multi-select tag.
|
|
470
811
|
*/
|
|
471
812
|
function parseMultiSelectField(node) {
|
|
472
|
-
const id =
|
|
473
|
-
|
|
474
|
-
if (!id) throw new ParseError("multi-select missing required 'id' attribute");
|
|
475
|
-
if (!label) throw new ParseError(`multi-select '${id}' missing required 'label' attribute`);
|
|
476
|
-
const required = getBooleanAttr(node, "required") ?? false;
|
|
813
|
+
const { id, label, required } = parseBaseFieldAttrs(node, "multi_select");
|
|
814
|
+
validateNoPlaceholderExamples(node, "multi-select", id);
|
|
477
815
|
const { options, selected } = parseOptions(node, id);
|
|
478
816
|
const field = {
|
|
479
817
|
kind: "multi_select",
|
|
480
818
|
id,
|
|
481
819
|
label,
|
|
482
820
|
required,
|
|
483
|
-
|
|
484
|
-
role: getStringAttr(node, "role") ?? AGENT_ROLE,
|
|
821
|
+
...getCommonFieldAttrs(node),
|
|
485
822
|
options,
|
|
486
823
|
minSelections: getNumberAttr(node, "minSelections"),
|
|
487
|
-
maxSelections: getNumberAttr(node, "maxSelections")
|
|
488
|
-
validate: getValidateAttr(node),
|
|
489
|
-
report: getBooleanAttr(node, "report")
|
|
824
|
+
maxSelections: getNumberAttr(node, "maxSelections")
|
|
490
825
|
};
|
|
491
826
|
const selectedOptions = [];
|
|
492
827
|
for (const [optId, state] of Object.entries(selected)) if (state === "done") selectedOptions.push(optId);
|
|
@@ -504,8 +839,9 @@ function parseMultiSelectField(node) {
|
|
|
504
839
|
function parseCheckboxesField(node) {
|
|
505
840
|
const id = getStringAttr(node, "id");
|
|
506
841
|
const label = getStringAttr(node, "label");
|
|
507
|
-
if (!id) throw new ParseError("checkboxes missing required 'id' attribute");
|
|
508
|
-
if (!label) throw new ParseError(`
|
|
842
|
+
if (!id) throw new ParseError("field kind=\"checkboxes\" missing required 'id' attribute");
|
|
843
|
+
if (!label) throw new ParseError(`field '${id}' missing required 'label' attribute`);
|
|
844
|
+
validateNoPlaceholderExamples(node, "checkboxes", id);
|
|
509
845
|
const { options, selected } = parseOptions(node, id);
|
|
510
846
|
const checkboxModeStr = getStringAttr(node, "checkboxMode");
|
|
511
847
|
let checkboxMode = "multi";
|
|
@@ -524,14 +860,11 @@ function parseCheckboxesField(node) {
|
|
|
524
860
|
id,
|
|
525
861
|
label,
|
|
526
862
|
required,
|
|
527
|
-
|
|
528
|
-
role: getStringAttr(node, "role") ?? AGENT_ROLE,
|
|
863
|
+
...getCommonFieldAttrs(node),
|
|
529
864
|
checkboxMode,
|
|
530
865
|
minDone: getNumberAttr(node, "minDone"),
|
|
531
866
|
options,
|
|
532
|
-
approvalMode
|
|
533
|
-
validate: getValidateAttr(node),
|
|
534
|
-
report: getBooleanAttr(node, "report")
|
|
867
|
+
approvalMode
|
|
535
868
|
};
|
|
536
869
|
const values = {};
|
|
537
870
|
for (const opt of options) {
|
|
@@ -551,20 +884,18 @@ function parseCheckboxesField(node) {
|
|
|
551
884
|
* Parse a url-field tag.
|
|
552
885
|
*/
|
|
553
886
|
function parseUrlField(node) {
|
|
554
|
-
const id =
|
|
555
|
-
const
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
const required = getBooleanAttr(node, "required") ?? false;
|
|
887
|
+
const { id, label, required } = parseBaseFieldAttrs(node, "url");
|
|
888
|
+
const placeholder = getStringAttr(node, "placeholder");
|
|
889
|
+
const examples = getStringArrayAttr(node, "examples");
|
|
890
|
+
validateUrlExamples(examples, id);
|
|
559
891
|
const field = {
|
|
560
892
|
kind: "url",
|
|
561
893
|
id,
|
|
562
894
|
label,
|
|
563
895
|
required,
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
report: getBooleanAttr(node, "report")
|
|
896
|
+
...getCommonFieldAttrs(node),
|
|
897
|
+
placeholder,
|
|
898
|
+
examples
|
|
568
899
|
};
|
|
569
900
|
const sentinelResponse = tryParseSentinelResponse(node, id, required);
|
|
570
901
|
if (sentinelResponse) return {
|
|
@@ -584,23 +915,21 @@ function parseUrlField(node) {
|
|
|
584
915
|
* Parse a url-list tag.
|
|
585
916
|
*/
|
|
586
917
|
function parseUrlListField(node) {
|
|
587
|
-
const id =
|
|
588
|
-
const
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
const required = getBooleanAttr(node, "required") ?? false;
|
|
918
|
+
const { id, label, required } = parseBaseFieldAttrs(node, "url_list");
|
|
919
|
+
const placeholder = getStringAttr(node, "placeholder");
|
|
920
|
+
const examples = getStringArrayAttr(node, "examples");
|
|
921
|
+
validateUrlExamples(examples, id);
|
|
592
922
|
const field = {
|
|
593
923
|
kind: "url_list",
|
|
594
924
|
id,
|
|
595
925
|
label,
|
|
596
926
|
required,
|
|
597
|
-
|
|
598
|
-
role: getStringAttr(node, "role") ?? AGENT_ROLE,
|
|
927
|
+
...getCommonFieldAttrs(node),
|
|
599
928
|
minItems: getNumberAttr(node, "minItems"),
|
|
600
929
|
maxItems: getNumberAttr(node, "maxItems"),
|
|
601
930
|
uniqueItems: getBooleanAttr(node, "uniqueItems"),
|
|
602
|
-
|
|
603
|
-
|
|
931
|
+
placeholder,
|
|
932
|
+
examples
|
|
604
933
|
};
|
|
605
934
|
const sentinelResponse = tryParseSentinelResponse(node, id, required);
|
|
606
935
|
if (sentinelResponse) return {
|
|
@@ -628,22 +957,15 @@ function parseUrlListField(node) {
|
|
|
628
957
|
* Parse a date-field tag.
|
|
629
958
|
*/
|
|
630
959
|
function parseDateField(node) {
|
|
631
|
-
const id =
|
|
632
|
-
const label = getStringAttr(node, "label");
|
|
633
|
-
if (!id) throw new ParseError("date-field missing required 'id' attribute");
|
|
634
|
-
if (!label) throw new ParseError(`date-field '${id}' missing required 'label' attribute`);
|
|
635
|
-
const required = getBooleanAttr(node, "required") ?? false;
|
|
960
|
+
const { id, label, required } = parseBaseFieldAttrs(node, "date");
|
|
636
961
|
const field = {
|
|
637
962
|
kind: "date",
|
|
638
963
|
id,
|
|
639
964
|
label,
|
|
640
965
|
required,
|
|
641
|
-
|
|
642
|
-
role: getStringAttr(node, "role") ?? AGENT_ROLE,
|
|
966
|
+
...getCommonFieldAttrs(node),
|
|
643
967
|
min: getStringAttr(node, "min"),
|
|
644
|
-
max: getStringAttr(node, "max")
|
|
645
|
-
validate: getValidateAttr(node),
|
|
646
|
-
report: getBooleanAttr(node, "report")
|
|
968
|
+
max: getStringAttr(node, "max")
|
|
647
969
|
};
|
|
648
970
|
const sentinelResponse = tryParseSentinelResponse(node, id, required);
|
|
649
971
|
if (sentinelResponse) return {
|
|
@@ -668,22 +990,15 @@ function parseDateField(node) {
|
|
|
668
990
|
* Parse a year-field tag.
|
|
669
991
|
*/
|
|
670
992
|
function parseYearField(node) {
|
|
671
|
-
const id =
|
|
672
|
-
const label = getStringAttr(node, "label");
|
|
673
|
-
if (!id) throw new ParseError("year-field missing required 'id' attribute");
|
|
674
|
-
if (!label) throw new ParseError(`year-field '${id}' missing required 'label' attribute`);
|
|
675
|
-
const required = getBooleanAttr(node, "required") ?? false;
|
|
993
|
+
const { id, label, required } = parseBaseFieldAttrs(node, "year");
|
|
676
994
|
const field = {
|
|
677
995
|
kind: "year",
|
|
678
996
|
id,
|
|
679
997
|
label,
|
|
680
998
|
required,
|
|
681
|
-
|
|
682
|
-
role: getStringAttr(node, "role") ?? AGENT_ROLE,
|
|
999
|
+
...getCommonFieldAttrs(node),
|
|
683
1000
|
min: getNumberAttr(node, "min"),
|
|
684
|
-
max: getNumberAttr(node, "max")
|
|
685
|
-
validate: getValidateAttr(node),
|
|
686
|
-
report: getBooleanAttr(node, "report")
|
|
1001
|
+
max: getNumberAttr(node, "max")
|
|
687
1002
|
};
|
|
688
1003
|
const sentinelResponse = tryParseSentinelResponse(node, id, required);
|
|
689
1004
|
if (sentinelResponse) return {
|
|
@@ -706,23 +1021,145 @@ function parseYearField(node) {
|
|
|
706
1021
|
};
|
|
707
1022
|
}
|
|
708
1023
|
/**
|
|
1024
|
+
* Validate column type string.
|
|
1025
|
+
*/
|
|
1026
|
+
function isValidColumnType(type) {
|
|
1027
|
+
return type === "string" || type === "number" || type === "url" || type === "date" || type === "year";
|
|
1028
|
+
}
|
|
1029
|
+
/**
|
|
1030
|
+
* Parse column definitions from attributes.
|
|
1031
|
+
* columnIds is required. columnLabels is optional (backfilled from tableHeaderLabels if provided).
|
|
1032
|
+
*/
|
|
1033
|
+
function parseColumnsFromAttributes(node, fieldId, tableHeaderLabels) {
|
|
1034
|
+
const columnIds = getStringArrayAttr(node, "columnIds");
|
|
1035
|
+
const columnLabels = getStringArrayAttr(node, "columnLabels");
|
|
1036
|
+
const columnTypesRaw = node.attributes?.columnTypes;
|
|
1037
|
+
if (!columnIds || columnIds.length === 0) throw new ParseError(`table-field '${fieldId}' requires 'columnIds' attribute. Example: columnIds=["name", "title", "department"]`);
|
|
1038
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
1039
|
+
for (const id of columnIds) {
|
|
1040
|
+
if (seenIds.has(id)) throw new ParseError(`table-field '${fieldId}' has duplicate column ID '${id}'`);
|
|
1041
|
+
seenIds.add(id);
|
|
1042
|
+
}
|
|
1043
|
+
const columns = [];
|
|
1044
|
+
for (let i = 0; i < columnIds.length; i++) {
|
|
1045
|
+
const id = columnIds[i];
|
|
1046
|
+
const label = columnLabels?.[i] ?? tableHeaderLabels?.[i] ?? id;
|
|
1047
|
+
const typeSpec = columnTypesRaw?.[i];
|
|
1048
|
+
let type = "string";
|
|
1049
|
+
let required = false;
|
|
1050
|
+
if (typeSpec !== void 0) {
|
|
1051
|
+
if (typeof typeSpec === "string") {
|
|
1052
|
+
if (!isValidColumnType(typeSpec)) throw new ParseError(`table-field '${fieldId}' has invalid column type '${String(typeSpec)}' for column '${id}'. Valid types: string, number, url, date, year`);
|
|
1053
|
+
type = typeSpec;
|
|
1054
|
+
} else if (typeof typeSpec === "object" && typeSpec !== null) {
|
|
1055
|
+
const typeObj = typeSpec;
|
|
1056
|
+
if (!isValidColumnType(typeObj.type)) throw new ParseError(`table-field '${fieldId}' has invalid column type '${String(typeObj.type)}' for column '${id}'. Valid types: string, number, url, date, year`);
|
|
1057
|
+
type = typeObj.type;
|
|
1058
|
+
required = typeObj.required ?? false;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
columns.push({
|
|
1062
|
+
id,
|
|
1063
|
+
label,
|
|
1064
|
+
type,
|
|
1065
|
+
required
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
return columns;
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* Parse a table-field tag.
|
|
1072
|
+
*
|
|
1073
|
+
* Column definitions come from attributes:
|
|
1074
|
+
* - columnIds (required): array of snake_case column identifiers
|
|
1075
|
+
* - columnLabels (optional): array of display labels (backfilled from table header row if omitted)
|
|
1076
|
+
* - columnTypes (optional): array of column types (defaults to all 'string')
|
|
1077
|
+
*
|
|
1078
|
+
* Table content is a raw markdown table inside the tag (NOT a value fence).
|
|
1079
|
+
*/
|
|
1080
|
+
function parseTableField(node) {
|
|
1081
|
+
const { id, label, required } = parseBaseFieldAttrs(node, "table");
|
|
1082
|
+
const sentinelResponse = tryParseSentinelResponse(node, id, required);
|
|
1083
|
+
const tableContent = extractTableContent(node);
|
|
1084
|
+
const columns = parseColumnsFromAttributes(node, id, extractTableHeaderLabels(tableContent));
|
|
1085
|
+
const dataStartLine = 2;
|
|
1086
|
+
const field = {
|
|
1087
|
+
kind: "table",
|
|
1088
|
+
id,
|
|
1089
|
+
label,
|
|
1090
|
+
required,
|
|
1091
|
+
...getCommonFieldAttrs(node),
|
|
1092
|
+
columns,
|
|
1093
|
+
minRows: getNumberAttr(node, "minRows"),
|
|
1094
|
+
maxRows: getNumberAttr(node, "maxRows")
|
|
1095
|
+
};
|
|
1096
|
+
if (sentinelResponse) return {
|
|
1097
|
+
field,
|
|
1098
|
+
response: sentinelResponse
|
|
1099
|
+
};
|
|
1100
|
+
if (tableContent === null || tableContent.trim() === "") return {
|
|
1101
|
+
field,
|
|
1102
|
+
response: parseFieldResponse(node, {
|
|
1103
|
+
kind: "table",
|
|
1104
|
+
rows: []
|
|
1105
|
+
}, id, required)
|
|
1106
|
+
};
|
|
1107
|
+
const parseResult = parseMarkdownTable(tableContent, columns, dataStartLine);
|
|
1108
|
+
if (!parseResult.ok) throw new ParseError(`table-field '${id}': ${parseResult.error}`);
|
|
1109
|
+
return {
|
|
1110
|
+
field,
|
|
1111
|
+
response: parseFieldResponse(node, parseResult.value, id, required)
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
/**
|
|
1115
|
+
* Map legacy tag names to field kinds for error messages.
|
|
1116
|
+
*/
|
|
1117
|
+
const LEGACY_TAG_TO_KIND = {
|
|
1118
|
+
"string-field": "string",
|
|
1119
|
+
"number-field": "number",
|
|
1120
|
+
"string-list": "string_list",
|
|
1121
|
+
"single-select": "single_select",
|
|
1122
|
+
"multi-select": "multi_select",
|
|
1123
|
+
checkboxes: "checkboxes",
|
|
1124
|
+
"url-field": "url",
|
|
1125
|
+
"url-list": "url_list",
|
|
1126
|
+
"date-field": "date",
|
|
1127
|
+
"year-field": "year",
|
|
1128
|
+
"table-field": "table"
|
|
1129
|
+
};
|
|
1130
|
+
/**
|
|
1131
|
+
* Parse a unified field tag: {% field kind="..." ... %}
|
|
1132
|
+
*/
|
|
1133
|
+
function parseUnifiedField(node) {
|
|
1134
|
+
const kind = getStringAttr(node, "kind");
|
|
1135
|
+
if (!kind) throw new ParseError("field tag missing required 'kind' attribute");
|
|
1136
|
+
if (!FIELD_KINDS.includes(kind)) throw new ParseError(`field tag has invalid kind '${kind}'. Valid kinds: ${FIELD_KINDS.join(", ")}`);
|
|
1137
|
+
switch (kind) {
|
|
1138
|
+
case "string": return parseStringField(node);
|
|
1139
|
+
case "number": return parseNumberField(node);
|
|
1140
|
+
case "string_list": return parseStringListField(node);
|
|
1141
|
+
case "single_select": return parseSingleSelectField(node);
|
|
1142
|
+
case "multi_select": return parseMultiSelectField(node);
|
|
1143
|
+
case "checkboxes": return parseCheckboxesField(node);
|
|
1144
|
+
case "url": return parseUrlField(node);
|
|
1145
|
+
case "url_list": return parseUrlListField(node);
|
|
1146
|
+
case "date": return parseDateField(node);
|
|
1147
|
+
case "year": return parseYearField(node);
|
|
1148
|
+
case "table": return parseTableField(node);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
/**
|
|
709
1152
|
* Parse a field tag and return field schema and response.
|
|
1153
|
+
* Accepts both unified field syntax {% field kind="..." %} and legacy tags.
|
|
710
1154
|
*/
|
|
711
1155
|
function parseField(node) {
|
|
712
1156
|
if (!isTagNode(node)) return null;
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
case "single-select": return parseSingleSelectField(node);
|
|
718
|
-
case "multi-select": return parseMultiSelectField(node);
|
|
719
|
-
case "checkboxes": return parseCheckboxesField(node);
|
|
720
|
-
case "url-field": return parseUrlField(node);
|
|
721
|
-
case "url-list": return parseUrlListField(node);
|
|
722
|
-
case "date-field": return parseDateField(node);
|
|
723
|
-
case "year-field": return parseYearField(node);
|
|
724
|
-
default: return null;
|
|
1157
|
+
if (node.tag === "field") return parseUnifiedField(node);
|
|
1158
|
+
if (node.tag) {
|
|
1159
|
+
const kind = LEGACY_TAG_TO_KIND[node.tag];
|
|
1160
|
+
if (kind !== void 0) throw new ParseError(`Legacy field tag '${node.tag}' is no longer supported. Use {% field kind="${kind}" %} instead`);
|
|
725
1161
|
}
|
|
1162
|
+
return null;
|
|
726
1163
|
}
|
|
727
1164
|
|
|
728
1165
|
//#endregion
|
|
@@ -838,6 +1275,7 @@ function parseFieldGroup(node, responsesByFieldId, orderIndex, idIndex, parentId
|
|
|
838
1275
|
}
|
|
839
1276
|
/**
|
|
840
1277
|
* Parse a form tag.
|
|
1278
|
+
* Handles both explicit field-groups and fields placed directly under the form.
|
|
841
1279
|
*/
|
|
842
1280
|
function parseFormTag(node, responsesByFieldId, orderIndex, idIndex) {
|
|
843
1281
|
const id = getStringAttr(node, "id");
|
|
@@ -846,16 +1284,51 @@ function parseFormTag(node, responsesByFieldId, orderIndex, idIndex) {
|
|
|
846
1284
|
if (idIndex.has(id)) throw new ParseError(`Duplicate ID '${id}'`);
|
|
847
1285
|
idIndex.set(id, { nodeType: "form" });
|
|
848
1286
|
const groups = [];
|
|
849
|
-
|
|
1287
|
+
const ungroupedFields = [];
|
|
1288
|
+
function processContent(child) {
|
|
850
1289
|
if (!child || typeof child !== "object") return;
|
|
851
1290
|
if (isTagNode(child, "field-group")) {
|
|
852
1291
|
const group = parseFieldGroup(child, responsesByFieldId, orderIndex, idIndex, id);
|
|
853
1292
|
groups.push(group);
|
|
854
1293
|
return;
|
|
855
1294
|
}
|
|
856
|
-
|
|
1295
|
+
const result = parseField(child);
|
|
1296
|
+
if (result) {
|
|
1297
|
+
if (idIndex.has(result.field.id)) throw new ParseError(`Duplicate ID '${result.field.id}'`);
|
|
1298
|
+
idIndex.set(result.field.id, {
|
|
1299
|
+
nodeType: "field",
|
|
1300
|
+
parentId: id
|
|
1301
|
+
});
|
|
1302
|
+
ungroupedFields.push(result.field);
|
|
1303
|
+
responsesByFieldId[result.field.id] = result.response;
|
|
1304
|
+
orderIndex.push(result.field.id);
|
|
1305
|
+
if ("options" in result.field) for (const opt of result.field.options) {
|
|
1306
|
+
const qualifiedRef = `${result.field.id}.${opt.id}`;
|
|
1307
|
+
if (idIndex.has(qualifiedRef)) throw new ParseError(`Duplicate option ref '${qualifiedRef}'`);
|
|
1308
|
+
idIndex.set(qualifiedRef, {
|
|
1309
|
+
nodeType: "option",
|
|
1310
|
+
parentId: id,
|
|
1311
|
+
fieldId: result.field.id
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
if (child.children && Array.isArray(child.children)) for (const c of child.children) processContent(c);
|
|
1317
|
+
}
|
|
1318
|
+
if (node.children && Array.isArray(node.children)) for (const child of node.children) processContent(child);
|
|
1319
|
+
if (ungroupedFields.length > 0) {
|
|
1320
|
+
const implicitGroupId = `_default`;
|
|
1321
|
+
if (idIndex.has(implicitGroupId)) throw new ParseError(`ID '${implicitGroupId}' is reserved for implicit field groups. Please use a different ID for your field or group.`);
|
|
1322
|
+
idIndex.set(implicitGroupId, {
|
|
1323
|
+
nodeType: "group",
|
|
1324
|
+
parentId: id
|
|
1325
|
+
});
|
|
1326
|
+
groups.push({
|
|
1327
|
+
id: implicitGroupId,
|
|
1328
|
+
children: ungroupedFields,
|
|
1329
|
+
implicit: true
|
|
1330
|
+
});
|
|
857
1331
|
}
|
|
858
|
-
if (node.children && Array.isArray(node.children)) for (const child of node.children) findFieldGroups(child);
|
|
859
1332
|
return {
|
|
860
1333
|
id,
|
|
861
1334
|
title,
|
|
@@ -982,6 +1455,137 @@ function parseForm(markdown) {
|
|
|
982
1455
|
};
|
|
983
1456
|
}
|
|
984
1457
|
|
|
1458
|
+
//#endregion
|
|
1459
|
+
//#region src/engine/scopeRef.ts
|
|
1460
|
+
/**
|
|
1461
|
+
* Pattern for cell reference: fieldId[row].columnId
|
|
1462
|
+
* - fieldId: identifier (letters, digits, underscores, hyphens)
|
|
1463
|
+
* - row: non-negative integer
|
|
1464
|
+
* - columnId: identifier
|
|
1465
|
+
*/
|
|
1466
|
+
const CELL_REF_PATTERN = /^([a-zA-Z_][a-zA-Z0-9_-]*)\[(\d+)\]\.([a-zA-Z_][a-zA-Z0-9_-]*)$/;
|
|
1467
|
+
/**
|
|
1468
|
+
* Pattern for qualified reference: fieldId.qualifierId
|
|
1469
|
+
* - fieldId: identifier
|
|
1470
|
+
* - qualifierId: identifier (optionId or columnId)
|
|
1471
|
+
*/
|
|
1472
|
+
const QUALIFIED_REF_PATTERN = /^([a-zA-Z_][a-zA-Z0-9_-]*)\.([a-zA-Z_][a-zA-Z0-9_-]*)$/;
|
|
1473
|
+
/**
|
|
1474
|
+
* Pattern for simple field reference: fieldId
|
|
1475
|
+
* - fieldId: identifier
|
|
1476
|
+
*/
|
|
1477
|
+
const FIELD_REF_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_-]*$/;
|
|
1478
|
+
/**
|
|
1479
|
+
* Parse a scope reference string into a structured format.
|
|
1480
|
+
*
|
|
1481
|
+
* @param refStr - The scope reference string to parse
|
|
1482
|
+
* @returns Parsed scope reference or error
|
|
1483
|
+
*
|
|
1484
|
+
* @example
|
|
1485
|
+
* parseScopeRef('myField')
|
|
1486
|
+
* // => { ok: true, ref: { type: 'field', fieldId: 'myField' } }
|
|
1487
|
+
*
|
|
1488
|
+
* parseScopeRef('myField.optA')
|
|
1489
|
+
* // => { ok: true, ref: { type: 'qualified', fieldId: 'myField', qualifierId: 'optA' } }
|
|
1490
|
+
*
|
|
1491
|
+
* parseScopeRef('myTable[2].name')
|
|
1492
|
+
* // => { ok: true, ref: { type: 'cell', fieldId: 'myTable', row: 2, columnId: 'name' } }
|
|
1493
|
+
*/
|
|
1494
|
+
function parseScopeRef(refStr) {
|
|
1495
|
+
const trimmed = refStr.trim();
|
|
1496
|
+
if (!trimmed) return {
|
|
1497
|
+
ok: false,
|
|
1498
|
+
error: "Empty scope reference"
|
|
1499
|
+
};
|
|
1500
|
+
const cellMatch = CELL_REF_PATTERN.exec(trimmed);
|
|
1501
|
+
if (cellMatch) {
|
|
1502
|
+
const fieldId = cellMatch[1];
|
|
1503
|
+
const rowStr = cellMatch[2];
|
|
1504
|
+
const columnId = cellMatch[3];
|
|
1505
|
+
const row = parseInt(rowStr, 10);
|
|
1506
|
+
if (row < 0 || !Number.isInteger(row)) return {
|
|
1507
|
+
ok: false,
|
|
1508
|
+
error: `Invalid row index: ${rowStr}`
|
|
1509
|
+
};
|
|
1510
|
+
return {
|
|
1511
|
+
ok: true,
|
|
1512
|
+
ref: {
|
|
1513
|
+
type: "cell",
|
|
1514
|
+
fieldId,
|
|
1515
|
+
row,
|
|
1516
|
+
columnId
|
|
1517
|
+
}
|
|
1518
|
+
};
|
|
1519
|
+
}
|
|
1520
|
+
const qualifiedMatch = QUALIFIED_REF_PATTERN.exec(trimmed);
|
|
1521
|
+
if (qualifiedMatch) return {
|
|
1522
|
+
ok: true,
|
|
1523
|
+
ref: {
|
|
1524
|
+
type: "qualified",
|
|
1525
|
+
fieldId: qualifiedMatch[1],
|
|
1526
|
+
qualifierId: qualifiedMatch[2]
|
|
1527
|
+
}
|
|
1528
|
+
};
|
|
1529
|
+
if (FIELD_REF_PATTERN.test(trimmed)) return {
|
|
1530
|
+
ok: true,
|
|
1531
|
+
ref: {
|
|
1532
|
+
type: "field",
|
|
1533
|
+
fieldId: trimmed
|
|
1534
|
+
}
|
|
1535
|
+
};
|
|
1536
|
+
return {
|
|
1537
|
+
ok: false,
|
|
1538
|
+
error: `Invalid scope reference format: ${refStr}`
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
/**
|
|
1542
|
+
* Serialize a parsed scope reference back to a string.
|
|
1543
|
+
*
|
|
1544
|
+
* @param ref - The parsed scope reference
|
|
1545
|
+
* @returns Serialized string
|
|
1546
|
+
*
|
|
1547
|
+
* @example
|
|
1548
|
+
* serializeScopeRef({ type: 'field', fieldId: 'myField' })
|
|
1549
|
+
* // => 'myField'
|
|
1550
|
+
*
|
|
1551
|
+
* serializeScopeRef({ type: 'qualified', fieldId: 'myField', qualifierId: 'optA' })
|
|
1552
|
+
* // => 'myField.optA'
|
|
1553
|
+
*
|
|
1554
|
+
* serializeScopeRef({ type: 'cell', fieldId: 'myTable', row: 2, columnId: 'name' })
|
|
1555
|
+
* // => 'myTable[2].name'
|
|
1556
|
+
*/
|
|
1557
|
+
function serializeScopeRef(ref) {
|
|
1558
|
+
switch (ref.type) {
|
|
1559
|
+
case "field": return ref.fieldId;
|
|
1560
|
+
case "qualified": return `${ref.fieldId}.${ref.qualifierId}`;
|
|
1561
|
+
case "cell": return `${ref.fieldId}[${ref.row}].${ref.columnId}`;
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
/**
|
|
1565
|
+
* Check if a scope reference is a cell reference.
|
|
1566
|
+
*/
|
|
1567
|
+
function isCellRef(ref) {
|
|
1568
|
+
return ref.type === "cell";
|
|
1569
|
+
}
|
|
1570
|
+
/**
|
|
1571
|
+
* Check if a scope reference is a qualified reference.
|
|
1572
|
+
*/
|
|
1573
|
+
function isQualifiedRef(ref) {
|
|
1574
|
+
return ref.type === "qualified";
|
|
1575
|
+
}
|
|
1576
|
+
/**
|
|
1577
|
+
* Check if a scope reference is a simple field reference.
|
|
1578
|
+
*/
|
|
1579
|
+
function isFieldRef(ref) {
|
|
1580
|
+
return ref.type === "field";
|
|
1581
|
+
}
|
|
1582
|
+
/**
|
|
1583
|
+
* Extract the field ID from any scope reference.
|
|
1584
|
+
*/
|
|
1585
|
+
function getFieldId(ref) {
|
|
1586
|
+
return ref.fieldId;
|
|
1587
|
+
}
|
|
1588
|
+
|
|
985
1589
|
//#endregion
|
|
986
1590
|
//#region src/engine/valueCoercion.ts
|
|
987
1591
|
/**
|
|
@@ -1387,6 +1991,51 @@ function coerceToYear(fieldId, rawValue) {
|
|
|
1387
1991
|
};
|
|
1388
1992
|
}
|
|
1389
1993
|
/**
|
|
1994
|
+
* Coerce raw value to SetTablePatch.
|
|
1995
|
+
* Accepts:
|
|
1996
|
+
* - Array of row objects: [{ col1: value1, col2: value2 }, ...]
|
|
1997
|
+
* - Empty array: [] (valid for optional tables or minRows=0)
|
|
1998
|
+
*/
|
|
1999
|
+
function coerceToTable(fieldId, rawValue) {
|
|
2000
|
+
if (rawValue === null) return {
|
|
2001
|
+
ok: true,
|
|
2002
|
+
patch: {
|
|
2003
|
+
op: "set_table",
|
|
2004
|
+
fieldId,
|
|
2005
|
+
rows: []
|
|
2006
|
+
}
|
|
2007
|
+
};
|
|
2008
|
+
if (!Array.isArray(rawValue)) return {
|
|
2009
|
+
ok: false,
|
|
2010
|
+
error: `Table value for field '${fieldId}' must be an array of rows, got ${typeof rawValue}`
|
|
2011
|
+
};
|
|
2012
|
+
if (rawValue.length === 0) return {
|
|
2013
|
+
ok: true,
|
|
2014
|
+
patch: {
|
|
2015
|
+
op: "set_table",
|
|
2016
|
+
fieldId,
|
|
2017
|
+
rows: []
|
|
2018
|
+
}
|
|
2019
|
+
};
|
|
2020
|
+
const rows = [];
|
|
2021
|
+
for (let i = 0; i < rawValue.length; i++) {
|
|
2022
|
+
const row = rawValue[i];
|
|
2023
|
+
if (typeof row !== "object" || row === null || Array.isArray(row)) return {
|
|
2024
|
+
ok: false,
|
|
2025
|
+
error: `Row ${i} for table field '${fieldId}' must be an object, got ${Array.isArray(row) ? "array" : typeof row}`
|
|
2026
|
+
};
|
|
2027
|
+
rows.push(row);
|
|
2028
|
+
}
|
|
2029
|
+
return {
|
|
2030
|
+
ok: true,
|
|
2031
|
+
patch: {
|
|
2032
|
+
op: "set_table",
|
|
2033
|
+
fieldId,
|
|
2034
|
+
rows
|
|
2035
|
+
}
|
|
2036
|
+
};
|
|
2037
|
+
}
|
|
2038
|
+
/**
|
|
1390
2039
|
* Coerce a raw value to a Patch for a specific field.
|
|
1391
2040
|
*/
|
|
1392
2041
|
function coerceToFieldPatch(form, fieldId, rawValue) {
|
|
@@ -1406,6 +2055,7 @@ function coerceToFieldPatch(form, fieldId, rawValue) {
|
|
|
1406
2055
|
case "url_list": return coerceToUrlList(fieldId, rawValue);
|
|
1407
2056
|
case "date": return coerceToDate(fieldId, rawValue);
|
|
1408
2057
|
case "year": return coerceToYear(fieldId, rawValue);
|
|
2058
|
+
case "table": return coerceToTable(fieldId, rawValue);
|
|
1409
2059
|
}
|
|
1410
2060
|
}
|
|
1411
2061
|
/**
|
|
@@ -1758,6 +2408,9 @@ var MockAgent = class {
|
|
|
1758
2408
|
case "checkboxes": return true;
|
|
1759
2409
|
case "url": return value.value !== null && value.value !== "";
|
|
1760
2410
|
case "url_list": return value.items.length > 0;
|
|
2411
|
+
case "date": return value.value !== null;
|
|
2412
|
+
case "year": return value.value !== null;
|
|
2413
|
+
case "table": return value.rows.length > 0;
|
|
1761
2414
|
default: return false;
|
|
1762
2415
|
}
|
|
1763
2416
|
}
|
|
@@ -1806,6 +2459,25 @@ var MockAgent = class {
|
|
|
1806
2459
|
fieldId,
|
|
1807
2460
|
items: value.items
|
|
1808
2461
|
};
|
|
2462
|
+
case "date": return {
|
|
2463
|
+
op: "set_date",
|
|
2464
|
+
fieldId,
|
|
2465
|
+
value: value.value
|
|
2466
|
+
};
|
|
2467
|
+
case "year": return {
|
|
2468
|
+
op: "set_year",
|
|
2469
|
+
fieldId,
|
|
2470
|
+
value: value.value
|
|
2471
|
+
};
|
|
2472
|
+
case "table": return {
|
|
2473
|
+
op: "set_table",
|
|
2474
|
+
fieldId,
|
|
2475
|
+
rows: value.rows.map((row) => {
|
|
2476
|
+
const patchRow = {};
|
|
2477
|
+
for (const [colId, cellResponse] of Object.entries(row)) patchRow[colId] = cellResponse.value ?? null;
|
|
2478
|
+
return patchRow;
|
|
2479
|
+
})
|
|
2480
|
+
};
|
|
1809
2481
|
default: return null;
|
|
1810
2482
|
}
|
|
1811
2483
|
}
|
|
@@ -6886,12 +7558,12 @@ function getIssuesIntro(maxPatches) {
|
|
|
6886
7558
|
/**
|
|
6887
7559
|
* Instructions section for the context prompt.
|
|
6888
7560
|
*
|
|
6889
|
-
* This explains the patch format for each field
|
|
7561
|
+
* This explains the patch format for each field kind.
|
|
6890
7562
|
*/
|
|
6891
7563
|
const PATCH_FORMAT_INSTRUCTIONS = `# Instructions
|
|
6892
7564
|
|
|
6893
7565
|
Use the generatePatches tool to submit patches for the fields above.
|
|
6894
|
-
Each patch should match the field
|
|
7566
|
+
Each patch should match the field kind:
|
|
6895
7567
|
- string: { op: "set_string", fieldId: "...", value: "..." }
|
|
6896
7568
|
- number: { op: "set_number", fieldId: "...", value: 123 }
|
|
6897
7569
|
- string_list: { op: "set_string_list", fieldId: "...", items: ["...", "..."] }
|
|
@@ -6927,13 +7599,15 @@ var LiveAgent = class {
|
|
|
6927
7599
|
provider;
|
|
6928
7600
|
enableWebSearch;
|
|
6929
7601
|
webSearchTools = null;
|
|
7602
|
+
additionalTools;
|
|
6930
7603
|
constructor(config) {
|
|
6931
7604
|
this.model = config.model;
|
|
6932
7605
|
this.maxStepsPerTurn = config.maxStepsPerTurn ?? 3;
|
|
6933
7606
|
this.systemPromptAddition = config.systemPromptAddition;
|
|
6934
7607
|
this.targetRole = config.targetRole ?? AGENT_ROLE;
|
|
6935
7608
|
this.provider = config.provider;
|
|
6936
|
-
this.enableWebSearch = config.enableWebSearch
|
|
7609
|
+
this.enableWebSearch = config.enableWebSearch;
|
|
7610
|
+
this.additionalTools = config.additionalTools ?? {};
|
|
6937
7611
|
if (this.enableWebSearch && this.provider) this.webSearchTools = loadWebSearchTools(this.provider);
|
|
6938
7612
|
}
|
|
6939
7613
|
/**
|
|
@@ -6943,7 +7617,8 @@ var LiveAgent = class {
|
|
|
6943
7617
|
getAvailableToolNames() {
|
|
6944
7618
|
const tools = ["generatePatches"];
|
|
6945
7619
|
if (this.webSearchTools) tools.push(...Object.keys(this.webSearchTools));
|
|
6946
|
-
|
|
7620
|
+
tools.push(...Object.keys(this.additionalTools));
|
|
7621
|
+
return [...new Set(tools)];
|
|
6947
7622
|
}
|
|
6948
7623
|
/**
|
|
6949
7624
|
* Generate patches using the LLM.
|
|
@@ -6963,7 +7638,8 @@ var LiveAgent = class {
|
|
|
6963
7638
|
description: GENERATE_PATCHES_TOOL_DESCRIPTION,
|
|
6964
7639
|
inputSchema: zodSchema(z.object({ patches: z.array(PatchSchema).max(maxPatches).describe("Array of patches. Each patch sets a value for one field.") }))
|
|
6965
7640
|
},
|
|
6966
|
-
...this.webSearchTools
|
|
7641
|
+
...this.webSearchTools,
|
|
7642
|
+
...this.additionalTools
|
|
6967
7643
|
};
|
|
6968
7644
|
const result = await generateText({
|
|
6969
7645
|
model: this.model,
|
|
@@ -7389,7 +8065,8 @@ async function fillForm(options) {
|
|
|
7389
8065
|
systemPromptAddition: options.systemPromptAddition,
|
|
7390
8066
|
targetRole: targetRoles[0] ?? AGENT_ROLE,
|
|
7391
8067
|
provider,
|
|
7392
|
-
enableWebSearch:
|
|
8068
|
+
enableWebSearch: options.enableWebSearch,
|
|
8069
|
+
additionalTools: options.additionalTools
|
|
7393
8070
|
});
|
|
7394
8071
|
let turnCount = 0;
|
|
7395
8072
|
let stepResult = harness.step();
|
|
@@ -7488,7 +8165,9 @@ async function runResearch(form, options) {
|
|
|
7488
8165
|
const agent = createLiveAgent({
|
|
7489
8166
|
model,
|
|
7490
8167
|
provider,
|
|
7491
|
-
targetRole: config.targetRoles?.[0] ?? AGENT_ROLE
|
|
8168
|
+
targetRole: config.targetRoles?.[0] ?? AGENT_ROLE,
|
|
8169
|
+
enableWebSearch: options.enableWebSearch,
|
|
8170
|
+
additionalTools: options.additionalTools
|
|
7492
8171
|
});
|
|
7493
8172
|
const availableTools = agent.getAvailableToolNames();
|
|
7494
8173
|
let totalInputTokens = 0;
|
|
@@ -7580,8 +8259,9 @@ function validateResearchForm(form) {
|
|
|
7580
8259
|
* This is the main library entry point that exports the core engine,
|
|
7581
8260
|
* types, and utilities for working with .form.md files.
|
|
7582
8261
|
*/
|
|
7583
|
-
|
|
7584
|
-
|
|
8262
|
+
const pkg = createRequire(import.meta.url)("../package.json");
|
|
8263
|
+
/** Markform version (read from package.json). */
|
|
8264
|
+
const VERSION = pkg.version;
|
|
7585
8265
|
|
|
7586
8266
|
//#endregion
|
|
7587
|
-
export { findFieldById as _, resolveHarnessConfig as a, getProviderNames as c, MockAgent as d, createMockAgent as f, coerceToFieldPatch as g, coerceInputContext as h, runResearch as i, resolveModel as l, createHarness as m, isResearchForm as n, fillForm as o, FormHarness as p, validateResearchForm as r, getProviderInfo as s, VERSION as t, createLiveAgent as u,
|
|
8267
|
+
export { serializeScopeRef as C, parseRawTable as D, parseMarkdownTable as E, ParseError as O, parseScopeRef as S, parseCellValue as T, findFieldById as _, resolveHarnessConfig as a, isFieldRef as b, getProviderNames as c, MockAgent as d, createMockAgent as f, coerceToFieldPatch as g, coerceInputContext as h, runResearch as i, resolveModel as l, createHarness as m, isResearchForm as n, fillForm as o, FormHarness as p, validateResearchForm as r, getProviderInfo as s, VERSION as t, createLiveAgent as u, getFieldId as v, parseForm as w, isQualifiedRef as x, isCellRef as y };
|