@delofarag/ai-utils 1.7.1 → 1.7.2

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 (172) hide show
  1. package/dist/heart/tools/ToolRegistry.d.ts +7 -1
  2. package/dist/heart/tools/ToolRegistry.d.ts.map +1 -1
  3. package/dist/heart/tools/ToolRegistry.js.map +1 -1
  4. package/dist/helpers/cli-llms.d.ts +192 -0
  5. package/dist/helpers/cli-llms.d.ts.map +1 -0
  6. package/dist/helpers/cli-llms.js +737 -0
  7. package/dist/helpers/cli-llms.js.map +1 -0
  8. package/dist/helpers/llms.d.ts +32 -2
  9. package/dist/helpers/llms.d.ts.map +1 -1
  10. package/dist/helpers/llms.js +27 -0
  11. package/dist/helpers/llms.js.map +1 -1
  12. package/dist/imports.d.ts +10 -4
  13. package/dist/imports.d.ts.map +1 -1
  14. package/dist/imports.js +6 -3
  15. package/dist/imports.js.map +1 -1
  16. package/dist/index.d.ts +1 -2
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +1 -2
  19. package/dist/index.js.map +1 -1
  20. package/package.json +4 -2
  21. package/dist/chatbot.d.ts +0 -33
  22. package/dist/chatbot.d.ts.map +0 -1
  23. package/dist/chatbot.js +0 -72
  24. package/dist/chatbot.js.map +0 -1
  25. package/dist/functions/answerers/ask.d.ts +0 -6
  26. package/dist/functions/answerers/ask.d.ts.map +0 -1
  27. package/dist/functions/answerers/ask.js +0 -14
  28. package/dist/functions/answerers/ask.js.map +0 -1
  29. package/dist/functions/answerers/websearch.d.ts +0 -2
  30. package/dist/functions/answerers/websearch.d.ts.map +0 -1
  31. package/dist/functions/answerers/websearch.js +0 -19
  32. package/dist/functions/answerers/websearch.js.map +0 -1
  33. package/dist/functions/evaluators/classify.d.ts +0 -23
  34. package/dist/functions/evaluators/classify.d.ts.map +0 -1
  35. package/dist/functions/evaluators/classify.js +0 -61
  36. package/dist/functions/evaluators/classify.js.map +0 -1
  37. package/dist/functions/evaluators/decide.d.ts +0 -22
  38. package/dist/functions/evaluators/decide.d.ts.map +0 -1
  39. package/dist/functions/evaluators/decide.js +0 -52
  40. package/dist/functions/evaluators/decide.js.map +0 -1
  41. package/dist/functions/optimizers/promptify.d.ts +0 -8
  42. package/dist/functions/optimizers/promptify.d.ts.map +0 -1
  43. package/dist/functions/optimizers/promptify.js +0 -48
  44. package/dist/functions/optimizers/promptify.js.map +0 -1
  45. package/dist/functions/optimizers/ragify.d.ts +0 -6
  46. package/dist/functions/optimizers/ragify.d.ts.map +0 -1
  47. package/dist/functions/optimizers/ragify.js +0 -40
  48. package/dist/functions/optimizers/ragify.js.map +0 -1
  49. package/dist/functions/parsers/extract.d.ts +0 -9
  50. package/dist/functions/parsers/extract.d.ts.map +0 -1
  51. package/dist/functions/parsers/extract.js +0 -37
  52. package/dist/functions/parsers/extract.js.map +0 -1
  53. package/dist/functions/parsers/normalize.d.ts +0 -9
  54. package/dist/functions/parsers/normalize.d.ts.map +0 -1
  55. package/dist/functions/parsers/normalize.js +0 -63
  56. package/dist/functions/parsers/normalize.js.map +0 -1
  57. package/dist/functions/parsers/structure.d.ts +0 -9
  58. package/dist/functions/parsers/structure.d.ts.map +0 -1
  59. package/dist/functions/parsers/structure.js +0 -36
  60. package/dist/functions/parsers/structure.js.map +0 -1
  61. package/dist/functions/parsers/summarize.d.ts +0 -8
  62. package/dist/functions/parsers/summarize.d.ts.map +0 -1
  63. package/dist/functions/parsers/summarize.js +0 -46
  64. package/dist/functions/parsers/summarize.js.map +0 -1
  65. package/dist/heart/chatbot.d.ts +0 -55
  66. package/dist/heart/chatbot.d.ts.map +0 -1
  67. package/dist/heart/chatbot.js +0 -99
  68. package/dist/heart/chatbot.js.map +0 -1
  69. package/dist/heart/memorychain.d.ts +0 -60
  70. package/dist/heart/memorychain.d.ts.map +0 -1
  71. package/dist/heart/memorychain.js +0 -190
  72. package/dist/heart/memorychain.js.map +0 -1
  73. package/dist/heart/tools/BasicToolRegistry.d.ts +0 -22
  74. package/dist/heart/tools/BasicToolRegistry.d.ts.map +0 -1
  75. package/dist/heart/tools/BasicToolRegistry.js +0 -45
  76. package/dist/heart/tools/BasicToolRegistry.js.map +0 -1
  77. package/dist/heart/tools/CombinedRegistry.d.ts +0 -41
  78. package/dist/heart/tools/CombinedRegistry.d.ts.map +0 -1
  79. package/dist/heart/tools/CombinedRegistry.js +0 -46
  80. package/dist/heart/tools/CombinedRegistry.js.map +0 -1
  81. package/dist/heart/tools/RAGTool 2.d.ts +0 -1
  82. package/dist/heart/tools/RAGTool 2.d.ts.map +0 -1
  83. package/dist/heart/tools/RAGTool 2.js +0 -2
  84. package/dist/heart/tools/RAGTool 2.js.map +0 -1
  85. package/dist/heart/tools/ZodiosToolRegistry.d.ts +0 -22
  86. package/dist/heart/tools/ZodiosToolRegistry.d.ts.map +0 -1
  87. package/dist/heart/tools/ZodiosToolRegistry.js +0 -80
  88. package/dist/heart/tools/ZodiosToolRegistry.js.map +0 -1
  89. package/dist/heart/tools/zodios/CombinedRegistry.d.ts +0 -41
  90. package/dist/heart/tools/zodios/CombinedRegistry.d.ts.map +0 -1
  91. package/dist/heart/tools/zodios/CombinedRegistry.js +0 -58
  92. package/dist/heart/tools/zodios/CombinedRegistry.js.map +0 -1
  93. package/dist/heart/tools/zodios/ZodiosToolRegistry.d.ts +0 -22
  94. package/dist/heart/tools/zodios/ZodiosToolRegistry.d.ts.map +0 -1
  95. package/dist/heart/tools/zodios/ZodiosToolRegistry.js +0 -80
  96. package/dist/heart/tools/zodios/ZodiosToolRegistry.js.map +0 -1
  97. package/dist/helpers.d.ts +0 -51
  98. package/dist/helpers.d.ts.map +0 -1
  99. package/dist/helpers.js +0 -70
  100. package/dist/helpers.js.map +0 -1
  101. package/dist/magic-funcs/decide.d.ts +0 -1
  102. package/dist/magic-funcs/decide.d.ts.map +0 -1
  103. package/dist/magic-funcs/decide.js +0 -2
  104. package/dist/magic-funcs/decide.js.map +0 -1
  105. package/dist/magic-funcs/extract.d.ts +0 -1
  106. package/dist/magic-funcs/extract.d.ts.map +0 -1
  107. package/dist/magic-funcs/extract.js +0 -2
  108. package/dist/magic-funcs/extract.js.map +0 -1
  109. package/dist/magic-funcs/structure.d.ts +0 -1
  110. package/dist/magic-funcs/structure.d.ts.map +0 -1
  111. package/dist/magic-funcs/structure.js +0 -2
  112. package/dist/magic-funcs/structure.js.map +0 -1
  113. package/dist/magic-funcs/summarize.d.ts +0 -1
  114. package/dist/magic-funcs/summarize.d.ts.map +0 -1
  115. package/dist/magic-funcs/summarize.js +0 -2
  116. package/dist/magic-funcs/summarize.js.map +0 -1
  117. package/dist/memory.d.ts +0 -91
  118. package/dist/memory.d.ts.map +0 -1
  119. package/dist/memory.js +0 -350
  120. package/dist/memory.js.map +0 -1
  121. package/dist/rag.d.ts +0 -42
  122. package/dist/rag.d.ts.map +0 -1
  123. package/dist/rag.js +0 -66
  124. package/dist/rag.js.map +0 -1
  125. package/dist/session.d.ts +0 -12
  126. package/dist/session.d.ts.map +0 -1
  127. package/dist/session.js +0 -31
  128. package/dist/session.js.map +0 -1
  129. package/dist/src/heart/agent.d.ts +0 -68
  130. package/dist/src/heart/agent.d.ts.map +0 -1
  131. package/dist/src/heart/agent.js +0 -140
  132. package/dist/src/heart/agent.js.map +0 -1
  133. package/dist/src/heart/chain.d.ts +0 -42
  134. package/dist/src/heart/chain.d.ts.map +0 -1
  135. package/dist/src/heart/chain.js +0 -152
  136. package/dist/src/heart/chain.js.map +0 -1
  137. package/dist/src/heart/chatbot.d.ts +0 -27
  138. package/dist/src/heart/chatbot.d.ts.map +0 -1
  139. package/dist/src/heart/chatbot.js +0 -100
  140. package/dist/src/heart/chatbot.js.map +0 -1
  141. package/dist/src/heart/memorychain.d.ts +0 -59
  142. package/dist/src/heart/memorychain.d.ts.map +0 -1
  143. package/dist/src/heart/memorychain.js +0 -165
  144. package/dist/src/heart/memorychain.js.map +0 -1
  145. package/dist/src/heart/tools/BasicToolRegistry.d.ts +0 -22
  146. package/dist/src/heart/tools/BasicToolRegistry.d.ts.map +0 -1
  147. package/dist/src/heart/tools/BasicToolRegistry.js +0 -45
  148. package/dist/src/heart/tools/BasicToolRegistry.js.map +0 -1
  149. package/dist/src/heart/tools/CombinedRegistry.d.ts +0 -41
  150. package/dist/src/heart/tools/CombinedRegistry.d.ts.map +0 -1
  151. package/dist/src/heart/tools/CombinedRegistry.js +0 -46
  152. package/dist/src/heart/tools/CombinedRegistry.js.map +0 -1
  153. package/dist/src/heart/tools/ZodiosToolRegistry.d.ts +0 -22
  154. package/dist/src/heart/tools/ZodiosToolRegistry.d.ts.map +0 -1
  155. package/dist/src/heart/tools/ZodiosToolRegistry.js +0 -80
  156. package/dist/src/heart/tools/ZodiosToolRegistry.js.map +0 -1
  157. package/dist/src/helpers.d.ts +0 -31
  158. package/dist/src/helpers.d.ts.map +0 -1
  159. package/dist/src/helpers.js +0 -105
  160. package/dist/src/helpers.js.map +0 -1
  161. package/dist/src/imports.d.ts +0 -44
  162. package/dist/src/imports.d.ts.map +0 -1
  163. package/dist/src/imports.js +0 -56
  164. package/dist/src/imports.js.map +0 -1
  165. package/dist/src/memory.d.ts +0 -78
  166. package/dist/src/memory.d.ts.map +0 -1
  167. package/dist/src/memory.js +0 -282
  168. package/dist/src/memory.js.map +0 -1
  169. package/dist/src/rag.d.ts +0 -58
  170. package/dist/src/rag.d.ts.map +0 -1
  171. package/dist/src/rag.js +0 -72
  172. package/dist/src/rag.js.map +0 -1
@@ -0,0 +1,737 @@
1
+ import { BaseChatModel, convertToOpenAITool, AIMessage, AIMessageChunk, ChatGenerationChunk, } from "../imports";
2
+ import { spawn } from "node:child_process";
3
+ import { createInterface } from "node:readline";
4
+ import { tmpdir } from "node:os";
5
+ import { mkdtempSync } from "node:fs";
6
+ import { join } from "node:path";
7
+ /**
8
+ * CLI_LLM — nutzt die headless CLIs der Provider (z.B. `claude -p`, `codex exec`)
9
+ * wie ein ganz normales LangChain-`BaseChatModel`.
10
+ *
11
+ * Idee: statt die HTTP-API aufzurufen, wird pro `.invoke()`/`.stream()` ein
12
+ * Subprozess der jeweiligen CLI gestartet. Vorteil: es greift die eingeloggte
13
+ * Abo-Auth (kein API-Key/keine Per-Token-Kosten). Die Klasse verhält sich nach
14
+ * außen wie jedes andere `BaseChatModel` und ist damit drop-in für `Agent`/`Chain`.
15
+ *
16
+ * WICHTIG (bewusste Grenzen):
17
+ * - Tools: die internen Tools der CLI werden deaktiviert/eingeschränkt → reines LLM.
18
+ * `.bindTools()` / das LangChain-Tool-Calling funktioniert hier NICHT wie bei
19
+ * `ChatOpenAI` (die CLI führt eigene Tools intern aus, gibt keine `tool_calls` zurück).
20
+ * - Memory/Kontext: läuft per Default in einem neutralen Verzeichnis (kein Projekt-CLAUDE.md).
21
+ * - Multi-Turn: die Konversation wird ins Prompt eingebettet (stateless pro Aufruf).
22
+ */
23
+ let _neutralCwd;
24
+ /** Einmaliges, leeres Temp-Verzeichnis → kein Projekt-CLAUDE.md / keine Coder-Kontamination. */
25
+ function neutralCwd() {
26
+ if (!_neutralCwd)
27
+ _neutralCwd = mkdtempSync(join(tmpdir(), "cli-llm-"));
28
+ return _neutralCwd;
29
+ }
30
+ /** Liest die Rolle einer Message robust aus (`.type` ist die moderne API, `.getType()` Fallback). */
31
+ function messageRole(message) {
32
+ const anyMsg = message;
33
+ return anyMsg.type ?? (typeof anyMsg.getType === "function" ? anyMsg.getType() : "human");
34
+ }
35
+ /** Flacht `content` (string oder Content-Blocks) auf reinen Text ab (Bilder werden v1 ignoriert). */
36
+ function contentToText(content) {
37
+ if (typeof content === "string")
38
+ return content;
39
+ if (Array.isArray(content)) {
40
+ return content
41
+ .map((part) => {
42
+ if (typeof part === "string")
43
+ return part;
44
+ if (part && typeof part === "object" && typeof part.text === "string") {
45
+ return part.text;
46
+ }
47
+ return "";
48
+ })
49
+ .join("");
50
+ }
51
+ return content == null ? "" : String(content);
52
+ }
53
+ /** Basis-Fehler für jeden Fehlschlag eines CLI-Aufrufs (`claude`, `codex`). */
54
+ export class CLIError extends Error {
55
+ provider;
56
+ exitCode;
57
+ status;
58
+ raw;
59
+ constructor(message, opts) {
60
+ super(message);
61
+ this.name = "CLIError";
62
+ this.provider = opts.provider;
63
+ this.exitCode = opts.exitCode;
64
+ this.status = opts.status;
65
+ this.raw = opts.raw;
66
+ // instanceof bleibt auch nach Transpilation/Bundling korrekt:
67
+ Object.setPrototypeOf(this, new.target.prototype);
68
+ }
69
+ }
70
+ /**
71
+ * Wird geworfen, wenn die CLI ein Usage-/Rate-Limit gemeldet hat (Abo-Limit,
72
+ * HTTP 429, Token-Quota oder Billing). So kann man im try/catch gezielt prüfen,
73
+ * WARUM der Aufruf fehlschlug:
74
+ *
75
+ * ```ts
76
+ * try { await llm.invoke(msgs) }
77
+ * catch (e) {
78
+ * if (e instanceof CLIUsageLimitError) {
79
+ * console.log(`Limit erreicht (${e.limitType}); reset:`, e.resetsAt)
80
+ * }
81
+ * }
82
+ * ```
83
+ */
84
+ export class CLIUsageLimitError extends CLIError {
85
+ /** Art des Limits, z.B. "five_hour" | "weekly" | "rate_limit" | "quota" | "billing" | "usage_limit". */
86
+ limitType;
87
+ /** Zeitpunkt, zu dem das Limit laut CLI zurückgesetzt wird (falls bekannt). */
88
+ resetsAt;
89
+ constructor(message, opts) {
90
+ super(message, opts);
91
+ this.name = "CLIUsageLimitError";
92
+ this.limitType = opts.limitType;
93
+ this.resetsAt = opts.resetsAt;
94
+ Object.setPrototypeOf(this, new.target.prototype);
95
+ }
96
+ }
97
+ /**
98
+ * Muster, die ein Usage-/Rate-/Quota-/Billing-Limit anzeigen.
99
+ * Bewusst NICHT der transiente 529-Overload ("overloaded") — der ist kein Limit,
100
+ * sondern vorübergehende Serverlast und wird von der CLI ohnehin automatisch retried.
101
+ */
102
+ const USAGE_LIMIT_RE = /\b(usage limit|rate[\s_-]?limit|quota|insufficient[\s_]?(?:quota|credits?)|credit balance|too many requests|limit (?:reached|exceeded)|hit your usage|429)\b/i;
103
+ /** Heuristik für die Limit-Art aus einer Fehlermeldung. */
104
+ function extractLimitType(message) {
105
+ if (/seven[\s_-]?day|weekly|per[\s-]?week|\bweek\b/i.test(message))
106
+ return "weekly";
107
+ if (/five[\s_-]?hour|5[\s-]?hour/i.test(message))
108
+ return "five_hour";
109
+ if (/insufficient[\s_]?quota|\bquota\b/i.test(message))
110
+ return "quota";
111
+ if (/credit|billing/i.test(message))
112
+ return "billing";
113
+ if (/rate[\s_-]?limit|429|too many requests/i.test(message))
114
+ return "rate_limit";
115
+ if (/usage limit|hit your usage/i.test(message))
116
+ return "usage_limit";
117
+ return undefined;
118
+ }
119
+ /** Liest – wenn möglich – einen Reset-Zeitpunkt aus ("resets at …", "try again at …", "|<epoch>"). */
120
+ function extractResetsAt(message) {
121
+ const phrase = message.match(/(?:try again at|resets? at|resets?)\s+([0-9A-Za-z:+\-.\s,]{6,40})/i);
122
+ if (phrase) {
123
+ const candidate = phrase[1].trim().replace(/[.,;]+$/, "");
124
+ const parsed = new Date(candidate);
125
+ if (!Number.isNaN(parsed.getTime()))
126
+ return parsed;
127
+ }
128
+ // Claude meldet das Abo-Reset teils als Unix-Epoch (Sekunden) hinter einem Pipe-Zeichen.
129
+ const epoch = message.match(/\|\s*(\d{10})\b/);
130
+ if (epoch)
131
+ return new Date(Number(epoch[1]) * 1000);
132
+ return undefined;
133
+ }
134
+ /**
135
+ * Baut den passenden Fehler: `CLIUsageLimitError`, wenn Meldung/Status/Limit-Typ
136
+ * auf ein Usage-/Rate-Limit hindeuten — sonst ein generischer `CLIError`.
137
+ */
138
+ function buildCliError(provider, message, opts = {}) {
139
+ const isUsageLimit = opts.status === 429 || opts.limitType != null || USAGE_LIMIT_RE.test(message);
140
+ if (isUsageLimit) {
141
+ return new CLIUsageLimitError(message, {
142
+ provider,
143
+ exitCode: opts.exitCode,
144
+ status: opts.status,
145
+ raw: opts.raw,
146
+ limitType: opts.limitType ?? extractLimitType(message),
147
+ resetsAt: opts.resetsAt ?? extractResetsAt(message),
148
+ });
149
+ }
150
+ return new CLIError(message, { provider, exitCode: opts.exitCode, status: opts.status, raw: opts.raw });
151
+ }
152
+ let _toolCallCounter = 0;
153
+ function nextToolCallId(name) {
154
+ _toolCallCounter += 1;
155
+ return `call_${name}_${_toolCallCounter}`;
156
+ }
157
+ function toToolSpec(tool) {
158
+ const oai = convertToOpenAITool(tool);
159
+ const fn = oai?.function ?? tool;
160
+ return {
161
+ name: String(fn?.name ?? "tool"),
162
+ description: String(fn?.description ?? ""),
163
+ parameters: fn?.parameters ?? { type: "object", properties: {} },
164
+ };
165
+ }
166
+ function safeJson(s) {
167
+ try {
168
+ return JSON.parse(s);
169
+ }
170
+ catch {
171
+ return undefined;
172
+ }
173
+ }
174
+ /** Extrahiert das erste JSON-Objekt aus einem Text (toleriert Markdown-Fences und Begleittext). */
175
+ function extractJsonObject(text) {
176
+ if (!text)
177
+ return undefined;
178
+ let s = text.trim();
179
+ const fence = s.match(/```(?:json)?\s*([\s\S]*?)```/i);
180
+ if (fence)
181
+ s = fence[1].trim();
182
+ const direct = safeJson(s);
183
+ if (direct !== undefined)
184
+ return direct;
185
+ const start = s.indexOf("{");
186
+ if (start === -1)
187
+ return undefined;
188
+ let depth = 0;
189
+ let inStr = false;
190
+ let esc = false;
191
+ for (let i = start; i < s.length; i++) {
192
+ const ch = s[i];
193
+ if (inStr) {
194
+ if (esc)
195
+ esc = false;
196
+ else if (ch === "\\")
197
+ esc = true;
198
+ else if (ch === '"')
199
+ inStr = false;
200
+ }
201
+ else if (ch === '"')
202
+ inStr = true;
203
+ else if (ch === "{")
204
+ depth++;
205
+ else if (ch === "}") {
206
+ depth--;
207
+ if (depth === 0)
208
+ return safeJson(s.slice(start, i + 1));
209
+ }
210
+ }
211
+ return undefined;
212
+ }
213
+ /** Interpretiert die Model-Ausgabe als Tool-Aufruf oder finale Antwort. */
214
+ function parseToolDecision(text) {
215
+ const obj = extractJsonObject(text);
216
+ if (obj && typeof obj === "object") {
217
+ const tc = obj.tool_call;
218
+ if (tc && typeof tc.name === "string") {
219
+ const rawArgs = tc.arguments;
220
+ const args = typeof rawArgs === "string"
221
+ ? (safeJson(rawArgs) ?? {})
222
+ : (rawArgs ?? {});
223
+ return { kind: "tool_call", calls: [{ name: tc.name, args, id: nextToolCallId(tc.name), type: "tool_call" }] };
224
+ }
225
+ const final = obj.final;
226
+ if (typeof final === "string")
227
+ return { kind: "final", text: final };
228
+ }
229
+ // Kein gültiges Protokoll-JSON → die gesamte Ausgabe als finale Antwort behandeln.
230
+ return { kind: "final", text };
231
+ }
232
+ /**
233
+ * Abstrakte Basisklasse. Subklassen (`ClaudeCLI_LLM`, `OpenAICLI_LLM`) definieren
234
+ * nur das CLI-spezifische: Binary, Args, und das Parsen von Output/Stream.
235
+ */
236
+ export class CLI_LLM extends BaseChatModel {
237
+ model;
238
+ systemPrompt;
239
+ cwd;
240
+ cliPath;
241
+ extraArgs;
242
+ timeoutMs;
243
+ env;
244
+ /** Provider-Tag, analog zu `getLLM()`-Rückgaben (`"claude-cli"`, `"openai-cli"`). */
245
+ provider;
246
+ /**
247
+ * Wie der Prompt an stdin übergeben wird. Default: nur der User-Prompt.
248
+ * Codex hat z.B. kein `--system-prompt`-Flag → überschreibt das, um den
249
+ * System-Prompt als Präambel voranzustellen.
250
+ */
251
+ composeStdin(_systemPrompt, userPrompt) {
252
+ return userPrompt;
253
+ }
254
+ constructor(fields, defaults) {
255
+ super(fields);
256
+ this.provider = defaults.provider;
257
+ this.model = fields.model ?? defaults.model;
258
+ this.systemPrompt = fields.systemPrompt ?? "";
259
+ this.cwd = fields.cwd ?? neutralCwd();
260
+ this.cliPath = fields.cliPath ?? defaults.cliPath;
261
+ this.extraArgs = fields.extraArgs ?? [];
262
+ this.timeoutMs = fields.timeoutMs;
263
+ this.env = fields.env;
264
+ }
265
+ _llmType() {
266
+ return this.provider;
267
+ }
268
+ /**
269
+ * Macht das Model in LangGraph-React-Agents nutzbar. Da die CLI keine nativen
270
+ * `tool_calls` liefert, werden die Tools als Spezifikationen gebunden und in
271
+ * `_generate` per Prompt-Protokoll emuliert (siehe `parseToolDecision`).
272
+ */
273
+ bindTools(tools, kwargs) {
274
+ const specs = tools.map(toToolSpec);
275
+ return this.withConfig({ tools: specs, ...kwargs });
276
+ }
277
+ /** Baut die Tool-Anweisung, die dem System-Prompt angehängt wird. */
278
+ buildToolInstruction(tools) {
279
+ const list = tools
280
+ .map((t) => `- ${t.name}: ${t.description}\n Argumente (JSON-Schema): ${JSON.stringify(t.parameters)}`)
281
+ .join("\n");
282
+ return [
283
+ "Du hast Zugriff auf folgende Tools:",
284
+ list,
285
+ "",
286
+ "Wenn ein Tool die Anfrage beantworten hilft, rufe es auf. Beziehe dich bei Bedarf auf frühere Tool-Ergebnisse im Verlauf.",
287
+ "Antworte AUSSCHLIESSLICH mit GENAU EINEM JSON-Objekt — kein weiterer Text, keine Markdown-Codeblöcke:",
288
+ '- Tool aufrufen: {"tool_call": {"name": "<toolname>", "arguments": { ... }}}',
289
+ '- Endgültige Antwort an den Nutzer: {"final": "<deine Antwort>"}',
290
+ ].join("\n");
291
+ }
292
+ /** Messages → `{ systemPrompt, userPrompt }`. SystemMessages überschreiben `this.systemPrompt`. */
293
+ messagesToCliInput(messages) {
294
+ const systemParts = [];
295
+ const turns = [];
296
+ const nonSystem = [];
297
+ for (const message of messages) {
298
+ const role = messageRole(message);
299
+ const text = contentToText(message.content);
300
+ if (role === "system") {
301
+ systemParts.push(text);
302
+ continue;
303
+ }
304
+ nonSystem.push(message);
305
+ if (role === "tool") {
306
+ // Ergebnis eines Tool-Aufrufs (LangGraph `ToolMessage`).
307
+ const name = message.name ?? "tool";
308
+ turns.push(`Tool-Ergebnis [${name}]: ${text}`);
309
+ }
310
+ else if (role === "ai") {
311
+ const toolCalls = message.tool_calls;
312
+ if (toolCalls && toolCalls.length > 0) {
313
+ const calls = toolCalls.map((tc) => `${tc.name}(${JSON.stringify(tc.args ?? {})})`).join(", ");
314
+ turns.push(`Assistant (Tool-Aufruf): ${calls}`);
315
+ }
316
+ else {
317
+ turns.push(`Assistant: ${text}`);
318
+ }
319
+ }
320
+ else {
321
+ turns.push(`Human: ${text}`);
322
+ }
323
+ }
324
+ const systemPrompt = systemParts.length > 0 ? systemParts.join("\n\n") : this.systemPrompt;
325
+ // Einzelner reiner User-Turn → nur dessen Inhalt; sonst ein lesbares Transkript.
326
+ const userPrompt = nonSystem.length === 1 && messageRole(nonSystem[0]) === "human"
327
+ ? contentToText(nonSystem[0].content)
328
+ : turns.join("\n\n");
329
+ return { systemPrompt, userPrompt };
330
+ }
331
+ async _generate(messages, options, _runManager) {
332
+ const { systemPrompt, userPrompt } = this.messagesToCliInput(messages);
333
+ const tools = options?.tools;
334
+ // Tool-Modus: System-Prompt um das Tool-Protokoll ergänzen und die Antwort als Tool-Aufruf/Final interpretieren.
335
+ const effectiveSystem = tools && tools.length > 0
336
+ ? [systemPrompt, this.buildToolInstruction(tools)].filter((s) => s && s.trim()).join("\n\n")
337
+ : systemPrompt;
338
+ const args = this.buildArgs(false, effectiveSystem);
339
+ const stdin = this.composeStdin(effectiveSystem, userPrompt);
340
+ const stdout = await this.runCli(args, stdin, options?.signal);
341
+ const parsed = this.parseFinal(stdout);
342
+ const responseMetadata = { ...parsed.responseMetadata, model: this.model, provider: this.provider };
343
+ if (tools && tools.length > 0) {
344
+ const decision = parseToolDecision(parsed.text);
345
+ if (decision.kind === "tool_call") {
346
+ const message = new AIMessage({
347
+ content: "",
348
+ tool_calls: decision.calls,
349
+ usage_metadata: parsed.usage,
350
+ response_metadata: responseMetadata,
351
+ });
352
+ return { generations: [{ text: "", message }], llmOutput: { tokenUsage: parsed.usage } };
353
+ }
354
+ const message = new AIMessage({
355
+ content: decision.text,
356
+ usage_metadata: parsed.usage,
357
+ response_metadata: responseMetadata,
358
+ });
359
+ return { generations: [{ text: decision.text, message }], llmOutput: { tokenUsage: parsed.usage } };
360
+ }
361
+ const message = new AIMessage({
362
+ content: parsed.text,
363
+ usage_metadata: parsed.usage,
364
+ response_metadata: responseMetadata,
365
+ });
366
+ return {
367
+ generations: [{ text: parsed.text, message }],
368
+ llmOutput: { ...parsed.responseMetadata, tokenUsage: parsed.usage },
369
+ };
370
+ }
371
+ async *_streamResponseChunks(messages, options, runManager) {
372
+ // Tool-Modus lässt sich nicht token-weise streamen (wir brauchen das ganze JSON
373
+ // zum Parsen). → einmal `_generate` ausführen und als ein Chunk samt tool_calls liefern.
374
+ if (options?.tools && options.tools.length > 0) {
375
+ const result = await this._generate(messages, options, runManager);
376
+ const gen = result.generations[0];
377
+ const aiMsg = gen.message;
378
+ const toolCalls = (aiMsg.tool_calls ?? []);
379
+ yield new ChatGenerationChunk({
380
+ text: gen.text,
381
+ message: new AIMessageChunk({
382
+ content: aiMsg.content,
383
+ tool_call_chunks: toolCalls.map((tc, i) => ({
384
+ name: tc.name,
385
+ args: JSON.stringify(tc.args ?? {}),
386
+ id: tc.id,
387
+ index: i,
388
+ type: "tool_call_chunk",
389
+ })),
390
+ usage_metadata: aiMsg.usage_metadata,
391
+ response_metadata: aiMsg.response_metadata,
392
+ }),
393
+ });
394
+ return;
395
+ }
396
+ const { systemPrompt, userPrompt } = this.messagesToCliInput(messages);
397
+ const args = this.buildArgs(true, systemPrompt);
398
+ const stdin = this.composeStdin(systemPrompt, userPrompt);
399
+ let finalUsage;
400
+ let finalMeta = {};
401
+ let lastRateLimit;
402
+ for await (const line of this.runCliStreaming(args, stdin, options?.signal)) {
403
+ const parsed = this.parseStreamLine(line);
404
+ if (!parsed)
405
+ continue;
406
+ if (parsed.rateLimit)
407
+ lastRateLimit = parsed.rateLimit;
408
+ if (parsed.error) {
409
+ // Mit zuvor gesehenen Rate-Limit-Infos anreichern (resetsAt/limitType aus `rate_limit_event`).
410
+ throw buildCliError(this.provider, parsed.error.message, {
411
+ status: parsed.error.status,
412
+ raw: line,
413
+ limitType: parsed.error.limitType ?? lastRateLimit?.limitType,
414
+ resetsAt: parsed.error.resetsAt ?? lastRateLimit?.resetsAt,
415
+ });
416
+ }
417
+ if (parsed.text) {
418
+ await runManager?.handleLLMNewToken(parsed.text);
419
+ yield new ChatGenerationChunk({
420
+ text: parsed.text,
421
+ message: new AIMessageChunk({ content: parsed.text }),
422
+ });
423
+ }
424
+ if (parsed.usage)
425
+ finalUsage = parsed.usage;
426
+ if (parsed.responseMetadata)
427
+ finalMeta = { ...finalMeta, ...parsed.responseMetadata };
428
+ }
429
+ // Abschluss-Chunk trägt Usage/Metadaten (analog zu nativen Chat-Models).
430
+ if (finalUsage || Object.keys(finalMeta).length > 0) {
431
+ yield new ChatGenerationChunk({
432
+ text: "",
433
+ message: new AIMessageChunk({
434
+ content: "",
435
+ usage_metadata: finalUsage,
436
+ response_metadata: { ...finalMeta, model: this.model, provider: this.provider },
437
+ }),
438
+ });
439
+ }
440
+ }
441
+ /** Startet die CLI, schreibt stdin, sammelt stdout. Wirft bei Exit≠0, Timeout oder Abort. */
442
+ runCli(args, stdin, signal) {
443
+ return new Promise((resolve, reject) => {
444
+ const child = spawn(this.cliPath, args, {
445
+ cwd: this.cwd,
446
+ env: { ...process.env, ...this.env },
447
+ stdio: ["pipe", "pipe", "pipe"],
448
+ });
449
+ let stdout = "";
450
+ let stderr = "";
451
+ let timedOut = false;
452
+ const timer = this.timeoutMs
453
+ ? setTimeout(() => {
454
+ timedOut = true;
455
+ child.kill("SIGTERM");
456
+ }, this.timeoutMs)
457
+ : undefined;
458
+ const onAbort = () => child.kill("SIGTERM");
459
+ if (signal) {
460
+ if (signal.aborted)
461
+ child.kill("SIGTERM");
462
+ else
463
+ signal.addEventListener("abort", onAbort, { once: true });
464
+ }
465
+ const cleanup = () => {
466
+ if (timer)
467
+ clearTimeout(timer);
468
+ if (signal)
469
+ signal.removeEventListener("abort", onAbort);
470
+ };
471
+ child.stdout.on("data", (d) => (stdout += d.toString()));
472
+ child.stderr.on("data", (d) => (stderr += d.toString()));
473
+ child.on("error", (err) => {
474
+ cleanup();
475
+ reject(new CLIError(`Konnte '${this.cliPath}' nicht starten: ${err.message}. Ist die CLI installiert und im PATH?`, { provider: this.provider }));
476
+ });
477
+ child.on("close", (code) => {
478
+ cleanup();
479
+ if (timedOut)
480
+ return reject(new CLIError(`${this.provider} CLI Timeout nach ${this.timeoutMs}ms`, { provider: this.provider, exitCode: code }));
481
+ if (signal?.aborted)
482
+ return reject(new CLIError(`${this.provider} CLI abgebrochen`, { provider: this.provider, exitCode: code }));
483
+ if (code === 0)
484
+ return resolve(stdout);
485
+ // Exit≠0: viele CLIs liefern einen strukturierten Fehler auf stdout (z.B. claude `is_error`).
486
+ // Den an parseFinal durchreichen, damit eine klare (ggf. Usage-Limit-)Fehlermeldung entsteht.
487
+ if (stdout.trim().length > 0)
488
+ return resolve(stdout);
489
+ // Sonst aus stderr klassifizieren (z.B. ein Limit-Hinweis dort).
490
+ reject(buildCliError(this.provider, stderr.trim() || `CLI Exit-Code ${code}`, { exitCode: code }));
491
+ });
492
+ if (stdin)
493
+ child.stdin.write(stdin);
494
+ child.stdin.end();
495
+ });
496
+ }
497
+ /** Wie `runCli`, liefert stdout aber zeilenweise als Async-Iterator (für Streaming). */
498
+ async *runCliStreaming(args, stdin, signal) {
499
+ const child = spawn(this.cliPath, args, {
500
+ cwd: this.cwd,
501
+ env: { ...process.env, ...this.env },
502
+ stdio: ["pipe", "pipe", "pipe"],
503
+ });
504
+ let stderr = "";
505
+ let spawnError;
506
+ let timedOut = false;
507
+ child.stderr.on("data", (d) => (stderr += d.toString()));
508
+ child.on("error", (err) => {
509
+ spawnError = new CLIError(`Konnte '${this.cliPath}' nicht starten: ${err.message}. Ist die CLI installiert und im PATH?`, { provider: this.provider });
510
+ });
511
+ const closed = new Promise((resolve) => child.on("close", resolve));
512
+ const onAbort = () => child.kill("SIGTERM");
513
+ if (signal) {
514
+ if (signal.aborted)
515
+ child.kill("SIGTERM");
516
+ else
517
+ signal.addEventListener("abort", onAbort, { once: true });
518
+ }
519
+ const timer = this.timeoutMs
520
+ ? setTimeout(() => {
521
+ timedOut = true;
522
+ child.kill("SIGTERM");
523
+ }, this.timeoutMs)
524
+ : undefined;
525
+ if (stdin)
526
+ child.stdin.write(stdin);
527
+ child.stdin.end();
528
+ const rl = createInterface({ input: child.stdout, crlfDelay: Infinity });
529
+ try {
530
+ for await (const line of rl) {
531
+ if (line.trim().length > 0)
532
+ yield line;
533
+ }
534
+ }
535
+ finally {
536
+ rl.close();
537
+ if (timer)
538
+ clearTimeout(timer);
539
+ if (signal)
540
+ signal.removeEventListener("abort", onAbort);
541
+ }
542
+ const code = await closed;
543
+ if (spawnError)
544
+ throw spawnError;
545
+ if (timedOut)
546
+ throw new CLIError(`${this.provider} CLI Timeout nach ${this.timeoutMs}ms`, { provider: this.provider, exitCode: code });
547
+ if (signal?.aborted)
548
+ throw new CLIError(`${this.provider} CLI abgebrochen`, { provider: this.provider, exitCode: code });
549
+ if (code !== 0 && code !== null) {
550
+ // Falls der Stream selbst kein Fehler-Event lieferte: aus stderr klassifizieren.
551
+ throw buildCliError(this.provider, stderr.trim() || `CLI Exit-Code ${code}`, { exitCode: code });
552
+ }
553
+ }
554
+ }
555
+ export class ClaudeCLI_LLM extends CLI_LLM {
556
+ static lc_name() {
557
+ return "ClaudeCLI_LLM";
558
+ }
559
+ constructor(fields = {}) {
560
+ super(fields, { model: "claude-opus-4-8", cliPath: "claude", provider: "claude-cli" });
561
+ }
562
+ buildArgs(stream, systemPrompt) {
563
+ const base = [
564
+ "-p",
565
+ "--model", this.model,
566
+ "--system-prompt", systemPrompt, // "" ersetzt den Default-Coding-Prompt
567
+ "--tools", "", // alle Built-in-Tools aus → reines LLM
568
+ "--strict-mcp-config", // keine fremden MCP-Server (saubere Umgebung)
569
+ ];
570
+ const output = stream
571
+ ? ["--output-format", "stream-json", "--verbose", "--include-partial-messages"]
572
+ : ["--output-format", "json"];
573
+ return [...base, ...output, ...this.extraArgs];
574
+ }
575
+ parseFinal(stdout) {
576
+ const obj = JSON.parse(stdout.trim());
577
+ if (obj.is_error) {
578
+ const message = typeof obj.result === "string" && obj.result.trim() ? obj.result : (obj.subtype ?? "unbekannter Fehler");
579
+ throw buildCliError(this.provider, message, {
580
+ status: typeof obj.api_error_status === "number" ? obj.api_error_status : undefined,
581
+ raw: obj,
582
+ });
583
+ }
584
+ const usage = obj.usage ?? {};
585
+ const input = Number(usage.input_tokens ?? 0);
586
+ const output = Number(usage.output_tokens ?? 0);
587
+ return {
588
+ text: typeof obj.result === "string" ? obj.result : "",
589
+ usage: { input_tokens: input, output_tokens: output, total_tokens: input + output },
590
+ responseMetadata: {
591
+ session_id: obj.session_id,
592
+ total_cost_usd: obj.total_cost_usd,
593
+ stop_reason: obj.stop_reason,
594
+ },
595
+ raw: obj,
596
+ };
597
+ }
598
+ parseStreamLine(line) {
599
+ let obj;
600
+ try {
601
+ obj = JSON.parse(line);
602
+ }
603
+ catch {
604
+ return undefined;
605
+ }
606
+ // Rate-Limit-Snapshot (kein Fehler) → als Hinweis (resetsAt/Bucket) für einen evtl. späteren Fehler.
607
+ if (obj.type === "rate_limit_event" && obj.rate_limit_info) {
608
+ const info = obj.rate_limit_info;
609
+ return {
610
+ rateLimit: {
611
+ limitType: typeof info.rateLimitType === "string" ? info.rateLimitType : undefined,
612
+ resetsAt: typeof info.resetsAt === "number" ? new Date(info.resetsAt * 1000) : undefined,
613
+ },
614
+ };
615
+ }
616
+ // Retry-Grund (rate_limit / billing_error) als Limit-Hinweis.
617
+ if (obj.type === "system" && obj.subtype === "api_retry" && (obj.error === "rate_limit" || obj.error === "billing_error")) {
618
+ return { rateLimit: { limitType: obj.error === "billing_error" ? "billing" : "rate_limit" } };
619
+ }
620
+ // Token-Deltas: { type:"stream_event", event:{ delta:{ type:"text_delta", text } } }
621
+ if (obj.type === "stream_event" && obj.event?.delta?.type === "text_delta") {
622
+ return { text: obj.event.delta.text };
623
+ }
624
+ // Abschluss-Event: Fehler oder Usage/Kosten.
625
+ if (obj.type === "result") {
626
+ if (obj.is_error) {
627
+ const message = typeof obj.result === "string" && obj.result.trim() ? obj.result : (obj.subtype ?? "unbekannter Fehler");
628
+ return {
629
+ error: { message, status: typeof obj.api_error_status === "number" ? obj.api_error_status : undefined },
630
+ done: true,
631
+ };
632
+ }
633
+ const usage = obj.usage ?? {};
634
+ const input = Number(usage.input_tokens ?? 0);
635
+ const output = Number(usage.output_tokens ?? 0);
636
+ return {
637
+ usage: { input_tokens: input, output_tokens: output, total_tokens: input + output },
638
+ responseMetadata: {
639
+ session_id: obj.session_id,
640
+ total_cost_usd: obj.total_cost_usd,
641
+ stop_reason: obj.stop_reason,
642
+ },
643
+ done: true,
644
+ };
645
+ }
646
+ return undefined;
647
+ }
648
+ }
649
+ export class OpenAICLI_LLM extends CLI_LLM {
650
+ static lc_name() {
651
+ return "OpenAICLI_LLM";
652
+ }
653
+ constructor(fields = {}) {
654
+ super(fields, { model: "gpt-5.5", cliPath: "codex", provider: "openai-cli" });
655
+ }
656
+ buildArgs(_stream, _systemPrompt) {
657
+ // codex exec gibt mit --json in beiden Fällen NDJSON aus.
658
+ return [
659
+ "exec",
660
+ "--json",
661
+ "--sandbox", "read-only", // keine Datei-Schreibzugriffe
662
+ "--ask-for-approval", "never", // headless: nie nach Erlaubnis fragen
663
+ "--skip-git-repo-check", // läuft auch im neutralen (Nicht-Git-)cwd
664
+ "-m", this.model,
665
+ ...this.extraArgs,
666
+ "-", // Prompt komplett aus stdin lesen
667
+ ];
668
+ }
669
+ /** Codex hat kein `--system-prompt` → als Präambel voranstellen. */
670
+ composeStdin(systemPrompt, userPrompt) {
671
+ if (systemPrompt && systemPrompt.trim().length > 0) {
672
+ return `System:\n${systemPrompt}\n\n${userPrompt}`;
673
+ }
674
+ return userPrompt;
675
+ }
676
+ parseFinal(stdout) {
677
+ let text = "";
678
+ let usage;
679
+ const meta = {};
680
+ for (const line of stdout.split("\n")) {
681
+ const trimmed = line.trim();
682
+ if (!trimmed)
683
+ continue;
684
+ let obj;
685
+ try {
686
+ obj = JSON.parse(trimmed);
687
+ }
688
+ catch {
689
+ continue;
690
+ }
691
+ if (obj.type === "thread.started" && obj.thread_id)
692
+ meta.thread_id = obj.thread_id;
693
+ if (obj.type === "item.completed" && obj.item?.type === "agent_message" && typeof obj.item.text === "string") {
694
+ text = obj.item.text;
695
+ }
696
+ if (obj.type === "turn.completed" && obj.usage) {
697
+ const input = Number(obj.usage.input_tokens ?? 0);
698
+ const output = Number(obj.usage.output_tokens ?? 0);
699
+ usage = { input_tokens: input, output_tokens: output, total_tokens: input + output };
700
+ }
701
+ if (obj.type === "turn.failed" || obj.type === "error") {
702
+ const errObj = obj.error ?? obj;
703
+ const detail = typeof errObj?.message === "string" ? errObj.message : JSON.stringify(errObj);
704
+ throw buildCliError(this.provider, `codex: ${detail}`, { raw: obj });
705
+ }
706
+ }
707
+ return { text, usage, responseMetadata: meta };
708
+ }
709
+ parseStreamLine(line) {
710
+ let obj;
711
+ try {
712
+ obj = JSON.parse(line);
713
+ }
714
+ catch {
715
+ return undefined;
716
+ }
717
+ // Codex liefert keine Token-Deltas → die finale agent_message kommt als ein Chunk.
718
+ if (obj.type === "item.completed" && obj.item?.type === "agent_message" && typeof obj.item.text === "string") {
719
+ return { text: obj.item.text };
720
+ }
721
+ if (obj.type === "turn.completed" && obj.usage) {
722
+ const input = Number(obj.usage.input_tokens ?? 0);
723
+ const output = Number(obj.usage.output_tokens ?? 0);
724
+ return {
725
+ usage: { input_tokens: input, output_tokens: output, total_tokens: input + output },
726
+ done: true,
727
+ };
728
+ }
729
+ if (obj.type === "turn.failed" || obj.type === "error") {
730
+ const errObj = obj.error ?? obj;
731
+ const detail = typeof errObj?.message === "string" ? errObj.message : JSON.stringify(errObj);
732
+ return { error: { message: `codex: ${detail}` }, done: true };
733
+ }
734
+ return undefined;
735
+ }
736
+ }
737
+ //# sourceMappingURL=cli-llms.js.map