feat: add @tag model override support and remove Qdrant dependencies

- Add model-tags parser for @Tag syntax in chat messages
- Support Anthropic models (Sonnet, Haiku, Opus) via @tag
- Remove Qdrant vector database from infrastructure and configs
- Simplify license model config to use null fallbacks
- Add greeting stream after model switch via @tag
- Fix protobuf field names to camelCase for v7 compatibility
- Add 429 rate limit retry logic with exponential backoff
- Remove RAG references from agent harness documentation
This commit is contained in:
2026-04-27 20:55:18 -04:00
parent 6f937f9e5e
commit d41fcd0499
50 changed files with 956 additions and 798 deletions

View File

@@ -421,15 +421,79 @@ export class CCXTFetcher {
const amount = Math.round(trade.amount * sizeMult);
const quoteAmount = Math.round((trade.price * trade.amount) * priceMult);
// protobufjs v7 uses camelCase field names internally — must use camelCase here
return {
trade_id: trade.id || `${trade.timestamp}`,
tradeId: trade.id || `${trade.timestamp}`,
ticker,
timestamp: (trade.timestamp * 1_000_000).toString(), // Convert ms to nanoseconds
price: price.toString(),
amount: amount.toString(),
quote_amount: quoteAmount.toString(),
taker_buy: trade.side === 'buy',
sequence: trade.order ? trade.order.toString() : undefined
timestamp: (trade.timestamp * 1_000_000).toString(), // Convert ms to nanoseconds
price: price.toString(),
amount: amount.toString(),
quoteAmount: quoteAmount.toString(),
takerBuy: trade.side === 'buy',
sequence: trade.order ? trade.order.toString() : undefined
};
}
/**
* Fetch 1-minute bars covering the current open window for each configured period,
* rolling them up into a single aggregate per period for Flink accumulator seeding.
*
* Returns one seed object per period (or null for periods that just started with no
* completed 1m bars yet). Throws on exchange errors — caller handles retries.
*
* @param {string} ticker
* @param {number[]} periodSeconds - configured periods (e.g. [60, 300, 900, 3600, 14400, 86400])
* @returns {Promise<Array<{periodSeconds, open, high, low, close, volume, windowStartMs}|null>>}
*/
async fetchSeedCandles(ticker, periodSeconds) {
const nowMs = Date.now();
const maxPeriod = Math.max(...periodSeconds);
const longestWindowStart = Math.floor(nowMs / (maxPeriod * 1000)) * (maxPeriod * 1000);
// fetchHistoricalOHLC expects nanoseconds as strings
const startNs = (longestWindowStart * 1_000_000).toString();
const endNs = (nowMs * 1_000_000).toString();
const bars1m = await this.fetchHistoricalOHLC(ticker, startNs, endNs, 60, null);
return periodSeconds.map(period => {
const windowStart = Math.floor(nowMs / (period * 1000)) * (period * 1000);
const relevant = bars1m.filter(b => {
const tsMs = parseInt(b.timestamp) / 1_000_000;
return tsMs >= windowStart && tsMs < nowMs;
});
if (relevant.length === 0) return null;
const open = parseInt(relevant[0].open);
const high = Math.max(...relevant.map(b => parseInt(b.high)));
const low = Math.min(...relevant.map(b => parseInt(b.low)));
const close = parseInt(relevant[relevant.length - 1].close);
const volume = relevant.reduce((sum, b) => sum + parseInt(b.volume), 0);
return { periodSeconds: period, open, high, low, close, volume, windowStartMs: windowStart };
});
}
/**
* Convert a seed candle aggregate into a Tick-shaped object for Kafka.
* price = open (scaled int), amount = volume (scaled int); seed_* fields carry H/L/C/period.
*/
convertSeedToTick(seed, ticker) {
// protobufjs v7 uses camelCase field names internally — must use camelCase here
return {
tradeId: `seed-${ticker}-${seed.periodSeconds}-${seed.windowStartMs}`,
ticker,
timestamp: (seed.windowStartMs * 1_000_000).toString(),
price: seed.open,
amount: seed.volume,
quoteAmount: 0,
takerBuy: false,
isSeed: true,
seedHigh: seed.high,
seedLow: seed.low,
seedClose: seed.close,
seedWindowStartMs: seed.windowStartMs,
seedPeriodSeconds: seed.periodSeconds
};
}