@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,758 @@
1
+ import path from "node:path";
2
+ import { promises as fsp } from "node:fs";
3
+ import { createHash } from "node:crypto";
4
+ import { Ajv } from "ajv";
5
+ import { ensureDirectory, isJsonSyntaxError, readStateJson, stableStringify, writeFileAtomic, writeStateJson, } from "../../state/store.js";
6
+ import { createCompileCached } from "../../validation.js";
7
+ const ajv = new Ajv({ allErrors: true, strict: false });
8
+ const compileCachedLocal = createCompileCached(ajv);
9
+ const artifactSchema = {
10
+ type: "object",
11
+ properties: {
12
+ kind: { type: "string" },
13
+ role: { type: "string" },
14
+ name: { type: "string" },
15
+ mimeType: { type: "string" },
16
+ text: { type: "string" },
17
+ data: {},
18
+ uri: { type: "string" },
19
+ },
20
+ additionalProperties: true,
21
+ };
22
+ const payloadSchema = {
23
+ type: "object",
24
+ properties: {
25
+ prompt: { type: "string", minLength: 1 },
26
+ model: { type: "string", minLength: 1 },
27
+ artifacts: { type: "array", items: artifactSchema },
28
+ artifactHashes: { type: "array", items: { type: "string", minLength: 10 } },
29
+ schemaVersion: { type: "string" },
30
+ metadata: { type: "object", additionalProperties: true },
31
+ outputSchema: { type: "object", additionalProperties: true },
32
+ temperature: { type: "number" },
33
+ maxOutputTokens: { type: "number" },
34
+ retryContext: {
35
+ type: "object",
36
+ properties: {
37
+ attempt: { type: "number" },
38
+ validationErrors: { type: "array", items: { type: "string" } },
39
+ },
40
+ additionalProperties: false,
41
+ },
42
+ },
43
+ required: ["prompt", "artifacts", "artifactHashes"],
44
+ additionalProperties: false,
45
+ };
46
+ const responseSchema = {
47
+ type: "object",
48
+ properties: {
49
+ ok: { type: "boolean" },
50
+ result: {
51
+ type: "object",
52
+ properties: {
53
+ runId: { type: "string" },
54
+ model: { type: "string" },
55
+ prompt: { type: "string" },
56
+ status: { type: "string" },
57
+ output: {
58
+ type: "object",
59
+ properties: {
60
+ text: { type: "string" },
61
+ data: {},
62
+ format: { type: "string" },
63
+ },
64
+ required: [],
65
+ additionalProperties: true,
66
+ },
67
+ usage: {
68
+ type: "object",
69
+ properties: {
70
+ inputTokens: { type: "number" },
71
+ outputTokens: { type: "number" },
72
+ totalTokens: { type: "number" },
73
+ },
74
+ additionalProperties: true,
75
+ },
76
+ warnings: { type: "array", items: { type: "string" } },
77
+ metadata: { type: "object", additionalProperties: true },
78
+ diagnostics: { type: "object", additionalProperties: true },
79
+ },
80
+ required: ["output"],
81
+ additionalProperties: true,
82
+ },
83
+ error: { type: "object", additionalProperties: true },
84
+ },
85
+ required: ["ok"],
86
+ additionalProperties: true,
87
+ };
88
+ const validatePayload = ajv.compile(payloadSchema);
89
+ const validateResponseEnvelope = ajv.compile(responseSchema);
90
+ const DEFAULT_MAX_VALIDATION_RETRIES = 1;
91
+ const STATE_VERSION = 1;
92
+ export const llmInvokeCommand = createLlmInvokeCommand({
93
+ name: "llm.invoke",
94
+ itemKind: "llm.invoke",
95
+ stateType: "llm.invoke",
96
+ cacheNamespace: "llm.invoke",
97
+ defaultProvider: null,
98
+ description: "Call a configured LLM adapter with typed payloads and caching",
99
+ helpTitle: "llm.invoke — call a configured LLM adapter with caching and schema validation",
100
+ helpConfig: [
101
+ "Provider resolution order: --provider, LOBSTER_LLM_PROVIDER, then environment auto-detect.",
102
+ "Built-in providers: openclaw, pi, http.",
103
+ "OpenClaw provider uses OPENCLAW_URL (CLAWD_URL also supported) and OPENCLAW_TOKEN.",
104
+ "Pi provider uses LOBSTER_PI_LLM_ADAPTER_URL and is intended to be supplied by a Pi extension.",
105
+ "Generic http provider uses LOBSTER_LLM_ADAPTER_URL and optional LOBSTER_LLM_ADAPTER_TOKEN.",
106
+ ],
107
+ helpExamples: [
108
+ "llm.invoke --prompt 'Write summary'",
109
+ "llm.invoke --provider openclaw --model claude-3-sonnet --prompt 'Write summary'",
110
+ "cat artifacts.json | llm.invoke --provider pi --prompt 'Score each item'",
111
+ "... | llm.invoke --prompt 'Plan next steps' --output-schema '{\"type\":\"object\"}'",
112
+ ],
113
+ sourceForProvider(provider) {
114
+ return provider;
115
+ },
116
+ legacyEnvCompat: true,
117
+ });
118
+ export const llmTaskInvokeCommand = createLlmInvokeCommand({
119
+ name: "llm_task.invoke",
120
+ itemKind: "llm_task.invoke",
121
+ stateType: "llm_task.invoke",
122
+ cacheNamespace: "llm_task.invoke",
123
+ defaultProvider: "openclaw",
124
+ description: "Backward-compatible alias for llm.invoke using the OpenClaw adapter",
125
+ helpTitle: "llm_task.invoke — backward-compatible alias for llm.invoke using OpenClaw",
126
+ helpConfig: [
127
+ "Requires OPENCLAW_URL (or CLAWD_URL) and optionally OPENCLAW_TOKEN.",
128
+ "Use llm.invoke for new workflows and non-OpenClaw adapters.",
129
+ ],
130
+ helpExamples: [
131
+ "llm_task.invoke --prompt 'Write summary'",
132
+ "llm_task.invoke --model claude-3-sonnet --prompt 'Write summary'",
133
+ "cat artifacts.json | llm_task.invoke --prompt 'Score each item'",
134
+ ],
135
+ sourceForProvider() {
136
+ return "clawd";
137
+ },
138
+ legacyEnvCompat: true,
139
+ });
140
+ export function createLlmInvokeCommand(config) {
141
+ return {
142
+ name: config.name,
143
+ meta: {
144
+ description: config.description,
145
+ argsSchema: {
146
+ type: "object",
147
+ properties: {
148
+ provider: {
149
+ type: "string",
150
+ description: "LLM adapter provider (openclaw, pi, http). Optional if auto-detected.",
151
+ },
152
+ token: {
153
+ type: "string",
154
+ description: "Optional bearer token for providers that support it.",
155
+ },
156
+ prompt: { type: "string", description: "Primary prompt / instructions" },
157
+ model: {
158
+ type: "string",
159
+ description: "Model identifier. Optional; adapter defaults may apply if omitted.",
160
+ },
161
+ "artifacts-json": { type: "string", description: "JSON array of artifacts to send" },
162
+ "metadata-json": { type: "string", description: "JSON object of metadata to include" },
163
+ "output-schema": { type: "string", description: "JSON schema LLM output must satisfy" },
164
+ "schema-version": { type: "string", description: "Logical schema version for caching" },
165
+ "max-validation-retries": {
166
+ type: "number",
167
+ description: "Retries when schema validation fails",
168
+ },
169
+ temperature: { type: "number", description: "Sampling temperature" },
170
+ "max-output-tokens": { type: "number", description: "Max completion tokens" },
171
+ "state-key": {
172
+ type: "string",
173
+ description: "Run-state key override (else LOBSTER_RUN_STATE_KEY)",
174
+ },
175
+ refresh: { type: "boolean", description: "Bypass run-state + cache" },
176
+ "disable-cache": { type: "boolean", description: "Skip persistent cache" },
177
+ _: { type: "array", items: { type: "string" } },
178
+ },
179
+ required: [],
180
+ },
181
+ sideEffects: ["calls_llm"],
182
+ },
183
+ help() {
184
+ const lines = [
185
+ config.helpTitle,
186
+ "",
187
+ "Usage:",
188
+ ...config.helpExamples.map((example) => ` ${example}`),
189
+ "",
190
+ "Features:",
191
+ " - Typed payload validation before invoking the adapter.",
192
+ " - Run-state + file cache so resumes do not re-call the LLM.",
193
+ " - Optional JSON-schema enforcement with bounded retries.",
194
+ "",
195
+ "Config:",
196
+ ...config.helpConfig.map((line) => ` - ${line}`),
197
+ ];
198
+ return `${lines.join("\n")}\n`;
199
+ },
200
+ async run({ input, args, ctx }) {
201
+ return runLlmInvoke({ input, args, ctx, config });
202
+ },
203
+ };
204
+ }
205
+ async function runLlmInvoke({ input, args, ctx, config, }) {
206
+ const env = ctx.env ?? process.env;
207
+ const provider = resolveProvider(args, env, config.defaultProvider, ctx);
208
+ const adapter = resolveAdapter({ provider, env, args, config, ctx });
209
+ const prompt = extractPrompt(args);
210
+ if (!prompt)
211
+ throw new Error(`${config.name} requires --prompt or positional text`);
212
+ const model = resolveModel(args, env, config.legacyEnvCompat);
213
+ const schemaVersion = resolveEnvString(args["schema-version"], ["LOBSTER_LLM_SCHEMA_VERSION", ...(config.legacyEnvCompat ? ["LLM_TASK_SCHEMA_VERSION"] : [])], env, "v1");
214
+ const maxOutputTokens = parseOptionalNumber(args["max-output-tokens"]);
215
+ const temperature = parseOptionalNumber(args.temperature);
216
+ const providedArtifacts = parseJsonArray(args["artifacts-json"], `${config.name} --artifacts-json`);
217
+ const metadataObject = parseJsonObject(args["metadata-json"], `${config.name} --metadata-json`);
218
+ const userOutputSchema = parseJsonObject(args["output-schema"], `${config.name} --output-schema`);
219
+ const maxValidationRetriesRaw = args["max-validation-retries"] ??
220
+ getFirstEnv(env, [
221
+ "LOBSTER_LLM_VALIDATION_RETRIES",
222
+ ...(config.legacyEnvCompat ? ["LLM_TASK_VALIDATION_RETRIES"] : []),
223
+ ]);
224
+ const maxValidationRetries = userOutputSchema
225
+ ? Math.max(0, Number.isFinite(Number(maxValidationRetriesRaw))
226
+ ? Number(maxValidationRetriesRaw)
227
+ : DEFAULT_MAX_VALIDATION_RETRIES)
228
+ : 0;
229
+ const disableCache = flag(args["disable-cache"]);
230
+ const forceRefresh = flag(args.refresh ??
231
+ getFirstEnv(env, [
232
+ "LOBSTER_LLM_FORCE_REFRESH",
233
+ ...(config.legacyEnvCompat ? ["LLM_TASK_FORCE_REFRESH"] : []),
234
+ ]));
235
+ const stateKey = String(args["state-key"] ?? env.LOBSTER_RUN_STATE_KEY ?? "").trim() || null;
236
+ const inputArtifacts = [];
237
+ for await (const item of input)
238
+ inputArtifacts.push(item);
239
+ const normalizedArtifacts = [...inputArtifacts, ...providedArtifacts].map(normalizeArtifact);
240
+ const artifactHashes = normalizedArtifacts.map(hashArtifact);
241
+ const cacheKey = computeCacheKey({
242
+ provider,
243
+ prompt,
244
+ model,
245
+ schemaVersion,
246
+ artifactHashes,
247
+ outputSchema: userOutputSchema,
248
+ });
249
+ if (stateKey && !forceRefresh) {
250
+ const stored = await readReusableLlmState(env, stateKey);
251
+ const reused = pickReusableState(stored, cacheKey, config.stateType);
252
+ if (reused) {
253
+ return {
254
+ output: streamOf(reused.items.map((item) => ({ ...item, source: "run_state", cached: true }))),
255
+ };
256
+ }
257
+ }
258
+ if (!disableCache && !forceRefresh) {
259
+ const cache = await readCacheEntry(env, cacheKey, config.cacheNamespace);
260
+ if (cache) {
261
+ return {
262
+ output: streamOf(cache.items.map((item) => ({ ...item, source: "cache", cached: true }))),
263
+ };
264
+ }
265
+ }
266
+ const payload = {
267
+ prompt,
268
+ ...(model ? { model } : null),
269
+ artifacts: normalizedArtifacts,
270
+ artifactHashes,
271
+ };
272
+ if (metadataObject)
273
+ payload.metadata = metadataObject;
274
+ if (userOutputSchema)
275
+ payload.outputSchema = userOutputSchema;
276
+ if (schemaVersion)
277
+ payload.schemaVersion = schemaVersion;
278
+ if (Number.isFinite(maxOutputTokens ?? NaN))
279
+ payload.maxOutputTokens = Number(maxOutputTokens);
280
+ if (Number.isFinite(temperature ?? NaN))
281
+ payload.temperature = Number(temperature);
282
+ if (!validatePayload(payload)) {
283
+ throw new Error(`${config.name} payload invalid: ${ajv.errorsText(validatePayload.errors)}`);
284
+ }
285
+ const validator = userOutputSchema ? compileCachedLocal(userOutputSchema) : null;
286
+ let attempt = 0;
287
+ let lastValidationErrors = [];
288
+ while (true) {
289
+ attempt += 1;
290
+ if (attempt > 1) {
291
+ payload.retryContext = {
292
+ attempt,
293
+ ...(lastValidationErrors.length ? { validationErrors: lastValidationErrors } : null),
294
+ };
295
+ }
296
+ else {
297
+ delete payload.retryContext;
298
+ }
299
+ let responseEnvelope;
300
+ try {
301
+ responseEnvelope = await adapter.invoke({ env, args, payload });
302
+ }
303
+ catch (err) {
304
+ throw new Error(`${config.name} request failed: ${err?.message ?? String(err)}`);
305
+ }
306
+ if (!validateResponseEnvelope(responseEnvelope)) {
307
+ throw new Error(`${config.name} received invalid response envelope: ${JSON.stringify(responseEnvelope)}`);
308
+ }
309
+ if (responseEnvelope.ok !== true) {
310
+ const message = responseEnvelope.error?.message ?? "llm adapter returned an error";
311
+ throw new Error(`${config.name} remote error: ${message}`);
312
+ }
313
+ const normalized = normalizeResult({
314
+ envelope: responseEnvelope,
315
+ cacheKey,
316
+ schemaVersion,
317
+ artifactHashes,
318
+ source: adapter.source,
319
+ attempt,
320
+ itemKind: config.itemKind,
321
+ });
322
+ if (!validator) {
323
+ await persistOutputs({
324
+ env,
325
+ stateKey,
326
+ cacheKey,
327
+ items: normalized,
328
+ stateType: config.stateType,
329
+ });
330
+ if (!disableCache)
331
+ await writeCacheEntry(env, cacheKey, normalized, config.cacheNamespace);
332
+ return { output: streamOf(normalized) };
333
+ }
334
+ const structured = normalized[0]?.output?.data ?? null;
335
+ if (validator(structured)) {
336
+ await persistOutputs({
337
+ env,
338
+ stateKey,
339
+ cacheKey,
340
+ items: normalized,
341
+ stateType: config.stateType,
342
+ });
343
+ if (!disableCache)
344
+ await writeCacheEntry(env, cacheKey, normalized, config.cacheNamespace);
345
+ return { output: streamOf(normalized) };
346
+ }
347
+ lastValidationErrors = collectAjvErrors(validator.errors);
348
+ if (attempt > maxValidationRetries + 1) {
349
+ throw new Error(`${config.name} output failed schema validation: ${lastValidationErrors.join("; ")}`);
350
+ }
351
+ }
352
+ }
353
+ function resolveProvider(args, env, defaultProvider, ctx) {
354
+ const explicit = String(args.provider ?? env.LOBSTER_LLM_PROVIDER ?? "")
355
+ .trim()
356
+ .toLowerCase();
357
+ if (explicit) {
358
+ if (explicit === "openclaw" || explicit === "pi" || explicit === "http") {
359
+ return explicit;
360
+ }
361
+ if (getDirectAdapter(ctx, explicit)) {
362
+ return explicit;
363
+ }
364
+ throw new Error(`Unsupported llm provider: ${explicit}`);
365
+ }
366
+ if (defaultProvider)
367
+ return defaultProvider;
368
+ const directAdapters = ctx?.llmAdapters && typeof ctx.llmAdapters === "object"
369
+ ? Object.keys(ctx.llmAdapters).filter((key) => getDirectAdapter(ctx, key))
370
+ : [];
371
+ if (directAdapters.length === 1)
372
+ return directAdapters[0];
373
+ if (String(env.LOBSTER_PI_LLM_ADAPTER_URL ?? "").trim())
374
+ return "pi";
375
+ if (String(env.OPENCLAW_URL ?? env.CLAWD_URL ?? "").trim())
376
+ return "openclaw";
377
+ if (String(env.LOBSTER_LLM_ADAPTER_URL ?? "").trim())
378
+ return "http";
379
+ throw new Error("llm.invoke could not resolve a provider. Set --provider or LOBSTER_LLM_PROVIDER");
380
+ }
381
+ function resolveAdapter({ provider, env, args, config, ctx, }) {
382
+ const direct = getDirectAdapter(ctx, provider);
383
+ if (direct) {
384
+ const invoke = typeof direct === "function" ? direct : direct.invoke;
385
+ return {
386
+ provider,
387
+ source: typeof direct === "function" ? provider : (direct.source ?? provider),
388
+ async invoke({ payload }) {
389
+ return invoke({ env, args, payload, ctx });
390
+ },
391
+ };
392
+ }
393
+ if (provider === "openclaw") {
394
+ const openclawUrl = String(env.OPENCLAW_URL ?? env.CLAWD_URL ?? "").trim();
395
+ if (!openclawUrl) {
396
+ throw new Error(`${config.name} requires OPENCLAW_URL (or CLAWD_URL) for provider=openclaw`);
397
+ }
398
+ const endpoint = new URL("/tools/invoke", openclawUrl);
399
+ const token = String(args.token ?? env.OPENCLAW_TOKEN ?? env.CLAWD_TOKEN ?? "").trim();
400
+ return {
401
+ provider,
402
+ source: config.sourceForProvider?.(provider) ?? "openclaw",
403
+ async invoke({ payload }) {
404
+ return invokeOpenClawAdapter({ endpoint, token, payload });
405
+ },
406
+ };
407
+ }
408
+ if (provider === "pi") {
409
+ const adapterUrl = String(env.LOBSTER_PI_LLM_ADAPTER_URL ?? "").trim();
410
+ if (!adapterUrl) {
411
+ throw new Error(`${config.name} requires LOBSTER_PI_LLM_ADAPTER_URL for provider=pi`);
412
+ }
413
+ const token = String(args.token ?? env.LOBSTER_PI_LLM_ADAPTER_TOKEN ?? "").trim();
414
+ return {
415
+ provider,
416
+ source: config.sourceForProvider?.(provider) ?? "pi",
417
+ async invoke({ payload }) {
418
+ return invokeHttpAdapter({ endpoint: buildAdapterEndpoint(adapterUrl), token, payload });
419
+ },
420
+ };
421
+ }
422
+ const adapterUrl = String(env.LOBSTER_LLM_ADAPTER_URL ?? "").trim();
423
+ if (!adapterUrl) {
424
+ throw new Error(`${config.name} requires LOBSTER_LLM_ADAPTER_URL for provider=http`);
425
+ }
426
+ const token = String(args.token ?? env.LOBSTER_LLM_ADAPTER_TOKEN ?? "").trim();
427
+ return {
428
+ provider,
429
+ source: config.sourceForProvider?.(provider) ?? "http",
430
+ async invoke({ payload }) {
431
+ return invokeHttpAdapter({ endpoint: buildAdapterEndpoint(adapterUrl), token, payload });
432
+ },
433
+ };
434
+ }
435
+ function getDirectAdapter(ctx, provider) {
436
+ const adapters = ctx?.llmAdapters;
437
+ if (!adapters || typeof adapters !== "object")
438
+ return null;
439
+ const adapter = adapters[provider];
440
+ if (typeof adapter === "function")
441
+ return adapter;
442
+ if (adapter && typeof adapter === "object" && typeof adapter.invoke === "function") {
443
+ return adapter;
444
+ }
445
+ return null;
446
+ }
447
+ function buildAdapterEndpoint(rawUrl) {
448
+ const endpoint = new URL(rawUrl);
449
+ if (endpoint.pathname === "/" || endpoint.pathname === "") {
450
+ endpoint.pathname = "/invoke";
451
+ }
452
+ return endpoint;
453
+ }
454
+ async function invokeOpenClawAdapter({ endpoint, token, payload, }) {
455
+ const res = await fetch(endpoint, {
456
+ method: "POST",
457
+ headers: {
458
+ "content-type": "application/json",
459
+ ...(token ? { authorization: `Bearer ${token}` } : null),
460
+ },
461
+ body: JSON.stringify({
462
+ tool: "llm-task",
463
+ action: "invoke",
464
+ args: payload,
465
+ }),
466
+ });
467
+ const text = await res.text();
468
+ if (!res.ok) {
469
+ throw new Error(`${res.status} ${res.statusText}: ${text.slice(0, 400)}`);
470
+ }
471
+ let parsed;
472
+ try {
473
+ parsed = text ? JSON.parse(text) : null;
474
+ }
475
+ catch {
476
+ throw new Error("Response was not JSON");
477
+ }
478
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed) && "ok" in parsed) {
479
+ if (parsed.ok !== true) {
480
+ const msg = parsed?.error?.message ?? "Unknown error";
481
+ throw new Error(`openclaw adapter error: ${msg}`);
482
+ }
483
+ const inner = parsed.result;
484
+ if (inner && typeof inner === "object" && !Array.isArray(inner) && "ok" in inner) {
485
+ const envelope = inner;
486
+ return {
487
+ ...envelope,
488
+ result: envelope.result ? normalizeOpenClawToolResult(envelope.result) : envelope.result,
489
+ };
490
+ }
491
+ return { ok: true, result: normalizeOpenClawToolResult(inner) };
492
+ }
493
+ return { ok: true, result: parsed };
494
+ }
495
+ function normalizeOpenClawToolResult(result) {
496
+ if (!result || typeof result !== "object" || Array.isArray(result)) {
497
+ return {
498
+ output: {
499
+ text: result == null ? "" : String(result),
500
+ data: result,
501
+ format: "json",
502
+ },
503
+ };
504
+ }
505
+ if (result.output) {
506
+ return result;
507
+ }
508
+ const content = Array.isArray(result.content) ? result.content : [];
509
+ const text = content
510
+ .filter((item) => item?.type === "text" && typeof item.text === "string")
511
+ .map((item) => item.text)
512
+ .join("\n");
513
+ const details = result.details && typeof result.details === "object" ? result.details : null;
514
+ const data = details && "json" in details ? details.json : undefined;
515
+ return {
516
+ ...result,
517
+ model: result.model ?? details?.model ?? null,
518
+ output: {
519
+ text: text || null,
520
+ data: data ?? null,
521
+ format: data !== undefined ? "json" : "text",
522
+ },
523
+ metadata: {
524
+ ...(result.metadata ?? {}),
525
+ ...(details?.provider ? { provider: details.provider } : null),
526
+ ...(details ? { details } : null),
527
+ },
528
+ };
529
+ }
530
+ async function invokeHttpAdapter({ endpoint, token, payload, }) {
531
+ const res = await fetch(endpoint, {
532
+ method: "POST",
533
+ headers: {
534
+ "content-type": "application/json",
535
+ ...(token ? { authorization: `Bearer ${token}` } : null),
536
+ },
537
+ body: JSON.stringify(payload),
538
+ });
539
+ const text = await res.text();
540
+ if (!res.ok) {
541
+ throw new Error(`${res.status} ${res.statusText}: ${text.slice(0, 400)}`);
542
+ }
543
+ let parsed;
544
+ try {
545
+ parsed = text ? JSON.parse(text) : null;
546
+ }
547
+ catch {
548
+ throw new Error("Response was not JSON");
549
+ }
550
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed) && "ok" in parsed) {
551
+ return parsed;
552
+ }
553
+ return { ok: true, result: parsed };
554
+ }
555
+ function resolveModel(args, env, legacyEnvCompat) {
556
+ return resolveEnvString(args.model, ["LOBSTER_LLM_MODEL", ...(legacyEnvCompat ? ["LLM_TASK_MODEL"] : [])], env, "");
557
+ }
558
+ function resolveEnvString(raw, envKeys, env, fallback) {
559
+ if (raw !== undefined && raw !== null && String(raw).trim())
560
+ return String(raw).trim();
561
+ const fromEnv = getFirstEnv(env, envKeys);
562
+ if (fromEnv && String(fromEnv).trim())
563
+ return String(fromEnv).trim();
564
+ return fallback;
565
+ }
566
+ function getFirstEnv(env, keys) {
567
+ for (const key of keys) {
568
+ if (env?.[key] !== undefined && env?.[key] !== null && String(env[key]).trim()) {
569
+ return env[key];
570
+ }
571
+ }
572
+ return undefined;
573
+ }
574
+ function extractPrompt(args) {
575
+ if (args.prompt)
576
+ return String(args.prompt);
577
+ if (Array.isArray(args._) && args._.length) {
578
+ return args._.join(" ");
579
+ }
580
+ return "";
581
+ }
582
+ function parseJsonArray(raw, label) {
583
+ if (!raw)
584
+ return [];
585
+ try {
586
+ const parsed = JSON.parse(String(raw));
587
+ if (!Array.isArray(parsed))
588
+ throw new Error("must be array");
589
+ return parsed;
590
+ }
591
+ catch {
592
+ throw new Error(`${label} must be a JSON array`);
593
+ }
594
+ }
595
+ function parseJsonObject(raw, label) {
596
+ if (!raw)
597
+ return null;
598
+ try {
599
+ const parsed = JSON.parse(String(raw));
600
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
601
+ throw new Error("must be an object");
602
+ }
603
+ return parsed;
604
+ }
605
+ catch {
606
+ throw new Error(`${label} must be a JSON object`);
607
+ }
608
+ }
609
+ function parseOptionalNumber(value) {
610
+ if (value === undefined || value === null)
611
+ return null;
612
+ const num = Number(value);
613
+ return Number.isFinite(num) ? num : null;
614
+ }
615
+ function flag(value) {
616
+ if (value === undefined || value === null)
617
+ return false;
618
+ if (typeof value === "boolean")
619
+ return value;
620
+ if (typeof value === "string") {
621
+ const normalized = value.trim().toLowerCase();
622
+ if (["false", "0", "no"].includes(normalized))
623
+ return false;
624
+ if (["true", "1", "yes"].includes(normalized))
625
+ return true;
626
+ }
627
+ return Boolean(value);
628
+ }
629
+ function normalizeArtifact(raw) {
630
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
631
+ return raw;
632
+ }
633
+ if (typeof raw === "string") {
634
+ return { kind: "text", text: raw };
635
+ }
636
+ if (typeof raw === "number" || typeof raw === "boolean") {
637
+ return { kind: "text", text: String(raw) };
638
+ }
639
+ return { kind: "json", data: raw };
640
+ }
641
+ function hashArtifact(artifact) {
642
+ const stable = stableStringify(artifact);
643
+ return createHash("sha256").update(stable).digest("hex");
644
+ }
645
+ function computeCacheKey({ provider, prompt, model, schemaVersion, artifactHashes, outputSchema, }) {
646
+ const payload = {
647
+ provider,
648
+ prompt,
649
+ model: model || `${provider}-default`,
650
+ schemaVersion,
651
+ artifactHashes,
652
+ outputSchema: outputSchema ?? null,
653
+ };
654
+ return createHash("sha256").update(stableStringify(payload)).digest("hex");
655
+ }
656
+ function normalizeResult({ envelope, cacheKey, schemaVersion, artifactHashes, source, attempt, itemKind, }) {
657
+ const result = envelope.result ?? {};
658
+ const output = result.output ?? {};
659
+ const item = {
660
+ kind: itemKind,
661
+ runId: (result.runId ?? null),
662
+ prompt: (result.prompt ?? null),
663
+ model: (result.model ?? null),
664
+ schemaVersion,
665
+ status: String(result.status ?? "completed"),
666
+ cacheKey,
667
+ artifactHashes,
668
+ output: {
669
+ format: (output.format ?? (output.data ? "json" : "text")),
670
+ text: (output.text ?? null),
671
+ data: (output.data ?? null),
672
+ },
673
+ usage: (result.usage ?? null),
674
+ metadata: (result.metadata ?? null),
675
+ warnings: (result.warnings ?? null),
676
+ diagnostics: (result.diagnostics ?? null),
677
+ createdAt: new Date().toISOString(),
678
+ source,
679
+ cached: source !== "remote" &&
680
+ source !== "openclaw" &&
681
+ source !== "clawd" &&
682
+ source !== "pi" &&
683
+ source !== "http",
684
+ attemptCount: attempt,
685
+ };
686
+ return [item];
687
+ }
688
+ async function persistOutputs({ env, stateKey, cacheKey, items, stateType, }) {
689
+ if (!stateKey)
690
+ return;
691
+ const record = {
692
+ type: stateType,
693
+ version: STATE_VERSION,
694
+ cacheKey,
695
+ items,
696
+ storedAt: new Date().toISOString(),
697
+ };
698
+ await writeStateJson({ env, key: stateKey, value: record });
699
+ }
700
+ async function readReusableLlmState(env, stateKey) {
701
+ try {
702
+ return await readStateJson({ env, key: stateKey });
703
+ }
704
+ catch (err) {
705
+ if (isJsonSyntaxError(err))
706
+ return null;
707
+ throw err;
708
+ }
709
+ }
710
+ function pickReusableState(stored, cacheKey, stateType) {
711
+ if (!stored || typeof stored !== "object")
712
+ return null;
713
+ if (stored.type !== stateType)
714
+ return null;
715
+ if (stored.cacheKey !== cacheKey)
716
+ return null;
717
+ if (!Array.isArray(stored.items))
718
+ return null;
719
+ return { items: stored.items };
720
+ }
721
+ function collectAjvErrors(errors) {
722
+ if (!errors?.length)
723
+ return [];
724
+ return errors.map((err) => `${err.instancePath || "/"} ${err.message ?? ""}`.trim());
725
+ }
726
+ async function readCacheEntry(env, key, cacheNamespace) {
727
+ const filePath = path.join(getCacheDir(env), cacheNamespace, `${key}.json`);
728
+ try {
729
+ const text = await fsp.readFile(filePath, "utf8");
730
+ const parsed = JSON.parse(text);
731
+ if (parsed?.cacheKey !== key || !Array.isArray(parsed.items))
732
+ return null;
733
+ return parsed;
734
+ }
735
+ catch (err) {
736
+ if (err?.code === "ENOENT")
737
+ return null;
738
+ if (isJsonSyntaxError(err))
739
+ return null;
740
+ throw err;
741
+ }
742
+ }
743
+ async function writeCacheEntry(env, key, items, cacheNamespace) {
744
+ const dir = path.join(getCacheDir(env), cacheNamespace);
745
+ await ensureDirectory(dir);
746
+ const filePath = path.join(dir, `${key}.json`);
747
+ await writeFileAtomic(filePath, JSON.stringify({ items, cacheKey: key, storedAt: new Date().toISOString() }, null, 2) + "\n");
748
+ }
749
+ function getCacheDir(env) {
750
+ if (env?.LOBSTER_CACHE_DIR)
751
+ return String(env.LOBSTER_CACHE_DIR);
752
+ return path.join(process.cwd(), ".lobster-cache");
753
+ }
754
+ async function* streamOf(items) {
755
+ for (const item of items)
756
+ yield item;
757
+ }
758
+ //# sourceMappingURL=llm_invoke.js.map