@openclaw/lobster 2026.9.5 → 2026.9.7

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 (163) hide show
  1. package/assets/icon.png +0 -0
  2. package/dist/index.js +8 -294
  3. package/node_modules/@clawdbot/lobster/README.md +20 -133
  4. package/node_modules/@clawdbot/lobster/VISION.md +15 -238
  5. package/node_modules/@clawdbot/lobster/bin/clawd.invoke.js +2 -21
  6. package/node_modules/@clawdbot/lobster/bin/invoke.js +19 -0
  7. package/node_modules/@clawdbot/lobster/bin/lobster.js +2 -21
  8. package/node_modules/@clawdbot/lobster/bin/openclaw.invoke.js +2 -24
  9. package/node_modules/@clawdbot/lobster/dist/src/cli.js +32 -48
  10. package/node_modules/@clawdbot/lobster/dist/src/cli.js.map +1 -1
  11. package/node_modules/@clawdbot/lobster/dist/src/command_tokens.js +81 -0
  12. package/node_modules/@clawdbot/lobster/dist/src/command_tokens.js.map +1 -0
  13. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/dedupe.js +1 -12
  14. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/dedupe.js.map +1 -1
  15. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/group_by.js +2 -14
  16. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/group_by.js.map +1 -1
  17. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/llm_invoke.js +1 -337
  18. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/llm_invoke.js.map +1 -1
  19. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/map.js +2 -13
  20. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/map.js.map +1 -1
  21. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/sort.js +4 -21
  22. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/sort.js.map +1 -1
  23. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/state.js +1 -1
  24. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/state.js.map +1 -1
  25. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/template.js +2 -13
  26. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/template.js.map +1 -1
  27. package/node_modules/@clawdbot/lobster/dist/src/commands/workflows/workflows_run.js +0 -18
  28. package/node_modules/@clawdbot/lobster/dist/src/commands/workflows/workflows_run.js.map +1 -1
  29. package/node_modules/@clawdbot/lobster/dist/src/core/llm_accounting.js +304 -0
  30. package/node_modules/@clawdbot/lobster/dist/src/core/llm_accounting.js.map +1 -0
  31. package/node_modules/@clawdbot/lobster/dist/src/core/value_path.js +11 -0
  32. package/node_modules/@clawdbot/lobster/dist/src/core/value_path.js.map +1 -0
  33. package/node_modules/@clawdbot/lobster/dist/src/parser.js +2 -67
  34. package/node_modules/@clawdbot/lobster/dist/src/parser.js.map +1 -1
  35. package/node_modules/@clawdbot/lobster/dist/src/recipes/github/index.js +1 -1
  36. package/node_modules/@clawdbot/lobster/dist/src/recipes/github/pr-monitor.js +5 -64
  37. package/node_modules/@clawdbot/lobster/dist/src/recipes/github/pr-monitor.js.map +1 -1
  38. package/node_modules/@clawdbot/lobster/dist/src/recipes/github/read_pr.js +45 -0
  39. package/node_modules/@clawdbot/lobster/dist/src/recipes/github/read_pr.js.map +1 -0
  40. package/node_modules/@clawdbot/lobster/dist/src/recipes/github/snapshot.js +45 -0
  41. package/node_modules/@clawdbot/lobster/dist/src/recipes/github/snapshot.js.map +1 -0
  42. package/node_modules/@clawdbot/lobster/dist/src/recipes/github/stages/pr-view.js +9 -47
  43. package/node_modules/@clawdbot/lobster/dist/src/recipes/github/stages/pr-view.js.map +1 -1
  44. package/node_modules/@clawdbot/lobster/dist/src/sdk/Lobster.js +18 -135
  45. package/node_modules/@clawdbot/lobster/dist/src/sdk/Lobster.js.map +1 -1
  46. package/node_modules/@clawdbot/lobster/dist/src/sdk/index.js +1 -1
  47. package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/approve.js +1 -2
  48. package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/approve.js.map +1 -1
  49. package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/diff.js +2 -8
  50. package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/diff.js.map +1 -1
  51. package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/exec.js +15 -81
  52. package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/exec.js.map +1 -1
  53. package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/state.js +3 -9
  54. package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/state.js.map +1 -1
  55. package/node_modules/@clawdbot/lobster/dist/src/sdk/runtime.js +0 -12
  56. package/node_modules/@clawdbot/lobster/dist/src/sdk/runtime.js.map +1 -1
  57. package/node_modules/@clawdbot/lobster/dist/src/sdk/state_env.js +6 -0
  58. package/node_modules/@clawdbot/lobster/dist/src/sdk/state_env.js.map +1 -0
  59. package/node_modules/@clawdbot/lobster/dist/src/state/atomic_file.js +192 -0
  60. package/node_modules/@clawdbot/lobster/dist/src/state/atomic_file.js.map +1 -0
  61. package/node_modules/@clawdbot/lobster/dist/src/state/store.js +2 -188
  62. package/node_modules/@clawdbot/lobster/dist/src/state/store.js.map +1 -1
  63. package/node_modules/@clawdbot/lobster/dist/src/workflows/dry_run.js +262 -0
  64. package/node_modules/@clawdbot/lobster/dist/src/workflows/dry_run.js.map +1 -0
  65. package/node_modules/@clawdbot/lobster/dist/src/workflows/expressions.js +345 -0
  66. package/node_modules/@clawdbot/lobster/dist/src/workflows/expressions.js.map +1 -0
  67. package/node_modules/@clawdbot/lobster/dist/src/workflows/file.js +21 -1036
  68. package/node_modules/@clawdbot/lobster/dist/src/workflows/file.js.map +1 -1
  69. package/node_modules/@clawdbot/lobster/dist/src/workflows/github_pr_monitor.js +8 -77
  70. package/node_modules/@clawdbot/lobster/dist/src/workflows/github_pr_monitor.js.map +1 -1
  71. package/node_modules/@clawdbot/lobster/dist/src/workflows/graph.js +1 -7
  72. package/node_modules/@clawdbot/lobster/dist/src/workflows/graph.js.map +1 -1
  73. package/node_modules/@clawdbot/lobster/dist/src/workflows/load.js +347 -0
  74. package/node_modules/@clawdbot/lobster/dist/src/workflows/load.js.map +1 -0
  75. package/node_modules/@clawdbot/lobster/dist/src/workflows/types.js +2 -0
  76. package/node_modules/@clawdbot/lobster/dist/src/workflows/types.js.map +1 -0
  77. package/node_modules/@clawdbot/lobster/dist/src/workflows/values.js +93 -0
  78. package/node_modules/@clawdbot/lobster/dist/src/workflows/values.js.map +1 -0
  79. package/node_modules/@clawdbot/lobster/dist/test/cancellation.test.js +1 -0
  80. package/node_modules/@clawdbot/lobster/dist/test/cancellation.test.js.map +1 -1
  81. package/node_modules/@clawdbot/lobster/dist/test/cli_run_errors.test.js +53 -0
  82. package/node_modules/@clawdbot/lobster/dist/test/cli_run_errors.test.js.map +1 -0
  83. package/node_modules/@clawdbot/lobster/dist/test/command_arguments.test.js +82 -0
  84. package/node_modules/@clawdbot/lobster/dist/test/command_arguments.test.js.map +1 -0
  85. package/node_modules/@clawdbot/lobster/dist/test/cost_tracker.test.js +49 -19
  86. package/node_modules/@clawdbot/lobster/dist/test/cost_tracker.test.js.map +1 -1
  87. package/node_modules/@clawdbot/lobster/dist/test/helpers/node_shell_fixture.js +17 -0
  88. package/node_modules/@clawdbot/lobster/dist/test/helpers/node_shell_fixture.js.map +1 -0
  89. package/node_modules/@clawdbot/lobster/dist/test/llm_invoke.test.js +2 -0
  90. package/node_modules/@clawdbot/lobster/dist/test/llm_invoke.test.js.map +1 -1
  91. package/node_modules/@clawdbot/lobster/dist/test/pipeline_resume_state.test.js +65 -0
  92. package/node_modules/@clawdbot/lobster/dist/test/pipeline_resume_state.test.js.map +1 -0
  93. package/node_modules/@clawdbot/lobster/dist/test/process_output_limit.test.js +5 -3
  94. package/node_modules/@clawdbot/lobster/dist/test/process_output_limit.test.js.map +1 -1
  95. package/node_modules/@clawdbot/lobster/dist/test/sdk_exec.test.js +72 -25
  96. package/node_modules/@clawdbot/lobster/dist/test/sdk_exec.test.js.map +1 -1
  97. package/node_modules/@clawdbot/lobster/dist/test/workflow_graph.test.js +1 -1
  98. package/node_modules/@clawdbot/lobster/dist/test/workflow_graph.test.js.map +1 -1
  99. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/README.md +1 -0
  100. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/SECURITY.md +62 -13
  101. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/index.js +40 -9
  102. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/lib/schemes.js +76 -16
  103. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/lib/utils.js +54 -10
  104. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/package.json +1 -1
  105. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/fixtures/uri-js-parse.json +1 -0
  106. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/ipv6-validation.test.js +130 -0
  107. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/mailto-authority-security.test.js +125 -0
  108. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/mailto-field-name-security.test.js +151 -0
  109. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/security-normalization.test.js +38 -0
  110. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/types/index.d.ts +16 -0
  111. package/node_modules/@clawdbot/lobster/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +19 -18
  112. package/node_modules/@clawdbot/lobster/node_modules/yaml/browser/dist/nodes/Alias.js +25 -23
  113. package/node_modules/@clawdbot/lobster/node_modules/yaml/dist/compose/resolve-flow-scalar.js +19 -18
  114. package/node_modules/@clawdbot/lobster/node_modules/yaml/dist/nodes/Alias.js +25 -23
  115. package/node_modules/@clawdbot/lobster/node_modules/yaml/package.json +1 -1
  116. package/node_modules/@clawdbot/lobster/package.json +6 -8
  117. package/node_modules/typebox/build/compile/code.mjs +1 -2
  118. package/node_modules/typebox/build/compile/validator.mjs +5 -1
  119. package/node_modules/typebox/build/format/iri.mjs +16 -9
  120. package/node_modules/typebox/build/guard/emit.mjs +2 -2
  121. package/node_modules/typebox/build/guard/guard.d.mts +7 -5
  122. package/node_modules/typebox/build/guard/guard.mjs +14 -9
  123. package/node_modules/typebox/build/guard/index.mjs +6 -0
  124. package/node_modules/typebox/build/guard/unicode/unicode.d.mts +6 -0
  125. package/node_modules/typebox/build/guard/unicode/unicode.mjs +35 -0
  126. package/node_modules/typebox/build/guard/{string.mjs → unicode/unicode_segment.mjs} +16 -19
  127. package/node_modules/typebox/build/schema/build.mjs +2 -3
  128. package/node_modules/typebox/build/schema/engine/_pathing.d.mts +1 -0
  129. package/node_modules/typebox/build/schema/engine/_pathing.mjs +11 -0
  130. package/node_modules/typebox/build/schema/engine/_refine.mjs +13 -5
  131. package/node_modules/typebox/build/schema/engine/additionalProperties.mjs +2 -1
  132. package/node_modules/typebox/build/schema/engine/dependencies.mjs +2 -1
  133. package/node_modules/typebox/build/schema/engine/dependentSchemas.mjs +2 -1
  134. package/node_modules/typebox/build/schema/engine/dynamicRef.d.mts +1 -1
  135. package/node_modules/typebox/build/schema/engine/dynamicRef.mjs +2 -2
  136. package/node_modules/typebox/build/schema/engine/format.mjs +9 -2
  137. package/node_modules/typebox/build/schema/engine/patternProperties.mjs +3 -2
  138. package/node_modules/typebox/build/schema/engine/properties.mjs +3 -2
  139. package/node_modules/typebox/build/schema/engine/propertyNames.mjs +2 -1
  140. package/node_modules/typebox/build/schema/engine/recursiveRef.d.mts +1 -1
  141. package/node_modules/typebox/build/schema/engine/recursiveRef.mjs +2 -2
  142. package/node_modules/typebox/build/schema/engine/ref.d.mts +1 -1
  143. package/node_modules/typebox/build/schema/engine/ref.mjs +2 -2
  144. package/node_modules/typebox/build/schema/engine/schema.mjs +24 -24
  145. package/node_modules/typebox/build/schema/parse.mjs +3 -0
  146. package/node_modules/typebox/build/schema/pointer/pointer.d.mts +1 -6
  147. package/node_modules/typebox/build/schema/pointer/pointer.mjs +47 -64
  148. package/node_modules/typebox/build/system/settings/internal.d.mts +2 -0
  149. package/node_modules/typebox/build/system/settings/internal.mjs +20 -0
  150. package/node_modules/typebox/build/system/settings/settings.d.mts +11 -0
  151. package/node_modules/typebox/build/system/settings/settings.mjs +14 -0
  152. package/node_modules/typebox/build/type/extends/inference.d.mts +1 -1
  153. package/node_modules/typebox/build/type/extends/inference.mjs +15 -2
  154. package/node_modules/typebox/build/type/extends/tuple.d.mts +2 -1
  155. package/node_modules/typebox/build/type/extends/tuple.mjs +21 -1
  156. package/node_modules/typebox/build/value/parse/parse.mjs +10 -4
  157. package/node_modules/typebox/package.json +1 -1
  158. package/node_modules/typebox/readme.md +3 -1
  159. package/package.json +6 -6
  160. package/dist/runtime-api.js +0 -3
  161. package/node_modules/@clawdbot/lobster/dist/src/sdk/token.js +0 -9
  162. package/node_modules/@clawdbot/lobster/dist/src/sdk/token.js.map +0 -1
  163. /package/node_modules/typebox/build/guard/{string.d.mts → unicode/unicode_segment.d.mts} +0 -0
@@ -0,0 +1,81 @@
1
+ export function tokenizeCommand(input, dialect = "pipeline") {
2
+ const tokens = [];
3
+ let current = "";
4
+ let quote = null;
5
+ let tokenStarted = false;
6
+ const push = () => {
7
+ if (tokenStarted)
8
+ tokens.push(current);
9
+ current = "";
10
+ tokenStarted = false;
11
+ };
12
+ for (let i = 0; i < input.length; i++) {
13
+ const ch = input[i];
14
+ if (quote) {
15
+ // The public SDK command string has historically unescaped any quoted character.
16
+ if (dialect === "sdk") {
17
+ if (ch === "\\" && input[i + 1]) {
18
+ current += input[++i];
19
+ continue;
20
+ }
21
+ if (ch === quote) {
22
+ quote = null;
23
+ continue;
24
+ }
25
+ current += ch;
26
+ continue;
27
+ }
28
+ if (quote === "'") {
29
+ if (ch === "\\" && input[i + 1] === quote) {
30
+ current += quote;
31
+ i++;
32
+ continue;
33
+ }
34
+ if (ch === quote) {
35
+ quote = null;
36
+ continue;
37
+ }
38
+ current += ch;
39
+ continue;
40
+ }
41
+ // Double-quoted mode: preserve unknown escapes (\n, \t, etc) while
42
+ // unescaping only shell-like quote/backslash escapes.
43
+ if (ch === "\\") {
44
+ const next = input[i + 1];
45
+ if (next === '"' || next === "\\" || next === "$" || next === "`") {
46
+ current += next;
47
+ i++;
48
+ continue;
49
+ }
50
+ if (next === "\n") {
51
+ i++;
52
+ continue;
53
+ }
54
+ current += ch;
55
+ continue;
56
+ }
57
+ if (ch === quote) {
58
+ quote = null;
59
+ continue;
60
+ }
61
+ current += ch;
62
+ continue;
63
+ }
64
+ if (ch === '"' || ch === "'") {
65
+ quote = ch;
66
+ tokenStarted = true;
67
+ continue;
68
+ }
69
+ if (ch === " " || ch === "\t" || (dialect === "pipeline" && (ch === "\n" || ch === "\r"))) {
70
+ push();
71
+ continue;
72
+ }
73
+ tokenStarted = true;
74
+ current += ch;
75
+ }
76
+ if (quote && dialect === "pipeline")
77
+ throw new Error("Unclosed quote");
78
+ push();
79
+ return tokens;
80
+ }
81
+ //# sourceMappingURL=command_tokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command_tokens.js","sourceRoot":"","sources":["../../src/command_tokens.ts"],"names":[],"mappings":"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"}
@@ -1,15 +1,4 @@
1
- function getByPath(obj, path) {
2
- if (!path)
3
- return obj;
4
- const parts = path.split(".").filter(Boolean);
5
- let cur = obj;
6
- for (const p of parts) {
7
- if (cur == null)
8
- return undefined;
9
- cur = cur[p];
10
- }
11
- return cur;
12
- }
1
+ import { getByPath } from "../../core/value_path.js";
13
2
  export const dedupeCommand = {
14
3
  name: "dedupe",
15
4
  meta: {
@@ -1 +1 @@
1
- {"version":3,"file":"dedupe.js","sourceRoot":"","sources":["../../../../src/commands/stdlib/dedupe.ts"],"names":[],"mappings":"AAAA,SAAS,SAAS,CAAC,GAAQ,EAAE,IAAY;IACxC,IAAI,CAAC,IAAI;QAAE,OAAO,GAAG,CAAC;IACtB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC9C,IAAI,GAAG,GAAQ,GAAG,CAAC;IACnB,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACvB,IAAI,GAAG,IAAI,IAAI;YAAE,OAAO,SAAS,CAAC;QAClC,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;IACd,CAAC;IACD,OAAO,GAAG,CAAC;AACZ,CAAC;AAED,MAAM,CAAC,MAAM,aAAa,GAAG;IAC5B,IAAI,EAAE,QAAQ;IACd,IAAI,EAAE;QACL,WAAW,EAAE,2DAA2D;QACxE,UAAU,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACX,GAAG,EAAE;oBACJ,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,yDAAyD;iBACtE;gBACD,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;aAC/C;YACD,QAAQ,EAAE,EAAE;SACZ;QACD,WAAW,EAAE,EAAE;KACf;IACD,IAAI;QACH,OAAO,CACN,8CAA8C;YAC9C,UAAU;YACV,kBAAkB;YAClB,6BAA6B;YAC7B,UAAU;YACV,mCAAmC,CACnC,CAAC;IACH,CAAC;IACD,KAAK,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,IAAI,EAAO;QAC7B,MAAM,GAAG,GAAG,OAAO,IAAI,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;QAChE,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAE/B,OAAO;YACN,MAAM,EAAE,CAAC,KAAK,SAAS,CAAC;gBACvB,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;oBAChC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;oBAC7C,MAAM,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;oBAC7B,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;wBAAE,SAAS;oBAC1B,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;oBACZ,MAAM,IAAI,CAAC;gBACZ,CAAC;YACF,CAAC,CAAC,EAAE;SACJ,CAAC;IACH,CAAC;CACD,CAAC"}
1
+ {"version":3,"file":"dedupe.js","sourceRoot":"","sources":["../../../../src/commands/stdlib/dedupe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAErD,MAAM,CAAC,MAAM,aAAa,GAAG;IAC5B,IAAI,EAAE,QAAQ;IACd,IAAI,EAAE;QACL,WAAW,EAAE,2DAA2D;QACxE,UAAU,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACX,GAAG,EAAE;oBACJ,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,yDAAyD;iBACtE;gBACD,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;aAC/C;YACD,QAAQ,EAAE,EAAE;SACZ;QACD,WAAW,EAAE,EAAE;KACf;IACD,IAAI;QACH,OAAO,CACN,8CAA8C;YAC9C,UAAU;YACV,kBAAkB;YAClB,6BAA6B;YAC7B,UAAU;YACV,mCAAmC,CACnC,CAAC;IACH,CAAC;IACD,KAAK,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,IAAI,EAAO;QAC7B,MAAM,GAAG,GAAG,OAAO,IAAI,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;QAChE,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAE/B,OAAO;YACN,MAAM,EAAE,CAAC,KAAK,SAAS,CAAC;gBACvB,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;oBAChC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;oBAC7C,MAAM,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;oBAC7B,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;wBAAE,SAAS;oBAC1B,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;oBACZ,MAAM,IAAI,CAAC;gBACZ,CAAC;YACF,CAAC,CAAC,EAAE;SACJ,CAAC;IACH,CAAC;CACD,CAAC"}
@@ -1,13 +1,4 @@
1
- function getByPath(obj, path) {
2
- const parts = path.split(".").filter(Boolean);
3
- let cur = obj;
4
- for (const p of parts) {
5
- if (cur == null)
6
- return undefined;
7
- cur = cur[p];
8
- }
9
- return cur;
10
- }
1
+ import { getByPath } from "../../core/value_path.js";
11
2
  export const groupByCommand = {
12
3
  name: "groupBy",
13
4
  meta: {
@@ -36,20 +27,17 @@ export const groupByCommand = {
36
27
  if (!keyPath)
37
28
  throw new Error("groupBy requires --key");
38
29
  const groups = new Map();
39
- const order = [];
40
30
  for await (const item of input) {
41
31
  const keyVal = getByPath(item, keyPath);
42
32
  const k = JSON.stringify(keyVal);
43
33
  if (!groups.has(k)) {
44
34
  groups.set(k, { key: keyVal, items: [] });
45
- order.push(k);
46
35
  }
47
36
  groups.get(k).items.push(item);
48
37
  }
49
38
  return {
50
39
  output: (async function* () {
51
- for (const k of order) {
52
- const g = groups.get(k);
40
+ for (const g of groups.values()) {
53
41
  yield { key: g.key, items: g.items, count: g.items.length };
54
42
  }
55
43
  })(),
@@ -1 +1 @@
1
- {"version":3,"file":"group_by.js","sourceRoot":"","sources":["../../../../src/commands/stdlib/group_by.ts"],"names":[],"mappings":"AAAA,SAAS,SAAS,CAAC,GAAQ,EAAE,IAAY;IACxC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC9C,IAAI,GAAG,GAAQ,GAAG,CAAC;IACnB,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACvB,IAAI,GAAG,IAAI,IAAI;YAAE,OAAO,SAAS,CAAC;QAClC,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;IACd,CAAC;IACD,OAAO,GAAG,CAAC;AACZ,CAAC;AAED,MAAM,CAAC,MAAM,cAAc,GAAG;IAC7B,IAAI,EAAE,SAAS;IACf,IAAI,EAAE;QACL,WAAW,EAAE,2CAA2C;QACxD,UAAU,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACX,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,qCAAqC,EAAE;gBAC3E,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;aAC/C;YACD,QAAQ,EAAE,CAAC,KAAK,CAAC;SACjB;QACD,WAAW,EAAE,EAAE;KACf;IACD,IAAI;QACH,OAAO,CACN,oCAAoC;YACpC,UAAU;YACV,gCAAgC;YAChC,WAAW;YACX,+CAA+C;YAC/C,UAAU;YACV,0DAA0D,CAC1D,CAAC;IACH,CAAC;IACD,KAAK,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,IAAI,EAAO;QAC7B,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAC9C,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;QAExD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAsC,CAAC;QAC7D,MAAM,KAAK,GAAa,EAAE,CAAC;QAE3B,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YAChC,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YACxC,MAAM,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;YACjC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;gBACpB,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;gBAC1C,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACf,CAAC;YACD,MAAM,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjC,CAAC;QAED,OAAO;YACN,MAAM,EAAE,CAAC,KAAK,SAAS,CAAC;gBACvB,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;oBACvB,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC;oBACzB,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;gBAC7D,CAAC;YACF,CAAC,CAAC,EAAE;SACJ,CAAC;IACH,CAAC;CACD,CAAC"}
1
+ {"version":3,"file":"group_by.js","sourceRoot":"","sources":["../../../../src/commands/stdlib/group_by.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAErD,MAAM,CAAC,MAAM,cAAc,GAAG;IAC7B,IAAI,EAAE,SAAS;IACf,IAAI,EAAE;QACL,WAAW,EAAE,2CAA2C;QACxD,UAAU,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACX,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,qCAAqC,EAAE;gBAC3E,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;aAC/C;YACD,QAAQ,EAAE,CAAC,KAAK,CAAC;SACjB;QACD,WAAW,EAAE,EAAE;KACf;IACD,IAAI;QACH,OAAO,CACN,oCAAoC;YACpC,UAAU;YACV,gCAAgC;YAChC,WAAW;YACX,+CAA+C;YAC/C,UAAU;YACV,0DAA0D,CAC1D,CAAC;IACH,CAAC;IACD,KAAK,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,IAAI,EAAO;QAC7B,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAC9C,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;QAExD,MAAM,MAAM,GAAG,IAAI,GAAG,EAA8C,CAAC;QAErE,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YAChC,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YACxC,MAAM,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;YACjC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;gBACpB,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;YAC3C,CAAC;YACD,MAAM,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjC,CAAC;QAED,OAAO;YACN,MAAM,EAAE,CAAC,KAAK,SAAS,CAAC;gBACvB,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC;oBACjC,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;gBAC7D,CAAC;YACF,CAAC,CAAC,EAAE;SACJ,CAAC;IACH,CAAC;CACD,CAAC"}
@@ -1,8 +1,8 @@
1
+ import { markLlmItem } from "../../core/llm_accounting.js";
1
2
  import path from "node:path";
2
3
  import { promises as fsp } from "node:fs";
3
4
  import { createHash } from "node:crypto";
4
5
  import { Ajv } from "ajv";
5
- import { billableTokens } from "../../core/cost_tracker.js";
6
6
  import { diffAndStore, ensureDirectory, atomicWriteWasPublished, isJsonSyntaxError, readStateJsonWithLock, stableStringify, withFileLock, writeFileAtomic, } from "../../state/store.js";
7
7
  import { createCompileCached } from "../../validation.js";
8
8
  import { httpResponseLimitFromEnv, readResponseTextCapped } from "../../read_response_text.js";
@@ -93,325 +93,6 @@ const DEFAULT_MAX_VALIDATION_RETRIES = 1;
93
93
  const STATE_VERSION = 1;
94
94
  // Identity version of the response cache key. See computeCacheKey.
95
95
  const CACHE_KEY_VERSION = 2;
96
- // Provenance for an item this module emitted: which stored answer it belongs to, and whether
97
- // it came from a provider call or from run state / the response cache.
98
- // A symbol key cannot come out of `JSON.parse`, so the JSON a workflow step reads from a
99
- // command's stdout can never carry it: only objects built here, in this process, take part in
100
- // the replay exemption in workflow cost accounting.
101
- const LLM_PROVENANCE = Symbol("lobster.llm.provenance");
102
- /**
103
- * Stamps provenance on a value this module is emitting. The property is enumerable so a
104
- * downstream `{ ...value }` keeps it, and `JSON.stringify` ignores symbol keys, so serialized
105
- * output — including what is written to the cache — is unchanged.
106
- */
107
- function markProvenance(value, provenance) {
108
- return Object.defineProperty(value, LLM_PROVENANCE, {
109
- value: provenance,
110
- enumerable: true,
111
- configurable: true,
112
- });
113
- }
114
- /**
115
- * Marks an item this module is emitting, and its usage record with it. A projection such as
116
- * `pick model,usage` builds a new object out of named fields, so the item's own mark does not
117
- * reach the consumer — but the usage record crosses by reference, and the usage record is what
118
- * gets billed. Marking both means provenance survives an in-process projection without the
119
- * consumer having to recognize every command that can build one.
120
- */
121
- function markLlmItem(item, provenance) {
122
- if (item.usage && typeof item.usage === "object") {
123
- markProvenance(item.usage, provenance);
124
- }
125
- return markProvenance(item, provenance);
126
- }
127
- /**
128
- * The provenance of an item — or of a usage record — this module produced in the current
129
- * process, and null for anything else. The public `replayed` field is deliberately not
130
- * consulted: any command can print it beside a real `usage` object, and honoring that would
131
- * drop real spend from `_meta.cost` and `cost_limit`.
132
- */
133
- export function llmProvenanceOf(value) {
134
- if (!value || typeof value !== "object")
135
- return null;
136
- const provenance = value[LLM_PROVENANCE];
137
- if (!provenance || typeof provenance !== "object")
138
- return null;
139
- const { cacheKey, replayed } = provenance;
140
- if (typeof cacheKey !== "string" || typeof replayed !== "boolean")
141
- return null;
142
- return { cacheKey, replayed };
143
- }
144
- /**
145
- * Marks `target` — a structurally identical value rebuilt from `source`'s own JSON — as standing
146
- * in for whatever calls `source` was marked with.
147
- *
148
- * The marks are deliberately not in that JSON, and re-deriving them from it would mean trusting a
149
- * payload, which the accounting must never do. Carrying them is a different act: the caller holds
150
- * both values in this process and knows one was built from the other, which nothing on disk can
151
- * claim for itself.
152
- *
153
- * What is carried is always a replay, whatever the source was. A value read back out of storage
154
- * re-emits an answer that already exists; the call behind it was made once, and this is not that
155
- * call happening again.
156
- */
157
- export function carryLlmProvenance(source, target) {
158
- if (!source || typeof source !== "object" || !target || typeof target !== "object")
159
- return;
160
- const provenance = llmProvenanceOf(source);
161
- if (provenance)
162
- markProvenance(target, { cacheKey: provenance.cacheKey, replayed: true });
163
- if (Array.isArray(source) || Array.isArray(target)) {
164
- if (!Array.isArray(source) || !Array.isArray(target))
165
- return;
166
- for (let index = 0; index < Math.min(source.length, target.length); index++) {
167
- carryLlmProvenance(source[index], target[index]);
168
- }
169
- return;
170
- }
171
- for (const key of Object.keys(source)) {
172
- carryLlmProvenance(source[key], target[key]);
173
- }
174
- }
175
- /**
176
- * Restores private replay provenance on completed step results loaded from Lobster's own
177
- * workflow-resume state. Resume state is the narrow trusted boundary: ordinary command JSON
178
- * never reaches this helper, so copying a public cache key cannot suppress its usage record.
179
- */
180
- export function restoreLlmProvenance(target, charges) {
181
- if (!target || typeof target !== "object" || !Array.isArray(charges))
182
- return;
183
- const settled = charges.filter((charge) => charge &&
184
- typeof charge === "object" &&
185
- typeof charge.cacheKey === "string" &&
186
- charge.cacheKey &&
187
- charge.usage &&
188
- typeof charge.usage === "object");
189
- if (!settled.length)
190
- return;
191
- const visit = (value) => {
192
- if (!value || typeof value !== "object")
193
- return;
194
- if (Array.isArray(value)) {
195
- for (const item of value)
196
- visit(item);
197
- return;
198
- }
199
- const record = value;
200
- const cacheKey = typeof record.cacheKey === "string" ? record.cacheKey : null;
201
- const model = typeof record.model === "string" ? record.model : null;
202
- const usage = record.usage;
203
- if (cacheKey && usage && typeof usage === "object") {
204
- const matches = settled.some((charge) => charge.cacheKey === cacheKey &&
205
- (charge.model ?? null) === model &&
206
- sameBillableUsage(charge.usage, usage));
207
- if (matches) {
208
- const provenance = { cacheKey, replayed: true };
209
- markProvenance(usage, provenance);
210
- markProvenance(record, provenance);
211
- }
212
- }
213
- for (const child of Object.values(record))
214
- visit(child);
215
- };
216
- visit(target);
217
- }
218
- // A call nothing ever bills — a step that failed outright — leaves its key behind, so the
219
- // oldest are dropped to keep a long run's ledger bounded.
220
- const MAX_UNBILLED_LIVE_INVOCATIONS = 256;
221
- /**
222
- * Creates the ledger for a single run. The run that paid for a call is the only one that can
223
- * recover its charge, because no other run holds this ledger: a workflow reusing a cache entry
224
- * written by an earlier run, or by an SDK caller outside cost accounting, finds nothing to
225
- * claim and is billed nothing. A live call made without a ledger in `ctx` opens no charge.
226
- *
227
- * A run composed by another one — a `workflow:` step — passes the composing run's ledger as
228
- * `parent`. Both bill the same answer at their own boundary: the child bills its step, the
229
- * parent bills the output handed back to it. So a charge is opened in both and each settles
230
- * its own copy exactly once, while a replay neither of them paid for still claims nothing.
231
- */
232
- export function createLlmSpendLedger(parent) {
233
- // Outstanding charges per cache key, held one entry per call rather than as a count: two
234
- // identical calls that race on a cold cache are two provider charges under one key, the
235
- // replays that later stand in for them have to be able to settle both, and each carries the
236
- // cost of the call that opened it.
237
- const unbilled = new Map();
238
- // Charges this run has already accounted for. A copy of an item that lost its mark can turn
239
- // up in any later step, and the only way to tell it from a call nobody has billed yet is to
240
- // remember what has been billed. Bounded like the open charges, oldest key first.
241
- const billed = new Map();
242
- function settle(cacheKey, charge) {
243
- const seen = billed.get(cacheKey) ?? [];
244
- seen.push(charge ?? {});
245
- billed.set(cacheKey, seen);
246
- for (const oldest of billed.keys()) {
247
- if (billed.size <= MAX_UNBILLED_LIVE_INVOCATIONS)
248
- break;
249
- billed.delete(oldest);
250
- }
251
- }
252
- return {
253
- record(cacheKey, charge) {
254
- if (!cacheKey)
255
- return;
256
- parent?.record(cacheKey, charge);
257
- const open = unbilled.get(cacheKey) ?? [];
258
- open.push({ ...charge });
259
- unbilled.set(cacheKey, open);
260
- for (const oldest of unbilled.keys()) {
261
- if (unbilled.size <= MAX_UNBILLED_LIVE_INVOCATIONS)
262
- break;
263
- unbilled.delete(oldest);
264
- }
265
- },
266
- /**
267
- * Settles one outstanding charge and hands back what that call cost, or null for the
268
- * caller that must not bill anything. A live item and every replay of it draw on the same
269
- * charges, so a provider call is billed exactly once however many steps re-emit its
270
- * answer — and N calls under one key can be billed N times, never fewer.
271
- *
272
- * The cost comes back because a replay is not a reliable witness of it: identical calls
273
- * that raced on a cold cache each paid their own way, and every replay of them carries
274
- * whichever single answer was stored.
275
- */
276
- claim(cacheKey, cost) {
277
- const open = unbilled.get(cacheKey);
278
- if (!open?.length)
279
- return null;
280
- // A caller that knows what its own call cost settles that call's charge. A step
281
- // retried after an attempt that failed *after* paying has more than one charge under
282
- // the key, and they did not cost the same: settling the wrong one leaves the other to
283
- // be billed at this call's price instead of its own.
284
- const own = cost
285
- ? open.findIndex((charge) => (charge.model ?? null) === (cost.model ?? null) &&
286
- sameBillableUsage(charge.usage, cost.usage))
287
- : -1;
288
- // Otherwise: a charge that records no cost settles nothing anyone can bill, so it
289
- // must not be the one handed to a caller with a real call to account for. Charges
290
- // restored from resume state written before they carried a cost are the ones this
291
- // can be.
292
- const index = own >= 0
293
- ? own
294
- : Math.max(open.findIndex((charge) => charge.usage !== undefined), 0);
295
- const [charge] = open.splice(index, 1);
296
- if (!open.length)
297
- unbilled.delete(cacheKey);
298
- settle(cacheKey, charge);
299
- return charge ?? {};
300
- },
301
- /**
302
- * Whether an item carrying a call's numbers but not its in-process mark should be billed.
303
- * A matching open charge is settled so the same provider call is not recorded again by
304
- * end-of-step cleanup. Once no charge is open, however, public fields can never suppress
305
- * usage: only the private provenance symbol can prove an item is a replay.
306
- */
307
- billCopy(cacheKey, model, usage) {
308
- const isSameCall = (charge) => (charge.model ?? null) === model && sameBillableUsage(charge.usage, usage);
309
- // A copy that still names a cache key is read against that key alone: the key is
310
- // evidence of which call it came from, and honoring it keeps one call's copy from
311
- // settling another call's charge. A transform can emit `{ model, usage }` and drop
312
- // the key with the symbols, leaving the cost as the only evidence there is.
313
- for (const key of cacheKey === null ? [...unbilled.keys()] : [cacheKey]) {
314
- const open = unbilled.get(key);
315
- const index = open?.findIndex(isSameCall) ?? -1;
316
- if (!open || index < 0)
317
- continue;
318
- settle(key, open.splice(index, 1)[0]);
319
- if (!open.length)
320
- unbilled.delete(key);
321
- return true;
322
- }
323
- return true;
324
- },
325
- outstanding() {
326
- const charges = [];
327
- for (const [cacheKey, open] of unbilled) {
328
- for (const charge of open)
329
- charges.push({ cacheKey, count: 1, ...charge });
330
- }
331
- return charges;
332
- },
333
- /**
334
- * Reopens charges a paused run had not billed. A pipeline can suspend mid-step — at an
335
- * `ask` gate — after its LLM call has been paid for but before the step succeeded, so
336
- * without this the charge would exist in no run: the paused one never billed it, and the
337
- * resumed one would exempt the replay that stands in for it.
338
- */
339
- restore(charges) {
340
- if (!Array.isArray(charges))
341
- return;
342
- for (const charge of charges) {
343
- if (!charge || typeof charge !== "object")
344
- continue;
345
- if (typeof charge.cacheKey !== "string" || !charge.cacheKey)
346
- continue;
347
- const count = Math.floor(Number(charge.count ?? 0));
348
- if (!Number.isFinite(count) || count < 1)
349
- continue;
350
- // State written before this field existed carries no cost, and a hand-edited file
351
- // should not be able to invent one: only a plain object of numbers is taken.
352
- const { cacheKey, count: _count, ...cost } = charge;
353
- const restored = sanitizeChargeCost(cost);
354
- for (let i = 0; i < count; i++)
355
- this.record(cacheKey, restored);
356
- }
357
- },
358
- /**
359
- * The calls this run has already billed. A run that pauses carries this the way it carries
360
- * what it spent: the total says how much, and this says which calls it was for.
361
- */
362
- settled() {
363
- const charges = [];
364
- for (const [cacheKey, seen] of billed) {
365
- for (const charge of seen)
366
- charges.push({ cacheKey, count: 1, ...charge });
367
- }
368
- return charges;
369
- },
370
- /**
371
- * Restores what a paused run had billed, so the two halves of its accounting agree after
372
- * the pause. `cost` brings the money back; without this the run that resumes has no record
373
- * of what the money was for, and a later step that re-emits a completed LLM output — `head`
374
- * over `$live.json` — hands on a copy the fresh ledger has never heard of. It is billed on
375
- * top of the restored total: one provider call, twice in `_meta.cost` and against
376
- * `cost_limit`.
377
- *
378
- * A settled charge is only ever read against a cache key, so what this restores can excuse
379
- * a copy of a named call and nothing else. It cannot touch a live call — those settle out
380
- * of the open charges — and the spend it is reconstructing is already in the `cost` this
381
- * same state carries.
382
- */
383
- restoreSettled(charges) {
384
- if (!Array.isArray(charges))
385
- return;
386
- for (const charge of charges) {
387
- if (!charge || typeof charge !== "object")
388
- continue;
389
- if (typeof charge.cacheKey !== "string" || !charge.cacheKey)
390
- continue;
391
- const count = Math.floor(Number(charge.count ?? 0));
392
- if (!Number.isFinite(count) || count < 1)
393
- continue;
394
- const { cacheKey, count: _count, ...cost } = charge;
395
- const restored = sanitizeChargeCost(cost);
396
- for (let i = 0; i < count; i++)
397
- settle(cacheKey, restored);
398
- }
399
- },
400
- };
401
- }
402
- /**
403
- * Whether two usage records bill the same, asked of the accounting that bills them. Comparing
404
- * the records themselves would answer a different question twice over: a live item's usage
405
- * carries this module's provenance symbol, which a copy that went through JSON never can, and a
406
- * stage that rebuilds an item can drop or recompute a field nothing is charged for.
407
- */
408
- function sameBillableUsage(left, right) {
409
- if (!left || typeof left !== "object" || !right || typeof right !== "object")
410
- return false;
411
- const billed = billableTokens(left);
412
- const other = billableTokens(right);
413
- return billed.inputTokens === other.inputTokens && billed.outputTokens === other.outputTokens;
414
- }
415
96
  /**
416
97
  * Opens a charge for a live call, and only for one that costs something. An answer that reports
417
98
  * no usage is billed nowhere however many times it is replayed, so a charge for it would sit in
@@ -431,23 +112,6 @@ function chargeCostOf(item) {
431
112
  return undefined;
432
113
  return { model: typeof item?.model === "string" ? item.model : null, usage };
433
114
  }
434
- function sanitizeChargeCost(cost) {
435
- const usage = cost.usage;
436
- if (!usage || typeof usage !== "object" || Array.isArray(usage))
437
- return {};
438
- const numbers = {};
439
- for (const [key, value] of Object.entries(usage)) {
440
- if (typeof value === "number" && Number.isFinite(value))
441
- numbers[key] = value;
442
- }
443
- if (!Object.keys(numbers).length)
444
- return {};
445
- return {
446
- ...(typeof cost.stepId === "string" ? { stepId: cost.stepId } : null),
447
- model: typeof cost.model === "string" ? cost.model : null,
448
- usage: numbers,
449
- };
450
- }
451
115
  function ledgerFrom(ctx) {
452
116
  const ledger = ctx?.llmSpendLedger;
453
117
  if (!ledger || typeof ledger !== "object")