@trading-boy/core 0.1.0
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/README.md +8 -0
- package/dist/.gitkeep +0 -0
- package/dist/config/env.d.ts +102 -0
- package/dist/config/env.d.ts.map +1 -0
- package/dist/config/env.js +178 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +2 -0
- package/dist/config/index.js.map +1 -0
- package/dist/constants/index.d.ts +49 -0
- package/dist/constants/index.d.ts.map +1 -0
- package/dist/constants/index.js +63 -0
- package/dist/constants/index.js.map +1 -0
- package/dist/crypto.d.ts +47 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +56 -0
- package/dist/crypto.js.map +1 -0
- package/dist/errors/index.d.ts +25 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +50 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/client.d.ts +38 -0
- package/dist/llm/client.d.ts.map +1 -0
- package/dist/llm/client.js +194 -0
- package/dist/llm/client.js.map +1 -0
- package/dist/llm/config.d.ts +12 -0
- package/dist/llm/config.d.ts.map +1 -0
- package/dist/llm/config.js +47 -0
- package/dist/llm/config.js.map +1 -0
- package/dist/llm/index.d.ts +5 -0
- package/dist/llm/index.d.ts.map +1 -0
- package/dist/llm/index.js +4 -0
- package/dist/llm/index.js.map +1 -0
- package/dist/llm/structured-output.d.ts +31 -0
- package/dist/llm/structured-output.d.ts.map +1 -0
- package/dist/llm/structured-output.js +69 -0
- package/dist/llm/structured-output.js.map +1 -0
- package/dist/llm/types.d.ts +39 -0
- package/dist/llm/types.d.ts.map +1 -0
- package/dist/llm/types.js +6 -0
- package/dist/llm/types.js.map +1 -0
- package/dist/logging/index.d.ts +2 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/index.js +2 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/logging/logger.d.ts +7 -0
- package/dist/logging/logger.d.ts.map +1 -0
- package/dist/logging/logger.js +66 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/types/context-package.d.ts +100 -0
- package/dist/types/context-package.d.ts.map +1 -0
- package/dist/types/context-package.js +2 -0
- package/dist/types/context-package.js.map +1 -0
- package/dist/types/daily-summary.d.ts +212 -0
- package/dist/types/daily-summary.d.ts.map +1 -0
- package/dist/types/daily-summary.js +6 -0
- package/dist/types/daily-summary.js.map +1 -0
- package/dist/types/decision-traces.d.ts +123 -0
- package/dist/types/decision-traces.d.ts.map +1 -0
- package/dist/types/decision-traces.js +2 -0
- package/dist/types/decision-traces.js.map +1 -0
- package/dist/types/enums.d.ts +207 -0
- package/dist/types/enums.d.ts.map +1 -0
- package/dist/types/enums.js +247 -0
- package/dist/types/enums.js.map +1 -0
- package/dist/types/graph-edges.d.ts +127 -0
- package/dist/types/graph-edges.d.ts.map +1 -0
- package/dist/types/graph-edges.js +3 -0
- package/dist/types/graph-edges.js.map +1 -0
- package/dist/types/graph-nodes.d.ts +79 -0
- package/dist/types/graph-nodes.d.ts.map +1 -0
- package/dist/types/graph-nodes.js +2 -0
- package/dist/types/graph-nodes.js.map +1 -0
- package/package.json +31 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * from './types/enums.js';
|
|
2
|
+
export * from './types/graph-nodes.js';
|
|
3
|
+
export * from './types/graph-edges.js';
|
|
4
|
+
export * from './types/context-package.js';
|
|
5
|
+
export * from './types/decision-traces.js';
|
|
6
|
+
export * from './types/daily-summary.js';
|
|
7
|
+
export * from './constants/index.js';
|
|
8
|
+
export * from './config/index.js';
|
|
9
|
+
export * from './logging/index.js';
|
|
10
|
+
export * from './errors/index.js';
|
|
11
|
+
export { computeDecisionHash, verifyChain, type ChainVerificationResult, type VerifiableDecision } from './crypto.js';
|
|
12
|
+
export * from './llm/index.js';
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,KAAK,uBAAuB,EAAE,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtH,cAAc,gBAAgB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * from './types/enums.js';
|
|
2
|
+
export * from './types/graph-nodes.js';
|
|
3
|
+
export * from './types/graph-edges.js';
|
|
4
|
+
export * from './types/context-package.js';
|
|
5
|
+
export * from './types/decision-traces.js';
|
|
6
|
+
export * from './types/daily-summary.js';
|
|
7
|
+
export * from './constants/index.js';
|
|
8
|
+
export * from './config/index.js';
|
|
9
|
+
export * from './logging/index.js';
|
|
10
|
+
export * from './errors/index.js';
|
|
11
|
+
export { computeDecisionHash, verifyChain } from './crypto.js';
|
|
12
|
+
export * from './llm/index.js';
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAyD,MAAM,aAAa,CAAC;AACtH,cAAc,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { TradingBoyError } from '../errors/index.js';
|
|
2
|
+
import type { LLMClientConfig, LLMCompletionOptions, LLMCompletionResult } from './types.js';
|
|
3
|
+
export declare class LLMError extends TradingBoyError {
|
|
4
|
+
constructor(message: string);
|
|
5
|
+
}
|
|
6
|
+
export declare class LLMDisabledError extends TradingBoyError {
|
|
7
|
+
constructor();
|
|
8
|
+
}
|
|
9
|
+
export declare class LLMClient {
|
|
10
|
+
private readonly primary;
|
|
11
|
+
private readonly fallback;
|
|
12
|
+
private readonly config;
|
|
13
|
+
constructor(config: LLMClientConfig);
|
|
14
|
+
/**
|
|
15
|
+
* Send a chat completion request with automatic retry + failover.
|
|
16
|
+
*/
|
|
17
|
+
complete(options: LLMCompletionOptions): Promise<LLMCompletionResult>;
|
|
18
|
+
/**
|
|
19
|
+
* Health check — verify API connectivity.
|
|
20
|
+
*/
|
|
21
|
+
healthCheck(): Promise<{
|
|
22
|
+
primary: boolean;
|
|
23
|
+
fallback: boolean;
|
|
24
|
+
}>;
|
|
25
|
+
private sendRequest;
|
|
26
|
+
private isRetryable;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Get the singleton LLM client. Reads config from environment.
|
|
30
|
+
* If ENABLE_LLM is false, returns a stub that throws LLMDisabledError.
|
|
31
|
+
*/
|
|
32
|
+
export declare function getLLMClient(): LLMClient;
|
|
33
|
+
/**
|
|
34
|
+
* Reset the singleton. Used in tests.
|
|
35
|
+
* @internal
|
|
36
|
+
*/
|
|
37
|
+
export declare function resetLLMClient(): void;
|
|
38
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/llm/client.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,OAAO,KAAK,EACV,eAAe,EACf,oBAAoB,EACpB,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAMpB,qBAAa,QAAS,SAAQ,eAAe;gBAC/B,OAAO,EAAE,MAAM;CAG5B;AAED,qBAAa,gBAAiB,SAAQ,eAAe;;CAOpD;AAID,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgB;IACzC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;gBAE7B,MAAM,EAAE,eAAe;IAyBnC;;OAEG;IACG,QAAQ,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IA6C3E;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC;YAwBvD,WAAW;IA4CzB,OAAO,CAAC,WAAW;CAYpB;AAOD;;;GAGG;AACH,wBAAgB,YAAY,IAAI,SAAS,CAqBxC;AAED;;;GAGG;AACH,wBAAgB,cAAc,IAAI,IAAI,CAGrC"}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// ─── LLM Client ───
|
|
2
|
+
//
|
|
3
|
+
// Model-agnostic LLM client with failover. Uses the `openai` npm package
|
|
4
|
+
// as a universal client — works with Anthropic, OpenRouter, Ollama, and
|
|
5
|
+
// any OpenAI-compatible endpoint via configurable baseURL.
|
|
6
|
+
import OpenAI from 'openai';
|
|
7
|
+
import { createLogger } from '../logging/index.js';
|
|
8
|
+
import { TradingBoyError } from '../errors/index.js';
|
|
9
|
+
import { resolveBaseURL, buildLLMConfig } from './config.js';
|
|
10
|
+
const logger = createLogger('llm');
|
|
11
|
+
// ─── Errors ───
|
|
12
|
+
export class LLMError extends TradingBoyError {
|
|
13
|
+
constructor(message) {
|
|
14
|
+
super(message, 'LLM_ERROR');
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export class LLMDisabledError extends TradingBoyError {
|
|
18
|
+
constructor() {
|
|
19
|
+
super('LLM features are disabled. Set ENABLE_LLM=true to enable.', 'LLM_DISABLED');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// ─── Client ───
|
|
23
|
+
export class LLMClient {
|
|
24
|
+
primary;
|
|
25
|
+
fallback;
|
|
26
|
+
config;
|
|
27
|
+
constructor(config) {
|
|
28
|
+
this.config = config;
|
|
29
|
+
this.primary = new OpenAI({
|
|
30
|
+
apiKey: config.apiKey || 'ollama',
|
|
31
|
+
baseURL: resolveBaseURL(config.provider, config.baseURL),
|
|
32
|
+
timeout: config.timeoutMs ?? 30000,
|
|
33
|
+
maxRetries: 0, // We handle retries ourselves for failover control
|
|
34
|
+
defaultHeaders: config.provider === 'openrouter'
|
|
35
|
+
? { 'HTTP-Referer': 'https://tradingboy.dev', 'X-Title': 'Trading Boy' }
|
|
36
|
+
: undefined,
|
|
37
|
+
});
|
|
38
|
+
if (config.fallback) {
|
|
39
|
+
this.fallback = new OpenAI({
|
|
40
|
+
apiKey: config.fallback.apiKey || config.apiKey || 'ollama',
|
|
41
|
+
baseURL: resolveBaseURL(config.fallback.provider, config.fallback.baseURL),
|
|
42
|
+
timeout: config.timeoutMs ?? 30000,
|
|
43
|
+
maxRetries: 0,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
this.fallback = null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Send a chat completion request with automatic retry + failover.
|
|
52
|
+
*/
|
|
53
|
+
async complete(options) {
|
|
54
|
+
const maxRetries = this.config.maxRetries ?? 2;
|
|
55
|
+
const model = options.model ?? this.config.model;
|
|
56
|
+
// Try primary with retries
|
|
57
|
+
let lastError = null;
|
|
58
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
59
|
+
try {
|
|
60
|
+
const result = await this.sendRequest(this.primary, model, this.config.provider, options);
|
|
61
|
+
return { ...result, fromFallback: false };
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
65
|
+
if (attempt < maxRetries && this.isRetryable(error)) {
|
|
66
|
+
const delayMs = Math.pow(2, attempt) * 1000;
|
|
67
|
+
logger.warn({ attempt: attempt + 1, maxRetries, delayMs, error: lastError.message }, 'LLM request failed, retrying');
|
|
68
|
+
await sleep(delayMs);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Try fallback if configured
|
|
73
|
+
if (this.fallback && this.config.fallback) {
|
|
74
|
+
logger.info('Primary LLM failed, attempting fallback');
|
|
75
|
+
try {
|
|
76
|
+
const result = await this.sendRequest(this.fallback, this.config.fallback.model, this.config.fallback.provider, options);
|
|
77
|
+
return { ...result, fromFallback: true };
|
|
78
|
+
}
|
|
79
|
+
catch (fallbackError) {
|
|
80
|
+
const msg = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
|
|
81
|
+
throw new LLMError(`Both primary and fallback LLM failed. Primary: ${lastError?.message}. Fallback: ${msg}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
throw new LLMError(`LLM request failed after ${maxRetries + 1} attempts: ${lastError?.message}`);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Health check — verify API connectivity.
|
|
88
|
+
*/
|
|
89
|
+
async healthCheck() {
|
|
90
|
+
const checkClient = async (client, model) => {
|
|
91
|
+
try {
|
|
92
|
+
await client.chat.completions.create({
|
|
93
|
+
model,
|
|
94
|
+
messages: [{ role: 'user', content: 'ping' }],
|
|
95
|
+
max_tokens: 1,
|
|
96
|
+
});
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const primary = await checkClient(this.primary, this.config.model);
|
|
104
|
+
const fallback = this.fallback && this.config.fallback
|
|
105
|
+
? await checkClient(this.fallback, this.config.fallback.model)
|
|
106
|
+
: false;
|
|
107
|
+
return { primary, fallback };
|
|
108
|
+
}
|
|
109
|
+
// ─── Private ───
|
|
110
|
+
async sendRequest(client, model, provider, options) {
|
|
111
|
+
const start = Date.now();
|
|
112
|
+
const response = await client.chat.completions.create({
|
|
113
|
+
model,
|
|
114
|
+
messages: options.messages,
|
|
115
|
+
temperature: options.temperature ?? 0,
|
|
116
|
+
max_tokens: options.maxTokens ?? 1024,
|
|
117
|
+
...(options.jsonMode ? { response_format: { type: 'json_object' } } : {}),
|
|
118
|
+
});
|
|
119
|
+
const latencyMs = Date.now() - start;
|
|
120
|
+
const choice = response.choices[0];
|
|
121
|
+
const content = choice?.message?.content ?? '';
|
|
122
|
+
logger.debug({
|
|
123
|
+
model: response.model,
|
|
124
|
+
provider,
|
|
125
|
+
latencyMs,
|
|
126
|
+
inputTokens: response.usage?.prompt_tokens,
|
|
127
|
+
outputTokens: response.usage?.completion_tokens,
|
|
128
|
+
}, 'LLM request completed');
|
|
129
|
+
return {
|
|
130
|
+
content,
|
|
131
|
+
model: response.model,
|
|
132
|
+
provider,
|
|
133
|
+
usage: {
|
|
134
|
+
inputTokens: response.usage?.prompt_tokens ?? 0,
|
|
135
|
+
outputTokens: response.usage?.completion_tokens ?? 0,
|
|
136
|
+
totalTokens: response.usage?.total_tokens ?? 0,
|
|
137
|
+
},
|
|
138
|
+
latencyMs,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
isRetryable(error) {
|
|
142
|
+
// Check for API errors with status codes (rate limit 429, server errors 5xx)
|
|
143
|
+
const status = error.status;
|
|
144
|
+
if (typeof status === 'number') {
|
|
145
|
+
return status === 429 || status >= 500;
|
|
146
|
+
}
|
|
147
|
+
// Retry on network errors
|
|
148
|
+
if (error instanceof Error && (error.message.includes('ECONNREFUSED') || error.message.includes('timeout'))) {
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// ─── Singleton ───
|
|
155
|
+
let _client = null;
|
|
156
|
+
let _stubClient = null;
|
|
157
|
+
/**
|
|
158
|
+
* Get the singleton LLM client. Reads config from environment.
|
|
159
|
+
* If ENABLE_LLM is false, returns a stub that throws LLMDisabledError.
|
|
160
|
+
*/
|
|
161
|
+
export function getLLMClient() {
|
|
162
|
+
const enableLLM = process.env.ENABLE_LLM === 'true';
|
|
163
|
+
if (!enableLLM) {
|
|
164
|
+
if (!_stubClient) {
|
|
165
|
+
_stubClient = new Proxy({}, {
|
|
166
|
+
get(_, prop) {
|
|
167
|
+
if (prop === 'complete' || prop === 'healthCheck') {
|
|
168
|
+
return () => { throw new LLMDisabledError(); };
|
|
169
|
+
}
|
|
170
|
+
return undefined;
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
return _stubClient;
|
|
175
|
+
}
|
|
176
|
+
if (!_client) {
|
|
177
|
+
const config = buildLLMConfig(process.env);
|
|
178
|
+
_client = new LLMClient(config);
|
|
179
|
+
}
|
|
180
|
+
return _client;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Reset the singleton. Used in tests.
|
|
184
|
+
* @internal
|
|
185
|
+
*/
|
|
186
|
+
export function resetLLMClient() {
|
|
187
|
+
_client = null;
|
|
188
|
+
_stubClient = null;
|
|
189
|
+
}
|
|
190
|
+
// ─── Helpers ───
|
|
191
|
+
function sleep(ms) {
|
|
192
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/llm/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { LLMClientConfig } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Resolve the base URL for a provider.
|
|
4
|
+
* User-provided override takes precedence over preset.
|
|
5
|
+
*/
|
|
6
|
+
export declare function resolveBaseURL(provider: string, override?: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* Build LLMClientConfig from environment variables.
|
|
9
|
+
* Called by getLLMClient() singleton.
|
|
10
|
+
*/
|
|
11
|
+
export declare function buildLLMConfig(env: Record<string, string | undefined>): LLMClientConfig;
|
|
12
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/llm/config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAe,MAAM,YAAY,CAAC;AAU/D;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAG1E;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,eAAe,CA0BvF"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// ─── LLM Config ───
|
|
2
|
+
//
|
|
3
|
+
// Resolves provider-specific base URLs and builds LLMClientConfig from env.
|
|
4
|
+
// ─── Provider Base URLs ───
|
|
5
|
+
const PROVIDER_BASE_URLS = {
|
|
6
|
+
anthropic: 'https://api.anthropic.com/v1/',
|
|
7
|
+
openrouter: 'https://openrouter.ai/api/v1',
|
|
8
|
+
ollama: 'http://localhost:11434/v1',
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Resolve the base URL for a provider.
|
|
12
|
+
* User-provided override takes precedence over preset.
|
|
13
|
+
*/
|
|
14
|
+
export function resolveBaseURL(provider, override) {
|
|
15
|
+
if (override)
|
|
16
|
+
return override;
|
|
17
|
+
return PROVIDER_BASE_URLS[provider] ?? '';
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Build LLMClientConfig from environment variables.
|
|
21
|
+
* Called by getLLMClient() singleton.
|
|
22
|
+
*/
|
|
23
|
+
export function buildLLMConfig(env) {
|
|
24
|
+
const provider = (env.LLM_PROVIDER ?? 'anthropic');
|
|
25
|
+
const apiKey = env.LLM_API_KEY ?? env.ANTHROPIC_API_KEY ?? '';
|
|
26
|
+
const model = env.LLM_MODEL ?? 'claude-sonnet-4-6';
|
|
27
|
+
const config = {
|
|
28
|
+
provider,
|
|
29
|
+
apiKey,
|
|
30
|
+
baseURL: env.LLM_BASE_URL || undefined,
|
|
31
|
+
model,
|
|
32
|
+
maxRetries: env.LLM_MAX_RETRIES ? parseInt(env.LLM_MAX_RETRIES, 10) : 2,
|
|
33
|
+
timeoutMs: env.LLM_TIMEOUT_MS ? parseInt(env.LLM_TIMEOUT_MS, 10) : 30000,
|
|
34
|
+
};
|
|
35
|
+
// Configure fallback if specified
|
|
36
|
+
const fallbackProvider = env.LLM_FALLBACK_PROVIDER;
|
|
37
|
+
const fallbackModel = env.LLM_FALLBACK_MODEL;
|
|
38
|
+
if (fallbackProvider && fallbackModel) {
|
|
39
|
+
config.fallback = {
|
|
40
|
+
provider: fallbackProvider,
|
|
41
|
+
apiKey: env.LLM_FALLBACK_API_KEY,
|
|
42
|
+
model: fallbackModel,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return config;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/llm/config.ts"],"names":[],"mappings":"AAAA,qBAAqB;AACrB,EAAE;AACF,4EAA4E;AAI5E,6BAA6B;AAE7B,MAAM,kBAAkB,GAA2B;IACjD,SAAS,EAAE,+BAA+B;IAC1C,UAAU,EAAE,8BAA8B;IAC1C,MAAM,EAAE,2BAA2B;CACpC,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,QAAgB,EAAE,QAAiB;IAChE,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,OAAO,kBAAkB,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;AAC5C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,GAAuC;IACpE,MAAM,QAAQ,GAAG,CAAC,GAAG,CAAC,YAAY,IAAI,WAAW,CAAgB,CAAC;IAClE,MAAM,MAAM,GAAG,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,iBAAiB,IAAI,EAAE,CAAC;IAC9D,MAAM,KAAK,GAAG,GAAG,CAAC,SAAS,IAAI,mBAAmB,CAAC;IAEnD,MAAM,MAAM,GAAoB;QAC9B,QAAQ;QACR,MAAM;QACN,OAAO,EAAE,GAAG,CAAC,YAAY,IAAI,SAAS;QACtC,KAAK;QACL,UAAU,EAAE,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QACvE,SAAS,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK;KACzE,CAAC;IAEF,kCAAkC;IAClC,MAAM,gBAAgB,GAAG,GAAG,CAAC,qBAAqB,CAAC;IACnD,MAAM,aAAa,GAAG,GAAG,CAAC,kBAAkB,CAAC;IAC7C,IAAI,gBAAgB,IAAI,aAAa,EAAE,CAAC;QACtC,MAAM,CAAC,QAAQ,GAAG;YAChB,QAAQ,EAAE,gBAA+B;YACzC,MAAM,EAAE,GAAG,CAAC,oBAAoB;YAChC,KAAK,EAAE,aAAa;SACrB,CAAC;IACJ,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { LLMProvider, LLMClientConfig, LLMMessage, LLMCompletionOptions, LLMCompletionResult, } from './types.js';
|
|
2
|
+
export { LLMClient, LLMError, LLMDisabledError, getLLMClient, resetLLMClient, } from './client.js';
|
|
3
|
+
export { extractStructured, StructuredOutputError, type ExtractOptions, type ExtractResult, } from './structured-output.js';
|
|
4
|
+
export { resolveBaseURL, buildLLMConfig } from './config.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/llm/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,WAAW,EACX,eAAe,EACf,UAAU,EACV,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,SAAS,EACT,QAAQ,EACR,gBAAgB,EAChB,YAAY,EACZ,cAAc,GACf,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,KAAK,cAAc,EACnB,KAAK,aAAa,GACnB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { LLMClient, LLMError, LLMDisabledError, getLLMClient, resetLLMClient, } from './client.js';
|
|
2
|
+
export { extractStructured, StructuredOutputError, } from './structured-output.js';
|
|
3
|
+
export { resolveBaseURL, buildLLMConfig } from './config.js';
|
|
4
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/llm/index.ts"],"names":[],"mappings":"AAQA,OAAO,EACL,SAAS,EACT,QAAQ,EACR,gBAAgB,EAChB,YAAY,EACZ,cAAc,GACf,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,iBAAiB,EACjB,qBAAqB,GAGtB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
import { TradingBoyError } from '../errors/index.js';
|
|
3
|
+
import type { LLMClient } from './client.js';
|
|
4
|
+
import type { LLMCompletionResult, LLMMessage } from './types.js';
|
|
5
|
+
export declare class StructuredOutputError extends TradingBoyError {
|
|
6
|
+
constructor(message: string);
|
|
7
|
+
}
|
|
8
|
+
export interface ExtractOptions {
|
|
9
|
+
messages: LLMMessage[];
|
|
10
|
+
schemaDescription?: string;
|
|
11
|
+
temperature?: number;
|
|
12
|
+
maxTokens?: number;
|
|
13
|
+
maxRetries?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface ExtractResult<T> {
|
|
16
|
+
data: T;
|
|
17
|
+
raw: string;
|
|
18
|
+
meta: LLMCompletionResult;
|
|
19
|
+
retried: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Extract structured data from an LLM response using Zod validation.
|
|
23
|
+
*
|
|
24
|
+
* 1. Appends schema description to messages (if provided)
|
|
25
|
+
* 2. Sends request with jsonMode=true
|
|
26
|
+
* 3. Parses response with schema
|
|
27
|
+
* 4. If validation fails, retries once with error feedback
|
|
28
|
+
* 5. If second attempt fails, throws StructuredOutputError
|
|
29
|
+
*/
|
|
30
|
+
export declare function extractStructured<T>(client: LLMClient, schema: z.ZodSchema<T>, options: ExtractOptions): Promise<ExtractResult<T>>;
|
|
31
|
+
//# sourceMappingURL=structured-output.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"structured-output.d.ts","sourceRoot":"","sources":["../../src/llm/structured-output.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAMlE,qBAAa,qBAAsB,SAAQ,eAAe;gBAC5C,OAAO,EAAE,MAAM;CAG5B;AAID,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,aAAa,CAAC,CAAC;IAC9B,IAAI,EAAE,CAAC,CAAC;IACR,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,mBAAmB,CAAC;IAC1B,OAAO,EAAE,OAAO,CAAC;CAClB;AAID;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CAAC,CAAC,EACvC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EACtB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAwD3B"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// ─── Structured Output Helper ───
|
|
2
|
+
//
|
|
3
|
+
// Zod-validated JSON extraction from LLM responses. Sends a completion
|
|
4
|
+
// request with jsonMode, parses the response with the provided schema,
|
|
5
|
+
// and retries once with error feedback if validation fails.
|
|
6
|
+
import { createLogger } from '../logging/index.js';
|
|
7
|
+
import { TradingBoyError } from '../errors/index.js';
|
|
8
|
+
const logger = createLogger('llm-structured');
|
|
9
|
+
// ─── Errors ───
|
|
10
|
+
export class StructuredOutputError extends TradingBoyError {
|
|
11
|
+
constructor(message) {
|
|
12
|
+
super(message, 'STRUCTURED_OUTPUT_ERROR');
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
// ─── Extract ───
|
|
16
|
+
/**
|
|
17
|
+
* Extract structured data from an LLM response using Zod validation.
|
|
18
|
+
*
|
|
19
|
+
* 1. Appends schema description to messages (if provided)
|
|
20
|
+
* 2. Sends request with jsonMode=true
|
|
21
|
+
* 3. Parses response with schema
|
|
22
|
+
* 4. If validation fails, retries once with error feedback
|
|
23
|
+
* 5. If second attempt fails, throws StructuredOutputError
|
|
24
|
+
*/
|
|
25
|
+
export async function extractStructured(client, schema, options) {
|
|
26
|
+
const maxRetries = options.maxRetries ?? 1;
|
|
27
|
+
const messages = [...options.messages];
|
|
28
|
+
// Add schema description hint to system message if provided
|
|
29
|
+
if (options.schemaDescription) {
|
|
30
|
+
messages.push({
|
|
31
|
+
role: 'user',
|
|
32
|
+
content: `Respond with a JSON object matching this schema: ${options.schemaDescription}. Output ONLY valid JSON, no markdown or explanation.`,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
let lastRaw = '';
|
|
36
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
37
|
+
const result = await client.complete({
|
|
38
|
+
messages,
|
|
39
|
+
temperature: options.temperature ?? 0,
|
|
40
|
+
maxTokens: options.maxTokens ?? 1024,
|
|
41
|
+
jsonMode: true,
|
|
42
|
+
});
|
|
43
|
+
lastRaw = result.content;
|
|
44
|
+
try {
|
|
45
|
+
const parsed = JSON.parse(result.content);
|
|
46
|
+
const validated = schema.parse(parsed);
|
|
47
|
+
return {
|
|
48
|
+
data: validated,
|
|
49
|
+
raw: result.content,
|
|
50
|
+
meta: result,
|
|
51
|
+
retried: attempt > 0,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
catch (parseError) {
|
|
55
|
+
const errorMsg = parseError instanceof Error ? parseError.message : String(parseError);
|
|
56
|
+
if (attempt < maxRetries) {
|
|
57
|
+
logger.warn({ attempt: attempt + 1, error: errorMsg }, 'Structured output validation failed, retrying with error feedback');
|
|
58
|
+
// Add error feedback for retry
|
|
59
|
+
messages.push({ role: 'assistant', content: result.content });
|
|
60
|
+
messages.push({
|
|
61
|
+
role: 'user',
|
|
62
|
+
content: `Your JSON response was invalid: ${errorMsg}. Fix the errors and respond with valid JSON only.`,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
throw new StructuredOutputError(`Failed to extract valid structured output after ${maxRetries + 1} attempts. Last response: ${lastRaw.slice(0, 200)}`);
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=structured-output.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"structured-output.js","sourceRoot":"","sources":["../../src/llm/structured-output.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,EAAE;AACF,uEAAuE;AACvE,uEAAuE;AACvE,4DAA4D;AAG5D,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAIrD,MAAM,MAAM,GAAG,YAAY,CAAC,gBAAgB,CAAC,CAAC;AAE9C,iBAAiB;AAEjB,MAAM,OAAO,qBAAsB,SAAQ,eAAe;IACxD,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,EAAE,yBAAyB,CAAC,CAAC;IAC5C,CAAC;CACF;AAmBD,kBAAkB;AAElB;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,MAAiB,EACjB,MAAsB,EACtB,OAAuB;IAEvB,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,CAAC,CAAC;IAC3C,MAAM,QAAQ,GAAG,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAEvC,4DAA4D;IAC5D,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC;QAC9B,QAAQ,CAAC,IAAI,CAAC;YACZ,IAAI,EAAE,MAAM;YACZ,OAAO,EAAE,oDAAoD,OAAO,CAAC,iBAAiB,uDAAuD;SAC9I,CAAC,CAAC;IACL,CAAC;IAED,IAAI,OAAO,GAAG,EAAE,CAAC;IAEjB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,UAAU,EAAE,OAAO,EAAE,EAAE,CAAC;QACvD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC;YACnC,QAAQ;YACR,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,CAAC;YACrC,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,IAAI;YACpC,QAAQ,EAAE,IAAI;SACf,CAAC,CAAC;QAEH,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;QAEzB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAC1C,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAEvC,OAAO;gBACL,IAAI,EAAE,SAAS;gBACf,GAAG,EAAE,MAAM,CAAC,OAAO;gBACnB,IAAI,EAAE,MAAM;gBACZ,OAAO,EAAE,OAAO,GAAG,CAAC;aACrB,CAAC;QACJ,CAAC;QAAC,OAAO,UAAU,EAAE,CAAC;YACpB,MAAM,QAAQ,GAAG,UAAU,YAAY,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YAEvF,IAAI,OAAO,GAAG,UAAU,EAAE,CAAC;gBACzB,MAAM,CAAC,IAAI,CACT,EAAE,OAAO,EAAE,OAAO,GAAG,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EACzC,mEAAmE,CACpE,CAAC;gBAEF,+BAA+B;gBAC/B,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;gBAC9D,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,MAAM;oBACZ,OAAO,EAAE,mCAAmC,QAAQ,oDAAoD;iBACzG,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,IAAI,qBAAqB,CAC7B,mDAAmD,UAAU,GAAG,CAAC,6BAA6B,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CACtH,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export type LLMProvider = 'anthropic' | 'openrouter' | 'ollama' | 'custom';
|
|
2
|
+
export interface LLMClientConfig {
|
|
3
|
+
provider: LLMProvider;
|
|
4
|
+
apiKey: string;
|
|
5
|
+
baseURL?: string;
|
|
6
|
+
model: string;
|
|
7
|
+
maxRetries?: number;
|
|
8
|
+
timeoutMs?: number;
|
|
9
|
+
fallback?: {
|
|
10
|
+
provider: LLMProvider;
|
|
11
|
+
apiKey?: string;
|
|
12
|
+
baseURL?: string;
|
|
13
|
+
model: string;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export interface LLMMessage {
|
|
17
|
+
role: 'system' | 'user' | 'assistant';
|
|
18
|
+
content: string;
|
|
19
|
+
}
|
|
20
|
+
export interface LLMCompletionOptions {
|
|
21
|
+
messages: LLMMessage[];
|
|
22
|
+
model?: string;
|
|
23
|
+
temperature?: number;
|
|
24
|
+
maxTokens?: number;
|
|
25
|
+
jsonMode?: boolean;
|
|
26
|
+
}
|
|
27
|
+
export interface LLMCompletionResult {
|
|
28
|
+
content: string;
|
|
29
|
+
model: string;
|
|
30
|
+
provider: string;
|
|
31
|
+
usage: {
|
|
32
|
+
inputTokens: number;
|
|
33
|
+
outputTokens: number;
|
|
34
|
+
totalTokens: number;
|
|
35
|
+
};
|
|
36
|
+
latencyMs: number;
|
|
37
|
+
fromFallback: boolean;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/llm/types.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG,YAAY,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE3E,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,WAAW,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE;QACT,QAAQ,EAAE,WAAW,CAAC;QACtB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;CACH;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE;QACL,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;CACvB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/llm/types.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB,EAAE;AACF,kDAAkD;AAClD,oFAAoF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/logging/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/logging/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,EAAe,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type Logger } from 'pino';
|
|
2
|
+
export declare const REDACT_PATHS: string[];
|
|
3
|
+
declare const baseLogger: Logger;
|
|
4
|
+
export declare function createLogger(name: string): Logger;
|
|
5
|
+
export { baseLogger as logger };
|
|
6
|
+
export type { Logger };
|
|
7
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/logging/logger.ts"],"names":[],"mappings":"AAAA,OAAa,EAAE,KAAK,MAAM,EAAE,MAAM,MAAM,CAAC;AAmDzC,eAAO,MAAM,YAAY,UAAuD,CAAC;AAEjF,QAAA,MAAM,UAAU,EAAE,MAahB,CAAC;AAEH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,OAAO,EAAE,UAAU,IAAI,MAAM,EAAE,CAAC;AAChC,YAAY,EAAE,MAAM,EAAE,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import pino from 'pino';
|
|
2
|
+
const isDev = process.env.NODE_ENV !== 'production';
|
|
3
|
+
/**
|
|
4
|
+
* Sensitive field names that must be redacted from all log output.
|
|
5
|
+
* Auth-related `token` is intentionally included; crypto token fields
|
|
6
|
+
* use different names (`tokenSymbol`, `tokenAddress`, etc.) and are
|
|
7
|
+
* not affected.
|
|
8
|
+
*/
|
|
9
|
+
const SENSITIVE_FIELDS = [
|
|
10
|
+
'password',
|
|
11
|
+
'apiKey',
|
|
12
|
+
'api_key',
|
|
13
|
+
'authorization',
|
|
14
|
+
'secret',
|
|
15
|
+
'token',
|
|
16
|
+
'privateKey',
|
|
17
|
+
'email',
|
|
18
|
+
'customer_email',
|
|
19
|
+
'x-api-key',
|
|
20
|
+
'stripe_signature',
|
|
21
|
+
];
|
|
22
|
+
/** Maximum object nesting depth to apply redaction. */
|
|
23
|
+
const MAX_REDACT_DEPTH = 5;
|
|
24
|
+
/**
|
|
25
|
+
* Build redaction paths for a field name at every nesting depth from 0
|
|
26
|
+
* up to MAX_REDACT_DEPTH.
|
|
27
|
+
*
|
|
28
|
+
* Pino uses `@pinojs/redact` which supports single `*` wildcards (one
|
|
29
|
+
* level) but not `**` recursive globs. We therefore generate explicit
|
|
30
|
+
* paths for each depth:
|
|
31
|
+
* depth 0 → `password`
|
|
32
|
+
* depth 1 → `*.password`
|
|
33
|
+
* depth 2 → `*.*.password`
|
|
34
|
+
* ...
|
|
35
|
+
*/
|
|
36
|
+
function buildRedactPaths(fields, maxDepth) {
|
|
37
|
+
const paths = [];
|
|
38
|
+
for (const field of fields) {
|
|
39
|
+
for (let depth = 0; depth <= maxDepth; depth++) {
|
|
40
|
+
const prefix = '*.'
|
|
41
|
+
.repeat(depth);
|
|
42
|
+
paths.push(`${prefix}${field}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return paths;
|
|
46
|
+
}
|
|
47
|
+
export const REDACT_PATHS = buildRedactPaths(SENSITIVE_FIELDS, MAX_REDACT_DEPTH);
|
|
48
|
+
const baseLogger = pino({
|
|
49
|
+
name: 'trading-boy',
|
|
50
|
+
level: process.env.LOG_LEVEL ?? 'info',
|
|
51
|
+
redact: {
|
|
52
|
+
paths: REDACT_PATHS,
|
|
53
|
+
censor: '[REDACTED]',
|
|
54
|
+
},
|
|
55
|
+
...(isDev && {
|
|
56
|
+
transport: {
|
|
57
|
+
target: 'pino-pretty',
|
|
58
|
+
options: { destination: 2 },
|
|
59
|
+
},
|
|
60
|
+
}),
|
|
61
|
+
});
|
|
62
|
+
export function createLogger(name) {
|
|
63
|
+
return baseLogger.child({ component: name });
|
|
64
|
+
}
|
|
65
|
+
export { baseLogger as logger };
|
|
66
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/logging/logger.ts"],"names":[],"mappings":"AAAA,OAAO,IAAqB,MAAM,MAAM,CAAC;AAEzC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,CAAC;AAEpD;;;;;GAKG;AACH,MAAM,gBAAgB,GAAG;IACvB,UAAU;IACV,QAAQ;IACR,SAAS;IACT,eAAe;IACf,QAAQ;IACR,OAAO;IACP,YAAY;IACZ,OAAO;IACP,gBAAgB;IAChB,WAAW;IACX,kBAAkB;CACV,CAAC;AAEX,uDAAuD;AACvD,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAE3B;;;;;;;;;;;GAWG;AACH,SAAS,gBAAgB,CAAC,MAAyB,EAAE,QAAgB;IACnE,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,IAAI,QAAQ,EAAE,KAAK,EAAE,EAAE,CAAC;YAC/C,MAAM,MAAM,GAAG,IAAI;iBAChB,MAAM,CAAC,KAAK,CAAC,CAAC;YACjB,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,GAAG,KAAK,EAAE,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,CAAC,MAAM,YAAY,GAAG,gBAAgB,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,CAAC;AAEjF,MAAM,UAAU,GAAW,IAAI,CAAC;IAC9B,IAAI,EAAE,aAAa;IACnB,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,MAAM;IACtC,MAAM,EAAE;QACN,KAAK,EAAE,YAAY;QACnB,MAAM,EAAE,YAAY;KACrB;IACD,GAAG,CAAC,KAAK,IAAI;QACX,SAAS,EAAE;YACT,MAAM,EAAE,aAAa;YACrB,OAAO,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE;SAC5B;KACF,CAAC;CACH,CAAC,CAAC;AAEH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,UAAU,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAC/C,CAAC;AAED,OAAO,EAAE,UAAU,IAAI,MAAM,EAAE,CAAC"}
|