@coinrithm/mcp-trading 0.7.15 → 0.7.17
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/CHANGELOG.md +885 -692
- package/README.md +486 -400
- package/dist/agent/bench/bench.d.ts +59 -0
- package/dist/agent/bench/bench.js +319 -0
- package/dist/agent/bench/cassette.d.ts +97 -0
- package/dist/agent/bench/cassette.js +181 -0
- package/dist/agent/bench/costs.d.ts +28 -0
- package/dist/agent/bench/costs.js +60 -0
- package/dist/agent/bench/labelBuilder.d.ts +123 -0
- package/dist/agent/bench/labelBuilder.js +282 -0
- package/dist/agent/bench/labels.d.ts +75 -0
- package/dist/agent/bench/labels.js +259 -0
- package/dist/agent/bench/recordingClient.d.ts +51 -0
- package/dist/agent/bench/recordingClient.js +193 -0
- package/dist/agent/bench/replayClient.d.ts +41 -0
- package/dist/agent/bench/replayClient.js +262 -0
- package/dist/agent/bench/report.d.ts +115 -0
- package/dist/agent/bench/report.js +435 -0
- package/dist/agent/capitalSizing.d.ts +10 -1
- package/dist/agent/capitalSizing.js +28 -4
- package/dist/agent/cli.d.ts +25 -0
- package/dist/agent/cli.js +300 -2
- package/dist/agent/client.d.ts +14 -0
- package/dist/agent/client.js +23 -6
- package/dist/agent/configurationWarnings.js +11 -0
- package/dist/agent/decision.d.ts +24 -16
- package/dist/agent/decision.js +92 -3
- package/dist/agent/decisionValidator.d.ts +31 -0
- package/dist/agent/decisionValidator.js +148 -3
- package/dist/agent/engine.d.ts +5 -3
- package/dist/agent/engine.js +5 -3
- package/dist/agent/futuresCloseOutcome.d.ts +19 -0
- package/dist/agent/futuresCloseOutcome.js +54 -0
- package/dist/agent/futuresEligibility.d.ts +13 -0
- package/dist/agent/futuresEligibility.js +43 -5
- package/dist/agent/observe.d.ts +28 -2
- package/dist/agent/observe.js +530 -90
- package/dist/agent/pmPaperCandidates.d.ts +17 -0
- package/dist/agent/pmPaperCandidates.js +125 -0
- package/dist/agent/pmPaperCycle.d.ts +16 -0
- package/dist/agent/pmPaperCycle.js +372 -0
- package/dist/agent/pmPaperDecision.d.ts +187 -0
- package/dist/agent/pmPaperDecision.js +101 -0
- package/dist/agent/pmPaperPolicy.d.ts +71 -0
- package/dist/agent/pmPaperPolicy.js +46 -0
- package/dist/agent/prompt.d.ts +15 -1
- package/dist/agent/prompt.js +187 -9
- package/dist/agent/promptTables.d.ts +1 -0
- package/dist/agent/promptTables.js +33 -0
- package/dist/agent/providerCapabilities.d.ts +7 -0
- package/dist/agent/providerCapabilities.js +27 -1
- package/dist/agent/providers.d.ts +26 -7
- package/dist/agent/providers.js +193 -48
- package/dist/agent/resolve.js +3 -0
- package/dist/agent/runner.d.ts +17 -1
- package/dist/agent/runner.js +210 -38
- package/dist/agent/setups.d.ts +2 -1
- package/dist/agent/setups.js +17 -16
- package/dist/agent/signals.d.ts +33 -0
- package/dist/agent/signals.js +89 -0
- package/dist/agent/skill.js +46 -0
- package/dist/agent/skillValidator.js +56 -0
- package/dist/agent/strictLint.js +30 -0
- package/dist/agent/templates.js +30 -30
- package/dist/agent/types.d.ts +180 -12
- package/dist/agent/universe.d.ts +78 -0
- package/dist/agent/universe.js +215 -0
- package/dist/agent/version.d.ts +1 -1
- package/dist/agent/version.js +1 -1
- package/dist/client.d.ts +14 -0
- package/dist/client.js +31 -1
- package/dist/http.js +1 -1
- package/dist/pmPaperV2.d.ts +81 -0
- package/dist/pmPaperV2.js +53 -0
- package/dist/tools.js +84 -6
- package/package.json +100 -97
package/dist/agent/prompt.js
CHANGED
|
@@ -4,8 +4,14 @@
|
|
|
4
4
|
// prefix stable and avoiding balances or counts in the system prompt.
|
|
5
5
|
// The model only PROPOSES — the runner re-checks every action against the caps,
|
|
6
6
|
// so the prompt states the caps but never relies on the model to honor them.
|
|
7
|
+
import { barLabel, indicatorRangeOf, signalThresholdsOf } from "./signals.js";
|
|
8
|
+
import { describeUniverse, scansUniverse } from "./universe.js";
|
|
7
9
|
import { pmQualityOf, pmDecisionSupportOf } from "./pmContext.js";
|
|
8
10
|
import { usesCapitalSizing } from "./capitalSizing.js";
|
|
11
|
+
import { serializePromptObservation } from "./promptTables.js";
|
|
12
|
+
import { TRIGGER_PRICE_EPS } from "./decisionValidator.js";
|
|
13
|
+
import { futuresEntryChoices } from "./futuresEligibility.js";
|
|
14
|
+
import { baseSymbol } from "./setups.js";
|
|
9
15
|
// The runner has already called rollDay. Use the same counter as validation,
|
|
10
16
|
// including conservative legacy-state migration; never infer it from writes,
|
|
11
17
|
// model calls, or the number of positions that remain open.
|
|
@@ -69,6 +75,23 @@ export function formatPmResolutions(resolutions) {
|
|
|
69
75
|
`Resolved since last cycle: ${items.join("; ")}.`,
|
|
70
76
|
];
|
|
71
77
|
}
|
|
78
|
+
// The agent's declared objective (spec.objective.primary), stated to the
|
|
79
|
+
// model as how to weigh trade-offs (Studio audit, root 56825: the Studio
|
|
80
|
+
// promised the objective "shapes how it weighs risk vs return", but nothing
|
|
81
|
+
// read it). Guidance only: the hard caps below still decide what executes.
|
|
82
|
+
export const OBJECTIVE_GUIDANCE = {
|
|
83
|
+
realized_pnl: "realized PnL after fees. Prefer setups with clear reward over risk; closing winners and cutting losers counts as much as entries.",
|
|
84
|
+
risk_adjusted: "return per unit of risk. Prefer fewer, cleaner setups with a meaningful stop; when the risk is unclear, skip.",
|
|
85
|
+
drawdown_control: "preserving capital and keeping drawdowns small, within your declared strategy and the existing risk limits.",
|
|
86
|
+
calibration: "honest, evidence-based probabilities. Report what the evidence supports and keep your uncertainty visible; a forecast is valid without a trade, and a trade needs adequate edge, otherwise skip it.",
|
|
87
|
+
};
|
|
88
|
+
export function objectiveLine(spec) {
|
|
89
|
+
const primary = spec.objective?.primary;
|
|
90
|
+
return primary &&
|
|
91
|
+
Object.prototype.hasOwnProperty.call(OBJECTIVE_GUIDANCE, primary)
|
|
92
|
+
? `Optimise for ${OBJECTIVE_GUIDANCE[primary]} This is how to weigh trade-offs within your strategy; it never overrides a hard cap.`
|
|
93
|
+
: null;
|
|
94
|
+
}
|
|
72
95
|
export function buildSystemPrompt(spec, mergedProse,
|
|
73
96
|
// includeForecast (default OFF here; the runner passes the house-agent flag):
|
|
74
97
|
// when true, pm_open asks for a market-aware forecastProbability (1..99):
|
|
@@ -128,6 +151,9 @@ opts = {}) {
|
|
|
128
151
|
"## Your strategy (your borders)",
|
|
129
152
|
mergedProse.trim() || "(no strategy prose provided)",
|
|
130
153
|
"",
|
|
154
|
+
...(objectiveLine(spec)
|
|
155
|
+
? ["## Your objective", objectiveLine(spec), ""]
|
|
156
|
+
: []),
|
|
131
157
|
"## Hard caps the runner enforces (do not exceed; proposing over a cap wastes the cycle)",
|
|
132
158
|
"- When supplied, the user prompt's dailyRiskBudget is the remaining UTC-day entry/add allowance. It outranks setup/entry pressure: exhaustion is a legitimate skip for new risk, never a reason to skip otherwise-valid closes or protection. All other caps still apply, even when this daily count is unlimited.",
|
|
133
159
|
`- venues you may act in: ${v.join(", ")}`,
|
|
@@ -135,6 +161,11 @@ opts = {}) {
|
|
|
135
161
|
...(usesCapitalSizing(spec)
|
|
136
162
|
? [
|
|
137
163
|
`- Opt-in paper capital policy ${spec.capitalSizing?.version ?? "invalid"}: the runner REPLACES proposed futures margins and PM stakes using current owned-book evidence, stops and fixed policy limits; it does not treat your confidence or the nominal starting grant as a sizing instruction. Choose the market, direction and meaningful protection; invalid policy, quoted costs, shared allocation and cash reserve can still reject an entry.`,
|
|
164
|
+
...(spec.capitalSizing?.highConviction
|
|
165
|
+
? [
|
|
166
|
+
`- HIGH-CONVICTION exception: a futures_open with confidence >= ${spec.capitalSizing.highConviction.minConfidence} is sized with the larger limits (ticket up to ${spec.capitalSizing.highConviction.perTicketCapitalPct}% of equity, stop risk up to ${spec.capitalSizing.highConviction.futuresRiskPct}%). Rate a setup that high ONLY when it is your best opportunity in days: its stop-out costs far more. Everything else stays at the base size.`,
|
|
167
|
+
]
|
|
168
|
+
: []),
|
|
138
169
|
]
|
|
139
170
|
: []),
|
|
140
171
|
...(hasFutures
|
|
@@ -162,7 +193,7 @@ opts = {}) {
|
|
|
162
193
|
// sections telling one story.
|
|
163
194
|
...(hasCoinVenue
|
|
164
195
|
? [
|
|
165
|
-
spec
|
|
196
|
+
scansUniverse(spec)
|
|
166
197
|
? `- tradable symbols (${coinVenueLabel}): your watchlist (${r.watchlist.join(", ")}) PLUS this cycle's watch entries marked \`discovered: true\` — nothing outside those`
|
|
167
198
|
: `- watchlist (${coinVenueLabel} use ONLY these): ${r.watchlist.join(", ")}`,
|
|
168
199
|
]
|
|
@@ -184,6 +215,31 @@ opts = {}) {
|
|
|
184
215
|
`- PM ENTRY FLOOR: pm_open on an outcome whose market probability is below ${r.pmMinEntryProbabilityPct} points is REJECTED by the runner (the chosen outcome's own price; fees are not counted). Do not propose cheaper longshots; look for edge on outcomes priced at or above the floor.`,
|
|
185
216
|
]
|
|
186
217
|
: []),
|
|
218
|
+
...(hasPm && typeof r.pmMinEdgeGapPct === "number"
|
|
219
|
+
? [
|
|
220
|
+
`- PM EDGE RULE: every pm_open MUST carry forecastProbability, and it must beat the fee-inclusive cost by ${r.pmMinEdgeGapPct}% of the room left to 100 (cost 50 needs ${(50 + (r.pmMinEdgeGapPct / 100) * 50).toFixed(1)}, cost 70 needs ${(70 + (r.pmMinEdgeGapPct / 100) * 30).toFixed(1)}). The runner REJECTS an open without a forecast or under this bar.`,
|
|
221
|
+
]
|
|
222
|
+
: []),
|
|
223
|
+
...(hasPm && typeof r.pmMaxEdgePoints === "number"
|
|
224
|
+
? [
|
|
225
|
+
`- PM OVERCONFIDENCE CAP: every pm_open MUST carry forecastProbability, and it may beat the fee-inclusive cost by at most ${r.pmMaxEdgePoints} points. Base the forecast on current evidence. Do not alter a forecast to pass this cap: skip the trade if your evidence-based forecast falls outside the permitted range. Opens over the cap are REJECTED.`,
|
|
226
|
+
]
|
|
227
|
+
: []),
|
|
228
|
+
...(hasPm && typeof r.pmMaxOpenPerEvent === "number"
|
|
229
|
+
? [
|
|
230
|
+
`- PM PER-EVENT CAP: at most ${r.pmMaxOpenPerEvent} open bet(s) per event (same market slug, counting bets you already hold). Extra opens are REJECTED.`,
|
|
231
|
+
]
|
|
232
|
+
: []),
|
|
233
|
+
...(hasPm && typeof r.pmMaxOpenPerCoinHorizon === "number"
|
|
234
|
+
? [
|
|
235
|
+
`- PM COIN-HORIZON CAP: at most ${r.pmMaxOpenPerCoinHorizon} open bet(s) on the same coin whose events end within 24 h of each other, across DIFFERENT events (counting bets you already hold; uses each market's linked coins and end date). Extra opens are REJECTED.`,
|
|
236
|
+
]
|
|
237
|
+
: []),
|
|
238
|
+
...(hasPm && typeof r.pmMinMinutesToClose === "number"
|
|
239
|
+
? [
|
|
240
|
+
`- PM CLOSE CUTOFF: a market whose \`end\` is less than ${r.pmMinMinutesToClose} minutes away when the runner validates your action is REJECTED; the price already knows.`,
|
|
241
|
+
]
|
|
242
|
+
: []),
|
|
187
243
|
...(includeForecast
|
|
188
244
|
? [
|
|
189
245
|
"- FORECAST RULE (pm_open forecastProbability): the current market probability is already included in this request, so this is a market-aware estimate, not a blinded forecast. Form your own evidence-based probability that the outcome you are backing actually WINS, using the question, its resolution criteria, deadline, and available evidence. Put that number (1-99, whole or one decimal) in `forecastProbability`. This is graded against reality as your PUBLIC calibration record, so it must reflect your judgement: do NOT mechanically copy or round observation.pmMarkets `prob` to produce a forecast. It is FINE if your honest forecast happens to land on the market's number — but reaching that by echoing the price defeats the point. If you genuinely cannot form an evidence-based view, OMIT the field rather than parroting the market (an absent forecast is better than a fake one, and it never blocks the bet). A forecast you DO give is enforced: if it is not above what the outcome currently costs, the open is rejected, because buying something you price below the market is a losing trade by your own numbers.",
|
|
@@ -192,20 +248,37 @@ opts = {}) {
|
|
|
192
248
|
`- abstention.minConfidence ${spec.abstention.minConfidence}: opens below this are rejected, so act with genuine conviction — but routine caution is no reason to sit out a clear setup`,
|
|
193
249
|
...(hasCoinVenue
|
|
194
250
|
? [
|
|
251
|
+
"- Open interest (watch[].openInterest) is single-side perpetual exposure in USD across the named venues, not the entire market. Each contract has both a long and a short: OI with price alone cannot establish who opened, closed, or was liquidated. USD OI can also move as price changes without contract counts changing. Treat long/short-covering interpretations as hypotheses requiring other evidence, never as a standalone trade signal. Read each change WITH its change1hVenues/change24hVenues: only matching venue-contracts contribute, and that set may differ from the total's venues. Null changes are unknown; a reported zero total is valid. Ignore stale or missing readings and check asOf against the current observation clock.",
|
|
252
|
+
"- Positioning (watch[].positioning, Binance, top coins by open interest only) counts ACCOUNTS (longShortAccountRatio, longAccountPct) or top-trader POSITIONS (topTraderPositionRatio); takerBuySellRatio is taker buy over sell volume for the last CLOSED 15-minute period. Each value has its own asOf. They describe who is positioned, not where price goes: a crowded side is a hypothesis, never a standalone signal. Ignore stale values.",
|
|
253
|
+
"- Funding venues (watch[].fundingByVenue): rateFraction is a fraction per intervalHours, not a percent. hourlyEquivalentFraction = rateFraction / intervalHours is simple normalization, not realized performance, APR or forecast return; an unknown interval stays null. Venues are collected independently, not at the same time. Read each fetchedAt/ageSeconds/stale; sourceAt=null means provider observation time and delay are unknown. context_only does not replace the paper settlement reference or entry gate. Ignore stale or missing values and do not treat a funding difference alone as a trade signal.",
|
|
254
|
+
"- Depth: watch[].depth is ONE venue's visible order book (Hyperliquid, at most 20 price levels per side), NOT total market liquidity and NOT an executable fill. bid/ask each give usd (resting USD in the visible levels), reachPct (how far from the mid those levels reach, often well under 1%), levels and complete. complete=false means liquidity beyond reachPct was not returned and remains unknown, never zero; null is unknown. Resting orders can be cancelled at any time; treat a bid/ask imbalance as a weak hint, never a standalone signal. Ignore stale values.",
|
|
255
|
+
"- DeFi: watch[].chainTvl is the value locked on the chain associated with that asset by DefiLlama (an association, not proof it is the chain's native token), NOT the coin's market value; change1dPct/change7dPct compare the provider's dated daily points (dayAt). The current TVL has no observation time (sourceObservedAt null): publishedAt is when the provider published the response and fetchedAt is when we collected it; neither dates the TVL itself. observation.stablecoinSupply is total stablecoin supply market-wide with 1d/7d changes. Both are slow structural context, never a short-term trade trigger; a stale value or a null change is unknown.",
|
|
256
|
+
"- Macro (observation.macro): Hyperliquid xyz perpetuals that track the S&P 500, Nasdaq-100, Nikkei, KOSPI, gold, silver, oil, gas, copper, EUR/USD, GBP/USD, USD/JPY and TLT around the clock: PROXIES, not exchange quotes, each with its own asOf. Use them as risk-on/risk-off context only; a stale quote is unknown.",
|
|
257
|
+
"- Liquidations (watch[].liquidations, OKX USDT swaps only): long/short liquidated USDT notional over the last 1h / 24h, read WITH capturedPct, which is a LOWER BOUND of OUR capture uptime for the window (at least that share was captured), not exchange completeness. Low capturedPct means unknown, never 'no liquidations'. Other venues are not included.",
|
|
195
258
|
"- Community sentiment is a dated sample: read sentimentBullishPct WITH sentimentTotalVotes and sentimentDayUtc. A tiny or old cohort is weak evidence, not current market consensus. sentimentUpdatedAt is the cohort's write time. Missing counts/dates are unknown; price freshness does not date sentiment. marketMood.fetchedAt is Fear & Greed collection time, not its provider observation time. Compare each clock with observation.asOf; never invent currentness from a missing date.",
|
|
196
259
|
]
|
|
197
260
|
: []),
|
|
198
261
|
...(spec.capabilities.includes("indicators")
|
|
199
262
|
? [
|
|
200
263
|
"",
|
|
201
|
-
|
|
202
|
-
"- `indicatorContext` reports accepted candle counts, source `asOf` and intervalStatus (regular/irregular/unknown); compare its asOf with observation.asOf for age. /market freshness is separate. nominalIntervalSeconds
|
|
203
|
-
|
|
264
|
+
`## Signals — each watch entry may carry \`indicators\` (nominal ${barLabel(indicatorRangeOf(spec))} candles)`,
|
|
265
|
+
"- `indicatorContext` reports accepted candle counts, source `asOf` and intervalStatus (regular/irregular/unknown); compare its asOf with observation.asOf for age. /market freshness is separate. nominalIntervalSeconds does not prove fresh, continuous candles. Missing timestamps are unknown; stale, future-dated or irregular candles do not establish a current signal. `recent15` describes only recent spacing; Wilder atr14 also retains earlier history, so recent regularity does not erase older gaps.",
|
|
266
|
+
`- rsi14: momentum (this agent flags overbought at ${signalThresholdsOf(spec).rsiOverbought} and oversold at ${signalThresholdsOf(spec).rsiOversold}); ema20 & ema50: trend; atr14: volatility (size stops off it); bollinger {upper,mid,lower}; recent20 {high,low}: breakout levels.`,
|
|
204
267
|
"- boolean reads: aboveEma20, ema20AboveEma50 (uptrend when both true), brokeRecentHigh (breakout), brokeRecentLow (breakdown).",
|
|
205
268
|
"- a null field = not enough data; ignore it. These INFORM your decision; they never widen a cap.",
|
|
206
269
|
]
|
|
207
270
|
: []),
|
|
208
|
-
...(spec.
|
|
271
|
+
...(spec.universe
|
|
272
|
+
? [
|
|
273
|
+
"",
|
|
274
|
+
"## Your market (declared boundaries) — candidates beyond your watchlist",
|
|
275
|
+
`Each cycle the runner scans the market inside YOUR boundaries: ${describeUniverse(spec.universe)}. Watch entries with \`discovered: true\` are the top rows of that scan, resolved with the same price/sentiment (and indicators) data as your watchlist. observation.universeMovers lists further rows as symbol + 24h change only (context — you cannot trade those directly this cycle). Nothing outside these boundaries is shown to you or tradable.`,
|
|
276
|
+
"- Treat a discovered candidate like any other symbol: analyze it for catalysts, exhaustion and reversal BEFORE acting. A big move is as often a top as a beginning — chasing candles blind is how discovery loses money.",
|
|
277
|
+
"- All your normal risk rules apply unchanged: caps, stops, blocklist, confidence floor. Discovery widens what you can SEE, never what you may risk.",
|
|
278
|
+
"- Your boundaries also bind your watchlist: a watch entry with `withinBoundaries: false` (or no withinBoundaries and not discovered) is context only. You may close or sell an existing position in it, but a new entry is REJECTED (outside_universe).",
|
|
279
|
+
]
|
|
280
|
+
: []),
|
|
281
|
+
...(!spec.universe && spec.capabilities.includes("universe_scan")
|
|
209
282
|
? [
|
|
210
283
|
"",
|
|
211
284
|
"## Universe scan (discovered movers) — candidates beyond your watchlist",
|
|
@@ -339,6 +412,39 @@ opts = {}) {
|
|
|
339
412
|
`Each cycle, look at your OPEN positions FIRST, not just new entries. A position that is working is your best opportunity: once it moves your way, move the stop to breakeven and then TRAIL it behind the move with futures_set_sltp so a winner keeps running instead of being cut early${futuresOpenWithheld ? "" : " — and you may ADD to a confirming winner (scale in, never beyond your caps)"}. A position that is clearly wrong (its \`thesis.status\` reads invalidated, the level broke, the catalyst resolved against you) is cut cleanly instead of nursed; a position whose thesis is intact is held. Riding one good trade beats opening ten fresh ones.`,
|
|
340
413
|
].join("\n");
|
|
341
414
|
}
|
|
415
|
+
/**
|
|
416
|
+
* Per-position stop-loss side of the mark for futures_set_sltp. Live
|
|
417
|
+
* 2026-10-08: 473 stop_loss_not_above/below_mark refusals in 24 h, nearly all
|
|
418
|
+
* agents moving a short's stop to its entry price or trailing it when the
|
|
419
|
+
* mark was already at or past that level. States only the observed-mark
|
|
420
|
+
* constraint the validator applies (pos.markPrice, TRIGGER_PRICE_EPS); the
|
|
421
|
+
* take-profit side, liquidation and every other check stay authoritative.
|
|
422
|
+
* Prices print unrounded so the text never contradicts the validator.
|
|
423
|
+
*/
|
|
424
|
+
export function stopBoundLines(positions) {
|
|
425
|
+
const out = [];
|
|
426
|
+
for (const p of positions) {
|
|
427
|
+
const mark = p.markPrice;
|
|
428
|
+
const side = (p.side ?? "").toLowerCase();
|
|
429
|
+
if (typeof mark !== "number" || !Number.isFinite(mark) || mark <= 0)
|
|
430
|
+
continue;
|
|
431
|
+
if (side !== "long" && side !== "short")
|
|
432
|
+
continue;
|
|
433
|
+
const isShort = side === "short";
|
|
434
|
+
let line = `- pos#${p.id} ${side} ${p.symbol ?? ""}: observed mark ${String(mark)}; a new stopLossPrice must be ${isShort ? "ABOVE" : "BELOW"} it.`;
|
|
435
|
+
const entry = p.entryPrice;
|
|
436
|
+
if (typeof entry === "number" && Number.isFinite(entry) && entry > 0) {
|
|
437
|
+
const entrySideOk = isShort
|
|
438
|
+
? entry > mark + TRIGGER_PRICE_EPS
|
|
439
|
+
: entry < mark - TRIGGER_PRICE_EPS;
|
|
440
|
+
line += entrySideOk
|
|
441
|
+
? ` A stop at the entry price ${String(entry)} is on the allowed side of the mark.`
|
|
442
|
+
: ` A stop at the entry price ${String(entry)} is on the wrong side of the mark and would be refused; keep the current stop, or close the position if the thesis broke.`;
|
|
443
|
+
}
|
|
444
|
+
out.push(line);
|
|
445
|
+
}
|
|
446
|
+
return out;
|
|
447
|
+
}
|
|
342
448
|
export function buildUserPrompt(obs, journal, opts = {}) {
|
|
343
449
|
// Default to every venue for backwards-compatible direct callers and probes.
|
|
344
450
|
// The runner always supplies the real spec, so disabled venue instructions and
|
|
@@ -348,9 +454,36 @@ export function buildUserPrompt(obs, journal, opts = {}) {
|
|
|
348
454
|
const futuresOpenWithheld = hasFutures && opts.excludeActionTypes?.includes("futures_open") === true;
|
|
349
455
|
const hasSpot = venues.includes("spot");
|
|
350
456
|
const hasPm = venues.includes("pm");
|
|
457
|
+
const entryChoices = hasFutures ? futuresEntryChoices(obs) : undefined;
|
|
458
|
+
const blockedFutures = new Set(entryChoices?.blocked.map((entry) => baseSymbol(entry.symbol)));
|
|
459
|
+
// Structural signals remain in the observation/receipt. Only the model's
|
|
460
|
+
// action shortlist changes: an unsupported NEW futures entry is not an
|
|
461
|
+
// actionable futures setup. Spot and held-position context remain visible.
|
|
462
|
+
const promptSetups = (obs.setups ?? []).flatMap((setup) => {
|
|
463
|
+
if (!blockedFutures.has(baseSymbol(setup.symbol)))
|
|
464
|
+
return [setup];
|
|
465
|
+
return hasSpot
|
|
466
|
+
? [
|
|
467
|
+
{
|
|
468
|
+
...setup,
|
|
469
|
+
note: `${setup.note} [NEW FUTURES ENTRY BLOCKED: consider spot only if independently allowed, or skip]`,
|
|
470
|
+
},
|
|
471
|
+
]
|
|
472
|
+
: [];
|
|
473
|
+
});
|
|
474
|
+
const noFuturesEntryCandidates = entryChoices !== undefined &&
|
|
475
|
+
entryChoices.blocked.length > 0 &&
|
|
476
|
+
entryChoices.candidates.length === 0;
|
|
351
477
|
const lines = [
|
|
352
478
|
"Decide for THIS cycle using only the observation below (data available now — no look-ahead).",
|
|
353
479
|
];
|
|
480
|
+
if (entryChoices && entryChoices.blocked.length > 0) {
|
|
481
|
+
lines.push(`NEW FUTURES ENTRY AVAILABILITY: ${entryChoices.blocked.map((entry) => `${entry.symbol} (${entry.code})`).join(", ")} cannot be used for a NEW futures_open this cycle. A strong move or flagged structure does not override this block.`, "If futures_open is otherwise available, choose a new futures entry only from futuresEntryChoices.candidates below; this list checks perpetual-reference availability only, not other caps, universe boundaries, direction or quote/execution rules. reference=unknown is NOT eligibility: it still needs validation at quote time. held_position means the existing add/manage exception, under the unchanged add rules; it is not a fresh entry recommendation.", `Blocked coins remain in watch for market context${hasSpot ? " and otherwise-valid spot decisions" : ""}. Existing futures_close, futures_set_sltp and valid adds remain governed by their existing rules. ${hasSpot ? "Blocked setup notes apply only to NEW futures entries; do not force a spot trade to avoid skipping." : "Their unheld entry setups are omitted from this prompt's shortlist; the underlying market evidence remains visible."}`, ...(noFuturesEntryCandidates
|
|
482
|
+
? [
|
|
483
|
+
"There are NO new futures entry candidates this cycle. Manage/protect existing positions where valid, or skip. Unavailable futures entries are a valid reason to skip; do not invent an alternative trade.",
|
|
484
|
+
]
|
|
485
|
+
: []));
|
|
486
|
+
}
|
|
354
487
|
if (opts.capitalSizing) {
|
|
355
488
|
lines.push("capitalSizingPolicy is the opt-in paper sizing policy (percent fields use percentage points). capitalBook is captured owned-book collateral plus marked spot, reduced only by negative futures/PM marks on its walletId; positive open-position gains are excluded, so this is NOT complete marked equity. Positions on other walletIds remain visible for management but their collateral, marks and close proceeds do not fund this book. Missing/unavailable capitalBook means no new entries; otherwise-valid closes, protection, cancellations and spot sells remain available.");
|
|
356
489
|
}
|
|
@@ -391,6 +524,31 @@ export function buildUserPrompt(obs, journal, opts = {}) {
|
|
|
391
524
|
]
|
|
392
525
|
: []));
|
|
393
526
|
}
|
|
527
|
+
// Futures entry rules the runner enforces, stated with THIS agent's numbers.
|
|
528
|
+
// The weekly house report (2026-09-30..10-07) found the two largest refusal
|
|
529
|
+
// classes were opens below the fee-inclusive reward:risk floor
|
|
530
|
+
// (capital_quote_reward_risk_too_low) and SL/TP on an add
|
|
531
|
+
// (add_cannot_carry_sltp). Wording only: the validators are unchanged.
|
|
532
|
+
if (hasFutures && !futuresOpenWithheld) {
|
|
533
|
+
const held = Array.from(new Set(obs.openPositions
|
|
534
|
+
.filter((p) => p.venue === "futures" &&
|
|
535
|
+
(p.status ?? "open") === "open" &&
|
|
536
|
+
typeof p.symbol === "string" &&
|
|
537
|
+
p.symbol.length > 0)
|
|
538
|
+
.map((p) => p.symbol.toUpperCase())));
|
|
539
|
+
const minRewardRisk = opts.capitalSizing?.minRewardRisk;
|
|
540
|
+
if (typeof minRewardRisk === "number" &&
|
|
541
|
+
Number.isFinite(minRewardRisk) &&
|
|
542
|
+
minRewardRisk >= 1) {
|
|
543
|
+
lines.push(`REWARD:RISK FLOOR ${minRewardRisk} (your capitalSizing.minRewardRisk, enforced with fees; a correct-side take-profit inside it is widened by the runner to the floor, never narrowed, and an open that still cannot meet it is rejected as capital_quote_reward_risk_too_low): the take-profit's distance from entry, minus the entry and exit fees, must be at least ${minRewardRisk}x the stop's distance from entry plus those fees. A target exactly ${minRewardRisk}x the stop distance FAILS once fees are counted, so leave room. If the setup cannot offer such a target, do not open it.`);
|
|
544
|
+
}
|
|
545
|
+
if (held.length > 0) {
|
|
546
|
+
const list = held.join(", ");
|
|
547
|
+
lines.push(opts.capitalSizing
|
|
548
|
+
? `You already hold futures on ${list}. Under your capital policy you cannot ADD to them: every futures_open needs its own stopLossPrice and takeProfitPrice for sizing, and the server refuses SL/TP on an add (add_cannot_carry_sltp). Propose NO futures_open on ${list}; manage them with futures_set_sltp or futures_close on their positionId.`
|
|
549
|
+
: `You already hold futures on ${list}. A futures_open on any of them is an ADD and must carry NO stopLossPrice or takeProfitPrice (the whole open is rejected as add_cannot_carry_sltp); adjust protection with futures_set_sltp on the positionId instead.`);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
394
552
|
// Flat-state steer: when the agent holds NOTHING, weaker models (Llama 3.1 8B)
|
|
395
553
|
// still emit futures_close / futures_set_sltp / spot_cancel with a hallucinated
|
|
396
554
|
// positionId/orderId — which fails the whole cycle's strict parse (one bad id
|
|
@@ -399,7 +557,9 @@ export function buildUserPrompt(obs, journal, opts = {}) {
|
|
|
399
557
|
if ((obs.openPositions?.length ?? 0) === 0 &&
|
|
400
558
|
(!hasPm || (obs.pmPositions?.length ?? 0) === 0)) {
|
|
401
559
|
const openingActions = [
|
|
402
|
-
...(hasFutures && !futuresOpenWithheld
|
|
560
|
+
...(hasFutures && !futuresOpenWithheld && !noFuturesEntryCandidates
|
|
561
|
+
? ["futures_open"]
|
|
562
|
+
: []),
|
|
403
563
|
...(hasSpot ? ["spot_order"] : []),
|
|
404
564
|
...(hasPm ? ["pm_open"] : []),
|
|
405
565
|
];
|
|
@@ -415,6 +575,16 @@ export function buildUserPrompt(obs, journal, opts = {}) {
|
|
|
415
575
|
if (journal && journal.length > 0) {
|
|
416
576
|
lines.push("", "## Your recent moves (memory, newest last) — manage these with continuity; do NOT churn by re-opening an idea you just acted on:", ...journal.slice(-6).map((j) => `- ${j.did}`));
|
|
417
577
|
}
|
|
578
|
+
// Persisted state is read back defensively: only well-formed entries render.
|
|
579
|
+
const rejections = (Array.isArray(opts.lastRejections) ? opts.lastRejections : [])
|
|
580
|
+
.filter((r) => r &&
|
|
581
|
+
typeof r.action === "string" &&
|
|
582
|
+
typeof r.code === "string" &&
|
|
583
|
+
r.code.length > 0)
|
|
584
|
+
.slice(0, 5);
|
|
585
|
+
if (rejections.length > 0) {
|
|
586
|
+
lines.push("", "## Your last decision's REJECTED actions (refused, nothing happened) - do NOT resubmit them unchanged; fix the cause shown or choose differently:", ...rejections.map((r) => `- ${r.action.replace(/\s+/g, " ").slice(0, 200)}: ${r.code.replace(/\s+/g, " ").slice(0, 80)}${typeof r.reason === "string" && r.reason ? ` - ${r.reason.replace(/\s+/g, " ").slice(0, 200)}` : ""}`));
|
|
587
|
+
}
|
|
418
588
|
// Slice 2: name the positions whose stated thesis broke this cycle. On a live
|
|
419
589
|
// run the runner has already closed the futures ones (they are no longer in
|
|
420
590
|
// openPositions); whatever is listed here is for the MODEL to act on.
|
|
@@ -431,6 +601,10 @@ export function buildUserPrompt(obs, journal, opts = {}) {
|
|
|
431
601
|
if (brokenTheses.length > 0) {
|
|
432
602
|
lines.push("", "## Positions whose thesis is INVALIDATED this cycle", ...brokenTheses.map((b) => `- ${b}`));
|
|
433
603
|
}
|
|
604
|
+
const stopBounds = stopBoundLines(obs.openPositions);
|
|
605
|
+
if (stopBounds.length > 0) {
|
|
606
|
+
lines.push("", "## Stop-loss side of the mark for futures_set_sltp (a stop on the wrong side is refused; take-profit side, liquidation and the other trigger checks still apply)", ...stopBounds);
|
|
607
|
+
}
|
|
434
608
|
if (obs.openPositions.some((p) => p.fundingPaidMusd !== undefined ||
|
|
435
609
|
p.fundingAppliedThrough !== undefined)) {
|
|
436
610
|
lines.push("Futures funding shown in the position is already reflected in margin and balances; do not deduct it again when judging available capital or PnL.");
|
|
@@ -442,7 +616,7 @@ export function buildUserPrompt(obs, journal, opts = {}) {
|
|
|
442
616
|
lines.push("", "```json",
|
|
443
617
|
// Compact (no pretty-print indentation — ~40% fewer tokens, still valid JSON)
|
|
444
618
|
// and the trade ledger is capped so a busy shared book can't bloat the prompt.
|
|
445
|
-
|
|
619
|
+
serializePromptObservation({
|
|
446
620
|
asOf: obs.asOf,
|
|
447
621
|
...(opts.dailyRiskBudget
|
|
448
622
|
? { dailyRiskBudget: opts.dailyRiskBudget }
|
|
@@ -500,13 +674,17 @@ export function buildUserPrompt(obs, journal, opts = {}) {
|
|
|
500
674
|
? { pmCalibration: obs.pmCalibration }
|
|
501
675
|
: {}),
|
|
502
676
|
watch: obs.watch,
|
|
503
|
-
|
|
677
|
+
...(entryChoices && entryChoices.blocked.length > 0
|
|
678
|
+
? { futuresEntryChoices: entryChoices }
|
|
679
|
+
: {}),
|
|
680
|
+
setups: promptSetups,
|
|
504
681
|
news: obs.news,
|
|
505
682
|
universeMovers: obs.universeMovers,
|
|
506
683
|
whaleContext: obs.whaleContext,
|
|
507
684
|
marketMood: obs.marketMood,
|
|
685
|
+
...(obs.macro ? { macro: obs.macro } : {}),
|
|
508
686
|
newClosedTrades: obs.newClosedTrades.slice(0, 20),
|
|
509
687
|
polledBeforeWrite: obs.polledBeforeWrite,
|
|
510
|
-
}), "```", "", "Return ONLY the JSON decision object.");
|
|
688
|
+
}, opts.compactTables), "```", "", "Return ONLY the JSON decision object.");
|
|
511
689
|
return lines.join("\n");
|
|
512
690
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function serializePromptObservation(value: unknown, compact?: boolean): string;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const row = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
2
|
+
// Keep market refs and per-outcome settlement rules in their proven object
|
|
3
|
+
// representation. A provider probe mis-associated an optional rule with a ref.
|
|
4
|
+
const TABLE_FIELDS = ["watch", "pmPositions", "newClosedTrades"];
|
|
5
|
+
const FORMAT = "Tabular lists use columns and rows: each row's values map to the columns in order. Every row remains a separate item; nested objects keep their field names. References such as watch[].priceUsd refer to the reconstructed row. Only lists with identical fields use tables. Market refs and settlement rules remain objects. Return the normal decision JSON with actions as an array of objects.";
|
|
6
|
+
export function serializePromptObservation(value, compact = false) {
|
|
7
|
+
const original = JSON.stringify(value);
|
|
8
|
+
if (!compact)
|
|
9
|
+
return original;
|
|
10
|
+
const data = JSON.parse(original);
|
|
11
|
+
let changed = false;
|
|
12
|
+
for (const field of TABLE_FIELDS) {
|
|
13
|
+
const list = data[field];
|
|
14
|
+
if (!Array.isArray(list) || list.length < 3 || !list.every(row))
|
|
15
|
+
continue;
|
|
16
|
+
const columns = Object.keys(list[0]).filter((k) => list.every((r) => Object.hasOwn(r, k)));
|
|
17
|
+
if (columns.length < 3 ||
|
|
18
|
+
list.some((r) => Object.keys(r).length !== columns.length))
|
|
19
|
+
continue;
|
|
20
|
+
const table = {
|
|
21
|
+
columns,
|
|
22
|
+
rows: list.map((r) => columns.map((k) => r[k])),
|
|
23
|
+
};
|
|
24
|
+
if (JSON.stringify(list).length - JSON.stringify(table).length < 512)
|
|
25
|
+
continue;
|
|
26
|
+
data[field] = table;
|
|
27
|
+
changed = true;
|
|
28
|
+
}
|
|
29
|
+
if (!changed)
|
|
30
|
+
return original;
|
|
31
|
+
const result = JSON.stringify({ tableFormat: FORMAT, ...data });
|
|
32
|
+
return result.length < original.length ? result : original;
|
|
33
|
+
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { ProviderName } from "./types.js";
|
|
2
2
|
export declare const NVIDIA_BASE_URL = "https://integrate.api.nvidia.com/v1";
|
|
3
3
|
export declare const DECISION_TOOL_NAME = "submit_trading_decision";
|
|
4
|
+
export declare function canRetrySuperToolOutput(route: {
|
|
5
|
+
provider: string;
|
|
6
|
+
model: string;
|
|
7
|
+
baseUrl?: string | null;
|
|
8
|
+
}, responseSource: string | undefined, actionsStringDiagnostic: string | undefined): boolean;
|
|
4
9
|
export interface ChatShape {
|
|
5
10
|
family: "openai-reasoning" | "nvidia-nemotron" | "anthropic" | "openai-compat";
|
|
6
11
|
tokenParam: "max_tokens" | "max_completion_tokens";
|
|
@@ -13,6 +18,7 @@ export interface ChatShape {
|
|
|
13
18
|
minProbeCompletionTokens: number;
|
|
14
19
|
}
|
|
15
20
|
export declare function chatShapeFor(provider: ProviderName, model: string, baseUrl?: string): ChatShape;
|
|
21
|
+
export declare function withJsonContentTransport(shape: ChatShape): ChatShape;
|
|
16
22
|
export type DecisionActionExclusion = "futures_open";
|
|
17
23
|
export declare function restrictDecisionSchema(schema: Record<string, unknown>, exclude: readonly DecisionActionExclusion[] | undefined): Record<string, unknown>;
|
|
18
24
|
/** Build the chat-completions body for a route from its capability shape. */
|
|
@@ -23,4 +29,5 @@ export declare function buildChatBody(shape: ChatShape, args: {
|
|
|
23
29
|
maxTokens: number;
|
|
24
30
|
temperature?: number;
|
|
25
31
|
excludeActionTypes?: readonly DecisionActionExclusion[];
|
|
32
|
+
decisionContract?: "pm_paper_v2";
|
|
26
33
|
}): Record<string, unknown>;
|
|
@@ -1,6 +1,17 @@
|
|
|
1
|
+
import { PM_PAPER_DECISION_JSON_SCHEMA } from "./pmPaperDecision.js";
|
|
1
2
|
import { DECISION_JSON_SCHEMA } from "./decision.js";
|
|
2
3
|
export const NVIDIA_BASE_URL = "https://integrate.api.nvidia.com/v1";
|
|
3
4
|
export const DECISION_TOOL_NAME = "submit_trading_decision";
|
|
5
|
+
// A retry changes transport only after the strict parser has rejected a known
|
|
6
|
+
// Super tool-argument encoding failure. Valid responses and other routes keep
|
|
7
|
+
// their original request shape. A custom endpoint is deliberately excluded.
|
|
8
|
+
export function canRetrySuperToolOutput(route, responseSource, actionsStringDiagnostic) {
|
|
9
|
+
return (route.provider === "nvidia" &&
|
|
10
|
+
route.model === "nvidia/nemotron-3-super-120b-a12b" &&
|
|
11
|
+
(route.baseUrl == null || route.baseUrl === NVIDIA_BASE_URL) &&
|
|
12
|
+
responseSource === "tool_call" &&
|
|
13
|
+
actionsStringDiagnostic !== undefined);
|
|
14
|
+
}
|
|
4
15
|
// gpt-5*, o1/o3/o4* — the OpenAI reasoning-API family, wherever it is served.
|
|
5
16
|
const OPENAI_REASONING_MODEL = /^(gpt-5|o[0-9])/i;
|
|
6
17
|
const NEMOTRON_MODEL = /nemotron/i;
|
|
@@ -54,6 +65,19 @@ export function chatShapeFor(provider, model, baseUrl) {
|
|
|
54
65
|
minProbeCompletionTokens: 1024,
|
|
55
66
|
};
|
|
56
67
|
}
|
|
68
|
+
// Opt-in alternative for a hosted Nemotron route: plain JSON content
|
|
69
|
+
// (`response_format: json_object`) instead of the forced decision tool call.
|
|
70
|
+
// Hosted Super has returned the forced tool call with `actions` as a string
|
|
71
|
+
// (519 of 533 malformed attempts in a 24h sample, 2026-10-05). In an 8-call
|
|
72
|
+
// synthetic smoke pilot the tool-call arms failed 0/4 with the same coarse
|
|
73
|
+
// string shape and the JSON-content arm parsed 2/2; that is not a rate
|
|
74
|
+
// measurement and the exact serialization cause is not established. Only the
|
|
75
|
+
// request transport changes: parseDecision still validates the full contract.
|
|
76
|
+
export function withJsonContentTransport(shape) {
|
|
77
|
+
if (shape.family !== "nvidia-nemotron")
|
|
78
|
+
return shape;
|
|
79
|
+
return { ...shape, jsonSchema: undefined, jsonSchemaTransport: undefined };
|
|
80
|
+
}
|
|
57
81
|
// Transport-only restriction: drop the excluded variants from the action oneOf
|
|
58
82
|
// a route decodes against. parseDecision keeps validating the FULL contract, so
|
|
59
83
|
// a route that does not enforce a schema can still propose the action and the
|
|
@@ -87,7 +111,9 @@ export function buildChatBody(shape, args) {
|
|
|
87
111
|
? `${shape.systemHint}\n\n${args.system}`
|
|
88
112
|
: args.system;
|
|
89
113
|
const jsonSchema = shape.jsonSchema
|
|
90
|
-
?
|
|
114
|
+
? args.decisionContract === "pm_paper_v2"
|
|
115
|
+
? PM_PAPER_DECISION_JSON_SCHEMA
|
|
116
|
+
: restrictDecisionSchema(shape.jsonSchema, args.excludeActionTypes)
|
|
91
117
|
: undefined;
|
|
92
118
|
return {
|
|
93
119
|
model: args.model,
|
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
import { AgentSpec, ProviderName } from "./types.js";
|
|
2
|
+
import { type ActionsStringDiagnostic } from "./decision.js";
|
|
2
3
|
import { type DecisionActionExclusion } from "./providerCapabilities.js";
|
|
4
|
+
export interface ProviderUsage {
|
|
5
|
+
promptTokens: number;
|
|
6
|
+
completionTokens: number;
|
|
7
|
+
cacheReadTokens?: number;
|
|
8
|
+
cacheWriteTokens?: number;
|
|
9
|
+
cacheWrite5mTokens?: number;
|
|
10
|
+
cacheWrite1hTokens?: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function anthropicUsage(raw: unknown): ProviderUsage | undefined;
|
|
3
13
|
export interface DecideInput {
|
|
4
14
|
system: string;
|
|
5
15
|
user: string;
|
|
16
|
+
/** Optional equivalent presentation for explicitly verified hosted routes. */
|
|
17
|
+
compactUser?: string;
|
|
6
18
|
maxTokens?: number;
|
|
7
19
|
timeoutMs?: number;
|
|
8
20
|
excludeActionTypes?: readonly DecisionActionExclusion[];
|
|
21
|
+
decisionContract?: "pm_paper_v2";
|
|
9
22
|
}
|
|
10
23
|
export interface DecideRouteAttempt {
|
|
11
24
|
provider: string;
|
|
@@ -16,6 +29,10 @@ export interface DecideRouteAttempt {
|
|
|
16
29
|
retryAfterMs?: number;
|
|
17
30
|
latencyMs: number;
|
|
18
31
|
error?: string;
|
|
32
|
+
responseSource?: "tool_call" | "content_fallback" | "content";
|
|
33
|
+
actionsStringDiagnostic?: ActionsStringDiagnostic;
|
|
34
|
+
/** Scheduler admission categories, never free-form provider error text. */
|
|
35
|
+
admissionReasons?: Array<"request_budget" | "token_budget" | "concurrency" | "shared_key_cooldown" | "model_cooldown">;
|
|
19
36
|
}
|
|
20
37
|
export interface DecideRouteMeta {
|
|
21
38
|
policyVersion: string;
|
|
@@ -29,14 +46,13 @@ export type DecideResult = {
|
|
|
29
46
|
ok: true;
|
|
30
47
|
text: string;
|
|
31
48
|
responseSource?: "tool_call" | "content_fallback" | "content";
|
|
32
|
-
usage?:
|
|
33
|
-
promptTokens: number;
|
|
34
|
-
completionTokens: number;
|
|
35
|
-
};
|
|
49
|
+
usage?: ProviderUsage;
|
|
36
50
|
route?: DecideRouteMeta;
|
|
37
51
|
} | {
|
|
38
52
|
ok: false;
|
|
39
53
|
error: string;
|
|
54
|
+
failureClass?: "malformed";
|
|
55
|
+
usage?: ProviderUsage;
|
|
40
56
|
status?: number;
|
|
41
57
|
retryAfterMs?: number;
|
|
42
58
|
retryableHttpFailure?: true;
|
|
@@ -58,10 +74,13 @@ export interface ProviderEnv {
|
|
|
58
74
|
/** Keep direct and routed cycle evidence consistent about NVIDIA backpressure. */
|
|
59
75
|
export declare function classifyProviderFailure(result: Extract<DecideResult, {
|
|
60
76
|
ok: false;
|
|
61
|
-
}>): "capacity" | "permanent" | "transient";
|
|
62
|
-
export
|
|
77
|
+
}>): "capacity" | "permanent" | "transient" | "malformed";
|
|
78
|
+
export interface ProviderRouteOptions {
|
|
79
|
+
nemotronJsonContent?: boolean | (() => boolean);
|
|
80
|
+
}
|
|
81
|
+
export declare function selectProvider(spec: AgentSpec, env: ProviderEnv, fetchFn?: typeof fetch, options?: ProviderRouteOptions): Provider;
|
|
63
82
|
export declare function providerForRoute(route: {
|
|
64
83
|
provider: ProviderName;
|
|
65
84
|
model: string;
|
|
66
85
|
baseUrl?: string | null;
|
|
67
|
-
}, apiKey: string, fetchFn?: typeof fetch): Provider;
|
|
86
|
+
}, apiKey: string, fetchFn?: typeof fetch, options?: ProviderRouteOptions): Provider;
|