@dtlabs/ai-toolkit 0.14.3 → 0.15.1

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/CHANGELOG.md CHANGED
@@ -1,4 +1,26 @@
1
1
  ## 0.1.4 (2026-07-21)
2
+ ## [0.15.1](https://github.com/dtlab-tech/ai-toolkit/compare/v0.15.0...v0.15.1) (2026-10-01)
3
+
4
+
5
+ ### Bug Fixes
6
+
7
+ * **workflows:** recover ```json structured output wrapped around a ne… ([a96add9](https://github.com/dtlab-tech/ai-toolkit/commit/a96add902195ea0a125e0fbbc9be06126dfbd602))
8
+ * **workflows:** recover ```json structured output wrapped around a nested untagged fence ([6e92dd2](https://github.com/dtlab-tech/ai-toolkit/commit/6e92dd28bf96f6e96947b45bcc7c8e54d7cb9c38))
9
+
10
+ ## [0.15.0](https://github.com/dtlab-tech/ai-toolkit/compare/v0.14.3...v0.15.0) (2026-10-01)
11
+
12
+
13
+ ### Features
14
+
15
+ * Add isolated parallel task execution and task checkpoints documentation ([e17121f](https://github.com/dtlab-tech/ai-toolkit/commit/e17121fbf118673831634f981294d58e1a665d49))
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * **workflows:** never fail-closed inside extractJsonDocument itself ([dd5f0ce](https://github.com/dtlab-tech/ai-toolkit/commit/dd5f0ce07b7145ef73e29479e4282105000cfd42))
21
+ * **workflows:** recover JSON worker output wrapped in prose or a code fence ([b7a0d08](https://github.com/dtlab-tech/ai-toolkit/commit/b7a0d0817cf6056893906aacff87b40578c6625b))
22
+ * **workflows:** recover JSON worker output wrapped in prose or a code fence ([0dbaece](https://github.com/dtlab-tech/ai-toolkit/commit/0dbaeceb193521c2aca59a15de3762def4881935))
23
+
2
24
  ## [0.14.3](https://github.com/dtlab-tech/ai-toolkit/compare/v0.14.2...v0.14.3) (2026-10-01)
3
25
 
4
26
 
@@ -217,7 +217,7 @@ function createControl(options) {
217
217
  if (outputTarget) {
218
218
  const content = opts.schema ? payload.report : payload;
219
219
  if (typeof content !== 'string' || !content.trim()) throw new Error('Worker output content missing or empty: ' + id);
220
- writeOutput(outputTarget, content);
220
+ writeOutput(outputTarget, /\.json$/i.test(outputTarget) ? extractJsonDocument(content) : content);
221
221
  }
222
222
  evidence.save({ outcome: 'done', completedAt: new Date().toISOString() });
223
223
  return payload;
@@ -307,6 +307,19 @@ function extractDelimitedStructuredOutput(text, schema) {
307
307
  if (tagMatch) candidates.push(tagMatch[1]);
308
308
  const fenceMatches = [...text.matchAll(/```json\s*([\s\S]*?)```/gi)];
309
309
  if (fenceMatches.length) candidates.push(fenceMatches[fenceMatches.length - 1][1]);
310
+ // The non-greedy fence match above stops at the FIRST ``` following ```json, which is
311
+ // wrong when a schema string field (e.g. a "report") legitimately embeds its own
312
+ // untagged ``` ... ``` example (a markdown table, say) before the real closing fence —
313
+ // observed empirically with gaia-validate-feature-docs. Recover with a brace-balance
314
+ // span instead of a fence-delimiter match, anchored after the last ```json marker so it
315
+ // is immune to backtick nesting inside string values.
316
+ const lastJsonFence = text.lastIndexOf('```json');
317
+ if (lastJsonFence !== -1) {
318
+ const tail = text.slice(lastJsonFence + '```json'.length);
319
+ const first = tail.indexOf('{');
320
+ const last = tail.lastIndexOf('}');
321
+ if (first !== -1 && last > first) candidates.push(tail.slice(first, last + 1));
322
+ }
310
323
  for (const raw of candidates) {
311
324
  try {
312
325
  const parsed = JSON.parse(raw.trim());
@@ -317,6 +330,36 @@ function extractDelimitedStructuredOutput(text, schema) {
317
330
  return null;
318
331
  }
319
332
 
333
+ // A worker with a declared JSON output has no Write tool (see worker()'s own comment on why):
334
+ // its entire response text becomes the file verbatim. Some models still wrap that deliverable
335
+ // in conversational prose and/or a Markdown code fence despite being told not to (observed
336
+ // empirically with gaia-generate-work-breakdown on haiku: "Perfect. I have all the necessary
337
+ // information..." before the JSON), which is a hard JSON.parse failure for every downstream
338
+ // consumer of this file. Recover the document with the same "try each known convention, always
339
+ // re-validate as parseable JSON, never a blind substring guess" discipline as
340
+ // extractDelimitedStructuredOutput: the content as-is first, then the last fenced code block,
341
+ // then the outermost brace/bracket span. Never throws — genuinely malformed JSON (not merely
342
+ // wrapped) is passed through unchanged so the downstream deterministic validator (wb-validate.js)
343
+ // remains the one thing that fails closed on it; this function only undoes wrapping, it is not
344
+ // itself a validation gate.
345
+ function extractJsonDocument(text) {
346
+ const tryParse = candidate => { try { JSON.parse(candidate); return candidate; } catch (e) { return null; } };
347
+ const whole = tryParse(text.trim());
348
+ if (whole != null) return whole;
349
+ const fenceMatches = [...text.matchAll(/```(?:json)?\s*([\s\S]*?)```/gi)];
350
+ for (let i = fenceMatches.length - 1; i >= 0; i--) {
351
+ const candidate = tryParse(fenceMatches[i][1].trim());
352
+ if (candidate != null) return candidate;
353
+ }
354
+ const first = text.search(/[[{]/);
355
+ const last = Math.max(text.lastIndexOf('}'), text.lastIndexOf(']'));
356
+ if (first >= 0 && last > first) {
357
+ const candidate = tryParse(text.slice(first, last + 1).trim());
358
+ if (candidate != null) return candidate;
359
+ }
360
+ return text;
361
+ }
362
+
320
363
  function validateSchema(value, schema) {
321
364
  if (schema.type === 'object') {
322
365
  if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Invalid structured object');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dtlabs/ai-toolkit",
3
- "version": "0.14.3",
3
+ "version": "0.15.1",
4
4
  "description": "AI Toolkit — reusable agents and procedures for developers to build AI-powered applications.",
5
5
  "files": [
6
6
  "docs/",