@plateforme-ai/lobster 2026.6.12

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 (219) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +353 -0
  3. package/VISION.md +249 -0
  4. package/bin/clawd.invoke.js +18 -0
  5. package/bin/lobster.js +24 -0
  6. package/bin/openclaw.invoke.js +21 -0
  7. package/dist/src/cli.js +793 -0
  8. package/dist/src/cli.js.map +1 -0
  9. package/dist/src/commands/commands_list.js +49 -0
  10. package/dist/src/commands/commands_list.js.map +1 -0
  11. package/dist/src/commands/registry.js +66 -0
  12. package/dist/src/commands/registry.js.map +1 -0
  13. package/dist/src/commands/stdlib/approve.js +77 -0
  14. package/dist/src/commands/stdlib/approve.js.map +1 -0
  15. package/dist/src/commands/stdlib/ask.js +171 -0
  16. package/dist/src/commands/stdlib/ask.js.map +1 -0
  17. package/dist/src/commands/stdlib/dedupe.js +55 -0
  18. package/dist/src/commands/stdlib/dedupe.js.map +1 -0
  19. package/dist/src/commands/stdlib/diff_last.js +35 -0
  20. package/dist/src/commands/stdlib/diff_last.js.map +1 -0
  21. package/dist/src/commands/stdlib/email_triage.js +279 -0
  22. package/dist/src/commands/stdlib/email_triage.js.map +1 -0
  23. package/dist/src/commands/stdlib/exec.js +130 -0
  24. package/dist/src/commands/stdlib/exec.js.map +1 -0
  25. package/dist/src/commands/stdlib/gog_gmail_search.js +94 -0
  26. package/dist/src/commands/stdlib/gog_gmail_search.js.map +1 -0
  27. package/dist/src/commands/stdlib/gog_gmail_send.js +104 -0
  28. package/dist/src/commands/stdlib/gog_gmail_send.js.map +1 -0
  29. package/dist/src/commands/stdlib/group_by.js +59 -0
  30. package/dist/src/commands/stdlib/group_by.js.map +1 -0
  31. package/dist/src/commands/stdlib/head.js +34 -0
  32. package/dist/src/commands/stdlib/head.js.map +1 -0
  33. package/dist/src/commands/stdlib/json.js +20 -0
  34. package/dist/src/commands/stdlib/json.js.map +1 -0
  35. package/dist/src/commands/stdlib/llm_invoke.js +758 -0
  36. package/dist/src/commands/stdlib/llm_invoke.js.map +1 -0
  37. package/dist/src/commands/stdlib/llm_task_invoke.js +2 -0
  38. package/dist/src/commands/stdlib/llm_task_invoke.js.map +1 -0
  39. package/dist/src/commands/stdlib/map.js +104 -0
  40. package/dist/src/commands/stdlib/map.js.map +1 -0
  41. package/dist/src/commands/stdlib/openclaw_invoke.js +136 -0
  42. package/dist/src/commands/stdlib/openclaw_invoke.js.map +1 -0
  43. package/dist/src/commands/stdlib/pick.js +45 -0
  44. package/dist/src/commands/stdlib/pick.js.map +1 -0
  45. package/dist/src/commands/stdlib/sort.js +86 -0
  46. package/dist/src/commands/stdlib/sort.js.map +1 -0
  47. package/dist/src/commands/stdlib/state.js +76 -0
  48. package/dist/src/commands/stdlib/state.js.map +1 -0
  49. package/dist/src/commands/stdlib/table.js +57 -0
  50. package/dist/src/commands/stdlib/table.js.map +1 -0
  51. package/dist/src/commands/stdlib/template.js +126 -0
  52. package/dist/src/commands/stdlib/template.js.map +1 -0
  53. package/dist/src/commands/stdlib/where.js +81 -0
  54. package/dist/src/commands/stdlib/where.js.map +1 -0
  55. package/dist/src/commands/types.js +2 -0
  56. package/dist/src/commands/types.js.map +1 -0
  57. package/dist/src/commands/workflows/workflows_list.js +24 -0
  58. package/dist/src/commands/workflows/workflows_list.js.map +1 -0
  59. package/dist/src/commands/workflows/workflows_run.js +74 -0
  60. package/dist/src/commands/workflows/workflows_run.js.map +1 -0
  61. package/dist/src/core/cost_tracker.js +119 -0
  62. package/dist/src/core/cost_tracker.js.map +1 -0
  63. package/dist/src/core/filters.js +102 -0
  64. package/dist/src/core/filters.js.map +1 -0
  65. package/dist/src/core/index.js +7 -0
  66. package/dist/src/core/index.js.map +1 -0
  67. package/dist/src/core/retry.js +89 -0
  68. package/dist/src/core/retry.js.map +1 -0
  69. package/dist/src/core/tool_runtime.js +289 -0
  70. package/dist/src/core/tool_runtime.js.map +1 -0
  71. package/dist/src/input_request.js +430 -0
  72. package/dist/src/input_request.js.map +1 -0
  73. package/dist/src/parser.js +145 -0
  74. package/dist/src/parser.js.map +1 -0
  75. package/dist/src/pipeline_resume_state.js +186 -0
  76. package/dist/src/pipeline_resume_state.js.map +1 -0
  77. package/dist/src/read_line.js +50 -0
  78. package/dist/src/read_line.js.map +1 -0
  79. package/dist/src/recipes/github/index.js +16 -0
  80. package/dist/src/recipes/github/index.js.map +1 -0
  81. package/dist/src/recipes/github/pr-monitor.js +248 -0
  82. package/dist/src/recipes/github/pr-monitor.js.map +1 -0
  83. package/dist/src/recipes/github/stages/pr-view.js +107 -0
  84. package/dist/src/recipes/github/stages/pr-view.js.map +1 -0
  85. package/dist/src/recipes/index.js +7 -0
  86. package/dist/src/recipes/index.js.map +1 -0
  87. package/dist/src/recipes/registry.js +30 -0
  88. package/dist/src/recipes/registry.js.map +1 -0
  89. package/dist/src/renderers/json.js +13 -0
  90. package/dist/src/renderers/json.js.map +1 -0
  91. package/dist/src/resume.js +179 -0
  92. package/dist/src/resume.js.map +1 -0
  93. package/dist/src/runtime.js +230 -0
  94. package/dist/src/runtime.js.map +1 -0
  95. package/dist/src/sdk/Lobster.js +402 -0
  96. package/dist/src/sdk/Lobster.js.map +1 -0
  97. package/dist/src/sdk/index.js +25 -0
  98. package/dist/src/sdk/index.js.map +1 -0
  99. package/dist/src/sdk/primitives/approve.js +47 -0
  100. package/dist/src/sdk/primitives/approve.js.map +1 -0
  101. package/dist/src/sdk/primitives/diff.js +156 -0
  102. package/dist/src/sdk/primitives/diff.js.map +1 -0
  103. package/dist/src/sdk/primitives/exec.js +167 -0
  104. package/dist/src/sdk/primitives/exec.js.map +1 -0
  105. package/dist/src/sdk/primitives/state.js +203 -0
  106. package/dist/src/sdk/primitives/state.js.map +1 -0
  107. package/dist/src/sdk/runtime.js +131 -0
  108. package/dist/src/sdk/runtime.js.map +1 -0
  109. package/dist/src/sdk/token.js +9 -0
  110. package/dist/src/sdk/token.js.map +1 -0
  111. package/dist/src/shell.js +39 -0
  112. package/dist/src/shell.js.map +1 -0
  113. package/dist/src/state/store.js +337 -0
  114. package/dist/src/state/store.js.map +1 -0
  115. package/dist/src/token.js +15 -0
  116. package/dist/src/token.js.map +1 -0
  117. package/dist/src/validation.js +38 -0
  118. package/dist/src/validation.js.map +1 -0
  119. package/dist/src/workflows/file.js +2405 -0
  120. package/dist/src/workflows/file.js.map +1 -0
  121. package/dist/src/workflows/github_pr_monitor.js +167 -0
  122. package/dist/src/workflows/github_pr_monitor.js.map +1 -0
  123. package/dist/src/workflows/graph.js +234 -0
  124. package/dist/src/workflows/graph.js.map +1 -0
  125. package/dist/src/workflows/registry.js +57 -0
  126. package/dist/src/workflows/registry.js.map +1 -0
  127. package/dist/test/approval_id.test.js +171 -0
  128. package/dist/test/approval_id.test.js.map +1 -0
  129. package/dist/test/approve_preview.test.js +38 -0
  130. package/dist/test/approve_preview.test.js.map +1 -0
  131. package/dist/test/clawd_invoke.test.js +124 -0
  132. package/dist/test/clawd_invoke.test.js.map +1 -0
  133. package/dist/test/clawd_invoke_legacy.test.js +63 -0
  134. package/dist/test/clawd_invoke_legacy.test.js.map +1 -0
  135. package/dist/test/cli_run_file_args_json.test.js +27 -0
  136. package/dist/test/cli_run_file_args_json.test.js.map +1 -0
  137. package/dist/test/commands_list.test.js +44 -0
  138. package/dist/test/commands_list.test.js.map +1 -0
  139. package/dist/test/condition_comparison.test.js +127 -0
  140. package/dist/test/condition_comparison.test.js.map +1 -0
  141. package/dist/test/core_tool_runtime.test.js +160 -0
  142. package/dist/test/core_tool_runtime.test.js.map +1 -0
  143. package/dist/test/cost_tracker.test.js +231 -0
  144. package/dist/test/cost_tracker.test.js.map +1 -0
  145. package/dist/test/dedupe.test.js +48 -0
  146. package/dist/test/dedupe.test.js.map +1 -0
  147. package/dist/test/diff_last.test.js +70 -0
  148. package/dist/test/diff_last.test.js.map +1 -0
  149. package/dist/test/doctor.test.js +19 -0
  150. package/dist/test/doctor.test.js.map +1 -0
  151. package/dist/test/dry_run.test.js +502 -0
  152. package/dist/test/dry_run.test.js.map +1 -0
  153. package/dist/test/email_triage.test.js +296 -0
  154. package/dist/test/email_triage.test.js.map +1 -0
  155. package/dist/test/exec_stdin.test.js +43 -0
  156. package/dist/test/exec_stdin.test.js.map +1 -0
  157. package/dist/test/for_each.test.js +228 -0
  158. package/dist/test/for_each.test.js.map +1 -0
  159. package/dist/test/github_pr_notify_format.test.js +19 -0
  160. package/dist/test/github_pr_notify_format.test.js.map +1 -0
  161. package/dist/test/github_pr_summary.test.js +41 -0
  162. package/dist/test/github_pr_summary.test.js.map +1 -0
  163. package/dist/test/group_by.test.js +43 -0
  164. package/dist/test/group_by.test.js.map +1 -0
  165. package/dist/test/llm_invoke.test.js +166 -0
  166. package/dist/test/llm_invoke.test.js.map +1 -0
  167. package/dist/test/llm_task_invoke.test.js +416 -0
  168. package/dist/test/llm_task_invoke.test.js.map +1 -0
  169. package/dist/test/map.test.js +41 -0
  170. package/dist/test/map.test.js.map +1 -0
  171. package/dist/test/multi_approval_resume.test.js +48 -0
  172. package/dist/test/multi_approval_resume.test.js.map +1 -0
  173. package/dist/test/on_error.test.js +151 -0
  174. package/dist/test/on_error.test.js.map +1 -0
  175. package/dist/test/openclaw_invoke_alias.test.js +13 -0
  176. package/dist/test/openclaw_invoke_alias.test.js.map +1 -0
  177. package/dist/test/parallel.test.js +184 -0
  178. package/dist/test/parallel.test.js.map +1 -0
  179. package/dist/test/parser.test.js +39 -0
  180. package/dist/test/parser.test.js.map +1 -0
  181. package/dist/test/read_line.test.js +25 -0
  182. package/dist/test/read_line.test.js.map +1 -0
  183. package/dist/test/request_input.test.js +946 -0
  184. package/dist/test/request_input.test.js.map +1 -0
  185. package/dist/test/resume.test.js +82 -0
  186. package/dist/test/resume.test.js.map +1 -0
  187. package/dist/test/sdk_lobster.test.js +177 -0
  188. package/dist/test/sdk_lobster.test.js.map +1 -0
  189. package/dist/test/shell.test.js +31 -0
  190. package/dist/test/shell.test.js.map +1 -0
  191. package/dist/test/sort.test.js +51 -0
  192. package/dist/test/sort.test.js.map +1 -0
  193. package/dist/test/state.test.js +336 -0
  194. package/dist/test/state.test.js.map +1 -0
  195. package/dist/test/step_retry.test.js +254 -0
  196. package/dist/test/step_retry.test.js.map +1 -0
  197. package/dist/test/step_timeout.test.js +154 -0
  198. package/dist/test/step_timeout.test.js.map +1 -0
  199. package/dist/test/template.test.js +46 -0
  200. package/dist/test/template.test.js.map +1 -0
  201. package/dist/test/template_filters.test.js +107 -0
  202. package/dist/test/template_filters.test.js.map +1 -0
  203. package/dist/test/tool_envelope_version.test.js +15 -0
  204. package/dist/test/tool_envelope_version.test.js.map +1 -0
  205. package/dist/test/tool_mode.test.js +83 -0
  206. package/dist/test/tool_mode.test.js.map +1 -0
  207. package/dist/test/validation.test.js +28 -0
  208. package/dist/test/validation.test.js.map +1 -0
  209. package/dist/test/workflow_args_env.test.js +41 -0
  210. package/dist/test/workflow_args_env.test.js.map +1 -0
  211. package/dist/test/workflow_composition.test.js +238 -0
  212. package/dist/test/workflow_composition.test.js.map +1 -0
  213. package/dist/test/workflow_file.test.js +1399 -0
  214. package/dist/test/workflow_file.test.js.map +1 -0
  215. package/dist/test/workflow_graph.test.js +97 -0
  216. package/dist/test/workflow_graph.test.js.map +1 -0
  217. package/dist/test/workflows.test.js +32 -0
  218. package/dist/test/workflows.test.js.map +1 -0
  219. package/package.json +75 -0
@@ -0,0 +1,127 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { promises as fsp } from "node:fs";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { runWorkflowFile } from "../src/workflows/file.js";
7
+ async function runWorkflow(workflow) {
8
+ const tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), "lobster-cond-"));
9
+ const stateDir = path.join(tmpDir, "state");
10
+ const filePath = path.join(tmpDir, "workflow.lobster");
11
+ await fsp.writeFile(filePath, JSON.stringify(workflow, null, 2), "utf8");
12
+ return runWorkflowFile({
13
+ filePath,
14
+ ctx: {
15
+ stdin: process.stdin,
16
+ stdout: process.stdout,
17
+ stderr: process.stderr,
18
+ env: { ...process.env, LOBSTER_STATE_DIR: stateDir },
19
+ mode: "tool",
20
+ },
21
+ });
22
+ }
23
+ test("condition > works with numbers", async () => {
24
+ const result = await runWorkflow({
25
+ steps: [
26
+ { id: "data", command: 'node -e "process.stdout.write(JSON.stringify({count:5}))"' },
27
+ { id: "check", command: 'echo "big"', when: "$data.json.count > 3" },
28
+ ],
29
+ });
30
+ assert.equal(result.status, "ok");
31
+ assert.deepEqual(result.output, ["big\n"]);
32
+ });
33
+ test("condition > skips when false", async () => {
34
+ const result = await runWorkflow({
35
+ steps: [
36
+ { id: "data", command: 'node -e "process.stdout.write(JSON.stringify({count:1}))"' },
37
+ { id: "check", command: 'echo "big"', when: "$data.json.count > 3" },
38
+ { id: "fallback", command: 'echo "small"' },
39
+ ],
40
+ });
41
+ assert.equal(result.status, "ok");
42
+ assert.deepEqual(result.output, ["small\n"]);
43
+ });
44
+ test("condition < works", async () => {
45
+ const result = await runWorkflow({
46
+ steps: [
47
+ { id: "data", command: 'node -e "process.stdout.write(JSON.stringify({val:2}))"' },
48
+ { id: "check", command: 'echo "low"', when: "$data.json.val < 10" },
49
+ ],
50
+ });
51
+ assert.equal(result.status, "ok");
52
+ assert.deepEqual(result.output, ["low\n"]);
53
+ });
54
+ test("condition >= works at boundary", async () => {
55
+ const result = await runWorkflow({
56
+ steps: [
57
+ { id: "data", command: 'node -e "process.stdout.write(JSON.stringify({val:5}))"' },
58
+ { id: "check", command: 'echo "yes"', when: "$data.json.val >= 5" },
59
+ ],
60
+ });
61
+ assert.equal(result.status, "ok");
62
+ assert.deepEqual(result.output, ["yes\n"]);
63
+ });
64
+ test("condition <= works at boundary", async () => {
65
+ const result = await runWorkflow({
66
+ steps: [
67
+ { id: "data", command: 'node -e "process.stdout.write(JSON.stringify({val:5}))"' },
68
+ { id: "check", command: 'echo "yes"', when: "$data.json.val <= 5" },
69
+ ],
70
+ });
71
+ assert.equal(result.status, "ok");
72
+ assert.deepEqual(result.output, ["yes\n"]);
73
+ });
74
+ test("comparison operators combine with boolean operators", async () => {
75
+ const result = await runWorkflow({
76
+ steps: [
77
+ { id: "data", command: 'node -e "process.stdout.write(JSON.stringify({a:5,b:20}))"' },
78
+ { id: "check", command: 'echo "in range"', when: "$data.json.a >= 1 && $data.json.b < 100" },
79
+ ],
80
+ });
81
+ assert.equal(result.status, "ok");
82
+ assert.deepEqual(result.output, ["in range\n"]);
83
+ });
84
+ test("comparison with non-numeric string returns false", async () => {
85
+ const result = await runWorkflow({
86
+ steps: [
87
+ { id: "data", command: 'node -e "process.stdout.write(JSON.stringify({val:\\"hello\\"}))"' },
88
+ { id: "check", command: 'echo "yes"', when: "$data.json.val > 3" },
89
+ { id: "fallback", command: 'echo "no"' },
90
+ ],
91
+ });
92
+ assert.equal(result.status, "ok");
93
+ assert.deepEqual(result.output, ["no\n"]);
94
+ });
95
+ test("comparison rejects boolean as non-numeric", async () => {
96
+ const result = await runWorkflow({
97
+ steps: [
98
+ { id: "data", command: 'node -e "process.stdout.write(JSON.stringify({val:true}))"' },
99
+ { id: "check", command: 'echo "yes"', when: "$data.json.val > 0" },
100
+ { id: "fallback", command: 'echo "no"' },
101
+ ],
102
+ });
103
+ assert.equal(result.status, "ok");
104
+ assert.deepEqual(result.output, ["no\n"]);
105
+ });
106
+ test("comparison rejects null as non-numeric", async () => {
107
+ const result = await runWorkflow({
108
+ steps: [
109
+ { id: "data", command: 'node -e "process.stdout.write(JSON.stringify({val:null}))"' },
110
+ { id: "check", command: 'echo "yes"', when: "$data.json.val >= 0" },
111
+ { id: "fallback", command: 'echo "no"' },
112
+ ],
113
+ });
114
+ assert.equal(result.status, "ok");
115
+ assert.deepEqual(result.output, ["no\n"]);
116
+ });
117
+ test("existing == and != still work with new operators", async () => {
118
+ const result = await runWorkflow({
119
+ steps: [
120
+ { id: "data", command: 'node -e "process.stdout.write(JSON.stringify({status:\\"ok\\"}))"' },
121
+ { id: "check", command: 'echo "good"', when: '$data.json.status == "ok"' },
122
+ ],
123
+ });
124
+ assert.equal(result.status, "ok");
125
+ assert.deepEqual(result.output, ["good\n"]);
126
+ });
127
+ //# sourceMappingURL=condition_comparison.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"condition_comparison.test.js","sourceRoot":"","sources":["../../test/condition_comparison.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,160 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { promises as fsp } from "node:fs";
4
+ import path from "node:path";
5
+ import os from "node:os";
6
+ import { resumeToolRequest, runToolRequest } from "../src/core/index.js";
7
+ function createDirectAdapter(resultText) {
8
+ const calls = [];
9
+ return {
10
+ calls,
11
+ adapter: {
12
+ source: "test",
13
+ async invoke({ payload }) {
14
+ calls.push(payload);
15
+ return {
16
+ ok: true,
17
+ result: {
18
+ runId: "adapter_1",
19
+ model: "test/model",
20
+ prompt: payload.prompt,
21
+ status: "completed",
22
+ output: {
23
+ format: "json",
24
+ text: resultText,
25
+ data: JSON.parse(resultText),
26
+ },
27
+ },
28
+ };
29
+ },
30
+ },
31
+ };
32
+ }
33
+ test("runToolRequest executes pipeline with injected llm adapter", async () => {
34
+ const { adapter, calls } = createDirectAdapter('{"recommendation":"no jacket"}');
35
+ const envelope = await runToolRequest({
36
+ pipeline: 'exec --json=true node -e "process.stdout.write(JSON.stringify({location:\'Phoenix\',temp_f:73.8}))" | llm.invoke --provider pi --prompt "Should I wear a jacket?" --disable-cache',
37
+ ctx: {
38
+ env: {
39
+ ...process.env,
40
+ LOBSTER_LLM_PROVIDER: "pi",
41
+ LOBSTER_LLM_MODEL: "test/model",
42
+ },
43
+ llmAdapters: {
44
+ pi: adapter,
45
+ },
46
+ },
47
+ });
48
+ assert.equal(envelope.ok, true);
49
+ assert.equal(envelope.status, "ok");
50
+ assert.equal(envelope.output?.length, 1);
51
+ assert.equal(envelope.output[0].output.data.recommendation, "no jacket");
52
+ assert.equal(calls.length, 1);
53
+ assert.equal(calls[0].model, "test/model");
54
+ });
55
+ test("resumeToolRequest completes approval-gated workflow with injected llm adapter", async () => {
56
+ const { adapter, calls } = createDirectAdapter('{"recommendation":"no","reason":"warm"}');
57
+ const tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), "lobster-core-tool-runtime-"));
58
+ const filePath = path.join(tmpDir, "workflow.lobster");
59
+ await fsp.writeFile(filePath, JSON.stringify({
60
+ steps: [
61
+ {
62
+ id: "fetch",
63
+ run: "node -e \"process.stdout.write(JSON.stringify({location:'Phoenix',temp_f:73.8}))\"",
64
+ },
65
+ {
66
+ id: "confirm",
67
+ approval: "Want jacket advice?",
68
+ stdin: "$fetch.json",
69
+ },
70
+ {
71
+ id: "advice",
72
+ pipeline: 'llm.invoke --provider pi --prompt "Return JSON." --disable-cache',
73
+ stdin: "$fetch.json",
74
+ when: "$confirm.approved",
75
+ },
76
+ ],
77
+ }, null, 2), "utf8");
78
+ const env = {
79
+ ...process.env,
80
+ LOBSTER_STATE_DIR: path.join(tmpDir, "state"),
81
+ LOBSTER_LLM_PROVIDER: "pi",
82
+ LOBSTER_LLM_MODEL: "test/model",
83
+ };
84
+ const first = await runToolRequest({
85
+ filePath,
86
+ ctx: {
87
+ cwd: tmpDir,
88
+ env,
89
+ llmAdapters: { pi: adapter },
90
+ },
91
+ });
92
+ assert.equal(first.ok, true);
93
+ assert.equal(first.status, "needs_approval");
94
+ assert.ok(first.requiresApproval?.resumeToken);
95
+ const resumed = await resumeToolRequest({
96
+ token: first.requiresApproval?.resumeToken ?? "",
97
+ approved: true,
98
+ ctx: {
99
+ cwd: tmpDir,
100
+ env,
101
+ llmAdapters: { pi: adapter },
102
+ },
103
+ });
104
+ assert.equal(resumed.ok, true);
105
+ assert.equal(resumed.status, "ok");
106
+ assert.equal(resumed.output[0].output.data.reason, "warm");
107
+ assert.equal(calls.length, 1);
108
+ });
109
+ test("runToolRequest/resumeToolRequest handles needs_input workflow pauses", async () => {
110
+ const tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), "lobster-core-tool-input-"));
111
+ const filePath = path.join(tmpDir, "workflow.lobster");
112
+ await fsp.writeFile(filePath, JSON.stringify({
113
+ steps: [
114
+ {
115
+ id: "draft",
116
+ run: "node -e \"process.stdout.write(JSON.stringify({text:'hello'}))\"",
117
+ },
118
+ {
119
+ id: "review",
120
+ input: {
121
+ prompt: "Review draft?",
122
+ responseSchema: {
123
+ type: "object",
124
+ properties: { decision: { type: "string" } },
125
+ required: ["decision"],
126
+ },
127
+ },
128
+ },
129
+ {
130
+ id: "finish",
131
+ run: 'node -e "process.stdout.write(JSON.stringify({decision:process.env.DECISION,subject:process.env.SUBJECT}))"',
132
+ env: {
133
+ DECISION: "$review.response.decision",
134
+ SUBJECT: "$review.subject.text",
135
+ },
136
+ },
137
+ ],
138
+ }, null, 2), "utf8");
139
+ const env = {
140
+ ...process.env,
141
+ LOBSTER_STATE_DIR: path.join(tmpDir, "state"),
142
+ };
143
+ const first = await runToolRequest({
144
+ filePath,
145
+ ctx: { cwd: tmpDir, env },
146
+ });
147
+ assert.equal(first.ok, true);
148
+ assert.equal(first.status, "needs_input");
149
+ assert.deepEqual(first.requiresInput?.subject, { text: "hello" });
150
+ assert.ok(first.requiresInput?.resumeToken);
151
+ const resumed = await resumeToolRequest({
152
+ token: first.requiresInput?.resumeToken ?? "",
153
+ response: { decision: "approve" },
154
+ ctx: { cwd: tmpDir, env },
155
+ });
156
+ assert.equal(resumed.ok, true);
157
+ assert.equal(resumed.status, "ok");
158
+ assert.deepEqual(resumed.output, [{ decision: "approve", subject: "hello" }]);
159
+ });
160
+ //# sourceMappingURL=core_tool_runtime.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core_tool_runtime.test.js","sourceRoot":"","sources":["../../test/core_tool_runtime.test.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,QAAQ,IAAI,GAAG,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AAEzB,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEzE,SAAS,mBAAmB,CAAC,UAAkB;IAC7C,MAAM,KAAK,GAAmC,EAAE,CAAC;IACjD,OAAO;QACL,KAAK;QACL,OAAO,EAAE;YACP,MAAM,EAAE,MAAM;YACd,KAAK,CAAC,MAAM,CAAC,EAAE,OAAO,EAAwC;gBAC5D,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBACpB,OAAO;oBACL,EAAE,EAAE,IAAI;oBACR,MAAM,EAAE;wBACN,KAAK,EAAE,WAAW;wBAClB,KAAK,EAAE,YAAY;wBACnB,MAAM,EAAE,OAAO,CAAC,MAAM;wBACtB,MAAM,EAAE,WAAW;wBACnB,MAAM,EAAE;4BACN,MAAM,EAAE,MAAM;4BACd,IAAI,EAAE,UAAU;4BAChB,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC;yBAC7B;qBACF;iBACF,CAAC;YACJ,CAAC;SACF;KACF,CAAC;AACJ,CAAC;AAED,IAAI,CAAC,4DAA4D,EAAE,KAAK,IAAI,EAAE;IAC5E,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,mBAAmB,CAAC,gCAAgC,CAAC,CAAC;IACjF,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC;QACpC,QAAQ,EACN,mLAAmL;QACrL,GAAG,EAAE;YACH,GAAG,EAAE;gBACH,GAAG,OAAO,CAAC,GAAG;gBACd,oBAAoB,EAAE,IAAI;gBAC1B,iBAAiB,EAAE,YAAY;aAChC;YACD,WAAW,EAAE;gBACX,EAAE,EAAE,OAAO;aACZ;SACF;KACF,CAAC,CAAC;IAEH,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAChC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACpC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IACzC,MAAM,CAAC,KAAK,CAAE,QAAQ,CAAC,MAAO,CAAC,CAAC,CAAS,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC;IACnF,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC9B,MAAM,CAAC,KAAK,CAAE,KAAK,CAAC,CAAC,CAAS,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;AACtD,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,+EAA+E,EAAE,KAAK,IAAI,EAAE;IAC/F,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,mBAAmB,CAAC,yCAAyC,CAAC,CAAC;IAC1F,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,4BAA4B,CAAC,CAAC,CAAC;IACvF,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAEvD,MAAM,GAAG,CAAC,SAAS,CACjB,QAAQ,EACR,IAAI,CAAC,SAAS,CACZ;QACE,KAAK,EAAE;YACL;gBACE,EAAE,EAAE,OAAO;gBACX,GAAG,EAAE,oFAAoF;aAC1F;YACD;gBACE,EAAE,EAAE,SAAS;gBACb,QAAQ,EAAE,qBAAqB;gBAC/B,KAAK,EAAE,aAAa;aACrB;YACD;gBACE,EAAE,EAAE,QAAQ;gBACZ,QAAQ,EAAE,kEAAkE;gBAC5E,KAAK,EAAE,aAAa;gBACpB,IAAI,EAAE,mBAAmB;aAC1B;SACF;KACF,EACD,IAAI,EACJ,CAAC,CACF,EACD,MAAM,CACP,CAAC;IAEF,MAAM,GAAG,GAAG;QACV,GAAG,OAAO,CAAC,GAAG;QACd,iBAAiB,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC;QAC7C,oBAAoB,EAAE,IAAI;QAC1B,iBAAiB,EAAE,YAAY;KAChC,CAAC;IAEF,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC;QACjC,QAAQ;QACR,GAAG,EAAE;YACH,GAAG,EAAE,MAAM;YACX,GAAG;YACH,WAAW,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE;SAC7B;KACF,CAAC,CAAC;IAEH,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAC7B,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC7C,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,gBAAgB,EAAE,WAAW,CAAC,CAAC;IAE/C,MAAM,OAAO,GAAG,MAAM,iBAAiB,CAAC;QACtC,KAAK,EAAE,KAAK,CAAC,gBAAgB,EAAE,WAAW,IAAI,EAAE;QAChD,QAAQ,EAAE,IAAI;QACd,GAAG,EAAE;YACH,GAAG,EAAE,MAAM;YACX,GAAG;YACH,WAAW,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE;SAC7B;KACF,CAAC,CAAC;IAEH,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAC/B,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACnC,MAAM,CAAC,KAAK,CAAE,OAAO,CAAC,MAAO,CAAC,CAAC,CAAS,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrE,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;AAChC,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,sEAAsE,EAAE,KAAK,IAAI,EAAE;IACtF,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,0BAA0B,CAAC,CAAC,CAAC;IACrF,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAEvD,MAAM,GAAG,CAAC,SAAS,CACjB,QAAQ,EACR,IAAI,CAAC,SAAS,CACZ;QACE,KAAK,EAAE;YACL;gBACE,EAAE,EAAE,OAAO;gBACX,GAAG,EAAE,kEAAkE;aACxE;YACD;gBACE,EAAE,EAAE,QAAQ;gBACZ,KAAK,EAAE;oBACL,MAAM,EAAE,eAAe;oBACvB,cAAc,EAAE;wBACd,IAAI,EAAE,QAAQ;wBACd,UAAU,EAAE,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;wBAC5C,QAAQ,EAAE,CAAC,UAAU,CAAC;qBACvB;iBACF;aACF;YACD;gBACE,EAAE,EAAE,QAAQ;gBACZ,GAAG,EAAE,6GAA6G;gBAClH,GAAG,EAAE;oBACH,QAAQ,EAAE,2BAA2B;oBACrC,OAAO,EAAE,sBAAsB;iBAChC;aACF;SACF;KACF,EACD,IAAI,EACJ,CAAC,CACF,EACD,MAAM,CACP,CAAC;IAEF,MAAM,GAAG,GAAG;QACV,GAAG,OAAO,CAAC,GAAG;QACd,iBAAiB,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC;KAC9C,CAAC;IAEF,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC;QACjC,QAAQ;QACR,GAAG,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE;KAC1B,CAAC,CAAC;IAEH,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAC7B,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC1C,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,aAAa,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IAClE,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC;IAE5C,MAAM,OAAO,GAAG,MAAM,iBAAiB,CAAC;QACtC,KAAK,EAAE,KAAK,CAAC,aAAa,EAAE,WAAW,IAAI,EAAE;QAC7C,QAAQ,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE;QACjC,GAAG,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE;KAC1B,CAAC,CAAC;IAEH,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAC/B,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACnC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;AAChF,CAAC,CAAC,CAAC"}
@@ -0,0 +1,231 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { promises as fsp } from "node:fs";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { PassThrough } from "node:stream";
7
+ import { CostTracker } from "../src/core/cost_tracker.js";
8
+ import { runWorkflowFile } from "../src/workflows/file.js";
9
+ test("CostTracker records usage and computes totals", () => {
10
+ const tracker = new CostTracker();
11
+ tracker.recordUsage("step1", "gpt-4o", { inputTokens: 1000, outputTokens: 500 });
12
+ const summary = tracker.getSummary();
13
+ assert.equal(summary.totalInputTokens, 1000);
14
+ assert.equal(summary.totalOutputTokens, 500);
15
+ assert.equal(summary.estimatedCostUsd, 0.0075);
16
+ assert.equal(summary.byStep.length, 1);
17
+ assert.equal(summary.byStep[0].stepId, "step1");
18
+ });
19
+ test("CostTracker handles OpenAI token field names", () => {
20
+ const tracker = new CostTracker();
21
+ tracker.recordUsage("step1", "gpt-4o", { prompt_tokens: 1000, completion_tokens: 500 });
22
+ const summary = tracker.getSummary();
23
+ assert.equal(summary.totalInputTokens, 1000);
24
+ assert.equal(summary.totalOutputTokens, 500);
25
+ });
26
+ function captureWritable() {
27
+ const stream = new PassThrough();
28
+ let output = "";
29
+ stream.on("data", (d) => {
30
+ output += String(d);
31
+ });
32
+ return { stream, output: () => output };
33
+ }
34
+ test("CostTracker uses zero cost for unknown models and warns once", () => {
35
+ const stderr = captureWritable();
36
+ const tracker = new CostTracker(undefined, stderr.stream);
37
+ tracker.recordUsage("step1", "unknown-model", { inputTokens: 1000, outputTokens: 500 });
38
+ tracker.recordUsage("step2", "unknown-model", { inputTokens: 1000, outputTokens: 500 });
39
+ const summary = tracker.getSummary();
40
+ assert.equal(summary.estimatedCostUsd, 0);
41
+ assert.equal(stderr.output().match(/No LLM pricing configured for model "unknown-model"/g)?.length, 1);
42
+ });
43
+ test("CostTracker warns when usage omits the model id", () => {
44
+ const stderr = captureWritable();
45
+ const tracker = new CostTracker({ "": { input: 100, output: 100 } }, stderr.stream);
46
+ tracker.recordUsage("step1", null, { inputTokens: 1000, outputTokens: 500 });
47
+ tracker.recordUsage("step2", "", { inputTokens: 1000, outputTokens: 500 });
48
+ tracker.recordUsage("step3", " ", { inputTokens: 1000, outputTokens: 500 });
49
+ const summary = tracker.getSummary();
50
+ assert.equal(summary.estimatedCostUsd, 0);
51
+ assert.equal(stderr.output().match(/model "<missing>"/g)?.length, 1);
52
+ });
53
+ test("CostTracker treats inherited object keys as unknown model ids", () => {
54
+ const stderr = captureWritable();
55
+ const tracker = new CostTracker(undefined, stderr.stream);
56
+ tracker.recordUsage("step1", "constructor", { inputTokens: 1000, outputTokens: 500 });
57
+ const summary = tracker.getSummary();
58
+ assert.equal(summary.estimatedCostUsd, 0);
59
+ assert.equal(Number.isNaN(summary.byStep[0].costUsd), false);
60
+ assert.match(stderr.output(), /No LLM pricing configured for model "constructor"/);
61
+ });
62
+ test("CostTracker warns when pricing env json is invalid", () => {
63
+ const stderr = captureWritable();
64
+ const pricing = CostTracker.parsePricingFromEnv({
65
+ LOBSTER_LLM_PRICING_JSON: "{not-json",
66
+ }, stderr.stream);
67
+ assert.equal(pricing, undefined);
68
+ assert.match(stderr.output(), /Ignoring invalid LOBSTER_LLM_PRICING_JSON/);
69
+ });
70
+ test("CostTracker rejects structurally invalid pricing env json", () => {
71
+ const stderr = captureWritable();
72
+ const pricing = CostTracker.parsePricingFromEnv({
73
+ LOBSTER_LLM_PRICING_JSON: '{"my-model":{"input":1.0}}',
74
+ }, stderr.stream);
75
+ assert.equal(pricing, undefined);
76
+ assert.match(stderr.output(), /Ignoring invalid LOBSTER_LLM_PRICING_JSON/);
77
+ });
78
+ test("CostTracker rejects blank pricing model keys", () => {
79
+ const stderr = captureWritable();
80
+ const pricing = CostTracker.parsePricingFromEnv({
81
+ LOBSTER_LLM_PRICING_JSON: '{"":{"input":1.0,"output":2.0}}',
82
+ }, stderr.stream);
83
+ assert.equal(pricing, undefined);
84
+ assert.match(stderr.output(), /Ignoring invalid LOBSTER_LLM_PRICING_JSON/);
85
+ });
86
+ test("CostTracker supports custom pricing from env json", () => {
87
+ const pricing = CostTracker.parsePricingFromEnv({
88
+ LOBSTER_LLM_PRICING_JSON: '{"my-model":{"input":1.0,"output":2.0}}',
89
+ });
90
+ const tracker = new CostTracker(pricing);
91
+ tracker.recordUsage("step1", "my-model", { inputTokens: 1_000_000, outputTokens: 1_000_000 });
92
+ assert.equal(tracker.getSummary().estimatedCostUsd, 3);
93
+ });
94
+ test("CostTracker checkLimit throws when action=stop and limit exceeded", () => {
95
+ const tracker = new CostTracker();
96
+ tracker.recordUsage("step1", "gpt-4o", { inputTokens: 10_000_000, outputTokens: 10_000_000 });
97
+ assert.throws(() => tracker.checkLimit({ max_usd: 0.01, action: "stop" }), /Cost limit exceeded/);
98
+ });
99
+ test("CostTracker checkLimit warns when action=warn and limit exceeded", () => {
100
+ const tracker = new CostTracker();
101
+ tracker.recordUsage("step1", "gpt-4o", { inputTokens: 10_000_000, outputTokens: 10_000_000 });
102
+ const stderr = new PassThrough();
103
+ let out = "";
104
+ stderr.on("data", (d) => {
105
+ out += String(d);
106
+ });
107
+ tracker.checkLimit({ max_usd: 0.01, action: "warn" }, stderr);
108
+ assert.match(out, /\[WARN\] Cost/);
109
+ });
110
+ async function runWorkflow(workflow, envOverride) {
111
+ const tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), "lobster-cost-"));
112
+ const stateDir = path.join(tmpDir, "state");
113
+ const filePath = path.join(tmpDir, "workflow.lobster");
114
+ await fsp.writeFile(filePath, JSON.stringify(workflow, null, 2), "utf8");
115
+ const stderr = new PassThrough();
116
+ let stderrOutput = "";
117
+ stderr.on("data", (d) => {
118
+ stderrOutput += String(d);
119
+ });
120
+ const result = await runWorkflowFile({
121
+ filePath,
122
+ ctx: {
123
+ stdin: process.stdin,
124
+ stdout: process.stdout,
125
+ stderr,
126
+ env: { ...process.env, LOBSTER_STATE_DIR: stateDir, ...envOverride },
127
+ mode: "tool",
128
+ },
129
+ });
130
+ return { result, stderrOutput };
131
+ }
132
+ test("workflow result includes _meta.cost when usage is present", async () => {
133
+ const { result } = await runWorkflow({
134
+ steps: [
135
+ {
136
+ id: "llm",
137
+ command: "node -e \"process.stdout.write(JSON.stringify({model:'gpt-4o',usage:{inputTokens:100,outputTokens:50},output:{text:'hi'}}))\"",
138
+ },
139
+ ],
140
+ });
141
+ assert.equal(result.status, "ok");
142
+ assert.ok(result._meta?.cost);
143
+ assert.equal(result._meta.cost.totalInputTokens, 100);
144
+ assert.equal(result._meta.cost.totalOutputTokens, 50);
145
+ assert.equal(result._meta.cost.byStep[0].model, "gpt-4o");
146
+ });
147
+ test("workflow result omits _meta.cost when no usage exists", async () => {
148
+ const { result } = await runWorkflow({
149
+ steps: [{ id: "plain", command: 'echo "hello"' }],
150
+ });
151
+ assert.equal(result.status, "ok");
152
+ assert.equal(result._meta, undefined);
153
+ });
154
+ test("cost_limit warn logs warning and continues", async () => {
155
+ const { result, stderrOutput } = await runWorkflow({
156
+ cost_limit: { max_usd: 0.00001, action: "warn" },
157
+ steps: [
158
+ {
159
+ id: "llm",
160
+ command: "node -e \"process.stdout.write(JSON.stringify({model:'gpt-4o',usage:{inputTokens:1000,outputTokens:1000}}))\"",
161
+ },
162
+ { id: "after", command: "echo done" },
163
+ ],
164
+ });
165
+ assert.equal(result.status, "ok");
166
+ assert.match(stderrOutput, /\[WARN\] Cost/);
167
+ assert.deepEqual(result.output, ["done\n"]);
168
+ });
169
+ test("cost_limit stop throws when exceeded", async () => {
170
+ await assert.rejects(() => runWorkflow({
171
+ cost_limit: { max_usd: 0.00001, action: "stop" },
172
+ steps: [
173
+ {
174
+ id: "llm",
175
+ command: "node -e \"process.stdout.write(JSON.stringify({model:'gpt-4o',usage:{inputTokens:1000,outputTokens:1000}}))\"",
176
+ },
177
+ ],
178
+ }).then((x) => x.result), /Cost limit exceeded/);
179
+ });
180
+ test("workflow cost tracking warns for unknown model ids", async () => {
181
+ const { result, stderrOutput } = await runWorkflow({
182
+ cost_limit: { max_usd: 0.00001, action: "warn" },
183
+ steps: [
184
+ {
185
+ id: "llm",
186
+ command: "node -e \"process.stdout.write(JSON.stringify({model:'unknown-model',usage:{inputTokens:1000,outputTokens:1000}}))\"",
187
+ },
188
+ ],
189
+ });
190
+ assert.equal(result.status, "ok");
191
+ assert.equal(result._meta?.cost?.estimatedCostUsd, 0);
192
+ assert.match(stderrOutput, /No LLM pricing configured for model "unknown-model"/);
193
+ });
194
+ test("workflow cost tracking warns for invalid pricing env json", async () => {
195
+ const { result, stderrOutput } = await runWorkflow({
196
+ steps: [
197
+ {
198
+ id: "llm",
199
+ command: "node -e \"process.stdout.write(JSON.stringify({model:'gpt-4o',usage:{inputTokens:1000,outputTokens:1000}}))\"",
200
+ },
201
+ ],
202
+ }, { LOBSTER_LLM_PRICING_JSON: "{not-json" });
203
+ assert.equal(result.status, "ok");
204
+ assert.match(stderrOutput, /Ignoring invalid LOBSTER_LLM_PRICING_JSON/);
205
+ });
206
+ test("workflow cost tracking warns when usage omits the model id", async () => {
207
+ const { result, stderrOutput } = await runWorkflow({
208
+ steps: [
209
+ {
210
+ id: "llm",
211
+ command: 'node -e "process.stdout.write(JSON.stringify({usage:{inputTokens:1000,outputTokens:1000}}))"',
212
+ },
213
+ ],
214
+ });
215
+ assert.equal(result.status, "ok");
216
+ assert.equal(result._meta?.cost?.estimatedCostUsd, 0);
217
+ assert.match(stderrOutput, /No LLM pricing configured for model "<missing>"/);
218
+ });
219
+ test("CostTracker escapes unknown model ids in warnings", () => {
220
+ const stderr = captureWritable();
221
+ const tracker = new CostTracker(undefined, stderr.stream);
222
+ tracker.recordUsage("step1", "bad\n\u001b[31m\u009b31m\u2028next\u2029line", {
223
+ inputTokens: 1,
224
+ outputTokens: 1,
225
+ });
226
+ assert.ok(stderr.output().includes('"bad\\n\\u001b[31m\\u009b31m\\u2028next\\u2029line"'));
227
+ assert.equal(stderr.output().includes("\u009b"), false);
228
+ assert.equal(stderr.output().includes("\u2028"), false);
229
+ assert.equal(stderr.output().includes("\u2029"), false);
230
+ });
231
+ //# sourceMappingURL=cost_tracker.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cost_tracker.test.js","sourceRoot":"","sources":["../../test/cost_tracker.test.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,QAAQ,IAAI,GAAG,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAE3D,IAAI,CAAC,+CAA+C,EAAE,GAAG,EAAE;IACzD,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;IAClC,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC;IACjF,MAAM,OAAO,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IACrC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,gBAAgB,EAAE,IAAI,CAAC,CAAC;IAC7C,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;IAC7C,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC;IAC/C,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACvC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAClD,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,8CAA8C,EAAE,GAAG,EAAE;IACxD,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;IAClC,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,iBAAiB,EAAE,GAAG,EAAE,CAAC,CAAC;IACxF,MAAM,OAAO,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IACrC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,gBAAgB,EAAE,IAAI,CAAC,CAAC;IAC7C,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;AAC/C,CAAC,CAAC,CAAC;AAEH,SAAS,eAAe;IACtB,MAAM,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;IACjC,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAkB,EAAE,EAAE;QACvC,MAAM,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC;IACtB,CAAC,CAAC,CAAC;IACH,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC;AAC1C,CAAC;AAED,IAAI,CAAC,8DAA8D,EAAE,GAAG,EAAE;IACxE,MAAM,MAAM,GAAG,eAAe,EAAE,CAAC;IACjC,MAAM,OAAO,GAAG,IAAI,WAAW,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IAC1D,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE,eAAe,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC;IACxF,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE,eAAe,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC;IACxF,MAAM,OAAO,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IACrC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC;IAC1C,MAAM,CAAC,KAAK,CACV,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,sDAAsD,CAAC,EAAE,MAAM,EACrF,CAAC,CACF,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,iDAAiD,EAAE,GAAG,EAAE;IAC3D,MAAM,MAAM,GAAG,eAAe,EAAE,CAAC;IACjC,MAAM,OAAO,GAAG,IAAI,WAAW,CAAC,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IACpF,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC;IAC7E,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC;IAC3E,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC;IAC9E,MAAM,OAAO,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IACrC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC;IAC1C,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;AACvE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,+DAA+D,EAAE,GAAG,EAAE;IACzE,MAAM,MAAM,GAAG,eAAe,EAAE,CAAC;IACjC,MAAM,OAAO,GAAG,IAAI,WAAW,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IAC1D,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE,aAAa,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC;IACtF,MAAM,OAAO,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IACrC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC;IAC1C,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC;IAC7D,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,mDAAmD,CAAC,CAAC;AACrF,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,oDAAoD,EAAE,GAAG,EAAE;IAC9D,MAAM,MAAM,GAAG,eAAe,EAAE,CAAC;IACjC,MAAM,OAAO,GAAG,WAAW,CAAC,mBAAmB,CAC7C;QACE,wBAAwB,EAAE,WAAW;KACtC,EACD,MAAM,CAAC,MAAM,CACd,CAAC;IACF,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IACjC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,2CAA2C,CAAC,CAAC;AAC7E,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,2DAA2D,EAAE,GAAG,EAAE;IACrE,MAAM,MAAM,GAAG,eAAe,EAAE,CAAC;IACjC,MAAM,OAAO,GAAG,WAAW,CAAC,mBAAmB,CAC7C;QACE,wBAAwB,EAAE,4BAA4B;KACvD,EACD,MAAM,CAAC,MAAM,CACd,CAAC;IACF,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IACjC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,2CAA2C,CAAC,CAAC;AAC7E,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,8CAA8C,EAAE,GAAG,EAAE;IACxD,MAAM,MAAM,GAAG,eAAe,EAAE,CAAC;IACjC,MAAM,OAAO,GAAG,WAAW,CAAC,mBAAmB,CAC7C;QACE,wBAAwB,EAAE,iCAAiC;KAC5D,EACD,MAAM,CAAC,MAAM,CACd,CAAC;IACF,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IACjC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,2CAA2C,CAAC,CAAC;AAC7E,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,mDAAmD,EAAE,GAAG,EAAE;IAC7D,MAAM,OAAO,GAAG,WAAW,CAAC,mBAAmB,CAAC;QAC9C,wBAAwB,EAAE,yCAAyC;KACpE,CAAC,CAAC;IACH,MAAM,OAAO,GAAG,IAAI,WAAW,CAAC,OAAO,CAAC,CAAC;IACzC,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,WAAW,EAAE,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC,CAAC;IAC9F,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC;AACzD,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,mEAAmE,EAAE,GAAG,EAAE;IAC7E,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;IAClC,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,CAAC,CAAC;IAC9F,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,qBAAqB,CAAC,CAAC;AACpG,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,kEAAkE,EAAE,GAAG,EAAE;IAC5E,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;IAClC,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,CAAC,CAAC;IAC9F,MAAM,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;IACjC,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAkB,EAAE,EAAE;QACvC,GAAG,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC;IACnB,CAAC,CAAC,CAAC;IACH,OAAO,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,MAAM,CAAC,CAAC;IAC9D,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;AACrC,CAAC,CAAC,CAAC;AAEH,KAAK,UAAU,WAAW,CAAC,QAAiB,EAAE,WAAoC;IAChF,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,eAAe,CAAC,CAAC,CAAC;IAC1E,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5C,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IACvD,MAAM,GAAG,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IACzE,MAAM,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;IACjC,IAAI,YAAY,GAAG,EAAE,CAAC;IACtB,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAkB,EAAE,EAAE;QACvC,YAAY,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC;QACnC,QAAQ;QACR,GAAG,EAAE;YACH,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,MAAM;YACN,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,iBAAiB,EAAE,QAAQ,EAAE,GAAG,WAAW,EAAE;YACpE,IAAI,EAAE,MAAM;SACb;KACF,CAAC,CAAC;IAEH,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;AAClC,CAAC;AAED,IAAI,CAAC,2DAA2D,EAAE,KAAK,IAAI,EAAE;IAC3E,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,WAAW,CAAC;QACnC,KAAK,EAAE;YACL;gBACE,EAAE,EAAE,KAAK;gBACT,OAAO,EACL,+HAA+H;aAClI;SACF;KACF,CAAC,CAAC;IAEH,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAClC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC9B,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,KAAM,CAAC,IAAK,CAAC,gBAAgB,EAAE,GAAG,CAAC,CAAC;IACxD,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,KAAM,CAAC,IAAK,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAC;IACxD,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,KAAM,CAAC,IAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;AAC9D,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,uDAAuD,EAAE,KAAK,IAAI,EAAE;IACvE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,WAAW,CAAC;QACnC,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC;KAClD,CAAC,CAAC;IACH,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAClC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;AACxC,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;IAC5D,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,WAAW,CAAC;QACjD,UAAU,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE;QAChD,KAAK,EAAE;YACL;gBACE,EAAE,EAAE,KAAK;gBACT,OAAO,EACL,+GAA+G;aAClH;YACD,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE;SACtC;KACF,CAAC,CAAC;IACH,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAClC,MAAM,CAAC,KAAK,CAAC,YAAY,EAAE,eAAe,CAAC,CAAC;IAC5C,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;AAC9C,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;IACtD,MAAM,MAAM,CAAC,OAAO,CAClB,GAAG,EAAE,CACH,WAAW,CAAC;QACV,UAAU,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE;QAChD,KAAK,EAAE;YACL;gBACE,EAAE,EAAE,KAAK;gBACT,OAAO,EACL,+GAA+G;aAClH;SACF;KACF,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,EAC1B,qBAAqB,CACtB,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;IACpE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,WAAW,CAAC;QACjD,UAAU,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE;QAChD,KAAK,EAAE;YACL;gBACE,EAAE,EAAE,KAAK;gBACT,OAAO,EACL,sHAAsH;aACzH;SACF;KACF,CAAC,CAAC;IAEH,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAClC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAC;IACtD,MAAM,CAAC,KAAK,CAAC,YAAY,EAAE,qDAAqD,CAAC,CAAC;AACpF,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,2DAA2D,EAAE,KAAK,IAAI,EAAE;IAC3E,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,WAAW,CAChD;QACE,KAAK,EAAE;YACL;gBACE,EAAE,EAAE,KAAK;gBACT,OAAO,EACL,+GAA+G;aAClH;SACF;KACF,EACD,EAAE,wBAAwB,EAAE,WAAW,EAAE,CAC1C,CAAC;IAEF,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAClC,MAAM,CAAC,KAAK,CAAC,YAAY,EAAE,2CAA2C,CAAC,CAAC;AAC1E,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,4DAA4D,EAAE,KAAK,IAAI,EAAE;IAC5E,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,WAAW,CAAC;QACjD,KAAK,EAAE;YACL;gBACE,EAAE,EAAE,KAAK;gBACT,OAAO,EACL,8FAA8F;aACjG;SACF;KACF,CAAC,CAAC;IAEH,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAClC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAC;IACtD,MAAM,CAAC,KAAK,CAAC,YAAY,EAAE,iDAAiD,CAAC,CAAC;AAChF,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,mDAAmD,EAAE,GAAG,EAAE;IAC7D,MAAM,MAAM,GAAG,eAAe,EAAE,CAAC;IACjC,MAAM,OAAO,GAAG,IAAI,WAAW,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IAC1D,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE,8CAA8C,EAAE;QAC3E,WAAW,EAAE,CAAC;QACd,YAAY,EAAE,CAAC;KAChB,CAAC,CAAC;IACH,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,qDAAqD,CAAC,CAAC,CAAC;IAC3F,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAC,CAAC;IACxD,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAC,CAAC;IACxD,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAC,CAAC;AAC1D,CAAC,CAAC,CAAC"}
@@ -0,0 +1,48 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { runPipeline } from "../src/runtime.js";
4
+ import { createDefaultRegistry } from "../src/commands/registry.js";
5
+ import { parsePipeline } from "../src/parser.js";
6
+ async function run(pipelineText, input) {
7
+ const pipeline = parsePipeline(pipelineText);
8
+ const registry = createDefaultRegistry();
9
+ const res = await runPipeline({
10
+ pipeline,
11
+ registry,
12
+ stdin: process.stdin,
13
+ stdout: process.stdout,
14
+ stderr: process.stderr,
15
+ env: process.env,
16
+ mode: "tool",
17
+ input: (async function* () {
18
+ for (const x of input)
19
+ yield x;
20
+ })(),
21
+ });
22
+ return res.items;
23
+ }
24
+ test("dedupe removes duplicate primitives (stable)", async () => {
25
+ const out = await run("dedupe", [1, 2, 1, 3, 2]);
26
+ assert.deepEqual(out, [1, 2, 3]);
27
+ });
28
+ test("dedupe supports --key", async () => {
29
+ const input = [
30
+ { id: "a", v: 1 },
31
+ { id: "b", v: 2 },
32
+ { id: "a", v: 3 },
33
+ ];
34
+ const out = await run("dedupe --key id", input);
35
+ assert.deepEqual(out, [input[0], input[1]]);
36
+ });
37
+ test("dedupe treats undefined keys as a key value", async () => {
38
+ const input = [
39
+ { id: undefined, v: 1 },
40
+ { id: undefined, v: 2 },
41
+ { id: "x", v: 3 },
42
+ ];
43
+ const out = await run("dedupe --key id", input);
44
+ assert.equal(out.length, 2);
45
+ assert.equal(out[0].v, 1);
46
+ assert.equal(out[1].v, 3);
47
+ });
48
+ //# sourceMappingURL=dedupe.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dedupe.test.js","sourceRoot":"","sources":["../../test/dedupe.test.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,oBAAoB,CAAC;AAExC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,KAAK,UAAU,GAAG,CAAC,YAAoB,EAAE,KAAY;IACnD,MAAM,QAAQ,GAAG,aAAa,CAAC,YAAY,CAAC,CAAC;IAC7C,MAAM,QAAQ,GAAG,qBAAqB,EAAE,CAAC;IACzC,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC;QAC5B,QAAQ;QACR,QAAQ;QACR,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,IAAI,EAAE,MAAM;QACZ,KAAK,EAAE,CAAC,KAAK,SAAS,CAAC;YACrB,KAAK,MAAM,CAAC,IAAI,KAAK;gBAAE,MAAM,CAAC,CAAC;QACjC,CAAC,CAAC,EAAE;KACL,CAAC,CAAC;IACH,OAAO,GAAG,CAAC,KAAK,CAAC;AACnB,CAAC;AAED,IAAI,CAAC,8CAA8C,EAAE,KAAK,IAAI,EAAE;IAC9D,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACjD,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AACnC,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,uBAAuB,EAAE,KAAK,IAAI,EAAE;IACvC,MAAM,KAAK,GAAG;QACZ,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE;QACjB,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE;QACjB,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE;KAClB,CAAC;IACF,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAAC;IAChD,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC9C,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;IAC7D,MAAM,KAAK,GAAG;QACZ,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,EAAE;QACvB,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,EAAE;QACvB,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE;KAClB,CAAC;IACF,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAAC;IAChD,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC5B,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC1B,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAC5B,CAAC,CAAC,CAAC"}