@telora/daemon 0.17.99 → 0.17.101
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.
- package/build-info.json +9 -3
- package/dist/loop-llm-client.d.ts +57 -13
- package/dist/loop-llm-client.d.ts.map +1 -1
- package/dist/loop-llm-client.js +163 -93
- package/dist/loop-llm-client.js.map +1 -1
- package/package.json +1 -1
package/build-info.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commitSha": "
|
|
3
|
-
"builtAt": "2026-06-
|
|
2
|
+
"commitSha": "189a4a0d",
|
|
3
|
+
"builtAt": "2026-06-17T00:14:34.198Z",
|
|
4
4
|
"expectedMigrations": [
|
|
5
5
|
"20250829113330_create_org_nodes_table.sql",
|
|
6
6
|
"20250829113402_fix_function_security_search_path.sql",
|
|
@@ -645,6 +645,12 @@
|
|
|
645
645
|
"20260616092424_discovery_requests_api_key_env.sql",
|
|
646
646
|
"20260616105749_local_model_telora_managed_keys.sql",
|
|
647
647
|
"20260616132414_agent_escalations_kind_merge_classes.sql",
|
|
648
|
-
"20260616155133_lock_local_model_secret_write_rpcs.sql"
|
|
648
|
+
"20260616155133_lock_local_model_secret_write_rpcs.sql",
|
|
649
|
+
"20260616212949_drop_stale_redeem_connect_token_overload.sql",
|
|
650
|
+
"20260616221622_handle_new_user_drop_default_tracker.sql",
|
|
651
|
+
"20260616221712_drop_dead_default_tracker_rows.sql",
|
|
652
|
+
"20260616224152_get_product_sessions_overlapping.sql",
|
|
653
|
+
"20260616225633_add_issue_type_to_get_product_issue_transitions.sql",
|
|
654
|
+
"20260616233000_stable_order_get_product_issue_transitions.sql"
|
|
649
655
|
]
|
|
650
656
|
}
|
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* LLM client for loop data updates.
|
|
2
|
+
* LLM client for loop data updates and PRD/loop differential evaluation.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Runs ONE model call per focus per cycle by spawning the `claude` CLI in print
|
|
5
|
+
* mode -- the same pattern as `de-restatement.ts` / `negative-branch.ts` -- so it
|
|
6
|
+
* rides the operator's existing Claude Code auth and needs NO Anthropic API key,
|
|
7
|
+
* org LLM secret, or any other key beyond what the spawned agent teams already
|
|
8
|
+
* use. (Previously this did a raw fetch to api.anthropic.com with an x-api-key
|
|
9
|
+
* header; when no key was configured the call returned null and the differential
|
|
10
|
+
* loop / PRD steward froze -- exactly the gap this client now closes.)
|
|
6
11
|
*
|
|
7
|
-
* Capture:
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* the OTLP receiver feeds, so loop spend lands in
|
|
12
|
+
* Capture: a `--print` one-shot session produces NO OTLP rows (the OTLP exporter
|
|
13
|
+
* only fires for long-lived interactive sessions), so loop-layer token/cost spend
|
|
14
|
+
* would otherwise be invisible to agent_telemetry_metrics. We parse the usage
|
|
15
|
+
* block off the print-mode JSON (`--output-format json`) and buffer a telemetry
|
|
16
|
+
* metric row on the same writer the OTLP receiver feeds, so loop spend lands in
|
|
17
|
+
* agent_telemetry_metrics too -- the same capture path as the audit assessor
|
|
18
|
+
* (spawner/lifecycle.ts buildAuditMetricRow).
|
|
12
19
|
*/
|
|
20
|
+
import { type ChildProcess } from 'node:child_process';
|
|
13
21
|
import { type TelemetryMetricRow } from './telemetry-writer.js';
|
|
14
22
|
import type { LoopUpdatePrompt } from './loop-prompt-builder.js';
|
|
15
23
|
/** Anthropic usage block (token counts). All fields optional/defensive. */
|
|
@@ -19,14 +27,45 @@ interface AnthropicUsage {
|
|
|
19
27
|
cache_read_input_tokens?: number;
|
|
20
28
|
cache_creation_input_tokens?: number;
|
|
21
29
|
}
|
|
30
|
+
/** Parsed outcome of a single model invocation (default or test-seam). */
|
|
31
|
+
export interface LoopModelResult {
|
|
32
|
+
/** The model's text response, or null when none was produced. */
|
|
33
|
+
text: string | null;
|
|
34
|
+
/** Token usage for telemetry capture; absent when the result omitted it. */
|
|
35
|
+
usage?: AnthropicUsage;
|
|
36
|
+
/** Resolved model id (drives the telemetry model tag + cost fallback). */
|
|
37
|
+
model?: string;
|
|
38
|
+
/** Cost reported directly by Claude Code, if any (preferred over the estimate). */
|
|
39
|
+
costUsd?: number | null;
|
|
40
|
+
}
|
|
41
|
+
interface SpawnOpts {
|
|
42
|
+
stdio: ['pipe', 'pipe', 'pipe'];
|
|
43
|
+
}
|
|
44
|
+
export interface LoopLLMOptions {
|
|
45
|
+
/** Path to the `claude` CLI binary (default: 'claude'). */
|
|
46
|
+
claudeBin?: string;
|
|
47
|
+
/** Timeout in ms; defaults to 2 minutes. */
|
|
48
|
+
timeoutMs?: number;
|
|
49
|
+
/** Override spawn for tests. Defaults to node:child_process spawn. */
|
|
50
|
+
spawnFn?: (cmd: string, args: string[], opts: SpawnOpts) => ChildProcess;
|
|
51
|
+
/**
|
|
52
|
+
* Override the entire model invocation for tests -- bypasses spawning the
|
|
53
|
+
* CLI. Receives the system prompt and user message, returns the parsed model
|
|
54
|
+
* result. When omitted, the default spawns the `claude` CLI in print mode.
|
|
55
|
+
*/
|
|
56
|
+
invokeModel?: (systemPrompt: string, userMessage: string) => Promise<LoopModelResult>;
|
|
57
|
+
}
|
|
22
58
|
/**
|
|
23
|
-
* Estimate cost in USD for a single
|
|
59
|
+
* Estimate cost in USD for a single call from its usage block.
|
|
24
60
|
* Returns 0 when the model family is unrecognized.
|
|
25
61
|
*/
|
|
26
62
|
export declare function estimateLoopCostUsd(model: string, usage: AnthropicUsage): number;
|
|
27
63
|
/**
|
|
28
64
|
* Build a telemetry metric row from a loop LLM call's usage. Exported for
|
|
29
65
|
* testing the capture-path mapping in isolation.
|
|
66
|
+
*
|
|
67
|
+
* `costUsd`, when finite, is used verbatim (Claude Code's reported
|
|
68
|
+
* total_cost_usd); otherwise cost is estimated from the model + usage.
|
|
30
69
|
*/
|
|
31
70
|
export declare function buildLoopMetricRow(params: {
|
|
32
71
|
organizationId: string;
|
|
@@ -37,17 +76,22 @@ export declare function buildLoopMetricRow(params: {
|
|
|
37
76
|
periodEnd: string;
|
|
38
77
|
/** Loop phase that issued the call (e.g. 'audit'); null when not phase-scoped. */
|
|
39
78
|
phase?: string | null;
|
|
79
|
+
/** Cost reported by Claude Code; preferred over the pricing-table estimate. */
|
|
80
|
+
costUsd?: number | null;
|
|
40
81
|
}): TelemetryMetricRow;
|
|
41
82
|
/**
|
|
42
|
-
*
|
|
83
|
+
* Run a loop data update / differential evaluation through the operator's
|
|
84
|
+
* Claude Code auth (print-mode `claude` spawn). No API key beyond Claude Code
|
|
85
|
+
* auth is required.
|
|
43
86
|
*
|
|
44
|
-
* @param organizationId - Organization
|
|
87
|
+
* @param organizationId - Organization the evaluation belongs to (telemetry attribution)
|
|
45
88
|
* @param prompt - System + user prompt assembled by the prompt builder
|
|
46
89
|
* @param focusId - Focus the evaluation belongs to (for telemetry attribution)
|
|
47
90
|
* @param phase - Loop/execution phase that issued this call (e.g. 'loop_evaluate').
|
|
48
91
|
* Stamped onto the buffered telemetry row so spend is attributable by phase.
|
|
49
|
-
* @
|
|
92
|
+
* @param opts - Test seams (invokeModel / spawnFn / claudeBin / timeoutMs).
|
|
93
|
+
* @returns Raw text response, or null if the call failed / produced no text
|
|
50
94
|
*/
|
|
51
|
-
export declare function callLoopLLM(organizationId: string, prompt: LoopUpdatePrompt, focusId?: string | null, phase?: string | null): Promise<string | null>;
|
|
95
|
+
export declare function callLoopLLM(organizationId: string, prompt: LoopUpdatePrompt, focusId?: string | null, phase?: string | null, opts?: LoopLLMOptions): Promise<string | null>;
|
|
52
96
|
export {};
|
|
53
97
|
//# sourceMappingURL=loop-llm-client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loop-llm-client.d.ts","sourceRoot":"","sources":["../src/loop-llm-client.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"loop-llm-client.d.ts","sourceRoot":"","sources":["../src/loop-llm-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAS,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE9D,OAAO,EAAgB,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC9E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAMjE,2EAA2E;AAC3E,UAAU,cAAc;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,2BAA2B,CAAC,EAAE,MAAM,CAAC;CACtC;AAiBD,0EAA0E;AAC1E,MAAM,WAAW,eAAe;IAC9B,iEAAiE;IACjE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mFAAmF;IACnF,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,UAAU,SAAS;IACjB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,cAAc;IAC7B,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sEAAsE;IACtE,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,SAAS,KAAK,YAAY,CAAC;IACzE;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC;CACvF;AAkCD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,GAAG,MAAM,CAchF;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE;IACzC,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,cAAc,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,kFAAkF;IAClF,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,+EAA+E;IAC/E,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,GAAG,kBAAkB,CA4BrB;AAsGD;;;;;;;;;;;;GAYG;AACH,wBAAsB,WAAW,CAC/B,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,gBAAgB,EACxB,OAAO,GAAE,MAAM,GAAG,IAAW,EAC7B,KAAK,GAAE,MAAM,GAAG,IAAW,EAC3B,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAkCxB"}
|
package/dist/loop-llm-client.js
CHANGED
|
@@ -1,52 +1,46 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* LLM client for loop data updates.
|
|
2
|
+
* LLM client for loop data updates and PRD/loop differential evaluation.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Runs ONE model call per focus per cycle by spawning the `claude` CLI in print
|
|
5
|
+
* mode -- the same pattern as `de-restatement.ts` / `negative-branch.ts` -- so it
|
|
6
|
+
* rides the operator's existing Claude Code auth and needs NO Anthropic API key,
|
|
7
|
+
* org LLM secret, or any other key beyond what the spawned agent teams already
|
|
8
|
+
* use. (Previously this did a raw fetch to api.anthropic.com with an x-api-key
|
|
9
|
+
* header; when no key was configured the call returned null and the differential
|
|
10
|
+
* loop / PRD steward froze -- exactly the gap this client now closes.)
|
|
6
11
|
*
|
|
7
|
-
* Capture:
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* the OTLP receiver feeds, so loop spend lands in
|
|
12
|
+
* Capture: a `--print` one-shot session produces NO OTLP rows (the OTLP exporter
|
|
13
|
+
* only fires for long-lived interactive sessions), so loop-layer token/cost spend
|
|
14
|
+
* would otherwise be invisible to agent_telemetry_metrics. We parse the usage
|
|
15
|
+
* block off the print-mode JSON (`--output-format json`) and buffer a telemetry
|
|
16
|
+
* metric row on the same writer the OTLP receiver feeds, so loop spend lands in
|
|
17
|
+
* agent_telemetry_metrics too -- the same capture path as the audit assessor
|
|
18
|
+
* (spawner/lifecycle.ts buildAuditMetricRow).
|
|
12
19
|
*/
|
|
13
|
-
import {
|
|
20
|
+
import { spawn } from 'node:child_process';
|
|
14
21
|
import { bufferMetric } from './telemetry-writer.js';
|
|
15
22
|
// ---------------------------------------------------------------------------
|
|
16
|
-
//
|
|
23
|
+
// Model / pricing
|
|
17
24
|
// ---------------------------------------------------------------------------
|
|
18
25
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
26
|
+
* Default model tag for loop evaluations. Print mode runs on the operator's
|
|
27
|
+
* configured Claude Code model; we recover the real id from the JSON result's
|
|
28
|
+
* top-level `model` field (preferred) or the first `modelUsage` key, and fall
|
|
29
|
+
* back to this tag only when neither is present (it then affects just the
|
|
30
|
+
* telemetry model column and the cost-estimate fallback -- the real cost comes
|
|
31
|
+
* from total_cost_usd).
|
|
21
32
|
*/
|
|
22
|
-
async function getApiKey(_organizationId) {
|
|
23
|
-
try {
|
|
24
|
-
const result = await callApi('llm_secret_get', {
|
|
25
|
-
provider: 'anthropic',
|
|
26
|
-
});
|
|
27
|
-
if (result.apiKey)
|
|
28
|
-
return result.apiKey;
|
|
29
|
-
}
|
|
30
|
-
catch {
|
|
31
|
-
// Fall through to env var
|
|
32
|
-
}
|
|
33
|
-
return process.env.ANTHROPIC_API_KEY ?? null;
|
|
34
|
-
}
|
|
35
|
-
// ---------------------------------------------------------------------------
|
|
36
|
-
// LLM call
|
|
37
|
-
// ---------------------------------------------------------------------------
|
|
38
|
-
/** Default model for loop evaluations. */
|
|
39
33
|
const DEFAULT_MODEL = 'claude-sonnet-4-6';
|
|
40
|
-
|
|
41
|
-
const MAX_TOKENS = 4096;
|
|
34
|
+
const DEFAULT_TIMEOUT_MS = 2 * 60 * 1000;
|
|
42
35
|
/**
|
|
43
|
-
* Per-model-family pricing in USD per 1M tokens.
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* unknown families fall through to zero cost
|
|
36
|
+
* Per-model-family pricing in USD per 1M tokens. Used only as a FALLBACK when
|
|
37
|
+
* the print-mode JSON does not carry total_cost_usd; Claude Code normally
|
|
38
|
+
* reports the real cost, which is preferred. Matched by substring on the model
|
|
39
|
+
* id (sonnet / opus / haiku); unknown families fall through to zero cost
|
|
40
|
+
* (tokens are still recorded).
|
|
47
41
|
*
|
|
48
42
|
* NOTE: update these rates when Anthropic pricing changes -- a stale table
|
|
49
|
-
* understates loop spend
|
|
43
|
+
* understates loop spend.
|
|
50
44
|
*/
|
|
51
45
|
const MODEL_PRICING = {
|
|
52
46
|
sonnet: { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.30 },
|
|
@@ -54,7 +48,7 @@ const MODEL_PRICING = {
|
|
|
54
48
|
haiku: { input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.10 },
|
|
55
49
|
};
|
|
56
50
|
/**
|
|
57
|
-
* Estimate cost in USD for a single
|
|
51
|
+
* Estimate cost in USD for a single call from its usage block.
|
|
58
52
|
* Returns 0 when the model family is unrecognized.
|
|
59
53
|
*/
|
|
60
54
|
export function estimateLoopCostUsd(model, usage) {
|
|
@@ -74,12 +68,18 @@ export function estimateLoopCostUsd(model, usage) {
|
|
|
74
68
|
/**
|
|
75
69
|
* Build a telemetry metric row from a loop LLM call's usage. Exported for
|
|
76
70
|
* testing the capture-path mapping in isolation.
|
|
71
|
+
*
|
|
72
|
+
* `costUsd`, when finite, is used verbatim (Claude Code's reported
|
|
73
|
+
* total_cost_usd); otherwise cost is estimated from the model + usage.
|
|
77
74
|
*/
|
|
78
75
|
export function buildLoopMetricRow(params) {
|
|
79
76
|
const { organizationId, focusId, model, usage, periodStart, periodEnd } = params;
|
|
80
77
|
const start = Date.parse(periodStart);
|
|
81
78
|
const end = Date.parse(periodEnd);
|
|
82
79
|
const activeMs = Number.isFinite(start) && Number.isFinite(end) && end >= start ? end - start : 0;
|
|
80
|
+
const cost = params.costUsd !== undefined && params.costUsd !== null && Number.isFinite(params.costUsd)
|
|
81
|
+
? params.costUsd
|
|
82
|
+
: estimateLoopCostUsd(model, usage);
|
|
83
83
|
return {
|
|
84
84
|
organization_id: organizationId,
|
|
85
85
|
focus_id: focusId,
|
|
@@ -89,7 +89,7 @@ export function buildLoopMetricRow(params) {
|
|
|
89
89
|
output_tokens: usage.output_tokens ?? 0,
|
|
90
90
|
cache_read_tokens: usage.cache_read_input_tokens ?? 0,
|
|
91
91
|
cache_creation_tokens: usage.cache_creation_input_tokens ?? 0,
|
|
92
|
-
cost_usd:
|
|
92
|
+
cost_usd: cost,
|
|
93
93
|
active_time_ms: activeMs,
|
|
94
94
|
tool_call_count: 0,
|
|
95
95
|
tool_error_count: 0,
|
|
@@ -101,73 +101,143 @@ export function buildLoopMetricRow(params) {
|
|
|
101
101
|
period_end: periodEnd,
|
|
102
102
|
};
|
|
103
103
|
}
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
// Default model invocation: spawn the `claude` CLI in print mode
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
104
107
|
/**
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
* @param prompt - System + user prompt assembled by the prompt builder
|
|
109
|
-
* @param focusId - Focus the evaluation belongs to (for telemetry attribution)
|
|
110
|
-
* @param phase - Loop/execution phase that issued this call (e.g. 'loop_evaluate').
|
|
111
|
-
* Stamped onto the buffered telemetry row so spend is attributable by phase.
|
|
112
|
-
* @returns Raw text response, or null if the call failed
|
|
108
|
+
* Parse a `claude --print --output-format json` stdout payload into a
|
|
109
|
+
* LoopModelResult. Returns text:null on unparseable / empty output so the
|
|
110
|
+
* caller degrades gracefully (the loop skips a cycle rather than crashing).
|
|
113
111
|
*/
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
if (
|
|
117
|
-
|
|
118
|
-
|
|
112
|
+
function parsePrintJson(stdout) {
|
|
113
|
+
const trimmed = stdout.trim();
|
|
114
|
+
if (trimmed === '')
|
|
115
|
+
return { text: null };
|
|
116
|
+
let parsed;
|
|
117
|
+
try {
|
|
118
|
+
parsed = JSON.parse(trimmed);
|
|
119
119
|
}
|
|
120
|
-
|
|
121
|
-
{
|
|
120
|
+
catch {
|
|
121
|
+
return { text: null };
|
|
122
|
+
}
|
|
123
|
+
// Prefer the explicit top-level model id; fall back to the first modelUsage
|
|
124
|
+
// key. Only when neither is present does the caller apply DEFAULT_MODEL -- so
|
|
125
|
+
// the telemetry tag reflects the real model whenever the CLI reports it.
|
|
126
|
+
const model = typeof parsed.model === 'string' && parsed.model !== ''
|
|
127
|
+
? parsed.model
|
|
128
|
+
: parsed.modelUsage && typeof parsed.modelUsage === 'object'
|
|
129
|
+
? Object.keys(parsed.modelUsage)[0]
|
|
130
|
+
: undefined;
|
|
131
|
+
return {
|
|
132
|
+
text: typeof parsed.result === 'string' ? parsed.result : null,
|
|
133
|
+
usage: parsed.usage,
|
|
134
|
+
model,
|
|
135
|
+
costUsd: typeof parsed.total_cost_usd === 'number' ? parsed.total_cost_usd : null,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Default model invocation: spawn the `claude` CLI in print mode with JSON
|
|
140
|
+
* output and return the parsed result. Rides Claude Code auth -- no key wiring.
|
|
141
|
+
*/
|
|
142
|
+
function invokeClaudeCli(systemPrompt, userMessage, opts) {
|
|
143
|
+
const claudeBin = opts.claudeBin ?? 'claude';
|
|
144
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
145
|
+
const spawnFn = opts.spawnFn ?? ((cmd, args, sopts) => spawn(cmd, args, sopts));
|
|
146
|
+
const args = [
|
|
147
|
+
'--print',
|
|
148
|
+
'--output-format', 'json',
|
|
149
|
+
'--no-session-persistence',
|
|
150
|
+
'--system-prompt', systemPrompt,
|
|
122
151
|
];
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
},
|
|
132
|
-
body: JSON.stringify({
|
|
133
|
-
model: DEFAULT_MODEL,
|
|
134
|
-
max_tokens: MAX_TOKENS,
|
|
135
|
-
system: prompt.system,
|
|
136
|
-
messages,
|
|
137
|
-
}),
|
|
138
|
-
});
|
|
139
|
-
if (!response.ok) {
|
|
140
|
-
const errorBody = await response.text().catch(() => 'unknown error');
|
|
141
|
-
console.log(`[Loop] Anthropic API error (${response.status}): ${errorBody.slice(0, 200)}`);
|
|
142
|
-
return null;
|
|
152
|
+
return new Promise((resolve, reject) => {
|
|
153
|
+
let child;
|
|
154
|
+
try {
|
|
155
|
+
child = spawnFn(claudeBin, args, { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
reject(new Error(`loop-llm spawn failed: ${err.message}`));
|
|
159
|
+
return;
|
|
143
160
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (result.usage) {
|
|
161
|
+
let stdout = '';
|
|
162
|
+
let stderr = '';
|
|
163
|
+
child.stdout?.on('data', (chunk) => { stdout += chunk.toString('utf8'); });
|
|
164
|
+
child.stderr?.on('data', (chunk) => { stderr += chunk.toString('utf8'); });
|
|
165
|
+
const timer = setTimeout(() => {
|
|
150
166
|
try {
|
|
151
|
-
|
|
152
|
-
organizationId,
|
|
153
|
-
focusId,
|
|
154
|
-
model: result.model ?? DEFAULT_MODEL,
|
|
155
|
-
usage: result.usage,
|
|
156
|
-
periodStart,
|
|
157
|
-
periodEnd: new Date().toISOString(),
|
|
158
|
-
phase,
|
|
159
|
-
}));
|
|
167
|
+
child.kill('SIGKILL');
|
|
160
168
|
}
|
|
161
|
-
catch
|
|
162
|
-
|
|
169
|
+
catch { /* ignore */ }
|
|
170
|
+
}, timeoutMs);
|
|
171
|
+
child.on('error', (err) => {
|
|
172
|
+
clearTimeout(timer);
|
|
173
|
+
reject(new Error(`loop-llm spawn error: ${err.message}`));
|
|
174
|
+
});
|
|
175
|
+
child.on('close', (code) => {
|
|
176
|
+
clearTimeout(timer);
|
|
177
|
+
if (code !== 0) {
|
|
178
|
+
reject(new Error(`loop-llm exited ${code}: ${stderr.slice(-2000)}`));
|
|
179
|
+
return;
|
|
163
180
|
}
|
|
181
|
+
resolve(parsePrintJson(stdout));
|
|
182
|
+
});
|
|
183
|
+
try {
|
|
184
|
+
child.stdin?.write(userMessage);
|
|
185
|
+
child.stdin?.end();
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
clearTimeout(timer);
|
|
189
|
+
reject(new Error(`loop-llm stdin write failed: ${err.message}`));
|
|
164
190
|
}
|
|
165
|
-
|
|
166
|
-
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
// LLM call
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
/**
|
|
197
|
+
* Run a loop data update / differential evaluation through the operator's
|
|
198
|
+
* Claude Code auth (print-mode `claude` spawn). No API key beyond Claude Code
|
|
199
|
+
* auth is required.
|
|
200
|
+
*
|
|
201
|
+
* @param organizationId - Organization the evaluation belongs to (telemetry attribution)
|
|
202
|
+
* @param prompt - System + user prompt assembled by the prompt builder
|
|
203
|
+
* @param focusId - Focus the evaluation belongs to (for telemetry attribution)
|
|
204
|
+
* @param phase - Loop/execution phase that issued this call (e.g. 'loop_evaluate').
|
|
205
|
+
* Stamped onto the buffered telemetry row so spend is attributable by phase.
|
|
206
|
+
* @param opts - Test seams (invokeModel / spawnFn / claudeBin / timeoutMs).
|
|
207
|
+
* @returns Raw text response, or null if the call failed / produced no text
|
|
208
|
+
*/
|
|
209
|
+
export async function callLoopLLM(organizationId, prompt, focusId = null, phase = null, opts = {}) {
|
|
210
|
+
const invoke = opts.invokeModel ?? ((sys, msg) => invokeClaudeCli(sys, msg, opts));
|
|
211
|
+
const periodStart = new Date().toISOString();
|
|
212
|
+
let result;
|
|
213
|
+
try {
|
|
214
|
+
result = await invoke(prompt.system, prompt.user);
|
|
167
215
|
}
|
|
168
216
|
catch (err) {
|
|
169
|
-
console.log(`[Loop]
|
|
217
|
+
console.log(`[Loop] Claude Code evaluation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
170
218
|
return null;
|
|
171
219
|
}
|
|
220
|
+
// Capture token/cost on the same writer the OTLP receiver feeds. A --print
|
|
221
|
+
// one-shot session produces NO OTLP rows, so without this the loop-layer
|
|
222
|
+
// spend would never reach agent_telemetry_metrics. Best-effort: a capture
|
|
223
|
+
// failure must not break the loop evaluation.
|
|
224
|
+
if (result.usage) {
|
|
225
|
+
try {
|
|
226
|
+
bufferMetric(buildLoopMetricRow({
|
|
227
|
+
organizationId,
|
|
228
|
+
focusId,
|
|
229
|
+
model: result.model ?? DEFAULT_MODEL,
|
|
230
|
+
usage: result.usage,
|
|
231
|
+
periodStart,
|
|
232
|
+
periodEnd: new Date().toISOString(),
|
|
233
|
+
phase,
|
|
234
|
+
costUsd: result.costUsd,
|
|
235
|
+
}));
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
console.log(`[Loop] Telemetry capture failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return result.text;
|
|
172
242
|
}
|
|
173
243
|
//# sourceMappingURL=loop-llm-client.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loop-llm-client.js","sourceRoot":"","sources":["../src/loop-llm-client.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"loop-llm-client.js","sourceRoot":"","sources":["../src/loop-llm-client.ts"],"names":[],"mappings":"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"}
|