@copilotkit/aimock 1.12.0 → 1.13.0

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.
Files changed (178) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +20 -2
  4. package/dist/_virtual/_rolldown/runtime.cjs +2 -0
  5. package/dist/_virtual/_rolldown/runtime.js +29 -0
  6. package/dist/a2a-types.d.cts.map +1 -1
  7. package/dist/a2a-types.d.ts.map +1 -1
  8. package/dist/aimock-cli.cjs +16 -0
  9. package/dist/aimock-cli.cjs.map +1 -1
  10. package/dist/aimock-cli.d.cts +2 -0
  11. package/dist/aimock-cli.d.cts.map +1 -1
  12. package/dist/aimock-cli.d.ts +2 -0
  13. package/dist/aimock-cli.d.ts.map +1 -1
  14. package/dist/aimock-cli.js +16 -0
  15. package/dist/aimock-cli.js.map +1 -1
  16. package/dist/config-loader.d.cts.map +1 -1
  17. package/dist/convert-mockllm.cjs +232 -0
  18. package/dist/convert-mockllm.cjs.map +1 -0
  19. package/dist/convert-mockllm.js +230 -0
  20. package/dist/convert-mockllm.js.map +1 -0
  21. package/dist/convert-vidaimock.cjs +110 -0
  22. package/dist/convert-vidaimock.cjs.map +1 -0
  23. package/dist/convert-vidaimock.js +108 -0
  24. package/dist/convert-vidaimock.js.map +1 -0
  25. package/dist/convert.cjs +158 -0
  26. package/dist/convert.cjs.map +1 -0
  27. package/dist/convert.d.cts +16 -0
  28. package/dist/convert.d.cts.map +1 -0
  29. package/dist/convert.d.ts +16 -0
  30. package/dist/convert.d.ts.map +1 -0
  31. package/dist/convert.js +157 -0
  32. package/dist/convert.js.map +1 -0
  33. package/dist/jest.cjs +70 -0
  34. package/dist/jest.cjs.map +1 -0
  35. package/dist/jest.d.cts +33 -0
  36. package/dist/jest.d.cts.map +1 -0
  37. package/dist/jest.d.ts +33 -0
  38. package/dist/jest.d.ts.map +1 -0
  39. package/dist/jest.js +67 -0
  40. package/dist/jest.js.map +1 -0
  41. package/dist/node_modules/.pnpm/@vitest_pretty-format@3.2.4/node_modules/@vitest/pretty-format/dist/index.cjs +934 -0
  42. package/dist/node_modules/.pnpm/@vitest_pretty-format@3.2.4/node_modules/@vitest/pretty-format/dist/index.cjs.map +1 -0
  43. package/dist/node_modules/.pnpm/@vitest_pretty-format@3.2.4/node_modules/@vitest/pretty-format/dist/index.js +934 -0
  44. package/dist/node_modules/.pnpm/@vitest_pretty-format@3.2.4/node_modules/@vitest/pretty-format/dist/index.js.map +1 -0
  45. package/dist/node_modules/.pnpm/@vitest_runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.cjs +1051 -0
  46. package/dist/node_modules/.pnpm/@vitest_runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.cjs.map +1 -0
  47. package/dist/node_modules/.pnpm/@vitest_runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.js +1042 -0
  48. package/dist/node_modules/.pnpm/@vitest_runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.js.map +1 -0
  49. package/dist/node_modules/.pnpm/@vitest_runner@3.2.4/node_modules/@vitest/runner/dist/index.cjs +1 -0
  50. package/dist/node_modules/.pnpm/@vitest_runner@3.2.4/node_modules/@vitest/runner/dist/index.js +3 -0
  51. package/dist/node_modules/.pnpm/@vitest_utils@3.2.4/node_modules/@vitest/utils/dist/chunk-_commonjsHelpers.cjs +96 -0
  52. package/dist/node_modules/.pnpm/@vitest_utils@3.2.4/node_modules/@vitest/utils/dist/chunk-_commonjsHelpers.cjs.map +1 -0
  53. package/dist/node_modules/.pnpm/@vitest_utils@3.2.4/node_modules/@vitest/utils/dist/chunk-_commonjsHelpers.js +93 -0
  54. package/dist/node_modules/.pnpm/@vitest_utils@3.2.4/node_modules/@vitest/utils/dist/chunk-_commonjsHelpers.js.map +1 -0
  55. package/dist/node_modules/.pnpm/@vitest_utils@3.2.4/node_modules/@vitest/utils/dist/helpers.cjs +49 -0
  56. package/dist/node_modules/.pnpm/@vitest_utils@3.2.4/node_modules/@vitest/utils/dist/helpers.cjs.map +1 -0
  57. package/dist/node_modules/.pnpm/@vitest_utils@3.2.4/node_modules/@vitest/utils/dist/helpers.js +43 -0
  58. package/dist/node_modules/.pnpm/@vitest_utils@3.2.4/node_modules/@vitest/utils/dist/helpers.js.map +1 -0
  59. package/dist/node_modules/.pnpm/@vitest_utils@3.2.4/node_modules/@vitest/utils/dist/index.cjs +456 -0
  60. package/dist/node_modules/.pnpm/@vitest_utils@3.2.4/node_modules/@vitest/utils/dist/index.cjs.map +1 -0
  61. package/dist/node_modules/.pnpm/@vitest_utils@3.2.4/node_modules/@vitest/utils/dist/index.js +456 -0
  62. package/dist/node_modules/.pnpm/@vitest_utils@3.2.4/node_modules/@vitest/utils/dist/index.js.map +1 -0
  63. package/dist/node_modules/.pnpm/@vitest_utils@3.2.4/node_modules/@vitest/utils/dist/source-map.cjs +170 -0
  64. package/dist/node_modules/.pnpm/@vitest_utils@3.2.4/node_modules/@vitest/utils/dist/source-map.cjs.map +1 -0
  65. package/dist/node_modules/.pnpm/@vitest_utils@3.2.4/node_modules/@vitest/utils/dist/source-map.js +169 -0
  66. package/dist/node_modules/.pnpm/@vitest_utils@3.2.4/node_modules/@vitest/utils/dist/source-map.js.map +1 -0
  67. package/dist/node_modules/.pnpm/js-tokens@9.0.1/node_modules/js-tokens/index.cjs +388 -0
  68. package/dist/node_modules/.pnpm/js-tokens@9.0.1/node_modules/js-tokens/index.cjs.map +1 -0
  69. package/dist/node_modules/.pnpm/js-tokens@9.0.1/node_modules/js-tokens/index.js +385 -0
  70. package/dist/node_modules/.pnpm/js-tokens@9.0.1/node_modules/js-tokens/index.js.map +1 -0
  71. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/arguments.cjs +12 -0
  72. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/arguments.cjs.map +1 -0
  73. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/arguments.js +12 -0
  74. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/arguments.js.map +1 -0
  75. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/array.cjs +17 -0
  76. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/array.cjs.map +1 -0
  77. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/array.js +17 -0
  78. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/array.js.map +1 -0
  79. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/bigint.cjs +12 -0
  80. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/bigint.cjs.map +1 -0
  81. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/bigint.js +12 -0
  82. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/bigint.js.map +1 -0
  83. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/class.cjs +16 -0
  84. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/class.cjs.map +1 -0
  85. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/class.js +16 -0
  86. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/class.js.map +1 -0
  87. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/date.cjs +14 -0
  88. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/date.cjs.map +1 -0
  89. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/date.js +14 -0
  90. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/date.js.map +1 -0
  91. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/error.cjs +35 -0
  92. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/error.cjs.map +1 -0
  93. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/error.js +35 -0
  94. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/error.js.map +1 -0
  95. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/function.cjs +13 -0
  96. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/function.cjs.map +1 -0
  97. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/function.js +13 -0
  98. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/function.js.map +1 -0
  99. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/helpers.cjs +128 -0
  100. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/helpers.cjs.map +1 -0
  101. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/helpers.js +123 -0
  102. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/helpers.js.map +1 -0
  103. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/html.cjs +41 -0
  104. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/html.cjs.map +1 -0
  105. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/html.js +40 -0
  106. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/html.js.map +1 -0
  107. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/index.cjs +100 -0
  108. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/index.cjs.map +1 -0
  109. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/index.js +100 -0
  110. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/index.js.map +1 -0
  111. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/map.cjs +26 -0
  112. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/map.cjs.map +1 -0
  113. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/map.js +26 -0
  114. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/map.js.map +1 -0
  115. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/number.cjs +15 -0
  116. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/number.cjs.map +1 -0
  117. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/number.js +15 -0
  118. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/number.js.map +1 -0
  119. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/object.cjs +22 -0
  120. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/object.cjs.map +1 -0
  121. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/object.js +22 -0
  122. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/object.js.map +1 -0
  123. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/promise.cjs +7 -0
  124. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/promise.cjs.map +1 -0
  125. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/promise.js +6 -0
  126. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/promise.js.map +1 -0
  127. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/regexp.cjs +13 -0
  128. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/regexp.cjs.map +1 -0
  129. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/regexp.js +13 -0
  130. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/regexp.js.map +1 -0
  131. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/set.cjs +19 -0
  132. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/set.cjs.map +1 -0
  133. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/set.js +19 -0
  134. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/set.js.map +1 -0
  135. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/string.cjs +26 -0
  136. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/string.cjs.map +1 -0
  137. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/string.js +26 -0
  138. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/string.js.map +1 -0
  139. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/symbol.cjs +10 -0
  140. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/symbol.cjs.map +1 -0
  141. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/symbol.js +9 -0
  142. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/symbol.js.map +1 -0
  143. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/typedarray.cjs +31 -0
  144. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/typedarray.cjs.map +1 -0
  145. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/typedarray.js +31 -0
  146. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/typedarray.js.map +1 -0
  147. package/dist/node_modules/.pnpm/strip-literal@3.1.0/node_modules/strip-literal/dist/index.cjs +52 -0
  148. package/dist/node_modules/.pnpm/strip-literal@3.1.0/node_modules/strip-literal/dist/index.cjs.map +1 -0
  149. package/dist/node_modules/.pnpm/strip-literal@3.1.0/node_modules/strip-literal/dist/index.js +52 -0
  150. package/dist/node_modules/.pnpm/strip-literal@3.1.0/node_modules/strip-literal/dist/index.js.map +1 -0
  151. package/dist/node_modules/.pnpm/tinyrainbow@2.0.0/node_modules/tinyrainbow/dist/chunk-BVHSVHOK.cjs +83 -0
  152. package/dist/node_modules/.pnpm/tinyrainbow@2.0.0/node_modules/tinyrainbow/dist/chunk-BVHSVHOK.cjs.map +1 -0
  153. package/dist/node_modules/.pnpm/tinyrainbow@2.0.0/node_modules/tinyrainbow/dist/chunk-BVHSVHOK.js +82 -0
  154. package/dist/node_modules/.pnpm/tinyrainbow@2.0.0/node_modules/tinyrainbow/dist/chunk-BVHSVHOK.js.map +1 -0
  155. package/dist/node_modules/.pnpm/tinyrainbow@2.0.0/node_modules/tinyrainbow/dist/node.cjs +10 -0
  156. package/dist/node_modules/.pnpm/tinyrainbow@2.0.0/node_modules/tinyrainbow/dist/node.cjs.map +1 -0
  157. package/dist/node_modules/.pnpm/tinyrainbow@2.0.0/node_modules/tinyrainbow/dist/node.js +10 -0
  158. package/dist/node_modules/.pnpm/tinyrainbow@2.0.0/node_modules/tinyrainbow/dist/node.js.map +1 -0
  159. package/dist/vitest.cjs +80 -0
  160. package/dist/vitest.cjs.map +1 -0
  161. package/dist/vitest.d.cts +30 -0
  162. package/dist/vitest.d.cts.map +1 -0
  163. package/dist/vitest.d.ts +30 -0
  164. package/dist/vitest.d.ts.map +1 -0
  165. package/dist/vitest.js +77 -0
  166. package/dist/vitest.js.map +1 -0
  167. package/fixtures/examples/a2a/a2a-config.json +42 -0
  168. package/fixtures/examples/agui/agui-text-response.json +35 -0
  169. package/fixtures/examples/chaos/chaos-config.json +10 -0
  170. package/fixtures/examples/full-suite.json +116 -0
  171. package/fixtures/examples/llm/embeddings.json +10 -0
  172. package/fixtures/examples/llm/error-injection.json +15 -0
  173. package/fixtures/examples/llm/sequential-responses.json +20 -0
  174. package/fixtures/examples/llm/streaming-physics.json +15 -0
  175. package/fixtures/examples/mcp/mcp-config.json +26 -0
  176. package/fixtures/examples/record-replay/record-config.json +11 -0
  177. package/fixtures/examples/vector/vector-config.json +34 -0
  178. package/package.json +59 -1
@@ -0,0 +1,230 @@
1
+ //#region src/convert-mockllm.ts
2
+ function tokenizeYamlLines(input) {
3
+ const lines = [];
4
+ for (const raw of input.split("\n")) {
5
+ const trimmed = raw.trimStart();
6
+ if (trimmed === "" || trimmed.startsWith("#")) continue;
7
+ const indent = raw.length - raw.trimStart().length;
8
+ const content = stripTrailingComment(trimmed);
9
+ const isArrayItem = content.startsWith("- ");
10
+ const arrayItemContent = isArrayItem ? content.slice(2).trim() : "";
11
+ lines.push({
12
+ indent,
13
+ raw,
14
+ content,
15
+ isArrayItem,
16
+ arrayItemContent
17
+ });
18
+ }
19
+ return lines;
20
+ }
21
+ function stripTrailingComment(s) {
22
+ let inSingle = false;
23
+ let inDouble = false;
24
+ for (let i = 0; i < s.length; i++) {
25
+ const ch = s[i];
26
+ if (ch === "'" && !inDouble) inSingle = !inSingle;
27
+ if (ch === "\"" && !inSingle) inDouble = !inDouble;
28
+ if (ch === "#" && !inSingle && !inDouble && i > 0 && s[i - 1] === " ") return s.slice(0, i).trimEnd();
29
+ }
30
+ return s;
31
+ }
32
+ function parseScalar(value) {
33
+ if (value === "" || value === "~" || value === "null") return null;
34
+ if (value === "true") return true;
35
+ if (value === "false") return false;
36
+ if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
37
+ const num = Number(value);
38
+ if (!Number.isNaN(num) && value !== "") return num;
39
+ return value;
40
+ }
41
+ function parseSimpleYaml(input) {
42
+ const lines = tokenizeYamlLines(input);
43
+ if (lines.length === 0) return null;
44
+ return parseBlock(lines, 0, 0).value;
45
+ }
46
+ function parseBlock(lines, startIndex, minIndent) {
47
+ if (startIndex >= lines.length) return {
48
+ value: null,
49
+ nextIndex: startIndex
50
+ };
51
+ const line = lines[startIndex];
52
+ if (line.isArrayItem && line.indent >= minIndent) return parseArray(lines, startIndex, line.indent);
53
+ if (line.content.includes(":")) return parseMap(lines, startIndex, line.indent);
54
+ return {
55
+ value: parseScalar(line.content),
56
+ nextIndex: startIndex + 1
57
+ };
58
+ }
59
+ function parseArray(lines, startIndex, baseIndent) {
60
+ const arr = [];
61
+ let i = startIndex;
62
+ while (i < lines.length) {
63
+ const line = lines[i];
64
+ if (line.indent < baseIndent) break;
65
+ if (line.indent > baseIndent) break;
66
+ if (!line.isArrayItem) break;
67
+ const itemContent = line.arrayItemContent;
68
+ if (itemContent === "") {
69
+ const nested = parseBlock(lines, i + 1, baseIndent + 1);
70
+ arr.push(nested.value);
71
+ i = nested.nextIndex;
72
+ } else if (itemContent.includes(":")) {
73
+ const inlineMap = parseArrayItemMap(lines, i, baseIndent);
74
+ arr.push(inlineMap.value);
75
+ i = inlineMap.nextIndex;
76
+ } else {
77
+ arr.push(parseScalar(itemContent));
78
+ i++;
79
+ }
80
+ }
81
+ return {
82
+ value: arr,
83
+ nextIndex: i
84
+ };
85
+ }
86
+ function parseArrayItemMap(lines, startIndex, arrayIndent) {
87
+ const map = {};
88
+ const firstContent = lines[startIndex].arrayItemContent;
89
+ const colonIdx = findColon(firstContent);
90
+ if (colonIdx === -1) return {
91
+ value: parseScalar(firstContent),
92
+ nextIndex: startIndex + 1
93
+ };
94
+ const key = firstContent.slice(0, colonIdx).trim();
95
+ const valueStr = firstContent.slice(colonIdx + 1).trim();
96
+ if (valueStr === "") {
97
+ const nested = parseBlock(lines, startIndex + 1, arrayIndent + 2);
98
+ map[key] = nested.value;
99
+ let i = nested.nextIndex;
100
+ const siblingIndent = arrayIndent + 2;
101
+ while (i < lines.length && lines[i].indent >= siblingIndent && !lines[i].isArrayItem) if (lines[i].indent === siblingIndent || lines[i].indent > siblingIndent) if (lines[i].indent === siblingIndent && lines[i].content.includes(":")) i = parseMapEntries(lines, i, siblingIndent, map).nextIndex;
102
+ else break;
103
+ return {
104
+ value: map,
105
+ nextIndex: i
106
+ };
107
+ } else map[key] = parseScalar(valueStr);
108
+ let i = startIndex + 1;
109
+ const contentIndent = arrayIndent + 2;
110
+ while (i < lines.length) {
111
+ const line = lines[i];
112
+ if (line.indent < contentIndent) break;
113
+ if (line.isArrayItem && line.indent <= arrayIndent) break;
114
+ if (line.indent === contentIndent && !line.isArrayItem && line.content.includes(":")) {
115
+ const colonPos = findColon(line.content);
116
+ if (colonPos === -1) break;
117
+ const k = line.content.slice(0, colonPos).trim();
118
+ const v = line.content.slice(colonPos + 1).trim();
119
+ if (v === "") {
120
+ const nested = parseBlock(lines, i + 1, contentIndent + 1);
121
+ map[k] = nested.value;
122
+ i = nested.nextIndex;
123
+ } else {
124
+ map[k] = parseScalar(v);
125
+ i++;
126
+ }
127
+ } else if (line.indent === contentIndent && line.isArrayItem) break;
128
+ else if (line.indent > contentIndent) i++;
129
+ else break;
130
+ }
131
+ return {
132
+ value: map,
133
+ nextIndex: i
134
+ };
135
+ }
136
+ function parseMap(lines, startIndex, baseIndent) {
137
+ const map = {};
138
+ return {
139
+ value: map,
140
+ nextIndex: parseMapEntries(lines, startIndex, baseIndent, map).nextIndex
141
+ };
142
+ }
143
+ function parseMapEntries(lines, startIndex, baseIndent, map) {
144
+ let i = startIndex;
145
+ while (i < lines.length) {
146
+ const line = lines[i];
147
+ if (line.indent < baseIndent) break;
148
+ if (line.indent > baseIndent) {
149
+ i++;
150
+ continue;
151
+ }
152
+ if (line.isArrayItem) break;
153
+ const colonIdx = findColon(line.content);
154
+ if (colonIdx === -1) break;
155
+ const key = line.content.slice(0, colonIdx).trim();
156
+ const valueStr = line.content.slice(colonIdx + 1).trim();
157
+ if (valueStr === "") {
158
+ const nested = parseBlock(lines, i + 1, baseIndent + 1);
159
+ map[key] = nested.value;
160
+ i = nested.nextIndex;
161
+ } else {
162
+ map[key] = parseScalar(valueStr);
163
+ i++;
164
+ }
165
+ }
166
+ return {
167
+ value: map,
168
+ nextIndex: i
169
+ };
170
+ }
171
+ function findColon(s) {
172
+ let inSingle = false;
173
+ let inDouble = false;
174
+ for (let i = 0; i < s.length; i++) {
175
+ const ch = s[i];
176
+ if (ch === "'" && !inDouble) inSingle = !inSingle;
177
+ if (ch === "\"" && !inSingle) inDouble = !inDouble;
178
+ if (ch === ":" && !inSingle && !inDouble) {
179
+ if (i === s.length - 1 || s[i + 1] === " ") return i;
180
+ }
181
+ }
182
+ return -1;
183
+ }
184
+ function convertConfig(config) {
185
+ const fixtures = [];
186
+ if (config.routes) for (const route of config.routes) {
187
+ const fixture = convertRoute(route);
188
+ if (fixture) fixtures.push(fixture);
189
+ }
190
+ const result = { fixtures };
191
+ if (config.mcp?.tools && config.mcp.tools.length > 0) result.mcpTools = config.mcp.tools.map(convertMCPTool);
192
+ return result;
193
+ }
194
+ function convertRoute(route) {
195
+ const content = extractResponseContent(route.response);
196
+ if (content === null) return null;
197
+ const fixture = {
198
+ match: {},
199
+ response: { content }
200
+ };
201
+ const userMessage = extractUserMessage(route);
202
+ if (userMessage) fixture.match = { userMessage };
203
+ else fixture._comment = `${route.method ?? "POST"} ${route.path}`;
204
+ return fixture;
205
+ }
206
+ function extractResponseContent(response) {
207
+ const choices = response.choices;
208
+ if (!Array.isArray(choices) || choices.length === 0) return null;
209
+ const message = choices[0].message;
210
+ if (!message) return null;
211
+ const content = message.content;
212
+ if (typeof content !== "string") return null;
213
+ return content;
214
+ }
215
+ function extractUserMessage(route) {
216
+ const messages = route.match?.body?.messages;
217
+ if (!Array.isArray(messages) || messages.length === 0) return null;
218
+ for (let i = messages.length - 1; i >= 0; i--) if (messages[i].role === "user") return messages[i].content;
219
+ return messages[messages.length - 1].content ?? null;
220
+ }
221
+ function convertMCPTool(tool) {
222
+ const result = { name: tool.name };
223
+ if (tool.description) result.description = tool.description;
224
+ if (tool.parameters) result.inputSchema = tool.parameters;
225
+ return result;
226
+ }
227
+
228
+ //#endregion
229
+ export { convertConfig, parseSimpleYaml };
230
+ //# sourceMappingURL=convert-mockllm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"convert-mockllm.js","names":[],"sources":["../src/convert-mockllm.ts"],"sourcesContent":["/**\n * mock-llm (dwmkerr) -> aimock fixture converter\n *\n * Core conversion logic. Used by both the CLI (`aimock convert mockllm`)\n * and the standalone script (`scripts/convert-mockllm.ts`).\n */\n\n// ---------------------------------------------------------------------------\n// Minimal YAML parser\n// ---------------------------------------------------------------------------\n// Handles the subset used by mock-llm configs: indented maps, arrays with\n// `-` prefix, quoted/unquoted strings, numbers, booleans, and null.\n// Does NOT handle: anchors, aliases, multi-line scalars, flow collections,\n// tags, or other advanced YAML features.\n\ninterface YamlLine {\n indent: number;\n raw: string;\n content: string; // trimmed, without trailing comment\n isArrayItem: boolean;\n arrayItemContent: string; // content after \"- \"\n}\n\nfunction tokenizeYamlLines(input: string): YamlLine[] {\n const lines: YamlLine[] = [];\n for (const raw of input.split(\"\\n\")) {\n // Skip blank lines and full-line comments\n const trimmed = raw.trimStart();\n if (trimmed === \"\" || trimmed.startsWith(\"#\")) continue;\n\n const indent = raw.length - raw.trimStart().length;\n // Strip trailing comments (but not inside quoted strings)\n const content = stripTrailingComment(trimmed);\n const isArrayItem = content.startsWith(\"- \");\n const arrayItemContent = isArrayItem ? content.slice(2).trim() : \"\";\n\n lines.push({ indent, raw, content, isArrayItem, arrayItemContent });\n }\n return lines;\n}\n\nfunction stripTrailingComment(s: string): string {\n // Naive: find # not inside quotes\n let inSingle = false;\n let inDouble = false;\n for (let i = 0; i < s.length; i++) {\n const ch = s[i];\n if (ch === \"'\" && !inDouble) inSingle = !inSingle;\n if (ch === '\"' && !inSingle) inDouble = !inDouble;\n if (ch === \"#\" && !inSingle && !inDouble && i > 0 && s[i - 1] === \" \") {\n return s.slice(0, i).trimEnd();\n }\n }\n return s;\n}\n\nfunction parseScalar(value: string): unknown {\n if (value === \"\" || value === \"~\" || value === \"null\") return null;\n if (value === \"true\") return true;\n if (value === \"false\") return false;\n\n // Quoted string\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n return value.slice(1, -1);\n }\n\n // Number\n const num = Number(value);\n if (!Number.isNaN(num) && value !== \"\") return num;\n\n // Unquoted string\n return value;\n}\n\nexport function parseSimpleYaml(input: string): unknown {\n const lines = tokenizeYamlLines(input);\n if (lines.length === 0) return null;\n\n const result = parseBlock(lines, 0, 0);\n return result.value;\n}\n\ninterface ParseResult {\n value: unknown;\n nextIndex: number;\n}\n\nfunction parseBlock(lines: YamlLine[], startIndex: number, minIndent: number): ParseResult {\n if (startIndex >= lines.length) {\n return { value: null, nextIndex: startIndex };\n }\n\n const line = lines[startIndex];\n\n // Determine if this block is an array or a map\n if (line.isArrayItem && line.indent >= minIndent) {\n return parseArray(lines, startIndex, line.indent);\n }\n\n // Map\n if (line.content.includes(\":\")) {\n return parseMap(lines, startIndex, line.indent);\n }\n\n // Single scalar\n return { value: parseScalar(line.content), nextIndex: startIndex + 1 };\n}\n\nfunction parseArray(lines: YamlLine[], startIndex: number, baseIndent: number): ParseResult {\n const arr: unknown[] = [];\n let i = startIndex;\n\n while (i < lines.length) {\n const line = lines[i];\n if (line.indent < baseIndent) break;\n if (line.indent > baseIndent) break; // shouldn't happen at array level\n if (!line.isArrayItem) break;\n\n const itemContent = line.arrayItemContent;\n\n if (itemContent === \"\") {\n // Array item with nested block on next lines\n const nested = parseBlock(lines, i + 1, baseIndent + 1);\n arr.push(nested.value);\n i = nested.nextIndex;\n } else if (itemContent.includes(\":\")) {\n // Inline map start: \"- key: value\" possibly with more keys below\n // Parse as a map, treating the \"- \" offset as extra indent\n const inlineMap = parseArrayItemMap(lines, i, baseIndent);\n arr.push(inlineMap.value);\n i = inlineMap.nextIndex;\n } else {\n // Simple scalar array item\n arr.push(parseScalar(itemContent));\n i++;\n }\n }\n\n return { value: arr, nextIndex: i };\n}\n\nfunction parseArrayItemMap(\n lines: YamlLine[],\n startIndex: number,\n arrayIndent: number,\n): ParseResult {\n // First line is \"- key: value\", subsequent lines at indent > arrayIndent are part of this map\n const map: Record<string, unknown> = {};\n const firstLine = lines[startIndex];\n const firstContent = firstLine.arrayItemContent;\n\n // Parse the first key: value from the array item line\n const colonIdx = findColon(firstContent);\n if (colonIdx === -1) {\n return { value: parseScalar(firstContent), nextIndex: startIndex + 1 };\n }\n\n const key = firstContent.slice(0, colonIdx).trim();\n const valueStr = firstContent.slice(colonIdx + 1).trim();\n\n if (valueStr === \"\") {\n // Value is a nested block\n const nested = parseBlock(lines, startIndex + 1, arrayIndent + 2);\n map[key] = nested.value;\n let i = nested.nextIndex;\n\n // Continue reading sibling keys at the array-item's content indent\n const siblingIndent = arrayIndent + 2;\n while (i < lines.length && lines[i].indent >= siblingIndent && !lines[i].isArrayItem) {\n if (lines[i].indent === siblingIndent || lines[i].indent > siblingIndent) {\n // Only parse if at exactly sibling indent and is a map key\n if (lines[i].indent === siblingIndent && lines[i].content.includes(\":\")) {\n const mapResult = parseMapEntries(lines, i, siblingIndent, map);\n i = mapResult.nextIndex;\n } else {\n break;\n }\n }\n }\n\n return { value: map, nextIndex: i };\n } else {\n map[key] = parseScalar(valueStr);\n }\n\n // Read additional keys at indent > arrayIndent (the \" key: value\" lines after \"- first: val\")\n let i = startIndex + 1;\n const contentIndent = arrayIndent + 2; // \"- \" adds 2 to effective indent\n\n while (i < lines.length) {\n const line = lines[i];\n if (line.indent < contentIndent) break;\n if (line.isArrayItem && line.indent <= arrayIndent) break;\n\n if (line.indent === contentIndent && !line.isArrayItem && line.content.includes(\":\")) {\n const colonPos = findColon(line.content);\n if (colonPos === -1) break;\n const k = line.content.slice(0, colonPos).trim();\n const v = line.content.slice(colonPos + 1).trim();\n\n if (v === \"\") {\n const nested = parseBlock(lines, i + 1, contentIndent + 1);\n map[k] = nested.value;\n i = nested.nextIndex;\n } else {\n map[k] = parseScalar(v);\n i++;\n }\n } else if (line.indent === contentIndent && line.isArrayItem) {\n // This is a new array item at the same level -- not part of this map\n break;\n } else if (line.indent > contentIndent) {\n // Skip nested content already consumed\n i++;\n } else {\n break;\n }\n }\n\n return { value: map, nextIndex: i };\n}\n\nfunction parseMap(lines: YamlLine[], startIndex: number, baseIndent: number): ParseResult {\n const map: Record<string, unknown> = {};\n const result = parseMapEntries(lines, startIndex, baseIndent, map);\n return { value: map, nextIndex: result.nextIndex };\n}\n\nfunction parseMapEntries(\n lines: YamlLine[],\n startIndex: number,\n baseIndent: number,\n map: Record<string, unknown>,\n): ParseResult {\n let i = startIndex;\n\n while (i < lines.length) {\n const line = lines[i];\n if (line.indent < baseIndent) break;\n if (line.indent > baseIndent) {\n // Shouldn't happen at map level if properly structured -- skip\n i++;\n continue;\n }\n if (line.isArrayItem) break;\n\n const colonIdx = findColon(line.content);\n if (colonIdx === -1) {\n // Not a map entry\n break;\n }\n\n const key = line.content.slice(0, colonIdx).trim();\n const valueStr = line.content.slice(colonIdx + 1).trim();\n\n if (valueStr === \"\") {\n // Value is a nested block on subsequent lines\n const nested = parseBlock(lines, i + 1, baseIndent + 1);\n map[key] = nested.value;\n i = nested.nextIndex;\n } else {\n map[key] = parseScalar(valueStr);\n i++;\n }\n }\n\n return { value: map, nextIndex: i };\n}\n\nfunction findColon(s: string): number {\n let inSingle = false;\n let inDouble = false;\n for (let i = 0; i < s.length; i++) {\n const ch = s[i];\n if (ch === \"'\" && !inDouble) inSingle = !inSingle;\n if (ch === '\"' && !inSingle) inDouble = !inDouble;\n if (ch === \":\" && !inSingle && !inDouble) {\n // Must be followed by space, end of line, or nothing\n if (i === s.length - 1 || s[i + 1] === \" \") {\n return i;\n }\n }\n }\n return -1;\n}\n\n// ---------------------------------------------------------------------------\n// mock-llm config types\n// ---------------------------------------------------------------------------\n\nexport interface MockLLMRoute {\n path: string;\n method?: string;\n match?: {\n body?: {\n messages?: Array<{ role: string; content: string }>;\n };\n };\n response: Record<string, unknown>;\n}\n\nexport interface MockLLMTool {\n name: string;\n description?: string;\n parameters?: Record<string, unknown>;\n}\n\nexport interface MockLLMConfig {\n routes?: MockLLMRoute[];\n mcp?: {\n tools?: MockLLMTool[];\n };\n}\n\n// ---------------------------------------------------------------------------\n// aimock output types\n// ---------------------------------------------------------------------------\n\nexport interface AimockFixture {\n match?: { userMessage?: string };\n response: { content?: string; toolCalls?: Array<{ name: string; arguments: string }> };\n _comment?: string;\n}\n\nexport interface AimockMCPTool {\n name: string;\n description?: string;\n inputSchema?: Record<string, unknown>;\n}\n\nexport interface ConvertResult {\n fixtures: AimockFixture[];\n mcpTools?: AimockMCPTool[];\n}\n\n// ---------------------------------------------------------------------------\n// Converter\n// ---------------------------------------------------------------------------\n\nexport function convertConfig(config: MockLLMConfig): ConvertResult {\n const fixtures: AimockFixture[] = [];\n\n if (config.routes) {\n for (const route of config.routes) {\n const fixture = convertRoute(route);\n if (fixture) {\n fixtures.push(fixture);\n }\n }\n }\n\n const result: ConvertResult = { fixtures };\n\n if (config.mcp?.tools && config.mcp.tools.length > 0) {\n result.mcpTools = config.mcp.tools.map(convertMCPTool);\n }\n\n return result;\n}\n\nfunction convertRoute(route: MockLLMRoute): AimockFixture | null {\n // Extract content from response.choices[0].message.content\n const content = extractResponseContent(route.response);\n if (content === null) return null;\n\n const fixture: AimockFixture = {\n match: {},\n response: { content },\n };\n\n // Extract match criteria from match.body.messages\n const userMessage = extractUserMessage(route);\n if (userMessage) {\n fixture.match = { userMessage };\n } else {\n // Use path as a comment/identifier when no match criteria\n fixture._comment = `${route.method ?? \"POST\"} ${route.path}`;\n }\n\n return fixture;\n}\n\nfunction extractResponseContent(response: Record<string, unknown>): string | null {\n const choices = response.choices as Array<Record<string, unknown>> | undefined;\n if (!Array.isArray(choices) || choices.length === 0) return null;\n\n const firstChoice = choices[0];\n const message = firstChoice.message as Record<string, unknown> | undefined;\n if (!message) return null;\n\n const content = message.content;\n if (typeof content !== \"string\") return null;\n\n return content;\n}\n\nfunction extractUserMessage(route: MockLLMRoute): string | null {\n const messages = route.match?.body?.messages;\n if (!Array.isArray(messages) || messages.length === 0) return null;\n\n // Find the last user message\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === \"user\") {\n return messages[i].content;\n }\n }\n\n // Fall back to last message content regardless of role\n return messages[messages.length - 1].content ?? null;\n}\n\nfunction convertMCPTool(tool: MockLLMTool): AimockMCPTool {\n const result: AimockMCPTool = { name: tool.name };\n if (tool.description) result.description = tool.description;\n if (tool.parameters) result.inputSchema = tool.parameters;\n return result;\n}\n"],"mappings":";AAuBA,SAAS,kBAAkB,OAA2B;CACpD,MAAM,QAAoB,EAAE;AAC5B,MAAK,MAAM,OAAO,MAAM,MAAM,KAAK,EAAE;EAEnC,MAAM,UAAU,IAAI,WAAW;AAC/B,MAAI,YAAY,MAAM,QAAQ,WAAW,IAAI,CAAE;EAE/C,MAAM,SAAS,IAAI,SAAS,IAAI,WAAW,CAAC;EAE5C,MAAM,UAAU,qBAAqB,QAAQ;EAC7C,MAAM,cAAc,QAAQ,WAAW,KAAK;EAC5C,MAAM,mBAAmB,cAAc,QAAQ,MAAM,EAAE,CAAC,MAAM,GAAG;AAEjE,QAAM,KAAK;GAAE;GAAQ;GAAK;GAAS;GAAa;GAAkB,CAAC;;AAErE,QAAO;;AAGT,SAAS,qBAAqB,GAAmB;CAE/C,IAAI,WAAW;CACf,IAAI,WAAW;AACf,MAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;EACjC,MAAM,KAAK,EAAE;AACb,MAAI,OAAO,OAAO,CAAC,SAAU,YAAW,CAAC;AACzC,MAAI,OAAO,QAAO,CAAC,SAAU,YAAW,CAAC;AACzC,MAAI,OAAO,OAAO,CAAC,YAAY,CAAC,YAAY,IAAI,KAAK,EAAE,IAAI,OAAO,IAChE,QAAO,EAAE,MAAM,GAAG,EAAE,CAAC,SAAS;;AAGlC,QAAO;;AAGT,SAAS,YAAY,OAAwB;AAC3C,KAAI,UAAU,MAAM,UAAU,OAAO,UAAU,OAAQ,QAAO;AAC9D,KAAI,UAAU,OAAQ,QAAO;AAC7B,KAAI,UAAU,QAAS,QAAO;AAG9B,KACG,MAAM,WAAW,KAAI,IAAI,MAAM,SAAS,KAAI,IAC5C,MAAM,WAAW,IAAI,IAAI,MAAM,SAAS,IAAI,CAE7C,QAAO,MAAM,MAAM,GAAG,GAAG;CAI3B,MAAM,MAAM,OAAO,MAAM;AACzB,KAAI,CAAC,OAAO,MAAM,IAAI,IAAI,UAAU,GAAI,QAAO;AAG/C,QAAO;;AAGT,SAAgB,gBAAgB,OAAwB;CACtD,MAAM,QAAQ,kBAAkB,MAAM;AACtC,KAAI,MAAM,WAAW,EAAG,QAAO;AAG/B,QADe,WAAW,OAAO,GAAG,EAAE,CACxB;;AAQhB,SAAS,WAAW,OAAmB,YAAoB,WAAgC;AACzF,KAAI,cAAc,MAAM,OACtB,QAAO;EAAE,OAAO;EAAM,WAAW;EAAY;CAG/C,MAAM,OAAO,MAAM;AAGnB,KAAI,KAAK,eAAe,KAAK,UAAU,UACrC,QAAO,WAAW,OAAO,YAAY,KAAK,OAAO;AAInD,KAAI,KAAK,QAAQ,SAAS,IAAI,CAC5B,QAAO,SAAS,OAAO,YAAY,KAAK,OAAO;AAIjD,QAAO;EAAE,OAAO,YAAY,KAAK,QAAQ;EAAE,WAAW,aAAa;EAAG;;AAGxE,SAAS,WAAW,OAAmB,YAAoB,YAAiC;CAC1F,MAAM,MAAiB,EAAE;CACzB,IAAI,IAAI;AAER,QAAO,IAAI,MAAM,QAAQ;EACvB,MAAM,OAAO,MAAM;AACnB,MAAI,KAAK,SAAS,WAAY;AAC9B,MAAI,KAAK,SAAS,WAAY;AAC9B,MAAI,CAAC,KAAK,YAAa;EAEvB,MAAM,cAAc,KAAK;AAEzB,MAAI,gBAAgB,IAAI;GAEtB,MAAM,SAAS,WAAW,OAAO,IAAI,GAAG,aAAa,EAAE;AACvD,OAAI,KAAK,OAAO,MAAM;AACtB,OAAI,OAAO;aACF,YAAY,SAAS,IAAI,EAAE;GAGpC,MAAM,YAAY,kBAAkB,OAAO,GAAG,WAAW;AACzD,OAAI,KAAK,UAAU,MAAM;AACzB,OAAI,UAAU;SACT;AAEL,OAAI,KAAK,YAAY,YAAY,CAAC;AAClC;;;AAIJ,QAAO;EAAE,OAAO;EAAK,WAAW;EAAG;;AAGrC,SAAS,kBACP,OACA,YACA,aACa;CAEb,MAAM,MAA+B,EAAE;CAEvC,MAAM,eADY,MAAM,YACO;CAG/B,MAAM,WAAW,UAAU,aAAa;AACxC,KAAI,aAAa,GACf,QAAO;EAAE,OAAO,YAAY,aAAa;EAAE,WAAW,aAAa;EAAG;CAGxE,MAAM,MAAM,aAAa,MAAM,GAAG,SAAS,CAAC,MAAM;CAClD,MAAM,WAAW,aAAa,MAAM,WAAW,EAAE,CAAC,MAAM;AAExD,KAAI,aAAa,IAAI;EAEnB,MAAM,SAAS,WAAW,OAAO,aAAa,GAAG,cAAc,EAAE;AACjE,MAAI,OAAO,OAAO;EAClB,IAAI,IAAI,OAAO;EAGf,MAAM,gBAAgB,cAAc;AACpC,SAAO,IAAI,MAAM,UAAU,MAAM,GAAG,UAAU,iBAAiB,CAAC,MAAM,GAAG,YACvE,KAAI,MAAM,GAAG,WAAW,iBAAiB,MAAM,GAAG,SAAS,cAEzD,KAAI,MAAM,GAAG,WAAW,iBAAiB,MAAM,GAAG,QAAQ,SAAS,IAAI,CAErE,KADkB,gBAAgB,OAAO,GAAG,eAAe,IAAI,CACjD;MAEd;AAKN,SAAO;GAAE,OAAO;GAAK,WAAW;GAAG;OAEnC,KAAI,OAAO,YAAY,SAAS;CAIlC,IAAI,IAAI,aAAa;CACrB,MAAM,gBAAgB,cAAc;AAEpC,QAAO,IAAI,MAAM,QAAQ;EACvB,MAAM,OAAO,MAAM;AACnB,MAAI,KAAK,SAAS,cAAe;AACjC,MAAI,KAAK,eAAe,KAAK,UAAU,YAAa;AAEpD,MAAI,KAAK,WAAW,iBAAiB,CAAC,KAAK,eAAe,KAAK,QAAQ,SAAS,IAAI,EAAE;GACpF,MAAM,WAAW,UAAU,KAAK,QAAQ;AACxC,OAAI,aAAa,GAAI;GACrB,MAAM,IAAI,KAAK,QAAQ,MAAM,GAAG,SAAS,CAAC,MAAM;GAChD,MAAM,IAAI,KAAK,QAAQ,MAAM,WAAW,EAAE,CAAC,MAAM;AAEjD,OAAI,MAAM,IAAI;IACZ,MAAM,SAAS,WAAW,OAAO,IAAI,GAAG,gBAAgB,EAAE;AAC1D,QAAI,KAAK,OAAO;AAChB,QAAI,OAAO;UACN;AACL,QAAI,KAAK,YAAY,EAAE;AACvB;;aAEO,KAAK,WAAW,iBAAiB,KAAK,YAE/C;WACS,KAAK,SAAS,cAEvB;MAEA;;AAIJ,QAAO;EAAE,OAAO;EAAK,WAAW;EAAG;;AAGrC,SAAS,SAAS,OAAmB,YAAoB,YAAiC;CACxF,MAAM,MAA+B,EAAE;AAEvC,QAAO;EAAE,OAAO;EAAK,WADN,gBAAgB,OAAO,YAAY,YAAY,IAAI,CAC3B;EAAW;;AAGpD,SAAS,gBACP,OACA,YACA,YACA,KACa;CACb,IAAI,IAAI;AAER,QAAO,IAAI,MAAM,QAAQ;EACvB,MAAM,OAAO,MAAM;AACnB,MAAI,KAAK,SAAS,WAAY;AAC9B,MAAI,KAAK,SAAS,YAAY;AAE5B;AACA;;AAEF,MAAI,KAAK,YAAa;EAEtB,MAAM,WAAW,UAAU,KAAK,QAAQ;AACxC,MAAI,aAAa,GAEf;EAGF,MAAM,MAAM,KAAK,QAAQ,MAAM,GAAG,SAAS,CAAC,MAAM;EAClD,MAAM,WAAW,KAAK,QAAQ,MAAM,WAAW,EAAE,CAAC,MAAM;AAExD,MAAI,aAAa,IAAI;GAEnB,MAAM,SAAS,WAAW,OAAO,IAAI,GAAG,aAAa,EAAE;AACvD,OAAI,OAAO,OAAO;AAClB,OAAI,OAAO;SACN;AACL,OAAI,OAAO,YAAY,SAAS;AAChC;;;AAIJ,QAAO;EAAE,OAAO;EAAK,WAAW;EAAG;;AAGrC,SAAS,UAAU,GAAmB;CACpC,IAAI,WAAW;CACf,IAAI,WAAW;AACf,MAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;EACjC,MAAM,KAAK,EAAE;AACb,MAAI,OAAO,OAAO,CAAC,SAAU,YAAW,CAAC;AACzC,MAAI,OAAO,QAAO,CAAC,SAAU,YAAW,CAAC;AACzC,MAAI,OAAO,OAAO,CAAC,YAAY,CAAC,UAE9B;OAAI,MAAM,EAAE,SAAS,KAAK,EAAE,IAAI,OAAO,IACrC,QAAO;;;AAIb,QAAO;;AAwDT,SAAgB,cAAc,QAAsC;CAClE,MAAM,WAA4B,EAAE;AAEpC,KAAI,OAAO,OACT,MAAK,MAAM,SAAS,OAAO,QAAQ;EACjC,MAAM,UAAU,aAAa,MAAM;AACnC,MAAI,QACF,UAAS,KAAK,QAAQ;;CAK5B,MAAM,SAAwB,EAAE,UAAU;AAE1C,KAAI,OAAO,KAAK,SAAS,OAAO,IAAI,MAAM,SAAS,EACjD,QAAO,WAAW,OAAO,IAAI,MAAM,IAAI,eAAe;AAGxD,QAAO;;AAGT,SAAS,aAAa,OAA2C;CAE/D,MAAM,UAAU,uBAAuB,MAAM,SAAS;AACtD,KAAI,YAAY,KAAM,QAAO;CAE7B,MAAM,UAAyB;EAC7B,OAAO,EAAE;EACT,UAAU,EAAE,SAAS;EACtB;CAGD,MAAM,cAAc,mBAAmB,MAAM;AAC7C,KAAI,YACF,SAAQ,QAAQ,EAAE,aAAa;KAG/B,SAAQ,WAAW,GAAG,MAAM,UAAU,OAAO,GAAG,MAAM;AAGxD,QAAO;;AAGT,SAAS,uBAAuB,UAAkD;CAChF,MAAM,UAAU,SAAS;AACzB,KAAI,CAAC,MAAM,QAAQ,QAAQ,IAAI,QAAQ,WAAW,EAAG,QAAO;CAG5D,MAAM,UADc,QAAQ,GACA;AAC5B,KAAI,CAAC,QAAS,QAAO;CAErB,MAAM,UAAU,QAAQ;AACxB,KAAI,OAAO,YAAY,SAAU,QAAO;AAExC,QAAO;;AAGT,SAAS,mBAAmB,OAAoC;CAC9D,MAAM,WAAW,MAAM,OAAO,MAAM;AACpC,KAAI,CAAC,MAAM,QAAQ,SAAS,IAAI,SAAS,WAAW,EAAG,QAAO;AAG9D,MAAK,IAAI,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,IACxC,KAAI,SAAS,GAAG,SAAS,OACvB,QAAO,SAAS,GAAG;AAKvB,QAAO,SAAS,SAAS,SAAS,GAAG,WAAW;;AAGlD,SAAS,eAAe,MAAkC;CACxD,MAAM,SAAwB,EAAE,MAAM,KAAK,MAAM;AACjD,KAAI,KAAK,YAAa,QAAO,cAAc,KAAK;AAChD,KAAI,KAAK,WAAY,QAAO,cAAc,KAAK;AAC/C,QAAO"}
@@ -0,0 +1,110 @@
1
+ const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
2
+ let node_path = require("node:path");
3
+ let node_fs = require("node:fs");
4
+
5
+ //#region src/convert-vidaimock.ts
6
+ /**
7
+ * VidaiMock -> aimock Fixture Converter
8
+ *
9
+ * Core conversion logic. Used by both the CLI (`aimock convert vidaimock`)
10
+ * and the standalone script (`scripts/convert-vidaimock.ts`).
11
+ */
12
+ /**
13
+ * Strip Tera template syntax and extract a usable response content string.
14
+ *
15
+ * Strategy:
16
+ * 1. If the template looks like JSON, try to pull out the nested
17
+ * `choices[].message.content` value (the most common VidaiMock pattern).
18
+ * 2. Otherwise fall back to stripping all Tera delimiters and returning the
19
+ * remaining text with placeholder variable names.
20
+ */
21
+ function stripTeraTemplate(raw) {
22
+ const trimmed = raw.trim();
23
+ const contentValue = extractJsonContent(trimmed);
24
+ if (contentValue !== null) return contentValue;
25
+ let text = trimmed;
26
+ text = text.replace(/\{#[\s\S]*?#\}/g, "");
27
+ text = text.replace(/\{%[\s\S]*?%\}/g, "");
28
+ text = text.replace(/\{\{\s*([\w.]+)\s*\}\}/g, "[$1]");
29
+ text = text.split("\n").map((l) => l.trim()).filter((l) => l.length > 0).join("\n");
30
+ return text;
31
+ }
32
+ /**
33
+ * Try to parse the template as JSON (after substituting Tera expressions with
34
+ * dummy strings) and pull out `choices[0].message.content`.
35
+ */
36
+ function extractJsonContent(raw) {
37
+ try {
38
+ let substituted = raw.replace(/\{#[\s\S]*?#\}/g, "").replace(/\{%[\s\S]*?%\}/g, "");
39
+ substituted = substituted.replace(/"([^"]*?)\{\{\s*([\w.]+)\s*\}\}([^"]*?)"/g, (_, before, varName, after) => `"${before}[${varName}]${after}"`);
40
+ substituted = substituted.replace(/\{\{\s*([\w.]+)\s*\}\}/g, "\"[$1]\"");
41
+ const parsed = JSON.parse(substituted);
42
+ if (parsed && Array.isArray(parsed.choices) && parsed.choices.length > 0 && parsed.choices[0]?.message?.content !== void 0) return String(parsed.choices[0].message.content);
43
+ } catch {}
44
+ return null;
45
+ }
46
+ /**
47
+ * Derive a `userMessage` match string from the template filename.
48
+ *
49
+ * Examples:
50
+ * "greeting.tera" -> "greeting"
51
+ * "tell_me_a_joke.json" -> "tell me a joke"
52
+ * "003-weather.txt" -> "weather"
53
+ */
54
+ function deriveMatchFromFilename(filename) {
55
+ let name = (0, node_path.basename)(filename, (0, node_path.extname)(filename));
56
+ name = name.replace(/^\d+[-_]/, "");
57
+ name = name.replace(/[-_]+/g, " ");
58
+ return name.trim();
59
+ }
60
+ const TEMPLATE_EXTENSIONS = new Set([
61
+ ".tera",
62
+ ".json",
63
+ ".txt",
64
+ ".html",
65
+ ".jinja",
66
+ ".jinja2",
67
+ ".j2"
68
+ ]);
69
+ function convertFile(filePath) {
70
+ let raw;
71
+ try {
72
+ raw = (0, node_fs.readFileSync)(filePath, "utf-8");
73
+ } catch {
74
+ return null;
75
+ }
76
+ const content = stripTeraTemplate(raw);
77
+ if (!content) return null;
78
+ return {
79
+ match: { userMessage: deriveMatchFromFilename(filePath) },
80
+ response: { content }
81
+ };
82
+ }
83
+ function convertDirectory(dirPath) {
84
+ const fixtures = [];
85
+ let entries;
86
+ try {
87
+ entries = (0, node_fs.readdirSync)(dirPath);
88
+ } catch (err) {
89
+ if (err.code === "ENOENT") return fixtures;
90
+ throw err;
91
+ }
92
+ for (const entry of entries.sort()) {
93
+ const fullPath = (0, node_path.resolve)(dirPath, entry);
94
+ try {
95
+ if (!(0, node_fs.statSync)(fullPath).isFile()) continue;
96
+ } catch {
97
+ continue;
98
+ }
99
+ const ext = (0, node_path.extname)(entry).toLowerCase();
100
+ if (!TEMPLATE_EXTENSIONS.has(ext)) continue;
101
+ const fixture = convertFile(fullPath);
102
+ if (fixture) fixtures.push(fixture);
103
+ }
104
+ return fixtures;
105
+ }
106
+
107
+ //#endregion
108
+ exports.convertDirectory = convertDirectory;
109
+ exports.convertFile = convertFile;
110
+ //# sourceMappingURL=convert-vidaimock.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"convert-vidaimock.cjs","names":[],"sources":["../src/convert-vidaimock.ts"],"sourcesContent":["/**\n * VidaiMock -> aimock Fixture Converter\n *\n * Core conversion logic. Used by both the CLI (`aimock convert vidaimock`)\n * and the standalone script (`scripts/convert-vidaimock.ts`).\n */\n\nimport { readFileSync, readdirSync, statSync } from \"node:fs\";\nimport { resolve, basename, extname } from \"node:path\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface AimockFixture {\n match: { userMessage: string };\n response: { content: string };\n}\n\nexport interface AimockFixtureFile {\n fixtures: AimockFixture[];\n}\n\n// ---------------------------------------------------------------------------\n// Tera template stripping\n// ---------------------------------------------------------------------------\n\n/**\n * Strip Tera template syntax and extract a usable response content string.\n *\n * Strategy:\n * 1. If the template looks like JSON, try to pull out the nested\n * `choices[].message.content` value (the most common VidaiMock pattern).\n * 2. Otherwise fall back to stripping all Tera delimiters and returning the\n * remaining text with placeholder variable names.\n */\nexport function stripTeraTemplate(raw: string): string {\n const trimmed = raw.trim();\n\n // --- Attempt JSON extraction first -----------------------------------\n const contentValue = extractJsonContent(trimmed);\n if (contentValue !== null) return contentValue;\n\n // --- Fallback: strip Tera syntax -------------------------------------\n let text = trimmed;\n\n // Remove comment blocks {# ... #}\n text = text.replace(/\\{#[\\s\\S]*?#\\}/g, \"\");\n\n // Remove block tags {% ... %}\n text = text.replace(/\\{%[\\s\\S]*?%\\}/g, \"\");\n\n // Replace expression tags {{ expr }} with the expression name\n text = text.replace(/\\{\\{\\s*([\\w.]+)\\s*\\}\\}/g, \"[$1]\");\n\n // Collapse excessive whitespace but preserve intentional newlines\n text = text\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l.length > 0)\n .join(\"\\n\");\n\n return text;\n}\n\n/**\n * Try to parse the template as JSON (after substituting Tera expressions with\n * dummy strings) and pull out `choices[0].message.content`.\n */\nfunction extractJsonContent(raw: string): string | null {\n try {\n // Step 1: remove Tera comments and blocks\n let substituted = raw.replace(/\\{#[\\s\\S]*?#\\}/g, \"\").replace(/\\{%[\\s\\S]*?%\\}/g, \"\");\n\n // Step 2: Replace Tera expressions inside existing JSON strings.\n // Pattern: the expression is already within quotes, e.g. \"foo-{{ bar }}-baz\"\n // We replace the {{ ... }} with the placeholder without adding extra quotes.\n substituted = substituted.replace(\n /\"([^\"]*?)\\{\\{\\s*([\\w.]+)\\s*\\}\\}([^\"]*?)\"/g,\n (_, before, varName, after) => `\"${before}[${varName}]${after}\"`,\n );\n\n // Step 3: Replace standalone Tera expressions (not inside quotes),\n // e.g. a bare `{{ content }}` used as a JSON value — wrap with quotes.\n substituted = substituted.replace(/\\{\\{\\s*([\\w.]+)\\s*\\}\\}/g, '\"[$1]\"');\n\n const parsed = JSON.parse(substituted);\n\n if (\n parsed &&\n Array.isArray(parsed.choices) &&\n parsed.choices.length > 0 &&\n parsed.choices[0]?.message?.content !== undefined\n ) {\n return String(parsed.choices[0].message.content);\n }\n } catch {\n // Not valid JSON even after substitution — fall through\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Filename -> match derivation\n// ---------------------------------------------------------------------------\n\n/**\n * Derive a `userMessage` match string from the template filename.\n *\n * Examples:\n * \"greeting.tera\" -> \"greeting\"\n * \"tell_me_a_joke.json\" -> \"tell me a joke\"\n * \"003-weather.txt\" -> \"weather\"\n */\nexport function deriveMatchFromFilename(filename: string): string {\n let name = basename(filename, extname(filename));\n\n // Strip leading numeric prefixes like \"003-\"\n name = name.replace(/^\\d+[-_]/, \"\");\n\n // Replace underscores / hyphens with spaces\n name = name.replace(/[-_]+/g, \" \");\n\n return name.trim();\n}\n\n// ---------------------------------------------------------------------------\n// File / directory conversion\n// ---------------------------------------------------------------------------\n\nconst TEMPLATE_EXTENSIONS = new Set([\n \".tera\",\n \".json\",\n \".txt\",\n \".html\",\n \".jinja\",\n \".jinja2\",\n \".j2\",\n]);\n\nexport function convertFile(filePath: string): AimockFixture | null {\n let raw: string;\n try {\n raw = readFileSync(filePath, \"utf-8\");\n } catch {\n // Unreadable / binary file — skip gracefully\n return null;\n }\n\n const content = stripTeraTemplate(raw);\n if (!content) return null;\n\n const match = deriveMatchFromFilename(filePath);\n return { match: { userMessage: match }, response: { content } };\n}\n\nexport function convertDirectory(dirPath: string): AimockFixture[] {\n const fixtures: AimockFixture[] = [];\n\n let entries: string[];\n try {\n entries = readdirSync(dirPath);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return fixtures;\n throw err; // permission errors, etc. should propagate\n }\n\n for (const entry of entries.sort()) {\n const fullPath = resolve(dirPath, entry);\n try {\n if (!statSync(fullPath).isFile()) continue;\n } catch {\n continue;\n }\n\n const ext = extname(entry).toLowerCase();\n if (!TEMPLATE_EXTENSIONS.has(ext)) continue;\n\n const fixture = convertFile(fullPath);\n if (fixture) fixtures.push(fixture);\n }\n\n return fixtures;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAoCA,SAAgB,kBAAkB,KAAqB;CACrD,MAAM,UAAU,IAAI,MAAM;CAG1B,MAAM,eAAe,mBAAmB,QAAQ;AAChD,KAAI,iBAAiB,KAAM,QAAO;CAGlC,IAAI,OAAO;AAGX,QAAO,KAAK,QAAQ,mBAAmB,GAAG;AAG1C,QAAO,KAAK,QAAQ,mBAAmB,GAAG;AAG1C,QAAO,KAAK,QAAQ,2BAA2B,OAAO;AAGtD,QAAO,KACJ,MAAM,KAAK,CACX,KAAK,MAAM,EAAE,MAAM,CAAC,CACpB,QAAQ,MAAM,EAAE,SAAS,EAAE,CAC3B,KAAK,KAAK;AAEb,QAAO;;;;;;AAOT,SAAS,mBAAmB,KAA4B;AACtD,KAAI;EAEF,IAAI,cAAc,IAAI,QAAQ,mBAAmB,GAAG,CAAC,QAAQ,mBAAmB,GAAG;AAKnF,gBAAc,YAAY,QACxB,8CACC,GAAG,QAAQ,SAAS,UAAU,IAAI,OAAO,GAAG,QAAQ,GAAG,MAAM,GAC/D;AAID,gBAAc,YAAY,QAAQ,2BAA2B,WAAS;EAEtE,MAAM,SAAS,KAAK,MAAM,YAAY;AAEtC,MACE,UACA,MAAM,QAAQ,OAAO,QAAQ,IAC7B,OAAO,QAAQ,SAAS,KACxB,OAAO,QAAQ,IAAI,SAAS,YAAY,OAExC,QAAO,OAAO,OAAO,QAAQ,GAAG,QAAQ,QAAQ;SAE5C;AAGR,QAAO;;;;;;;;;;AAeT,SAAgB,wBAAwB,UAA0B;CAChE,IAAI,+BAAgB,iCAAkB,SAAS,CAAC;AAGhD,QAAO,KAAK,QAAQ,YAAY,GAAG;AAGnC,QAAO,KAAK,QAAQ,UAAU,IAAI;AAElC,QAAO,KAAK,MAAM;;AAOpB,MAAM,sBAAsB,IAAI,IAAI;CAClC;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,SAAgB,YAAY,UAAwC;CAClE,IAAI;AACJ,KAAI;AACF,kCAAmB,UAAU,QAAQ;SAC/B;AAEN,SAAO;;CAGT,MAAM,UAAU,kBAAkB,IAAI;AACtC,KAAI,CAAC,QAAS,QAAO;AAGrB,QAAO;EAAE,OAAO,EAAE,aADJ,wBAAwB,SAAS,EACT;EAAE,UAAU,EAAE,SAAS;EAAE;;AAGjE,SAAgB,iBAAiB,SAAkC;CACjE,MAAM,WAA4B,EAAE;CAEpC,IAAI;AACJ,KAAI;AACF,qCAAsB,QAAQ;UACvB,KAAK;AACZ,MAAK,IAA8B,SAAS,SAAU,QAAO;AAC7D,QAAM;;AAGR,MAAK,MAAM,SAAS,QAAQ,MAAM,EAAE;EAClC,MAAM,kCAAmB,SAAS,MAAM;AACxC,MAAI;AACF,OAAI,uBAAU,SAAS,CAAC,QAAQ,CAAE;UAC5B;AACN;;EAGF,MAAM,6BAAc,MAAM,CAAC,aAAa;AACxC,MAAI,CAAC,oBAAoB,IAAI,IAAI,CAAE;EAEnC,MAAM,UAAU,YAAY,SAAS;AACrC,MAAI,QAAS,UAAS,KAAK,QAAQ;;AAGrC,QAAO"}
@@ -0,0 +1,108 @@
1
+ import { basename, extname, resolve } from "node:path";
2
+ import { readFileSync, readdirSync, statSync } from "node:fs";
3
+
4
+ //#region src/convert-vidaimock.ts
5
+ /**
6
+ * VidaiMock -> aimock Fixture Converter
7
+ *
8
+ * Core conversion logic. Used by both the CLI (`aimock convert vidaimock`)
9
+ * and the standalone script (`scripts/convert-vidaimock.ts`).
10
+ */
11
+ /**
12
+ * Strip Tera template syntax and extract a usable response content string.
13
+ *
14
+ * Strategy:
15
+ * 1. If the template looks like JSON, try to pull out the nested
16
+ * `choices[].message.content` value (the most common VidaiMock pattern).
17
+ * 2. Otherwise fall back to stripping all Tera delimiters and returning the
18
+ * remaining text with placeholder variable names.
19
+ */
20
+ function stripTeraTemplate(raw) {
21
+ const trimmed = raw.trim();
22
+ const contentValue = extractJsonContent(trimmed);
23
+ if (contentValue !== null) return contentValue;
24
+ let text = trimmed;
25
+ text = text.replace(/\{#[\s\S]*?#\}/g, "");
26
+ text = text.replace(/\{%[\s\S]*?%\}/g, "");
27
+ text = text.replace(/\{\{\s*([\w.]+)\s*\}\}/g, "[$1]");
28
+ text = text.split("\n").map((l) => l.trim()).filter((l) => l.length > 0).join("\n");
29
+ return text;
30
+ }
31
+ /**
32
+ * Try to parse the template as JSON (after substituting Tera expressions with
33
+ * dummy strings) and pull out `choices[0].message.content`.
34
+ */
35
+ function extractJsonContent(raw) {
36
+ try {
37
+ let substituted = raw.replace(/\{#[\s\S]*?#\}/g, "").replace(/\{%[\s\S]*?%\}/g, "");
38
+ substituted = substituted.replace(/"([^"]*?)\{\{\s*([\w.]+)\s*\}\}([^"]*?)"/g, (_, before, varName, after) => `"${before}[${varName}]${after}"`);
39
+ substituted = substituted.replace(/\{\{\s*([\w.]+)\s*\}\}/g, "\"[$1]\"");
40
+ const parsed = JSON.parse(substituted);
41
+ if (parsed && Array.isArray(parsed.choices) && parsed.choices.length > 0 && parsed.choices[0]?.message?.content !== void 0) return String(parsed.choices[0].message.content);
42
+ } catch {}
43
+ return null;
44
+ }
45
+ /**
46
+ * Derive a `userMessage` match string from the template filename.
47
+ *
48
+ * Examples:
49
+ * "greeting.tera" -> "greeting"
50
+ * "tell_me_a_joke.json" -> "tell me a joke"
51
+ * "003-weather.txt" -> "weather"
52
+ */
53
+ function deriveMatchFromFilename(filename) {
54
+ let name = basename(filename, extname(filename));
55
+ name = name.replace(/^\d+[-_]/, "");
56
+ name = name.replace(/[-_]+/g, " ");
57
+ return name.trim();
58
+ }
59
+ const TEMPLATE_EXTENSIONS = new Set([
60
+ ".tera",
61
+ ".json",
62
+ ".txt",
63
+ ".html",
64
+ ".jinja",
65
+ ".jinja2",
66
+ ".j2"
67
+ ]);
68
+ function convertFile(filePath) {
69
+ let raw;
70
+ try {
71
+ raw = readFileSync(filePath, "utf-8");
72
+ } catch {
73
+ return null;
74
+ }
75
+ const content = stripTeraTemplate(raw);
76
+ if (!content) return null;
77
+ return {
78
+ match: { userMessage: deriveMatchFromFilename(filePath) },
79
+ response: { content }
80
+ };
81
+ }
82
+ function convertDirectory(dirPath) {
83
+ const fixtures = [];
84
+ let entries;
85
+ try {
86
+ entries = readdirSync(dirPath);
87
+ } catch (err) {
88
+ if (err.code === "ENOENT") return fixtures;
89
+ throw err;
90
+ }
91
+ for (const entry of entries.sort()) {
92
+ const fullPath = resolve(dirPath, entry);
93
+ try {
94
+ if (!statSync(fullPath).isFile()) continue;
95
+ } catch {
96
+ continue;
97
+ }
98
+ const ext = extname(entry).toLowerCase();
99
+ if (!TEMPLATE_EXTENSIONS.has(ext)) continue;
100
+ const fixture = convertFile(fullPath);
101
+ if (fixture) fixtures.push(fixture);
102
+ }
103
+ return fixtures;
104
+ }
105
+
106
+ //#endregion
107
+ export { convertDirectory, convertFile };
108
+ //# sourceMappingURL=convert-vidaimock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"convert-vidaimock.js","names":[],"sources":["../src/convert-vidaimock.ts"],"sourcesContent":["/**\n * VidaiMock -> aimock Fixture Converter\n *\n * Core conversion logic. Used by both the CLI (`aimock convert vidaimock`)\n * and the standalone script (`scripts/convert-vidaimock.ts`).\n */\n\nimport { readFileSync, readdirSync, statSync } from \"node:fs\";\nimport { resolve, basename, extname } from \"node:path\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface AimockFixture {\n match: { userMessage: string };\n response: { content: string };\n}\n\nexport interface AimockFixtureFile {\n fixtures: AimockFixture[];\n}\n\n// ---------------------------------------------------------------------------\n// Tera template stripping\n// ---------------------------------------------------------------------------\n\n/**\n * Strip Tera template syntax and extract a usable response content string.\n *\n * Strategy:\n * 1. If the template looks like JSON, try to pull out the nested\n * `choices[].message.content` value (the most common VidaiMock pattern).\n * 2. Otherwise fall back to stripping all Tera delimiters and returning the\n * remaining text with placeholder variable names.\n */\nexport function stripTeraTemplate(raw: string): string {\n const trimmed = raw.trim();\n\n // --- Attempt JSON extraction first -----------------------------------\n const contentValue = extractJsonContent(trimmed);\n if (contentValue !== null) return contentValue;\n\n // --- Fallback: strip Tera syntax -------------------------------------\n let text = trimmed;\n\n // Remove comment blocks {# ... #}\n text = text.replace(/\\{#[\\s\\S]*?#\\}/g, \"\");\n\n // Remove block tags {% ... %}\n text = text.replace(/\\{%[\\s\\S]*?%\\}/g, \"\");\n\n // Replace expression tags {{ expr }} with the expression name\n text = text.replace(/\\{\\{\\s*([\\w.]+)\\s*\\}\\}/g, \"[$1]\");\n\n // Collapse excessive whitespace but preserve intentional newlines\n text = text\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l.length > 0)\n .join(\"\\n\");\n\n return text;\n}\n\n/**\n * Try to parse the template as JSON (after substituting Tera expressions with\n * dummy strings) and pull out `choices[0].message.content`.\n */\nfunction extractJsonContent(raw: string): string | null {\n try {\n // Step 1: remove Tera comments and blocks\n let substituted = raw.replace(/\\{#[\\s\\S]*?#\\}/g, \"\").replace(/\\{%[\\s\\S]*?%\\}/g, \"\");\n\n // Step 2: Replace Tera expressions inside existing JSON strings.\n // Pattern: the expression is already within quotes, e.g. \"foo-{{ bar }}-baz\"\n // We replace the {{ ... }} with the placeholder without adding extra quotes.\n substituted = substituted.replace(\n /\"([^\"]*?)\\{\\{\\s*([\\w.]+)\\s*\\}\\}([^\"]*?)\"/g,\n (_, before, varName, after) => `\"${before}[${varName}]${after}\"`,\n );\n\n // Step 3: Replace standalone Tera expressions (not inside quotes),\n // e.g. a bare `{{ content }}` used as a JSON value — wrap with quotes.\n substituted = substituted.replace(/\\{\\{\\s*([\\w.]+)\\s*\\}\\}/g, '\"[$1]\"');\n\n const parsed = JSON.parse(substituted);\n\n if (\n parsed &&\n Array.isArray(parsed.choices) &&\n parsed.choices.length > 0 &&\n parsed.choices[0]?.message?.content !== undefined\n ) {\n return String(parsed.choices[0].message.content);\n }\n } catch {\n // Not valid JSON even after substitution — fall through\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Filename -> match derivation\n// ---------------------------------------------------------------------------\n\n/**\n * Derive a `userMessage` match string from the template filename.\n *\n * Examples:\n * \"greeting.tera\" -> \"greeting\"\n * \"tell_me_a_joke.json\" -> \"tell me a joke\"\n * \"003-weather.txt\" -> \"weather\"\n */\nexport function deriveMatchFromFilename(filename: string): string {\n let name = basename(filename, extname(filename));\n\n // Strip leading numeric prefixes like \"003-\"\n name = name.replace(/^\\d+[-_]/, \"\");\n\n // Replace underscores / hyphens with spaces\n name = name.replace(/[-_]+/g, \" \");\n\n return name.trim();\n}\n\n// ---------------------------------------------------------------------------\n// File / directory conversion\n// ---------------------------------------------------------------------------\n\nconst TEMPLATE_EXTENSIONS = new Set([\n \".tera\",\n \".json\",\n \".txt\",\n \".html\",\n \".jinja\",\n \".jinja2\",\n \".j2\",\n]);\n\nexport function convertFile(filePath: string): AimockFixture | null {\n let raw: string;\n try {\n raw = readFileSync(filePath, \"utf-8\");\n } catch {\n // Unreadable / binary file — skip gracefully\n return null;\n }\n\n const content = stripTeraTemplate(raw);\n if (!content) return null;\n\n const match = deriveMatchFromFilename(filePath);\n return { match: { userMessage: match }, response: { content } };\n}\n\nexport function convertDirectory(dirPath: string): AimockFixture[] {\n const fixtures: AimockFixture[] = [];\n\n let entries: string[];\n try {\n entries = readdirSync(dirPath);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return fixtures;\n throw err; // permission errors, etc. should propagate\n }\n\n for (const entry of entries.sort()) {\n const fullPath = resolve(dirPath, entry);\n try {\n if (!statSync(fullPath).isFile()) continue;\n } catch {\n continue;\n }\n\n const ext = extname(entry).toLowerCase();\n if (!TEMPLATE_EXTENSIONS.has(ext)) continue;\n\n const fixture = convertFile(fullPath);\n if (fixture) fixtures.push(fixture);\n }\n\n return fixtures;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAoCA,SAAgB,kBAAkB,KAAqB;CACrD,MAAM,UAAU,IAAI,MAAM;CAG1B,MAAM,eAAe,mBAAmB,QAAQ;AAChD,KAAI,iBAAiB,KAAM,QAAO;CAGlC,IAAI,OAAO;AAGX,QAAO,KAAK,QAAQ,mBAAmB,GAAG;AAG1C,QAAO,KAAK,QAAQ,mBAAmB,GAAG;AAG1C,QAAO,KAAK,QAAQ,2BAA2B,OAAO;AAGtD,QAAO,KACJ,MAAM,KAAK,CACX,KAAK,MAAM,EAAE,MAAM,CAAC,CACpB,QAAQ,MAAM,EAAE,SAAS,EAAE,CAC3B,KAAK,KAAK;AAEb,QAAO;;;;;;AAOT,SAAS,mBAAmB,KAA4B;AACtD,KAAI;EAEF,IAAI,cAAc,IAAI,QAAQ,mBAAmB,GAAG,CAAC,QAAQ,mBAAmB,GAAG;AAKnF,gBAAc,YAAY,QACxB,8CACC,GAAG,QAAQ,SAAS,UAAU,IAAI,OAAO,GAAG,QAAQ,GAAG,MAAM,GAC/D;AAID,gBAAc,YAAY,QAAQ,2BAA2B,WAAS;EAEtE,MAAM,SAAS,KAAK,MAAM,YAAY;AAEtC,MACE,UACA,MAAM,QAAQ,OAAO,QAAQ,IAC7B,OAAO,QAAQ,SAAS,KACxB,OAAO,QAAQ,IAAI,SAAS,YAAY,OAExC,QAAO,OAAO,OAAO,QAAQ,GAAG,QAAQ,QAAQ;SAE5C;AAGR,QAAO;;;;;;;;;;AAeT,SAAgB,wBAAwB,UAA0B;CAChE,IAAI,OAAO,SAAS,UAAU,QAAQ,SAAS,CAAC;AAGhD,QAAO,KAAK,QAAQ,YAAY,GAAG;AAGnC,QAAO,KAAK,QAAQ,UAAU,IAAI;AAElC,QAAO,KAAK,MAAM;;AAOpB,MAAM,sBAAsB,IAAI,IAAI;CAClC;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,SAAgB,YAAY,UAAwC;CAClE,IAAI;AACJ,KAAI;AACF,QAAM,aAAa,UAAU,QAAQ;SAC/B;AAEN,SAAO;;CAGT,MAAM,UAAU,kBAAkB,IAAI;AACtC,KAAI,CAAC,QAAS,QAAO;AAGrB,QAAO;EAAE,OAAO,EAAE,aADJ,wBAAwB,SAAS,EACT;EAAE,UAAU,EAAE,SAAS;EAAE;;AAGjE,SAAgB,iBAAiB,SAAkC;CACjE,MAAM,WAA4B,EAAE;CAEpC,IAAI;AACJ,KAAI;AACF,YAAU,YAAY,QAAQ;UACvB,KAAK;AACZ,MAAK,IAA8B,SAAS,SAAU,QAAO;AAC7D,QAAM;;AAGR,MAAK,MAAM,SAAS,QAAQ,MAAM,EAAE;EAClC,MAAM,WAAW,QAAQ,SAAS,MAAM;AACxC,MAAI;AACF,OAAI,CAAC,SAAS,SAAS,CAAC,QAAQ,CAAE;UAC5B;AACN;;EAGF,MAAM,MAAM,QAAQ,MAAM,CAAC,aAAa;AACxC,MAAI,CAAC,oBAAoB,IAAI,IAAI,CAAE;EAEnC,MAAM,UAAU,YAAY,SAAS;AACrC,MAAI,QAAS,UAAS,KAAK,QAAQ;;AAGrC,QAAO"}