markform 0.1.20 → 0.1.22
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 +44 -12
- package/dist/ai-sdk.d.mts +1 -1
- package/dist/ai-sdk.mjs +2 -2
- package/dist/{apply-DIvm1b1s.mjs → apply-C7mO7VkZ.mjs} +158 -95
- package/dist/apply-C7mO7VkZ.mjs.map +1 -0
- package/dist/bin.mjs +1 -1
- package/dist/{cli-FFMoEhFS.mjs → cli-C8F9yDsv.mjs} +102 -1225
- package/dist/cli-C8F9yDsv.mjs.map +1 -0
- package/dist/cli.mjs +1 -1
- package/dist/{coreTypes-CkxML8g2.d.mts → coreTypes-BlsJkU1w.d.mts} +28 -2
- package/dist/{coreTypes-CPKXf2dc.mjs → coreTypes-CTLr-NGd.mjs} +24 -3
- package/dist/coreTypes-CTLr-NGd.mjs.map +1 -0
- package/dist/fillRecord-DTl5lnK0.d.mts +345 -0
- package/dist/fillRecordRenderer-CruJrLkj.mjs +1256 -0
- package/dist/fillRecordRenderer-CruJrLkj.mjs.map +1 -0
- package/dist/index.d.mts +22 -342
- package/dist/index.mjs +5 -5
- package/dist/render.d.mts +74 -0
- package/dist/render.mjs +4 -0
- package/dist/{session-CK0x28RO.mjs → session-BCcltrLA.mjs} +2 -2
- package/dist/{session-CK0x28RO.mjs.map → session-BCcltrLA.mjs.map} +1 -1
- package/dist/{session-ZHBi3LVQ.mjs → session-VeSkVrck.mjs} +1 -1
- package/dist/{shared-DwdyWmvE.mjs → shared-CsdT2T7k.mjs} +1 -1
- package/dist/{shared-DwdyWmvE.mjs.map → shared-CsdT2T7k.mjs.map} +1 -1
- package/dist/{shared-BTR35aMz.mjs → shared-fb0nkzQi.mjs} +1 -1
- package/dist/{src-wR7GoftB.mjs → src-CbRnGzMK.mjs} +205 -138
- package/dist/src-CbRnGzMK.mjs.map +1 -0
- package/dist/urlFormat-lls7CsEP.mjs +71 -0
- package/dist/urlFormat-lls7CsEP.mjs.map +1 -0
- package/docs/markform-apis.md +53 -0
- package/examples/movie-research/movie-deep-research-mock-filled.form.md +320 -343
- package/examples/movie-research/movie-deep-research.form.md +273 -308
- package/examples/movie-research/movie-research-demo.form.md +27 -41
- package/examples/parallel/parallel-research.form.md +33 -29
- package/examples/parallel/parallel-research.mock.filled.form.md +88 -0
- package/examples/rejection-test/rejection-test-mock-filled.form.md +21 -16
- package/examples/rejection-test/rejection-test-mock-filled.schema.json +1 -1
- package/examples/rejection-test/rejection-test.form.md +17 -15
- package/examples/rejection-test/rejection-test.session.yaml +88 -60
- package/examples/simple/simple-mock-filled.form.md +113 -126
- package/examples/simple/simple-mock-filled.schema.json +2 -3
- package/examples/simple/simple-skipped-filled.form.md +112 -129
- package/examples/simple/simple-skipped-filled.report.md +8 -8
- package/examples/simple/simple-skipped-filled.schema.json +2 -3
- package/examples/simple/simple-tags-syntax.form.md +32 -0
- package/examples/simple/simple-with-skips.session.yaml +663 -627
- package/examples/simple/simple.form.md +97 -113
- package/examples/simple/simple.schema.json +2 -3
- package/examples/simple/simple.session.yaml +663 -627
- package/examples/startup-deep-research/startup-deep-research.form.md +191 -235
- package/examples/startup-research/startup-research-mock-filled.form.md +128 -147
- package/examples/startup-research/startup-research.form.md +90 -129
- package/examples/twitter-thread/twitter-thread.form.md +373 -0
- package/package.json +5 -1
- package/dist/apply-DIvm1b1s.mjs.map +0 -1
- package/dist/cli-FFMoEhFS.mjs.map +0 -1
- package/dist/coreTypes-CPKXf2dc.mjs.map +0 -1
- package/dist/src-wR7GoftB.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Markform
|
|
2
2
|
|
|
3
|
-
[](https://github.com/jlevy/markform/actions/runs/
|
|
4
|
-
[](https://github.com/jlevy/markform/actions/runs/
|
|
3
|
+
[](https://github.com/jlevy/markform/actions/runs/21885509869)
|
|
4
|
+
[](https://github.com/jlevy/markform/actions/runs/21885509869)
|
|
5
5
|
[](https://www.npmjs.com/package/markform)
|
|
6
6
|
[](https://x.com/ojoshe)
|
|
7
7
|
|
|
@@ -529,7 +529,10 @@ markform --help
|
|
|
529
529
|
## API Key Setup
|
|
530
530
|
|
|
531
531
|
Set the appropriate environment variable for your provider before running
|
|
532
|
-
`markform fill
|
|
532
|
+
`markform fill`. The CLI automatically loads from `.env.local` and `.env` files in the
|
|
533
|
+
current directory.
|
|
534
|
+
|
|
535
|
+
Supported providers:
|
|
533
536
|
|
|
534
537
|
| Provider | Env Variable | Native Web Search |
|
|
535
538
|
| --- | --- | :---: |
|
|
@@ -548,8 +551,8 @@ If unsure, try `gpt-5-mini` first as it’s fast and supports web search.
|
|
|
548
551
|
|
|
549
552
|
## Programmatic Usage
|
|
550
553
|
|
|
551
|
-
Markform exports a parsing engine and AI SDK integration for use in
|
|
552
|
-
applications.
|
|
554
|
+
Markform exports a parsing engine, rendering functions, and AI SDK integration for use in
|
|
555
|
+
your own applications.
|
|
553
556
|
|
|
554
557
|
### Basic Parsing
|
|
555
558
|
|
|
@@ -587,9 +590,38 @@ if (result.status.ok) {
|
|
|
587
590
|
}
|
|
588
591
|
```
|
|
589
592
|
|
|
590
|
-
See the
|
|
593
|
+
See the
|
|
594
|
+
[API documentation](https://github.com/jlevy/markform/blob/main/docs/markform-apis.md)
|
|
591
595
|
for options like parallel execution, callbacks, and checkpointing.
|
|
592
596
|
|
|
597
|
+
### Rendering API
|
|
598
|
+
|
|
599
|
+
Import from the `markform/render` subpath to render forms and fill records as HTML
|
|
600
|
+
fragments — the same output as `markform serve`, without pulling in CLI/server
|
|
601
|
+
dependencies:
|
|
602
|
+
|
|
603
|
+
```typescript
|
|
604
|
+
import {
|
|
605
|
+
renderViewContent,
|
|
606
|
+
renderFillRecordContent,
|
|
607
|
+
FILL_RECORD_STYLES,
|
|
608
|
+
FILL_RECORD_SCRIPTS,
|
|
609
|
+
} from "markform/render";
|
|
610
|
+
|
|
611
|
+
// Render a filled form as read-only HTML
|
|
612
|
+
const formHtml = renderViewContent(parsedForm);
|
|
613
|
+
|
|
614
|
+
// Render a fill record dashboard
|
|
615
|
+
const dashboardHtml = renderFillRecordContent(fillRecord);
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
Also exports `renderSourceContent`, `renderMarkdownContent`, `renderYamlContent`,
|
|
619
|
+
`renderJsonContent`, `escapeHtml`, `formatDuration`, and `formatTokens`.
|
|
620
|
+
|
|
621
|
+
See the
|
|
622
|
+
[API documentation](https://github.com/jlevy/markform/blob/main/docs/markform-apis.md#rendering-api)
|
|
623
|
+
for full details.
|
|
624
|
+
|
|
593
625
|
### AI SDK Integration
|
|
594
626
|
|
|
595
627
|
Markform provides tools compatible with the [Vercel AI SDK](https://sdk.vercel.ai/):
|
|
@@ -768,12 +800,14 @@ more on the philosophy behind “docs-as-data” that Markform extends to “for
|
|
|
768
800
|
We could use XML tags, but Markdoc has some niceties like tagging Markdown AST nodes
|
|
769
801
|
(`{% #some-id %}`) so I decided to go with this.
|
|
770
802
|
|
|
771
|
-
###
|
|
803
|
+
### What editor settings work best?
|
|
772
804
|
|
|
773
|
-
|
|
805
|
+
**HTML comment syntax (recommended):** Regular Markdown mode works perfectly since
|
|
806
|
+
`<!-- tag -->` comments are standard Markdown.
|
|
807
|
+
|
|
808
|
+
**Markdoc syntax (`{% tag %}`):** Install
|
|
774
809
|
[Better Jinja](https://marketplace.visualstudio.com/items?itemName=samuelcolvin.jinjahtml)
|
|
775
|
-
and associate `.form.md` files with
|
|
776
|
-
settings:
|
|
810
|
+
and associate `.form.md` files with `jinja-md` mode:
|
|
777
811
|
|
|
778
812
|
```json
|
|
779
813
|
"files.associations": {
|
|
@@ -781,8 +815,6 @@ settings:
|
|
|
781
815
|
}
|
|
782
816
|
```
|
|
783
817
|
|
|
784
|
-
Or see [markdoc/language-server](https://github.com/markdoc/language-server).
|
|
785
|
-
|
|
786
818
|
## License
|
|
787
819
|
|
|
788
820
|
This project uses a dual licensing approach:
|
package/dist/ai-sdk.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { At as
|
|
2
|
+
import { At as ParsedForm, Nt as PatchSchema, U as FieldResponse, Y as FormSchema, at as InspectResult, et as Id, hr as ValidatorRegistry, jt as Patch, r as ApplyResult } from "./coreTypes-BlsJkU1w.mjs";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
|
|
5
5
|
//#region src/integrations/toolTypes.d.ts
|
package/dist/ai-sdk.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
|
-
import {
|
|
3
|
-
import { d as serializeForm, i as inspect, t as applyPatches } from "./apply-
|
|
2
|
+
import { R as PatchSchema } from "./coreTypes-CTLr-NGd.mjs";
|
|
3
|
+
import { d as serializeForm, i as inspect, t as applyPatches } from "./apply-C7mO7VkZ.mjs";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
|
|
6
6
|
//#region src/integrations/vercelAiSdkTools.ts
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
|
|
2
|
+
import { n as formatUrlAsMarkdownLink } from "./urlFormat-lls7CsEP.mjs";
|
|
2
3
|
import YAML from "yaml";
|
|
3
4
|
|
|
4
5
|
//#region src/errors.ts
|
|
5
|
-
const VERSION = "0.1.
|
|
6
|
+
const VERSION = "0.1.22";
|
|
6
7
|
/**
|
|
7
8
|
* Base error class for all markform errors.
|
|
8
9
|
* Consumers can catch this to handle any markform error.
|
|
@@ -94,12 +95,15 @@ var MarkformLlmError = class extends MarkformError {
|
|
|
94
95
|
statusCode;
|
|
95
96
|
/** Whether this error is retryable */
|
|
96
97
|
retryable;
|
|
98
|
+
/** Raw response body from the API (for debugging) */
|
|
99
|
+
responseBody;
|
|
97
100
|
constructor(message, context) {
|
|
98
101
|
super(message, { cause: context.cause });
|
|
99
102
|
this.provider = context.provider;
|
|
100
103
|
this.model = context.model;
|
|
101
104
|
this.statusCode = context.statusCode;
|
|
102
105
|
this.retryable = context.retryable ?? false;
|
|
106
|
+
this.responseBody = context.responseBody;
|
|
103
107
|
}
|
|
104
108
|
};
|
|
105
109
|
/**
|
|
@@ -173,6 +177,86 @@ function isRetryableError(error) {
|
|
|
173
177
|
return isLlmError(error) && error.retryable;
|
|
174
178
|
}
|
|
175
179
|
/**
|
|
180
|
+
* Check if an error looks like a Vercel AI SDK APICallError.
|
|
181
|
+
* These errors have statusCode, responseBody, and isRetryable properties.
|
|
182
|
+
*/
|
|
183
|
+
function isApiCallError(error) {
|
|
184
|
+
return error instanceof Error && ("statusCode" in error || "responseBody" in error || "isRetryable" in error);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Generate troubleshooting hints based on error status and message.
|
|
188
|
+
*/
|
|
189
|
+
function getTroubleshootingHints(statusCode, message, provider, model) {
|
|
190
|
+
const hints = [];
|
|
191
|
+
const lowerMessage = message.toLowerCase();
|
|
192
|
+
if (statusCode === 404 || lowerMessage.includes("not found")) {
|
|
193
|
+
hints.push(`Check if model "${model}" exists and is available for the ${provider} API`);
|
|
194
|
+
hints.push("Verify the model ID is spelled correctly (check provider documentation)");
|
|
195
|
+
hints.push("Some models require specific API tier access or waitlist approval");
|
|
196
|
+
} else if (statusCode === 403 || lowerMessage.includes("forbidden")) {
|
|
197
|
+
hints.push(`Your API key may not have permission to use model "${model}"`);
|
|
198
|
+
hints.push("Check that your API key has the required access tier/plan");
|
|
199
|
+
hints.push("Some preview models require explicit opt-in via provider dashboard");
|
|
200
|
+
} else if (statusCode === 401 || lowerMessage.includes("unauthorized") || lowerMessage.includes("invalid api key")) {
|
|
201
|
+
hints.push("Verify your API key is correct and not expired");
|
|
202
|
+
hints.push(`Check that the correct env var is set (e.g., ${provider.toUpperCase()}_API_KEY)`);
|
|
203
|
+
} else if (statusCode === 429 || lowerMessage.includes("rate limit")) {
|
|
204
|
+
hints.push("You have hit the rate limit - wait a moment and retry");
|
|
205
|
+
hints.push("Consider using a model with higher rate limits or upgrading your plan");
|
|
206
|
+
} else if (statusCode === 500 || statusCode === 502 || statusCode === 503) {
|
|
207
|
+
hints.push("The API provider is experiencing issues - retry in a few minutes");
|
|
208
|
+
hints.push("Check the provider status page for any ongoing incidents");
|
|
209
|
+
}
|
|
210
|
+
if (hints.length === 0) {
|
|
211
|
+
hints.push("Check your API key and model availability");
|
|
212
|
+
hints.push(`Run "markform models" to see available models for ${provider}`);
|
|
213
|
+
}
|
|
214
|
+
return hints;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Wrap an API error with rich context for debugging.
|
|
218
|
+
*
|
|
219
|
+
* Extracts details from Vercel AI SDK APICallError and creates a MarkformLlmError
|
|
220
|
+
* with actionable information including model ID, status code, response body, and
|
|
221
|
+
* troubleshooting hints.
|
|
222
|
+
*
|
|
223
|
+
* @param error - The original error from the API call
|
|
224
|
+
* @param provider - The LLM provider name (e.g., 'anthropic', 'openai')
|
|
225
|
+
* @param model - The model identifier that was requested
|
|
226
|
+
* @returns A MarkformLlmError with rich context
|
|
227
|
+
*/
|
|
228
|
+
function wrapApiError(error, provider, model) {
|
|
229
|
+
let statusCode;
|
|
230
|
+
let responseBody;
|
|
231
|
+
let retryable = false;
|
|
232
|
+
let originalMessage = "Unknown error";
|
|
233
|
+
if (isApiCallError(error)) {
|
|
234
|
+
statusCode = error.statusCode;
|
|
235
|
+
responseBody = error.responseBody;
|
|
236
|
+
retryable = error.isRetryable ?? false;
|
|
237
|
+
originalMessage = error.message;
|
|
238
|
+
} else if (error instanceof Error) originalMessage = error.message;
|
|
239
|
+
else originalMessage = String(error);
|
|
240
|
+
const parts = [];
|
|
241
|
+
parts.push(`API call failed for model "${provider}/${model}"`);
|
|
242
|
+
if (statusCode !== void 0) parts.push(`HTTP ${statusCode}`);
|
|
243
|
+
parts.push(originalMessage);
|
|
244
|
+
if (responseBody) {
|
|
245
|
+
const truncated = responseBody.length > 200 ? responseBody.slice(0, 200) + "..." : responseBody;
|
|
246
|
+
parts.push(`Response: ${truncated}`);
|
|
247
|
+
}
|
|
248
|
+
const hints = getTroubleshootingHints(statusCode, originalMessage, provider, model);
|
|
249
|
+
if (hints.length > 0) parts.push(`\n\nTroubleshooting:\n - ${hints.join("\n - ")}`);
|
|
250
|
+
return new MarkformLlmError(parts.join(": "), {
|
|
251
|
+
provider,
|
|
252
|
+
model,
|
|
253
|
+
statusCode,
|
|
254
|
+
retryable,
|
|
255
|
+
responseBody,
|
|
256
|
+
cause: error instanceof Error ? error : void 0
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
176
260
|
* Alias for MarkformParseError.
|
|
177
261
|
* @deprecated Use MarkformParseError instead. ParseError will be removed in a future version.
|
|
178
262
|
*/
|
|
@@ -359,6 +443,56 @@ const MAX_FORMS_IN_MENU = 30;
|
|
|
359
443
|
*/
|
|
360
444
|
const DEFAULT_PORT = 3344;
|
|
361
445
|
/**
|
|
446
|
+
* Default line width for YAML output.
|
|
447
|
+
* Long strings will wrap at this column for readability.
|
|
448
|
+
* 88 is a common line width that works well with most editors.
|
|
449
|
+
*/
|
|
450
|
+
const DEFAULT_YAML_LINE_WIDTH = 88;
|
|
451
|
+
/**
|
|
452
|
+
* YAML stringify options for readable output.
|
|
453
|
+
* - No forced quoting (YAML only quotes when necessary)
|
|
454
|
+
* - lineWidth provides reasonable wrapping for long strings
|
|
455
|
+
* - Plain keys without quotes
|
|
456
|
+
*/
|
|
457
|
+
const YAML_STRINGIFY_OPTIONS = {
|
|
458
|
+
lineWidth: DEFAULT_YAML_LINE_WIDTH,
|
|
459
|
+
defaultKeyType: "PLAIN"
|
|
460
|
+
};
|
|
461
|
+
/**
|
|
462
|
+
* Mapping between YAML snake_case keys and TypeScript camelCase keys
|
|
463
|
+
* for harness configuration. Single source of truth for all transformations.
|
|
464
|
+
*/
|
|
465
|
+
const HARNESS_CONFIG_MAPPING = {
|
|
466
|
+
max_turns: "maxTurns",
|
|
467
|
+
max_patches_per_turn: "maxPatchesPerTurn",
|
|
468
|
+
max_issues_per_turn: "maxIssuesPerTurn",
|
|
469
|
+
max_parallel_agents: "maxParallelAgents"
|
|
470
|
+
};
|
|
471
|
+
/**
|
|
472
|
+
* Transform harness config from YAML snake_case to TypeScript camelCase.
|
|
473
|
+
* Used by the parser when reading frontmatter.
|
|
474
|
+
*/
|
|
475
|
+
function transformHarnessConfigToTs(yaml) {
|
|
476
|
+
const result = {};
|
|
477
|
+
for (const [snakeKey, camelKey] of Object.entries(HARNESS_CONFIG_MAPPING)) {
|
|
478
|
+
const value = yaml[snakeKey];
|
|
479
|
+
if (value !== void 0) result[camelKey] = value;
|
|
480
|
+
}
|
|
481
|
+
return result;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Transform harness config from TypeScript camelCase to YAML snake_case.
|
|
485
|
+
* Used by the serializer when writing frontmatter.
|
|
486
|
+
*/
|
|
487
|
+
function transformHarnessConfigToYaml(ts) {
|
|
488
|
+
const result = {};
|
|
489
|
+
for (const [snakeKey, camelKey] of Object.entries(HARNESS_CONFIG_MAPPING)) {
|
|
490
|
+
const value = ts[camelKey];
|
|
491
|
+
if (value !== void 0) result[snakeKey] = value;
|
|
492
|
+
}
|
|
493
|
+
return result;
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
362
496
|
* Default maximum turns for the fill harness.
|
|
363
497
|
* Prevents runaway loops during agent execution.
|
|
364
498
|
*/
|
|
@@ -657,6 +791,7 @@ function extractTableContent(node) {
|
|
|
657
791
|
function extractTextFromNode(n) {
|
|
658
792
|
if (!n || typeof n !== "object") return "";
|
|
659
793
|
if (n.type === "text" && typeof n.attributes?.content === "string") return n.attributes.content;
|
|
794
|
+
if (n.type === "link" && typeof n.attributes?.href === "string") return `[${n.children?.map(extractTextFromNode).join("") ?? ""}](${n.attributes.href})`;
|
|
660
795
|
if (n.children && Array.isArray(n.children)) return n.children.map(extractTextFromNode).join("");
|
|
661
796
|
return "";
|
|
662
797
|
}
|
|
@@ -1307,74 +1442,6 @@ function priorityKeyComparator(priorityKeys) {
|
|
|
1307
1442
|
});
|
|
1308
1443
|
}
|
|
1309
1444
|
|
|
1310
|
-
//#endregion
|
|
1311
|
-
//#region src/utils/urlFormat.ts
|
|
1312
|
-
/**
|
|
1313
|
-
* Create a friendly abbreviated display name for a URL.
|
|
1314
|
-
* - Drops "www." prefix from domain
|
|
1315
|
-
* - Adds first portion of path (up to maxPathChars) if present
|
|
1316
|
-
* - Adds ellipsis (…) if path is truncated
|
|
1317
|
-
*
|
|
1318
|
-
* @param url - The URL to abbreviate
|
|
1319
|
-
* @param maxPathChars - Maximum characters to include from the path (default: 12)
|
|
1320
|
-
* @returns Friendly abbreviated URL (e.g., "example.com/docs/api…")
|
|
1321
|
-
*/
|
|
1322
|
-
function friendlyUrlAbbrev(url, maxPathChars = 12) {
|
|
1323
|
-
try {
|
|
1324
|
-
const parsed = new URL(url);
|
|
1325
|
-
let hostname = parsed.hostname;
|
|
1326
|
-
if (hostname.startsWith("www.")) hostname = hostname.slice(4);
|
|
1327
|
-
const path = parsed.pathname.slice(1);
|
|
1328
|
-
if (!path) return hostname;
|
|
1329
|
-
if (path.length <= maxPathChars) return `${hostname}/${path}`;
|
|
1330
|
-
return `${hostname}/${path.slice(0, maxPathChars)}…`;
|
|
1331
|
-
} catch {
|
|
1332
|
-
let result = url;
|
|
1333
|
-
result = result.replace(/^https?:\/\//, "");
|
|
1334
|
-
result = result.replace(/^www\./, "");
|
|
1335
|
-
const maxLen = 30;
|
|
1336
|
-
if (result.length > maxLen) return result.slice(0, maxLen) + "…";
|
|
1337
|
-
return result;
|
|
1338
|
-
}
|
|
1339
|
-
}
|
|
1340
|
-
/**
|
|
1341
|
-
* Format a URL as a markdown link with a friendly abbreviated display text.
|
|
1342
|
-
* The full URL is preserved as the link target.
|
|
1343
|
-
*
|
|
1344
|
-
* @param url - The URL to format
|
|
1345
|
-
* @returns Markdown link in format [friendly-abbrev](url)
|
|
1346
|
-
*/
|
|
1347
|
-
function formatUrlAsMarkdownLink(url) {
|
|
1348
|
-
return `[${friendlyUrlAbbrev(url)}](${url})`;
|
|
1349
|
-
}
|
|
1350
|
-
/**
|
|
1351
|
-
* Format bare URLs in text as HTML links with abbreviated display text.
|
|
1352
|
-
* Also handles markdown-style links [text](url) for consistency.
|
|
1353
|
-
*
|
|
1354
|
-
* Processing order:
|
|
1355
|
-
* 1. Escape all HTML to prevent XSS
|
|
1356
|
-
* 2. Convert markdown links [text](url) to <a> tags
|
|
1357
|
-
* 3. Convert bare URLs (not already in links) to <a> tags with abbreviated display
|
|
1358
|
-
*
|
|
1359
|
-
* @param text - The raw text containing URLs (will be HTML-escaped)
|
|
1360
|
-
* @param escapeHtml - Function to escape HTML entities
|
|
1361
|
-
* @returns HTML-safe text with URLs converted to <a> tags
|
|
1362
|
-
*/
|
|
1363
|
-
function formatBareUrlsAsHtmlLinks(text, escapeHtml) {
|
|
1364
|
-
let result = escapeHtml(text);
|
|
1365
|
-
result = result.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, linkText, url) => {
|
|
1366
|
-
const cleanUrl = url.replace(/&/g, "&");
|
|
1367
|
-
return `<a href="${escapeHtml(cleanUrl)}" target="_blank" class="url-link" data-url="${escapeHtml(cleanUrl)}">${linkText}</a>`;
|
|
1368
|
-
});
|
|
1369
|
-
result = result.replace(/(?<!href="|data-url="|">)(?:https?:\/\/|www\.)[^\s<>"]+(?<![.,;:!?'")])/g, (url) => {
|
|
1370
|
-
const cleanUrl = url.replace(/&/g, "&");
|
|
1371
|
-
const fullUrl = cleanUrl.startsWith("www.") ? `https://${cleanUrl}` : cleanUrl;
|
|
1372
|
-
const display = friendlyUrlAbbrev(fullUrl);
|
|
1373
|
-
return `<a href="${escapeHtml(fullUrl)}" target="_blank" class="url-link" data-url="${escapeHtml(fullUrl)}">${escapeHtml(display)}</a>`;
|
|
1374
|
-
});
|
|
1375
|
-
return result;
|
|
1376
|
-
}
|
|
1377
|
-
|
|
1378
1445
|
//#endregion
|
|
1379
1446
|
//#region src/engine/serialize.ts
|
|
1380
1447
|
/**
|
|
@@ -1926,14 +1993,13 @@ function serializeYearField(field, response) {
|
|
|
1926
1993
|
}
|
|
1927
1994
|
/**
|
|
1928
1995
|
* Serialize a cell value for table output.
|
|
1929
|
-
*
|
|
1996
|
+
* Values are serialized as-is; HTML rendering handles display formatting.
|
|
1930
1997
|
*/
|
|
1931
|
-
function serializeCellValue(cell,
|
|
1998
|
+
function serializeCellValue(cell, _columnType) {
|
|
1932
1999
|
if (cell.state === "skipped") return cell.reason ? `%SKIP:${cell.reason}%` : "%SKIP%";
|
|
1933
2000
|
if (cell.state === "aborted") return cell.reason ? `%ABORT:${cell.reason}%` : "%ABORT%";
|
|
1934
2001
|
if (cell.value === void 0 || cell.value === null) return "";
|
|
1935
2002
|
if (typeof cell.value === "number") return String(cell.value);
|
|
1936
|
-
if (columnType === "url") return formatUrlAsMarkdownLink(cell.value);
|
|
1937
2003
|
return cell.value;
|
|
1938
2004
|
}
|
|
1939
2005
|
/**
|
|
@@ -2117,27 +2183,17 @@ function serializeFormSchema(schema, responses, docs, notes) {
|
|
|
2117
2183
|
return lines.join("\n");
|
|
2118
2184
|
}
|
|
2119
2185
|
/**
|
|
2120
|
-
* Build harness config object for YAML output (camelCase to snake_case).
|
|
2121
|
-
*/
|
|
2122
|
-
function buildHarnessConfig(config) {
|
|
2123
|
-
const result = {};
|
|
2124
|
-
if (config.maxTurns !== void 0) result.max_turns = config.maxTurns;
|
|
2125
|
-
if (config.maxPatchesPerTurn !== void 0) result.max_patches_per_turn = config.maxPatchesPerTurn;
|
|
2126
|
-
if (config.maxIssuesPerTurn !== void 0) result.max_issues_per_turn = config.maxIssuesPerTurn;
|
|
2127
|
-
if (config.maxParallelAgents !== void 0) result.max_parallel_agents = config.maxParallelAgents;
|
|
2128
|
-
return result;
|
|
2129
|
-
}
|
|
2130
|
-
/**
|
|
2131
2186
|
* Build frontmatter YAML from form metadata.
|
|
2132
|
-
* Preserves roles, role_instructions, harness config, and run_mode.
|
|
2187
|
+
* Preserves title, description, roles, role_instructions, harness config, and run_mode.
|
|
2133
2188
|
*/
|
|
2134
2189
|
function buildFrontmatter(metadata, specVersion) {
|
|
2135
2190
|
const markformSection = { spec: specVersion };
|
|
2191
|
+
if (metadata?.title) markformSection.title = metadata.title;
|
|
2192
|
+
if (metadata?.description) markformSection.description = metadata.description;
|
|
2136
2193
|
if (metadata?.runMode) markformSection.run_mode = metadata.runMode;
|
|
2137
|
-
if (metadata?.harnessConfig && Object.keys(metadata.harnessConfig).length > 0) markformSection.harness =
|
|
2138
|
-
const frontmatterObj = { markform: markformSection };
|
|
2194
|
+
if (metadata?.harnessConfig && Object.keys(metadata.harnessConfig).length > 0) markformSection.harness = transformHarnessConfigToYaml(metadata.harnessConfig);
|
|
2139
2195
|
const defaultRoles = ["user", "agent"];
|
|
2140
|
-
if (metadata?.roles && (metadata.roles.length !== defaultRoles.length || !metadata.roles.every((r, i) => r === defaultRoles[i])))
|
|
2196
|
+
if (metadata?.roles && (metadata.roles.length !== defaultRoles.length || !metadata.roles.every((r, i) => r === defaultRoles[i]))) markformSection.roles = metadata.roles;
|
|
2141
2197
|
const defaultInstructions = {
|
|
2142
2198
|
user: "",
|
|
2143
2199
|
agent: ""
|
|
@@ -2145,13 +2201,10 @@ function buildFrontmatter(metadata, specVersion) {
|
|
|
2145
2201
|
if (metadata?.roleInstructions) {
|
|
2146
2202
|
if (Object.entries(metadata.roleInstructions).some(([role, instruction]) => {
|
|
2147
2203
|
return instruction !== (defaultInstructions[role] ?? "") && instruction.trim() !== "";
|
|
2148
|
-
}))
|
|
2204
|
+
})) markformSection.role_instructions = metadata.roleInstructions;
|
|
2149
2205
|
}
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
defaultStringType: "QUOTE_DOUBLE",
|
|
2153
|
-
defaultKeyType: "PLAIN"
|
|
2154
|
-
})}---`;
|
|
2206
|
+
const frontmatterObj = { markform: markformSection };
|
|
2207
|
+
return `---\n${YAML.stringify(frontmatterObj, YAML_STRINGIFY_OPTIONS)}---`;
|
|
2155
2208
|
}
|
|
2156
2209
|
/**
|
|
2157
2210
|
* Serialize a ParsedForm to canonical Markdoc markdown format.
|
|
@@ -2346,6 +2399,16 @@ function serializeFieldRaw(field, responses) {
|
|
|
2346
2399
|
const lines = [];
|
|
2347
2400
|
lines.push(`**${field.label}:**`);
|
|
2348
2401
|
lines.push("");
|
|
2402
|
+
if (response?.state === "skipped") {
|
|
2403
|
+
const text = response.reason ? `_(skipped: ${response.reason})_` : "_(skipped)_";
|
|
2404
|
+
lines.push(text);
|
|
2405
|
+
return lines.join("\n");
|
|
2406
|
+
}
|
|
2407
|
+
if (response?.state === "aborted") {
|
|
2408
|
+
const text = response.reason ? `_(aborted: ${response.reason})_` : "_(aborted)_";
|
|
2409
|
+
lines.push(text);
|
|
2410
|
+
return lines.join("\n");
|
|
2411
|
+
}
|
|
2349
2412
|
const value = response?.state === "answered" ? response.value : void 0;
|
|
2350
2413
|
switch (field.kind) {
|
|
2351
2414
|
case "string": {
|
|
@@ -4293,5 +4356,5 @@ function applyPatches(form, patches) {
|
|
|
4293
4356
|
}
|
|
4294
4357
|
|
|
4295
4358
|
//#endregion
|
|
4296
|
-
export {
|
|
4297
|
-
//# sourceMappingURL=apply-
|
|
4359
|
+
export { WEB_SEARCH_CONFIG as $, DEFAULT_FORMS_DIR as A, DEFAULT_ROLES as B, getNumberAttr as C, isTagNode as D, getValidateAttr as E, DEFAULT_MAX_TURNS as F, deriveExportPath as G, MAX_FORMS_IN_MENU as H, DEFAULT_PORT as I, deriveSchemaPath as J, deriveFillRecordPath as K, DEFAULT_PRIORITY as L, DEFAULT_MAX_PARALLEL_AGENTS as M, DEFAULT_MAX_PATCHES_PER_TURN as N, parseOptionText as O, DEFAULT_MAX_STEPS_PER_TURN as P, SUGGESTED_LLMS as Q, DEFAULT_RESEARCH_MAX_ISSUES_PER_TURN as R, getBooleanAttr as S, getStringAttr as T, REPORT_EXTENSION as U, DEFAULT_ROLE_INSTRUCTIONS as V, USER_ROLE as W, parseRolesFlag as X, detectFileType as Y, transformHarnessConfigToTs as Z, tryParseSentinelResponse as _, isPatchError as _t, validate as a, MarkformConfigError as at, extractOptionItems as b, wrapApiError as bt, computeProgressSummary as c, MarkformParseError as ct, serializeForm as d, ParseError as dt, formatSuggestedLlms as et, serializeRawMarkdown as f, isAbortError as ft, validateSyntaxConsistency as g, isParseError as gt, preprocessCommentSyntax as h, isMarkformError as ht, inspect as i, MarkformAbortError as it, DEFAULT_MAX_ISSUES_PER_TURN as j, AGENT_ROLE as k, computeStructureSummary as l, MarkformPatchError as lt, detectSyntaxStyle as m, isLlmError as mt, getAllFields as n, hasWebSearchSupport as nt, computeAllSummaries as o, MarkformError as ot, serializeReport as p, isConfigError as pt, deriveReportPath as q, getFieldsForRoles as r, parseModelIdForDisplay as rt, computeFormState as s, MarkformLlmError as st, applyPatches as t, getWebSearchConfig as tt, isFormComplete as u, MarkformValidationError as ut, CHECKBOX_MARKERS as v, isRetryableError as vt, getStringArrayAttr as w, extractTableContent as x, extractFenceValue as y, isValidationError as yt, DEFAULT_RESEARCH_MAX_PATCHES_PER_TURN as z };
|
|
4360
|
+
//# sourceMappingURL=apply-C7mO7VkZ.mjs.map
|