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
|
@@ -2,11 +2,33 @@ import { resolve } from "node:path";
|
|
|
2
2
|
|
|
3
3
|
//#region src/llms.ts
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Parse a model ID string into provider and model components for display.
|
|
6
6
|
*
|
|
7
|
-
* This
|
|
8
|
-
*
|
|
9
|
-
|
|
7
|
+
* This is a non-throwing utility for extracting display-friendly components
|
|
8
|
+
* from a model ID. For validation and resolution, use `parseModelId` from
|
|
9
|
+
* `modelResolver.ts` instead.
|
|
10
|
+
*
|
|
11
|
+
* @param modelId - Model ID in format `provider/model-id`
|
|
12
|
+
* @returns Parsed components with 'unknown' provider if format is invalid
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* parseModelIdForDisplay('anthropic/claude-sonnet-4')
|
|
16
|
+
* // => { provider: 'anthropic', model: 'claude-sonnet-4' }
|
|
17
|
+
*
|
|
18
|
+
* parseModelIdForDisplay('claude-sonnet-4')
|
|
19
|
+
* // => { provider: 'unknown', model: 'claude-sonnet-4' }
|
|
20
|
+
*/
|
|
21
|
+
function parseModelIdForDisplay(modelId) {
|
|
22
|
+
const slashIndex = modelId.indexOf("/");
|
|
23
|
+
if (slashIndex === -1 || slashIndex === 0 || slashIndex === modelId.length - 1) return {
|
|
24
|
+
provider: "unknown",
|
|
25
|
+
model: modelId
|
|
26
|
+
};
|
|
27
|
+
return {
|
|
28
|
+
provider: modelId.slice(0, slashIndex),
|
|
29
|
+
model: modelId.slice(slashIndex + 1)
|
|
30
|
+
};
|
|
31
|
+
}
|
|
10
32
|
/**
|
|
11
33
|
* Suggested LLM models for the fill command, organized by provider.
|
|
12
34
|
* These are shown in help/error messages and model selection prompts.
|
|
@@ -233,6 +255,47 @@ function deriveExportPath(basePath, format) {
|
|
|
233
255
|
}
|
|
234
256
|
return base + EXPORT_EXTENSIONS[format];
|
|
235
257
|
}
|
|
258
|
+
/**
|
|
259
|
+
* Derive report path from any markform file path.
|
|
260
|
+
* Strips known extensions and appends .report.md.
|
|
261
|
+
*/
|
|
262
|
+
function deriveReportPath(basePath) {
|
|
263
|
+
let base = basePath;
|
|
264
|
+
for (const ext of Object.values(ALL_EXTENSIONS)) if (base.endsWith(ext)) {
|
|
265
|
+
base = base.slice(0, -ext.length);
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
return base + REPORT_EXTENSION;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
//#endregion
|
|
272
|
+
//#region src/utils/keySort.ts
|
|
273
|
+
/**
|
|
274
|
+
* Key-based sorting utilities.
|
|
275
|
+
*/
|
|
276
|
+
/** Create a comparator from a key function. */
|
|
277
|
+
function keyComparator(keyFn) {
|
|
278
|
+
return (a, b) => {
|
|
279
|
+
const ka = keyFn(a), kb = keyFn(b);
|
|
280
|
+
return ka < kb ? -1 : ka > kb ? 1 : 0;
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Comparator that sorts priority keys first (in order), then remaining keys alphabetically.
|
|
285
|
+
*
|
|
286
|
+
* @example
|
|
287
|
+
* ```ts
|
|
288
|
+
* const attrs = { label: 'Name', kind: 'string', id: 'name', required: true };
|
|
289
|
+
* const keys = Object.keys(attrs).sort(priorityKeyComparator(['kind', 'id']));
|
|
290
|
+
* // => ['kind', 'id', 'label', 'required']
|
|
291
|
+
* ```
|
|
292
|
+
*/
|
|
293
|
+
function priorityKeyComparator(priorityKeys) {
|
|
294
|
+
return keyComparator((key) => {
|
|
295
|
+
const i = priorityKeys.indexOf(key);
|
|
296
|
+
return [i !== -1 ? i : Infinity, key];
|
|
297
|
+
});
|
|
298
|
+
}
|
|
236
299
|
|
|
237
300
|
//#endregion
|
|
238
301
|
//#region src/engine/serialize.ts
|
|
@@ -308,11 +371,18 @@ function serializeAttrValue(value) {
|
|
|
308
371
|
if (typeof value === "undefined") return "null";
|
|
309
372
|
throw new Error(`Cannot serialize value of type ${typeof value} to Markdoc`);
|
|
310
373
|
}
|
|
374
|
+
/** Priority keys that appear first in serialized attributes, in this order. */
|
|
375
|
+
const ATTR_PRIORITY_KEYS = [
|
|
376
|
+
"kind",
|
|
377
|
+
"id",
|
|
378
|
+
"role"
|
|
379
|
+
];
|
|
311
380
|
/**
|
|
312
|
-
* Serialize attributes to Markdoc format
|
|
381
|
+
* Serialize attributes to Markdoc format.
|
|
382
|
+
* Priority keys (kind, id, role) appear first in order, then remaining keys alphabetically.
|
|
313
383
|
*/
|
|
314
384
|
function serializeAttrs(attrs) {
|
|
315
|
-
const keys = Object.keys(attrs).sort();
|
|
385
|
+
const keys = Object.keys(attrs).sort(priorityKeyComparator(ATTR_PRIORITY_KEYS));
|
|
316
386
|
const parts = [];
|
|
317
387
|
for (const key of keys) {
|
|
318
388
|
const value = attrs[key];
|
|
@@ -342,6 +412,7 @@ function getMarker(state) {
|
|
|
342
412
|
*/
|
|
343
413
|
function serializeStringField(field, response) {
|
|
344
414
|
const attrs = {
|
|
415
|
+
kind: "string",
|
|
345
416
|
id: field.id,
|
|
346
417
|
label: field.label
|
|
347
418
|
};
|
|
@@ -354,6 +425,8 @@ function serializeStringField(field, response) {
|
|
|
354
425
|
if (field.maxLength !== void 0) attrs.maxLength = field.maxLength;
|
|
355
426
|
if (field.validate) attrs.validate = field.validate;
|
|
356
427
|
if (field.report !== void 0) attrs.report = field.report;
|
|
428
|
+
if (field.placeholder) attrs.placeholder = field.placeholder;
|
|
429
|
+
if (field.examples && field.examples.length > 0) attrs.examples = field.examples;
|
|
357
430
|
if (response?.state === "skipped" || response?.state === "aborted") attrs.state = response.state;
|
|
358
431
|
const attrStr = serializeAttrs(attrs);
|
|
359
432
|
let content = "";
|
|
@@ -363,13 +436,14 @@ function serializeStringField(field, response) {
|
|
|
363
436
|
}
|
|
364
437
|
const sentinelContent = getSentinelContent(response);
|
|
365
438
|
if (sentinelContent) content = sentinelContent;
|
|
366
|
-
return `{%
|
|
439
|
+
return `{% field ${attrStr} %}${content}{% /field %}`;
|
|
367
440
|
}
|
|
368
441
|
/**
|
|
369
442
|
* Serialize a number field.
|
|
370
443
|
*/
|
|
371
444
|
function serializeNumberField(field, response) {
|
|
372
445
|
const attrs = {
|
|
446
|
+
kind: "number",
|
|
373
447
|
id: field.id,
|
|
374
448
|
label: field.label
|
|
375
449
|
};
|
|
@@ -381,6 +455,8 @@ function serializeNumberField(field, response) {
|
|
|
381
455
|
if (field.integer) attrs.integer = field.integer;
|
|
382
456
|
if (field.validate) attrs.validate = field.validate;
|
|
383
457
|
if (field.report !== void 0) attrs.report = field.report;
|
|
458
|
+
if (field.placeholder) attrs.placeholder = field.placeholder;
|
|
459
|
+
if (field.examples && field.examples.length > 0) attrs.examples = field.examples;
|
|
384
460
|
if (response?.state === "skipped" || response?.state === "aborted") attrs.state = response.state;
|
|
385
461
|
const attrStr = serializeAttrs(attrs);
|
|
386
462
|
let content = "";
|
|
@@ -390,13 +466,14 @@ function serializeNumberField(field, response) {
|
|
|
390
466
|
}
|
|
391
467
|
const sentinelContent = getSentinelContent(response);
|
|
392
468
|
if (sentinelContent) content = sentinelContent;
|
|
393
|
-
return `{%
|
|
469
|
+
return `{% field ${attrStr} %}${content}{% /field %}`;
|
|
394
470
|
}
|
|
395
471
|
/**
|
|
396
472
|
* Serialize a string-list field.
|
|
397
473
|
*/
|
|
398
474
|
function serializeStringListField(field, response) {
|
|
399
475
|
const attrs = {
|
|
476
|
+
kind: "string_list",
|
|
400
477
|
id: field.id,
|
|
401
478
|
label: field.label
|
|
402
479
|
};
|
|
@@ -410,6 +487,8 @@ function serializeStringListField(field, response) {
|
|
|
410
487
|
if (field.uniqueItems) attrs.uniqueItems = field.uniqueItems;
|
|
411
488
|
if (field.validate) attrs.validate = field.validate;
|
|
412
489
|
if (field.report !== void 0) attrs.report = field.report;
|
|
490
|
+
if (field.placeholder) attrs.placeholder = field.placeholder;
|
|
491
|
+
if (field.examples && field.examples.length > 0) attrs.examples = field.examples;
|
|
413
492
|
if (response?.state === "skipped" || response?.state === "aborted") attrs.state = response.state;
|
|
414
493
|
const attrStr = serializeAttrs(attrs);
|
|
415
494
|
let content = "";
|
|
@@ -419,7 +498,7 @@ function serializeStringListField(field, response) {
|
|
|
419
498
|
}
|
|
420
499
|
const sentinelContent = getSentinelContent(response);
|
|
421
500
|
if (sentinelContent) content = sentinelContent;
|
|
422
|
-
return `{%
|
|
501
|
+
return `{% field ${attrStr} %}${content}{% /field %}`;
|
|
423
502
|
}
|
|
424
503
|
/**
|
|
425
504
|
* Serialize options (for single-select, multi-select, checkboxes).
|
|
@@ -437,6 +516,7 @@ function serializeOptions(options, selected) {
|
|
|
437
516
|
*/
|
|
438
517
|
function serializeSingleSelectField(field, response) {
|
|
439
518
|
const attrs = {
|
|
519
|
+
kind: "single_select",
|
|
440
520
|
id: field.id,
|
|
441
521
|
label: field.label
|
|
442
522
|
};
|
|
@@ -451,13 +531,14 @@ function serializeSingleSelectField(field, response) {
|
|
|
451
531
|
if (response?.state === "answered" && response.value) value = response.value;
|
|
452
532
|
const selected = {};
|
|
453
533
|
for (const opt of field.options) selected[opt.id] = opt.id === value?.selected ? "done" : "todo";
|
|
454
|
-
return `{%
|
|
534
|
+
return `{% field ${attrStr} %}\n${serializeOptions(field.options, selected)}\n{% /field %}`;
|
|
455
535
|
}
|
|
456
536
|
/**
|
|
457
537
|
* Serialize a multi-select field.
|
|
458
538
|
*/
|
|
459
539
|
function serializeMultiSelectField(field, response) {
|
|
460
540
|
const attrs = {
|
|
541
|
+
kind: "multi_select",
|
|
461
542
|
id: field.id,
|
|
462
543
|
label: field.label
|
|
463
544
|
};
|
|
@@ -475,13 +556,14 @@ function serializeMultiSelectField(field, response) {
|
|
|
475
556
|
const selected = {};
|
|
476
557
|
const selectedSet = new Set(value?.selected ?? []);
|
|
477
558
|
for (const opt of field.options) selected[opt.id] = selectedSet.has(opt.id) ? "done" : "todo";
|
|
478
|
-
return `{%
|
|
559
|
+
return `{% field ${attrStr} %}\n${serializeOptions(field.options, selected)}\n{% /field %}`;
|
|
479
560
|
}
|
|
480
561
|
/**
|
|
481
562
|
* Serialize a checkboxes field.
|
|
482
563
|
*/
|
|
483
564
|
function serializeCheckboxesField(field, response) {
|
|
484
565
|
const attrs = {
|
|
566
|
+
kind: "checkboxes",
|
|
485
567
|
id: field.id,
|
|
486
568
|
label: field.label
|
|
487
569
|
};
|
|
@@ -497,13 +579,14 @@ function serializeCheckboxesField(field, response) {
|
|
|
497
579
|
const attrStr = serializeAttrs(attrs);
|
|
498
580
|
let value;
|
|
499
581
|
if (response?.state === "answered" && response.value) value = response.value;
|
|
500
|
-
return `{%
|
|
582
|
+
return `{% field ${attrStr} %}\n${serializeOptions(field.options, value?.values ?? {})}\n{% /field %}`;
|
|
501
583
|
}
|
|
502
584
|
/**
|
|
503
585
|
* Serialize a url-field.
|
|
504
586
|
*/
|
|
505
587
|
function serializeUrlField(field, response) {
|
|
506
588
|
const attrs = {
|
|
589
|
+
kind: "url",
|
|
507
590
|
id: field.id,
|
|
508
591
|
label: field.label
|
|
509
592
|
};
|
|
@@ -512,6 +595,8 @@ function serializeUrlField(field, response) {
|
|
|
512
595
|
if (field.role !== AGENT_ROLE) attrs.role = field.role;
|
|
513
596
|
if (field.validate) attrs.validate = field.validate;
|
|
514
597
|
if (field.report !== void 0) attrs.report = field.report;
|
|
598
|
+
if (field.placeholder) attrs.placeholder = field.placeholder;
|
|
599
|
+
if (field.examples && field.examples.length > 0) attrs.examples = field.examples;
|
|
515
600
|
if (response?.state === "skipped" || response?.state === "aborted") attrs.state = response.state;
|
|
516
601
|
const attrStr = serializeAttrs(attrs);
|
|
517
602
|
let content = "";
|
|
@@ -521,13 +606,14 @@ function serializeUrlField(field, response) {
|
|
|
521
606
|
}
|
|
522
607
|
const sentinelContent = getSentinelContent(response);
|
|
523
608
|
if (sentinelContent) content = sentinelContent;
|
|
524
|
-
return `{%
|
|
609
|
+
return `{% field ${attrStr} %}${content}{% /field %}`;
|
|
525
610
|
}
|
|
526
611
|
/**
|
|
527
612
|
* Serialize a url-list field.
|
|
528
613
|
*/
|
|
529
614
|
function serializeUrlListField(field, response) {
|
|
530
615
|
const attrs = {
|
|
616
|
+
kind: "url_list",
|
|
531
617
|
id: field.id,
|
|
532
618
|
label: field.label
|
|
533
619
|
};
|
|
@@ -539,6 +625,8 @@ function serializeUrlListField(field, response) {
|
|
|
539
625
|
if (field.uniqueItems) attrs.uniqueItems = field.uniqueItems;
|
|
540
626
|
if (field.validate) attrs.validate = field.validate;
|
|
541
627
|
if (field.report !== void 0) attrs.report = field.report;
|
|
628
|
+
if (field.placeholder) attrs.placeholder = field.placeholder;
|
|
629
|
+
if (field.examples && field.examples.length > 0) attrs.examples = field.examples;
|
|
542
630
|
if (response?.state === "skipped" || response?.state === "aborted") attrs.state = response.state;
|
|
543
631
|
const attrStr = serializeAttrs(attrs);
|
|
544
632
|
let content = "";
|
|
@@ -548,13 +636,14 @@ function serializeUrlListField(field, response) {
|
|
|
548
636
|
}
|
|
549
637
|
const sentinelContent = getSentinelContent(response);
|
|
550
638
|
if (sentinelContent) content = sentinelContent;
|
|
551
|
-
return `{%
|
|
639
|
+
return `{% field ${attrStr} %}${content}{% /field %}`;
|
|
552
640
|
}
|
|
553
641
|
/**
|
|
554
642
|
* Serialize a date-field.
|
|
555
643
|
*/
|
|
556
644
|
function serializeDateField(field, response) {
|
|
557
645
|
const attrs = {
|
|
646
|
+
kind: "date",
|
|
558
647
|
id: field.id,
|
|
559
648
|
label: field.label
|
|
560
649
|
};
|
|
@@ -574,13 +663,14 @@ function serializeDateField(field, response) {
|
|
|
574
663
|
}
|
|
575
664
|
const sentinelContent = getSentinelContent(response);
|
|
576
665
|
if (sentinelContent) content = sentinelContent;
|
|
577
|
-
return `{%
|
|
666
|
+
return `{% field ${attrStr} %}${content}{% /field %}`;
|
|
578
667
|
}
|
|
579
668
|
/**
|
|
580
669
|
* Serialize a year-field.
|
|
581
670
|
*/
|
|
582
671
|
function serializeYearField(field, response) {
|
|
583
672
|
const attrs = {
|
|
673
|
+
kind: "year",
|
|
584
674
|
id: field.id,
|
|
585
675
|
label: field.label
|
|
586
676
|
};
|
|
@@ -600,7 +690,74 @@ function serializeYearField(field, response) {
|
|
|
600
690
|
}
|
|
601
691
|
const sentinelContent = getSentinelContent(response);
|
|
602
692
|
if (sentinelContent) content = sentinelContent;
|
|
603
|
-
return `{%
|
|
693
|
+
return `{% field ${attrStr} %}${content}{% /field %}`;
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Serialize a cell value for table output.
|
|
697
|
+
*/
|
|
698
|
+
function serializeCellValue(cell, _columnType) {
|
|
699
|
+
if (cell.state === "skipped") return cell.reason ? `%SKIP:${cell.reason}%` : "%SKIP%";
|
|
700
|
+
if (cell.state === "aborted") return cell.reason ? `%ABORT:${cell.reason}%` : "%ABORT%";
|
|
701
|
+
if (cell.value === void 0 || cell.value === null) return "";
|
|
702
|
+
if (typeof cell.value === "number") return String(cell.value);
|
|
703
|
+
return cell.value;
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Serialize a table row to markdown table row format.
|
|
707
|
+
*/
|
|
708
|
+
function serializeTableRow(row, columns) {
|
|
709
|
+
return `| ${columns.map((col) => {
|
|
710
|
+
return serializeCellValue(row[col.id] ?? { state: "skipped" }, col.type);
|
|
711
|
+
}).join(" | ")} |`;
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Serialize a table value to markdown table format.
|
|
715
|
+
*/
|
|
716
|
+
function serializeMarkdownTable(value, columns) {
|
|
717
|
+
if (columns.length === 0) return "";
|
|
718
|
+
const lines = [];
|
|
719
|
+
const headerCells = columns.map((col) => col.label);
|
|
720
|
+
lines.push(`| ${headerCells.join(" | ")} |`);
|
|
721
|
+
const separatorCells = columns.map(() => "---");
|
|
722
|
+
lines.push(`| ${separatorCells.join(" | ")} |`);
|
|
723
|
+
for (const row of value.rows) lines.push(serializeTableRow(row, columns));
|
|
724
|
+
return lines.join("\n");
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Serialize a table-field.
|
|
728
|
+
*/
|
|
729
|
+
function serializeTableField(field, response) {
|
|
730
|
+
const attrs = {
|
|
731
|
+
kind: "table",
|
|
732
|
+
id: field.id,
|
|
733
|
+
label: field.label
|
|
734
|
+
};
|
|
735
|
+
if (field.required) attrs.required = field.required;
|
|
736
|
+
if (field.priority !== DEFAULT_PRIORITY) attrs.priority = field.priority;
|
|
737
|
+
if (field.role !== AGENT_ROLE) attrs.role = field.role;
|
|
738
|
+
attrs.columnIds = field.columns.map((c) => c.id);
|
|
739
|
+
attrs.columnLabels = field.columns.map((c) => c.label);
|
|
740
|
+
attrs.columnTypes = field.columns.map((c) => {
|
|
741
|
+
if (c.required) return {
|
|
742
|
+
type: c.type,
|
|
743
|
+
required: true
|
|
744
|
+
};
|
|
745
|
+
return c.type;
|
|
746
|
+
});
|
|
747
|
+
if (field.minRows !== void 0) attrs.minRows = field.minRows;
|
|
748
|
+
if (field.maxRows !== void 0) attrs.maxRows = field.maxRows;
|
|
749
|
+
if (field.validate) attrs.validate = field.validate;
|
|
750
|
+
if (field.report !== void 0) attrs.report = field.report;
|
|
751
|
+
if (response?.state === "skipped" || response?.state === "aborted") attrs.state = response.state;
|
|
752
|
+
const attrStr = serializeAttrs(attrs);
|
|
753
|
+
let content = "";
|
|
754
|
+
if (response?.state === "answered" && response.value) {
|
|
755
|
+
const value = response.value;
|
|
756
|
+
if (value.rows.length > 0) content = formatValueFence(serializeMarkdownTable(value, field.columns));
|
|
757
|
+
}
|
|
758
|
+
const sentinelContent = getSentinelContent(response);
|
|
759
|
+
if (sentinelContent) content = sentinelContent;
|
|
760
|
+
return `{% field ${attrStr} %}${content}{% /field %}`;
|
|
604
761
|
}
|
|
605
762
|
/**
|
|
606
763
|
* Serialize a field to Markdoc format.
|
|
@@ -618,6 +775,7 @@ function serializeField(field, responses) {
|
|
|
618
775
|
case "url_list": return serializeUrlListField(field, response);
|
|
619
776
|
case "date": return serializeDateField(field, response);
|
|
620
777
|
case "year": return serializeYearField(field, response);
|
|
778
|
+
case "table": return serializeTableField(field, response);
|
|
621
779
|
}
|
|
622
780
|
}
|
|
623
781
|
/**
|
|
@@ -652,13 +810,19 @@ function serializeNotes(notes) {
|
|
|
652
810
|
}
|
|
653
811
|
/**
|
|
654
812
|
* Serialize a field group.
|
|
813
|
+
* Implicit groups (fields placed directly under the form) are serialized
|
|
814
|
+
* without the field-group wrapper tags.
|
|
655
815
|
*/
|
|
656
816
|
function serializeFieldGroup(group, responses, docs) {
|
|
657
|
-
const
|
|
658
|
-
if (group.
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
817
|
+
const lines = [];
|
|
818
|
+
if (!group.implicit) {
|
|
819
|
+
const attrs = { id: group.id };
|
|
820
|
+
if (group.title) attrs.title = group.title;
|
|
821
|
+
if (group.validate) attrs.validate = group.validate;
|
|
822
|
+
if (group.report !== void 0) attrs.report = group.report;
|
|
823
|
+
const attrStr = serializeAttrs(attrs);
|
|
824
|
+
lines.push(`{% field-group ${attrStr} %}`);
|
|
825
|
+
}
|
|
662
826
|
const docsByRef = /* @__PURE__ */ new Map();
|
|
663
827
|
for (const doc of docs) {
|
|
664
828
|
const list = docsByRef.get(doc.ref) ?? [];
|
|
@@ -674,8 +838,10 @@ function serializeFieldGroup(group, responses, docs) {
|
|
|
674
838
|
lines.push(serializeDocBlock(doc));
|
|
675
839
|
}
|
|
676
840
|
}
|
|
677
|
-
|
|
678
|
-
|
|
841
|
+
if (!group.implicit) {
|
|
842
|
+
lines.push("");
|
|
843
|
+
lines.push("{% /field-group %}");
|
|
844
|
+
}
|
|
679
845
|
return lines.join("\n");
|
|
680
846
|
}
|
|
681
847
|
/**
|
|
@@ -807,6 +973,12 @@ function serializeFieldRaw(field, responses) {
|
|
|
807
973
|
else lines.push("_(empty)_");
|
|
808
974
|
break;
|
|
809
975
|
}
|
|
976
|
+
case "table": {
|
|
977
|
+
const tableValue = value;
|
|
978
|
+
if (tableValue?.rows && tableValue.rows.length > 0) lines.push(`_(${tableValue.rows.length} rows)_`);
|
|
979
|
+
else lines.push("_(empty)_");
|
|
980
|
+
break;
|
|
981
|
+
}
|
|
810
982
|
}
|
|
811
983
|
return lines.join("\n");
|
|
812
984
|
}
|
|
@@ -941,14 +1113,17 @@ function computeStructureSummary(schema) {
|
|
|
941
1113
|
url: 0,
|
|
942
1114
|
url_list: 0,
|
|
943
1115
|
date: 0,
|
|
944
|
-
year: 0
|
|
1116
|
+
year: 0,
|
|
1117
|
+
table: 0
|
|
945
1118
|
};
|
|
946
1119
|
const groupsById = {};
|
|
947
1120
|
const fieldsById = {};
|
|
948
1121
|
const optionsById = {};
|
|
1122
|
+
const columnsById = {};
|
|
949
1123
|
let groupCount = 0;
|
|
950
1124
|
let fieldCount = 0;
|
|
951
1125
|
let optionCount = 0;
|
|
1126
|
+
let columnCount = 0;
|
|
952
1127
|
for (const group of schema.groups) {
|
|
953
1128
|
groupCount++;
|
|
954
1129
|
groupsById[group.id] = "field_group";
|
|
@@ -964,16 +1139,26 @@ function computeStructureSummary(schema) {
|
|
|
964
1139
|
parentFieldKind: field.kind
|
|
965
1140
|
};
|
|
966
1141
|
}
|
|
1142
|
+
if (field.kind === "table") for (const column of field.columns) {
|
|
1143
|
+
columnCount++;
|
|
1144
|
+
const qualifiedRef = `${field.id}.${column.id}`;
|
|
1145
|
+
columnsById[qualifiedRef] = {
|
|
1146
|
+
parentFieldId: field.id,
|
|
1147
|
+
columnType: column.type
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
967
1150
|
}
|
|
968
1151
|
}
|
|
969
1152
|
return {
|
|
970
1153
|
groupCount,
|
|
971
1154
|
fieldCount,
|
|
972
1155
|
optionCount,
|
|
1156
|
+
columnCount,
|
|
973
1157
|
fieldCountByKind,
|
|
974
1158
|
groupsById,
|
|
975
1159
|
fieldsById,
|
|
976
|
-
optionsById
|
|
1160
|
+
optionsById,
|
|
1161
|
+
columnsById
|
|
977
1162
|
};
|
|
978
1163
|
}
|
|
979
1164
|
/**
|
|
@@ -1012,6 +1197,7 @@ function isFieldSubmitted(field, value) {
|
|
|
1012
1197
|
return v.value !== null && v.value.trim() !== "";
|
|
1013
1198
|
}
|
|
1014
1199
|
case "year": return value.value !== null;
|
|
1200
|
+
case "table": return value.rows.length > 0;
|
|
1015
1201
|
}
|
|
1016
1202
|
}
|
|
1017
1203
|
/**
|
|
@@ -1617,6 +1803,116 @@ function validateYearField(field, value) {
|
|
|
1617
1803
|
return issues;
|
|
1618
1804
|
}
|
|
1619
1805
|
/**
|
|
1806
|
+
* Validate a cell value according to its column type.
|
|
1807
|
+
*/
|
|
1808
|
+
function validateCellValue(cell, column, fieldId, rowIndex) {
|
|
1809
|
+
const issues = [];
|
|
1810
|
+
const cellRef = `${fieldId}[${rowIndex}].${column.id}`;
|
|
1811
|
+
if (cell.state === "skipped" || cell.state === "aborted") {
|
|
1812
|
+
if (column.required) issues.push({
|
|
1813
|
+
severity: "error",
|
|
1814
|
+
message: `Required cell "${column.label}" in row ${rowIndex + 1} is ${cell.state}`,
|
|
1815
|
+
ref: cellRef,
|
|
1816
|
+
source: "builtin"
|
|
1817
|
+
});
|
|
1818
|
+
return issues;
|
|
1819
|
+
}
|
|
1820
|
+
if (column.required && (cell.value === void 0 || cell.value === null || cell.value === "")) {
|
|
1821
|
+
issues.push({
|
|
1822
|
+
severity: "error",
|
|
1823
|
+
message: `Required cell "${column.label}" in row ${rowIndex + 1} is empty`,
|
|
1824
|
+
ref: cellRef,
|
|
1825
|
+
source: "builtin"
|
|
1826
|
+
});
|
|
1827
|
+
return issues;
|
|
1828
|
+
}
|
|
1829
|
+
if (cell.value === void 0 || cell.value === null || cell.value === "") return issues;
|
|
1830
|
+
switch (column.type) {
|
|
1831
|
+
case "number":
|
|
1832
|
+
if (typeof cell.value !== "number") issues.push({
|
|
1833
|
+
severity: "error",
|
|
1834
|
+
message: `Cell "${column.label}" in row ${rowIndex + 1} must be a number (got "${cell.value}")`,
|
|
1835
|
+
ref: cellRef,
|
|
1836
|
+
source: "builtin"
|
|
1837
|
+
});
|
|
1838
|
+
break;
|
|
1839
|
+
case "year":
|
|
1840
|
+
if (typeof cell.value !== "number" || !Number.isInteger(cell.value)) issues.push({
|
|
1841
|
+
severity: "error",
|
|
1842
|
+
message: `Cell "${column.label}" in row ${rowIndex + 1} must be an integer year (got "${cell.value}")`,
|
|
1843
|
+
ref: cellRef,
|
|
1844
|
+
source: "builtin"
|
|
1845
|
+
});
|
|
1846
|
+
break;
|
|
1847
|
+
case "url":
|
|
1848
|
+
if (typeof cell.value === "string") try {
|
|
1849
|
+
new URL(cell.value);
|
|
1850
|
+
} catch {
|
|
1851
|
+
issues.push({
|
|
1852
|
+
severity: "error",
|
|
1853
|
+
message: `Cell "${column.label}" in row ${rowIndex + 1} must be a valid URL (got "${cell.value}")`,
|
|
1854
|
+
ref: cellRef,
|
|
1855
|
+
source: "builtin"
|
|
1856
|
+
});
|
|
1857
|
+
}
|
|
1858
|
+
break;
|
|
1859
|
+
case "date":
|
|
1860
|
+
if (typeof cell.value === "string") {
|
|
1861
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(cell.value)) issues.push({
|
|
1862
|
+
severity: "error",
|
|
1863
|
+
message: `Cell "${column.label}" in row ${rowIndex + 1} must be a valid date in YYYY-MM-DD format (got "${cell.value}")`,
|
|
1864
|
+
ref: cellRef,
|
|
1865
|
+
source: "builtin"
|
|
1866
|
+
});
|
|
1867
|
+
}
|
|
1868
|
+
break;
|
|
1869
|
+
case "string": break;
|
|
1870
|
+
}
|
|
1871
|
+
return issues;
|
|
1872
|
+
}
|
|
1873
|
+
/**
|
|
1874
|
+
* Validate a table row.
|
|
1875
|
+
*/
|
|
1876
|
+
function validateTableRow(row, columns, fieldId, rowIndex) {
|
|
1877
|
+
const issues = [];
|
|
1878
|
+
for (const column of columns) {
|
|
1879
|
+
const cell = row[column.id] ?? { state: "skipped" };
|
|
1880
|
+
issues.push(...validateCellValue(cell, column, fieldId, rowIndex));
|
|
1881
|
+
}
|
|
1882
|
+
return issues;
|
|
1883
|
+
}
|
|
1884
|
+
/**
|
|
1885
|
+
* Validate a table field.
|
|
1886
|
+
*/
|
|
1887
|
+
function validateTableField(field, value) {
|
|
1888
|
+
const issues = [];
|
|
1889
|
+
const isEmpty = !value || value.rows.length === 0;
|
|
1890
|
+
if (field.required && isEmpty) {
|
|
1891
|
+
issues.push({
|
|
1892
|
+
severity: "error",
|
|
1893
|
+
message: `Required field "${field.label}" is empty`,
|
|
1894
|
+
ref: field.id,
|
|
1895
|
+
source: "builtin"
|
|
1896
|
+
});
|
|
1897
|
+
return issues;
|
|
1898
|
+
}
|
|
1899
|
+
if (isEmpty) return issues;
|
|
1900
|
+
if (field.minRows !== void 0 && value.rows.length < field.minRows) issues.push({
|
|
1901
|
+
severity: "error",
|
|
1902
|
+
message: `"${field.label}" must have at least ${field.minRows} row(s) (has ${value.rows.length})`,
|
|
1903
|
+
ref: field.id,
|
|
1904
|
+
source: "builtin"
|
|
1905
|
+
});
|
|
1906
|
+
if (field.maxRows !== void 0 && value.rows.length > field.maxRows) issues.push({
|
|
1907
|
+
severity: "error",
|
|
1908
|
+
message: `"${field.label}" must have at most ${field.maxRows} row(s) (has ${value.rows.length})`,
|
|
1909
|
+
ref: field.id,
|
|
1910
|
+
source: "builtin"
|
|
1911
|
+
});
|
|
1912
|
+
for (let i = 0; i < value.rows.length; i++) issues.push(...validateTableRow(value.rows[i], field.columns, field.id, i));
|
|
1913
|
+
return issues;
|
|
1914
|
+
}
|
|
1915
|
+
/**
|
|
1620
1916
|
* Validate a single field.
|
|
1621
1917
|
*/
|
|
1622
1918
|
function validateField(field, responses) {
|
|
@@ -1633,6 +1929,7 @@ function validateField(field, responses) {
|
|
|
1633
1929
|
case "url_list": return validateUrlListField(field, value);
|
|
1634
1930
|
case "date": return validateDateField(field, value);
|
|
1635
1931
|
case "year": return validateYearField(field, value);
|
|
1932
|
+
case "table": return validateTableField(field, value);
|
|
1636
1933
|
}
|
|
1637
1934
|
}
|
|
1638
1935
|
/**
|
|
@@ -2152,6 +2449,19 @@ function validatePatch(form, patch, index) {
|
|
|
2152
2449
|
message: `Cannot apply set_year to ${field.kind} field "${field.id}"`
|
|
2153
2450
|
};
|
|
2154
2451
|
break;
|
|
2452
|
+
case "set_table": {
|
|
2453
|
+
if (field.kind !== "table") return {
|
|
2454
|
+
patchIndex: index,
|
|
2455
|
+
message: `Cannot apply set_table to ${field.kind} field "${field.id}"`
|
|
2456
|
+
};
|
|
2457
|
+
const tableField = field;
|
|
2458
|
+
const validColumns = new Set(tableField.columns.map((c) => c.id));
|
|
2459
|
+
for (const row of patch.rows) for (const colId of Object.keys(row)) if (!validColumns.has(colId)) return {
|
|
2460
|
+
patchIndex: index,
|
|
2461
|
+
message: `Invalid column "${colId}" for table field "${field.id}"`
|
|
2462
|
+
};
|
|
2463
|
+
break;
|
|
2464
|
+
}
|
|
2155
2465
|
case "clear_field": break;
|
|
2156
2466
|
case "skip_field":
|
|
2157
2467
|
if (field.required) return {
|
|
@@ -2311,6 +2621,50 @@ function applySetYear(responses, patch) {
|
|
|
2311
2621
|
};
|
|
2312
2622
|
}
|
|
2313
2623
|
/**
|
|
2624
|
+
* Convert a patch row value to a cell response.
|
|
2625
|
+
*/
|
|
2626
|
+
function patchValueToCell(value) {
|
|
2627
|
+
if (value === null || value === void 0) return { state: "skipped" };
|
|
2628
|
+
if (typeof value === "string") {
|
|
2629
|
+
const trimmed = value.trim();
|
|
2630
|
+
const skipMatch = /^%SKIP(?:[:(](.*))?[)]?%$/i.exec(trimmed);
|
|
2631
|
+
if (skipMatch) return {
|
|
2632
|
+
state: "skipped",
|
|
2633
|
+
reason: skipMatch[1]
|
|
2634
|
+
};
|
|
2635
|
+
const abortMatch = /^%ABORT(?:[:(](.*))?[)]?%$/i.exec(trimmed);
|
|
2636
|
+
if (abortMatch) return {
|
|
2637
|
+
state: "aborted",
|
|
2638
|
+
reason: abortMatch[1]
|
|
2639
|
+
};
|
|
2640
|
+
return {
|
|
2641
|
+
state: "answered",
|
|
2642
|
+
value: trimmed
|
|
2643
|
+
};
|
|
2644
|
+
}
|
|
2645
|
+
return {
|
|
2646
|
+
state: "answered",
|
|
2647
|
+
value
|
|
2648
|
+
};
|
|
2649
|
+
}
|
|
2650
|
+
/**
|
|
2651
|
+
* Apply a set_table patch.
|
|
2652
|
+
*/
|
|
2653
|
+
function applySetTable(responses, patch) {
|
|
2654
|
+
const rows = patch.rows.map((patchRow) => {
|
|
2655
|
+
const row = {};
|
|
2656
|
+
for (const [colId, value] of Object.entries(patchRow)) row[colId] = patchValueToCell(value);
|
|
2657
|
+
return row;
|
|
2658
|
+
});
|
|
2659
|
+
responses[patch.fieldId] = {
|
|
2660
|
+
state: "answered",
|
|
2661
|
+
value: {
|
|
2662
|
+
kind: "table",
|
|
2663
|
+
rows
|
|
2664
|
+
}
|
|
2665
|
+
};
|
|
2666
|
+
}
|
|
2667
|
+
/**
|
|
2314
2668
|
* Apply a clear_field patch.
|
|
2315
2669
|
*/
|
|
2316
2670
|
function applyClearField(responses, patch) {
|
|
@@ -2392,6 +2746,9 @@ function applyPatch(form, responses, patch) {
|
|
|
2392
2746
|
case "set_year":
|
|
2393
2747
|
applySetYear(responses, patch);
|
|
2394
2748
|
break;
|
|
2749
|
+
case "set_table":
|
|
2750
|
+
applySetTable(responses, patch);
|
|
2751
|
+
break;
|
|
2395
2752
|
case "clear_field":
|
|
2396
2753
|
applyClearField(responses, patch);
|
|
2397
2754
|
break;
|
|
@@ -2467,4 +2824,4 @@ function applyPatches(form, patches) {
|
|
|
2467
2824
|
}
|
|
2468
2825
|
|
|
2469
2826
|
//#endregion
|
|
2470
|
-
export {
|
|
2827
|
+
export { parseRolesFlag as A, DEFAULT_ROLE_INSTRUCTIONS as C, deriveReportPath as D, deriveExportPath as E, hasWebSearchSupport as F, parseModelIdForDisplay as I, WEB_SEARCH_CONFIG as M, formatSuggestedLlms as N, detectFileType as O, getWebSearchConfig as P, DEFAULT_ROLES as S, USER_ROLE as T, DEFAULT_MAX_TURNS as _, computeAllSummaries as a, DEFAULT_RESEARCH_MAX_ISSUES_PER_TURN as b, computeStructureSummary as c, serializeRawMarkdown as d, serializeReportMarkdown as f, DEFAULT_MAX_PATCHES_PER_TURN as g, DEFAULT_MAX_ISSUES_PER_TURN as h, validate as i, SUGGESTED_LLMS as j, getFormsDir as k, isFormComplete as l, DEFAULT_FORMS_DIR as m, getFieldsForRoles as n, computeFormState as o, AGENT_ROLE as p, inspect as r, computeProgressSummary as s, applyPatches as t, serialize as u, DEFAULT_PORT as v, REPORT_EXTENSION as w, DEFAULT_RESEARCH_MAX_PATCHES_PER_TURN as x, DEFAULT_PRIORITY as y };
|