@coinrithm/mcp-trading 0.7.6 → 0.7.8
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 +134 -0
- package/README.md +37 -8
- package/dist/agent/act.js +19 -6
- package/dist/agent/capitalSizing.d.ts +32 -0
- package/dist/agent/capitalSizing.js +257 -0
- package/dist/agent/client.d.ts +2 -0
- package/dist/agent/client.js +4 -0
- package/dist/agent/decision.d.ts +392 -0
- package/dist/agent/decision.js +177 -0
- package/dist/agent/decisionProbe.d.ts +17 -0
- package/dist/agent/decisionProbe.js +70 -0
- package/dist/agent/decisionReceipt.d.ts +45 -0
- package/dist/agent/decisionReceipt.js +595 -0
- package/dist/agent/decisionValidator.d.ts +19 -2
- package/dist/agent/decisionValidator.js +97 -3
- package/dist/agent/engine.d.ts +5 -1
- package/dist/agent/engine.js +8 -1
- package/dist/agent/observe.js +194 -35
- package/dist/agent/pmContext.d.ts +13 -0
- package/dist/agent/pmContext.js +136 -0
- package/dist/agent/prompt.d.ts +14 -2
- package/dist/agent/prompt.js +232 -35
- package/dist/agent/providerCapabilities.d.ts +23 -0
- package/dist/agent/providerCapabilities.js +105 -0
- package/dist/agent/providers.d.ts +29 -1
- package/dist/agent/providers.js +159 -88
- package/dist/agent/resolve.d.ts +1 -1
- package/dist/agent/resolve.js +21 -1
- package/dist/agent/runner.d.ts +4 -1
- package/dist/agent/runner.js +418 -47
- package/dist/agent/scorecard.js +7 -1
- package/dist/agent/skill.js +23 -0
- package/dist/agent/skillValidator.d.ts +1 -0
- package/dist/agent/skillValidator.js +63 -0
- package/dist/agent/state.js +7 -1
- package/dist/agent/strictLint.js +20 -0
- package/dist/agent/thesis.d.ts +40 -0
- package/dist/agent/thesis.js +319 -0
- package/dist/agent/types.d.ts +151 -0
- package/dist/http.js +21 -0
- package/dist/tools.js +10 -10
- package/package.json +1 -1
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { DECISION_JSON_SCHEMA } from "./decision.js";
|
|
2
|
+
export const NVIDIA_BASE_URL = "https://integrate.api.nvidia.com/v1";
|
|
3
|
+
export const DECISION_TOOL_NAME = "submit_trading_decision";
|
|
4
|
+
// gpt-5*, o1/o3/o4* — the OpenAI reasoning-API family, wherever it is served.
|
|
5
|
+
const OPENAI_REASONING_MODEL = /^(gpt-5|o[0-9])/i;
|
|
6
|
+
const NEMOTRON_MODEL = /nemotron/i;
|
|
7
|
+
export function chatShapeFor(provider, model, baseUrl) {
|
|
8
|
+
if (provider === "anthropic") {
|
|
9
|
+
return {
|
|
10
|
+
family: "anthropic",
|
|
11
|
+
tokenParam: "max_tokens",
|
|
12
|
+
allowsTemperature: true,
|
|
13
|
+
jsonResponseFormat: false,
|
|
14
|
+
minProbeCompletionTokens: 1024,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
if (provider === "openai" || OPENAI_REASONING_MODEL.test(model)) {
|
|
18
|
+
return {
|
|
19
|
+
family: "openai-reasoning",
|
|
20
|
+
tokenParam: "max_completion_tokens",
|
|
21
|
+
allowsTemperature: false,
|
|
22
|
+
jsonResponseFormat: true,
|
|
23
|
+
minProbeCompletionTokens: 1024,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
if (NEMOTRON_MODEL.test(model)) {
|
|
27
|
+
const isNvidiaEndpoint = baseUrl === NVIDIA_BASE_URL;
|
|
28
|
+
return {
|
|
29
|
+
family: "nvidia-nemotron",
|
|
30
|
+
tokenParam: "max_tokens",
|
|
31
|
+
allowsTemperature: true,
|
|
32
|
+
jsonResponseFormat: true,
|
|
33
|
+
jsonSchema: isNvidiaEndpoint
|
|
34
|
+
? DECISION_JSON_SCHEMA
|
|
35
|
+
: undefined,
|
|
36
|
+
// integrate.api.nvidia.com currently ignores both response_format
|
|
37
|
+
// json_schema and guided_json for these hosted models. Its forced tool
|
|
38
|
+
// call path is the live-probed contract-enforcing transport.
|
|
39
|
+
jsonSchemaTransport: isNvidiaEndpoint ? "tool_call" : undefined,
|
|
40
|
+
// The kwargs switch is only honored (and only safe to send) on the NVIDIA
|
|
41
|
+
// endpoint; the system hint helps on any endpoint serving a Nemotron.
|
|
42
|
+
extraBody: isNvidiaEndpoint
|
|
43
|
+
? { chat_template_kwargs: { enable_thinking: false } }
|
|
44
|
+
: undefined,
|
|
45
|
+
systemHint: "detailed thinking off",
|
|
46
|
+
minProbeCompletionTokens: 1024,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
family: "openai-compat",
|
|
51
|
+
tokenParam: "max_tokens",
|
|
52
|
+
allowsTemperature: true,
|
|
53
|
+
jsonResponseFormat: true,
|
|
54
|
+
minProbeCompletionTokens: 1024,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/** Build the chat-completions body for a route from its capability shape. */
|
|
58
|
+
export function buildChatBody(shape, args) {
|
|
59
|
+
const system = shape.systemHint
|
|
60
|
+
? `${shape.systemHint}\n\n${args.system}`
|
|
61
|
+
: args.system;
|
|
62
|
+
return {
|
|
63
|
+
model: args.model,
|
|
64
|
+
...(shape.allowsTemperature
|
|
65
|
+
? { temperature: args.temperature ?? 0.2 }
|
|
66
|
+
: {}),
|
|
67
|
+
[shape.tokenParam]: args.maxTokens,
|
|
68
|
+
...(shape.jsonSchema && shape.jsonSchemaTransport === "tool_call"
|
|
69
|
+
? {
|
|
70
|
+
tools: [
|
|
71
|
+
{
|
|
72
|
+
type: "function",
|
|
73
|
+
function: {
|
|
74
|
+
name: DECISION_TOOL_NAME,
|
|
75
|
+
description: "Submit the complete CoinRithm paper-trading decision for this cycle.",
|
|
76
|
+
parameters: shape.jsonSchema,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
tool_choice: {
|
|
81
|
+
type: "function",
|
|
82
|
+
function: { name: DECISION_TOOL_NAME },
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
: {}),
|
|
86
|
+
...(shape.jsonResponseFormat && shape.jsonSchemaTransport !== "tool_call"
|
|
87
|
+
? {
|
|
88
|
+
response_format: shape.jsonSchema
|
|
89
|
+
? {
|
|
90
|
+
type: "json_schema",
|
|
91
|
+
json_schema: {
|
|
92
|
+
name: "coinrithm_trading_decision",
|
|
93
|
+
schema: shape.jsonSchema,
|
|
94
|
+
},
|
|
95
|
+
}
|
|
96
|
+
: { type: "json_object" },
|
|
97
|
+
}
|
|
98
|
+
: {}),
|
|
99
|
+
...(shape.extraBody ?? {}),
|
|
100
|
+
messages: [
|
|
101
|
+
{ role: "system", content: system },
|
|
102
|
+
{ role: "user", content: args.user },
|
|
103
|
+
],
|
|
104
|
+
};
|
|
105
|
+
}
|
|
@@ -1,10 +1,28 @@
|
|
|
1
|
-
import { AgentSpec } from "./types.js";
|
|
1
|
+
import { AgentSpec, ProviderName } from "./types.js";
|
|
2
2
|
export interface DecideInput {
|
|
3
3
|
system: string;
|
|
4
4
|
user: string;
|
|
5
5
|
maxTokens?: number;
|
|
6
6
|
timeoutMs?: number;
|
|
7
7
|
}
|
|
8
|
+
export interface DecideRouteAttempt {
|
|
9
|
+
provider: string;
|
|
10
|
+
model: string;
|
|
11
|
+
outcome: "success" | "failed" | "deferred";
|
|
12
|
+
failureClass?: "capacity" | "permanent" | "transient" | "malformed";
|
|
13
|
+
status?: number;
|
|
14
|
+
retryAfterMs?: number;
|
|
15
|
+
latencyMs: number;
|
|
16
|
+
error?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface DecideRouteMeta {
|
|
19
|
+
policyVersion: string;
|
|
20
|
+
profile: "fast" | "strong" | "configured";
|
|
21
|
+
effectiveProvider?: string;
|
|
22
|
+
effectiveModel?: string;
|
|
23
|
+
reason: "configured" | "circuit_fallback" | "capacity_fallback" | "provider_fallback" | "malformed_fallback" | "byo";
|
|
24
|
+
attempts: DecideRouteAttempt[];
|
|
25
|
+
}
|
|
8
26
|
export type DecideResult = {
|
|
9
27
|
ok: true;
|
|
10
28
|
text: string;
|
|
@@ -12,9 +30,14 @@ export type DecideResult = {
|
|
|
12
30
|
promptTokens: number;
|
|
13
31
|
completionTokens: number;
|
|
14
32
|
};
|
|
33
|
+
route?: DecideRouteMeta;
|
|
15
34
|
} | {
|
|
16
35
|
ok: false;
|
|
17
36
|
error: string;
|
|
37
|
+
status?: number;
|
|
38
|
+
retryAfterMs?: number;
|
|
39
|
+
deferred?: boolean;
|
|
40
|
+
route?: DecideRouteMeta;
|
|
18
41
|
};
|
|
19
42
|
export interface Provider {
|
|
20
43
|
label: string;
|
|
@@ -29,3 +52,8 @@ export interface ProviderEnv {
|
|
|
29
52
|
MODEL_API_KEY?: string;
|
|
30
53
|
}
|
|
31
54
|
export declare function selectProvider(spec: AgentSpec, env: ProviderEnv, fetchFn?: typeof fetch): Provider;
|
|
55
|
+
export declare function providerForRoute(route: {
|
|
56
|
+
provider: ProviderName;
|
|
57
|
+
model: string;
|
|
58
|
+
baseUrl?: string | null;
|
|
59
|
+
}, apiKey: string, fetchFn?: typeof fetch): Provider;
|
package/dist/agent/providers.js
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
// never from an agent file. One call returns one chunk of text that must be a
|
|
3
3
|
// single structured-JSON decision (parsed in decision.ts). No free-form tool
|
|
4
4
|
// execution — the model only proposes; the runner disposes.
|
|
5
|
+
import { chatShapeFor, buildChatBody, DECISION_TOOL_NAME, NVIDIA_BASE_URL as CAP_NVIDIA_BASE_URL, } from "./providerCapabilities.js";
|
|
5
6
|
// NVIDIA NIM is OpenAI-compatible; the `nvidia` preset hard-wires the hosted
|
|
6
7
|
// endpoint so an agent only needs `{ provider: nvidia, name: "<model id>" }`.
|
|
7
|
-
const NVIDIA_BASE_URL =
|
|
8
|
+
const NVIDIA_BASE_URL = CAP_NVIDIA_BASE_URL;
|
|
8
9
|
// Gemini exposes an OpenAI-compatible surface, so the `gemini` preset hard-wires
|
|
9
10
|
// its hosted endpoint — an agent only needs `{ provider: gemini, name: "gemini-2.0-flash" }`
|
|
10
11
|
// plus a GEMINI_API_KEY. The free tier (no credit card, generous Flash quota) makes
|
|
@@ -26,23 +27,26 @@ const GEMINI_BASE_URL = "https://generativelanguage.googleapis.com/v1beta/openai
|
|
|
26
27
|
// (the recurring Leo/70B timeout). A real hang still aborts -> retried next cadence.
|
|
27
28
|
// MUST stay below the scheduler's RUN_LOCK_SECONDS and HEARTBEAT_STALE_MS.
|
|
28
29
|
const DEFAULT_TIMEOUT_MS = 300_000;
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
: system;
|
|
38
|
-
}
|
|
39
|
-
// fetch with a hard timeout via AbortController. A custom fetchFn (tests) that
|
|
40
|
-
// ignores `signal` still works — the timer just never fires for it.
|
|
41
|
-
async function fetchWithTimeout(fetchFn, url, init, timeoutMs) {
|
|
30
|
+
// Per-route request quirks (reasoning toggles, token param, temperature) live
|
|
31
|
+
// in the capability table — providerCapabilities.ts is the single source; this
|
|
32
|
+
// module only assembles and sends.
|
|
33
|
+
// One deadline covers both headers AND response-body consumption. fetch resolves
|
|
34
|
+
// at headers, so clearing a fetch-only timer there leaves text/json unbounded.
|
|
35
|
+
// Abort native I/O and race the deadline as well: an injected implementation that
|
|
36
|
+
// ignores AbortSignal must still release the caller rather than its run lock.
|
|
37
|
+
async function withProviderTimeout(timeoutMs, operation) {
|
|
42
38
|
const controller = new AbortController();
|
|
43
|
-
|
|
39
|
+
let timer;
|
|
40
|
+
const deadline = new Promise((_resolve, reject) => {
|
|
41
|
+
timer = setTimeout(() => {
|
|
42
|
+
reject(Object.assign(new Error("model deadline exceeded"), {
|
|
43
|
+
name: "AbortError",
|
|
44
|
+
}));
|
|
45
|
+
controller.abort();
|
|
46
|
+
}, timeoutMs);
|
|
47
|
+
});
|
|
44
48
|
try {
|
|
45
|
-
return await
|
|
49
|
+
return await Promise.race([operation(controller.signal), deadline]);
|
|
46
50
|
}
|
|
47
51
|
finally {
|
|
48
52
|
clearTimeout(timer);
|
|
@@ -54,6 +58,23 @@ function callError(err, timeoutMs) {
|
|
|
54
58
|
}
|
|
55
59
|
return err instanceof Error ? err.message : String(err);
|
|
56
60
|
}
|
|
61
|
+
// Parse a Retry-After header (delta-seconds or HTTP-date) into ms, capped at
|
|
62
|
+
// one hour — a provider asking for more is treated as "an hour, then re-probe".
|
|
63
|
+
const RETRY_AFTER_CAP_MS = 3_600_000;
|
|
64
|
+
function retryAfterMs(res) {
|
|
65
|
+
const raw = res.headers.get("retry-after");
|
|
66
|
+
if (!raw)
|
|
67
|
+
return undefined;
|
|
68
|
+
const secs = Number(raw);
|
|
69
|
+
if (Number.isFinite(secs) && secs >= 0) {
|
|
70
|
+
return Math.min(Math.round(secs * 1000), RETRY_AFTER_CAP_MS);
|
|
71
|
+
}
|
|
72
|
+
const at = Date.parse(raw);
|
|
73
|
+
if (!Number.isFinite(at))
|
|
74
|
+
return undefined;
|
|
75
|
+
const ms = at - Date.now();
|
|
76
|
+
return ms > 0 ? Math.min(ms, RETRY_AFTER_CAP_MS) : 0;
|
|
77
|
+
}
|
|
57
78
|
function envKey(provider, env) {
|
|
58
79
|
switch (provider) {
|
|
59
80
|
case "anthropic":
|
|
@@ -120,52 +141,73 @@ class AnthropicProvider {
|
|
|
120
141
|
}
|
|
121
142
|
async decide(input) {
|
|
122
143
|
const timeoutMs = input.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
144
|
+
let failureResponse;
|
|
123
145
|
try {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
completionTokens: json.usage.output_tokens ?? 0,
|
|
146
|
+
return await withProviderTimeout(timeoutMs, async (signal) => {
|
|
147
|
+
const res = await this.fetchFn("https://api.anthropic.com/v1/messages", {
|
|
148
|
+
method: "POST",
|
|
149
|
+
signal,
|
|
150
|
+
headers: {
|
|
151
|
+
"x-api-key": this.apiKey,
|
|
152
|
+
"anthropic-version": "2023-06-01",
|
|
153
|
+
"content-type": "application/json",
|
|
154
|
+
},
|
|
155
|
+
body: JSON.stringify({
|
|
156
|
+
model: this.model,
|
|
157
|
+
max_tokens: input.maxTokens ?? 1024,
|
|
158
|
+
system: input.system,
|
|
159
|
+
messages: [{ role: "user", content: input.user }],
|
|
160
|
+
}),
|
|
161
|
+
});
|
|
162
|
+
if (!res.ok) {
|
|
163
|
+
failureResponse = res;
|
|
164
|
+
return {
|
|
165
|
+
ok: false,
|
|
166
|
+
// Cap the upstream body: it lands in agent_cycles.skip_reason, so an
|
|
167
|
+
// unbounded provider error page must not bloat the ledger row.
|
|
168
|
+
error: `anthropic HTTP ${res.status}: ${(await res.text()).slice(0, 2000)}`,
|
|
169
|
+
status: res.status,
|
|
170
|
+
retryAfterMs: retryAfterMs(res),
|
|
171
|
+
};
|
|
151
172
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
173
|
+
const json = (await res.json());
|
|
174
|
+
const text = json.content?.map((c) => c.text ?? "").join("") ?? "";
|
|
175
|
+
const usage = json.usage
|
|
176
|
+
? {
|
|
177
|
+
promptTokens: json.usage.input_tokens ?? 0,
|
|
178
|
+
completionTokens: json.usage.output_tokens ?? 0,
|
|
179
|
+
}
|
|
180
|
+
: undefined;
|
|
181
|
+
return text
|
|
182
|
+
? { ok: true, text, usage }
|
|
183
|
+
: { ok: false, error: "anthropic returned empty content" };
|
|
184
|
+
});
|
|
156
185
|
}
|
|
157
186
|
catch (err) {
|
|
158
|
-
return {
|
|
187
|
+
return {
|
|
188
|
+
ok: false,
|
|
189
|
+
error: failureResponse
|
|
190
|
+
? `anthropic HTTP ${failureResponse.status}: ${callError(err, timeoutMs)}`
|
|
191
|
+
: callError(err, timeoutMs),
|
|
192
|
+
...(failureResponse
|
|
193
|
+
? {
|
|
194
|
+
status: failureResponse.status,
|
|
195
|
+
retryAfterMs: retryAfterMs(failureResponse),
|
|
196
|
+
}
|
|
197
|
+
: {}),
|
|
198
|
+
};
|
|
159
199
|
}
|
|
160
200
|
}
|
|
161
201
|
}
|
|
162
202
|
class OpenAiCompatProvider {
|
|
203
|
+
provider;
|
|
163
204
|
model;
|
|
164
205
|
apiKey;
|
|
165
206
|
baseUrl;
|
|
166
207
|
fetchFn;
|
|
167
208
|
label;
|
|
168
|
-
constructor(model, apiKey, baseUrl, fetchFn) {
|
|
209
|
+
constructor(provider, model, apiKey, baseUrl, fetchFn) {
|
|
210
|
+
this.provider = provider;
|
|
169
211
|
this.model = model;
|
|
170
212
|
this.apiKey = apiKey;
|
|
171
213
|
this.baseUrl = baseUrl;
|
|
@@ -174,48 +216,63 @@ class OpenAiCompatProvider {
|
|
|
174
216
|
}
|
|
175
217
|
async decide(input) {
|
|
176
218
|
const timeoutMs = input.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
219
|
+
const shape = chatShapeFor(this.provider, this.model, this.baseUrl);
|
|
220
|
+
let failureResponse;
|
|
177
221
|
try {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
error: `provider HTTP ${res.status}: ${(await res.text()).slice(0, 2000)}`,
|
|
204
|
-
};
|
|
205
|
-
const json = (await res.json());
|
|
206
|
-
const text = json.choices?.[0]?.message?.content ?? "";
|
|
207
|
-
const usage = json.usage
|
|
208
|
-
? {
|
|
209
|
-
promptTokens: json.usage.prompt_tokens ?? 0,
|
|
210
|
-
completionTokens: json.usage.completion_tokens ?? 0,
|
|
222
|
+
return await withProviderTimeout(timeoutMs, async (signal) => {
|
|
223
|
+
const res = await this.fetchFn(`${this.baseUrl}/chat/completions`, {
|
|
224
|
+
method: "POST",
|
|
225
|
+
signal,
|
|
226
|
+
headers: {
|
|
227
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
228
|
+
"content-type": "application/json",
|
|
229
|
+
},
|
|
230
|
+
body: JSON.stringify(buildChatBody(shape, {
|
|
231
|
+
model: this.model,
|
|
232
|
+
system: input.system,
|
|
233
|
+
user: input.user,
|
|
234
|
+
maxTokens: input.maxTokens ?? 1024,
|
|
235
|
+
})),
|
|
236
|
+
});
|
|
237
|
+
if (!res.ok) {
|
|
238
|
+
failureResponse = res;
|
|
239
|
+
return {
|
|
240
|
+
ok: false,
|
|
241
|
+
// Cap the upstream body: it lands in agent_cycles.skip_reason, so an
|
|
242
|
+
// unbounded provider error page must not bloat the ledger row.
|
|
243
|
+
error: `provider HTTP ${res.status}: ${(await res.text()).slice(0, 2000)}`,
|
|
244
|
+
status: res.status,
|
|
245
|
+
retryAfterMs: retryAfterMs(res),
|
|
246
|
+
};
|
|
211
247
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
248
|
+
const json = (await res.json());
|
|
249
|
+
const message = json.choices?.[0]?.message;
|
|
250
|
+
const decisionArguments = message?.tool_calls?.find((call) => call.function?.name === DECISION_TOOL_NAME)?.function?.arguments;
|
|
251
|
+
const text = decisionArguments ?? message?.content ?? "";
|
|
252
|
+
const usage = json.usage
|
|
253
|
+
? {
|
|
254
|
+
promptTokens: json.usage.prompt_tokens ?? 0,
|
|
255
|
+
completionTokens: json.usage.completion_tokens ?? 0,
|
|
256
|
+
}
|
|
257
|
+
: undefined;
|
|
258
|
+
return text
|
|
259
|
+
? { ok: true, text, usage }
|
|
260
|
+
: { ok: false, error: "provider returned empty content" };
|
|
261
|
+
});
|
|
216
262
|
}
|
|
217
263
|
catch (err) {
|
|
218
|
-
return {
|
|
264
|
+
return {
|
|
265
|
+
ok: false,
|
|
266
|
+
error: failureResponse
|
|
267
|
+
? `provider HTTP ${failureResponse.status}: ${callError(err, timeoutMs)}`
|
|
268
|
+
: callError(err, timeoutMs),
|
|
269
|
+
...(failureResponse
|
|
270
|
+
? {
|
|
271
|
+
status: failureResponse.status,
|
|
272
|
+
retryAfterMs: retryAfterMs(failureResponse),
|
|
273
|
+
}
|
|
274
|
+
: {}),
|
|
275
|
+
};
|
|
219
276
|
}
|
|
220
277
|
}
|
|
221
278
|
}
|
|
@@ -251,5 +308,19 @@ export function selectProvider(spec, env, fetchFn = fetch) {
|
|
|
251
308
|
if (!resolvedBase) {
|
|
252
309
|
throw new Error("openai-compatible provider needs model.baseUrl");
|
|
253
310
|
}
|
|
254
|
-
return new OpenAiCompatProvider(name, key, resolvedBase, fetchFn);
|
|
311
|
+
return new OpenAiCompatProvider(provider, name, key, resolvedBase, fetchFn);
|
|
312
|
+
}
|
|
313
|
+
// Build a provider for an EXPLICIT route + raw key (no spec, no env) — the
|
|
314
|
+
// decision probe's entry point. Same classes as selectProvider, so a probe
|
|
315
|
+
// exercises byte-identical request shapes to a real cycle.
|
|
316
|
+
export function providerForRoute(route, apiKey, fetchFn = fetch) {
|
|
317
|
+
if (route.provider === "mechanical")
|
|
318
|
+
return new MechanicalProvider(route.model);
|
|
319
|
+
if (route.provider === "anthropic")
|
|
320
|
+
return new AnthropicProvider(route.model, apiKey, fetchFn);
|
|
321
|
+
const resolvedBase = baseUrlFor(route.provider, route.baseUrl ?? undefined);
|
|
322
|
+
if (!resolvedBase) {
|
|
323
|
+
throw new Error("openai-compatible route needs a baseUrl");
|
|
324
|
+
}
|
|
325
|
+
return new OpenAiCompatProvider(route.provider, route.model, apiKey, resolvedBase, fetchFn);
|
|
255
326
|
}
|
package/dist/agent/resolve.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export declare function mergeProseParts(parts: Array<{
|
|
|
14
14
|
text: string;
|
|
15
15
|
}>): string;
|
|
16
16
|
export declare function resolveAgent(inputPath: string): ResolvedAgent;
|
|
17
|
-
export declare const HOSTED_PROSE_MAX_CHARS =
|
|
17
|
+
export declare const HOSTED_PROSE_MAX_CHARS = 12000;
|
|
18
18
|
/** PURE — exported for tests. Mirrors the backend's trim-then-measure. */
|
|
19
19
|
export declare const hostedProseBudget: (mergedProse: string) => {
|
|
20
20
|
used: number;
|
package/dist/agent/resolve.js
CHANGED
|
@@ -36,6 +36,7 @@ const CONFIG_BLOCKS = [
|
|
|
36
36
|
"venues",
|
|
37
37
|
"risk",
|
|
38
38
|
"sizing",
|
|
39
|
+
"capitalSizing",
|
|
39
40
|
"limits",
|
|
40
41
|
"abstention",
|
|
41
42
|
"sync",
|
|
@@ -592,7 +593,26 @@ export function resolveAgent(inputPath) {
|
|
|
592
593
|
// tokens / 413s per cycle. Mirrored here (backend-v2
|
|
593
594
|
// controllers/agentManage.ts sanitizeStrategyProse) so `validate --hosted`
|
|
594
595
|
// can catch it before a user does.
|
|
595
|
-
|
|
596
|
+
//
|
|
597
|
+
// RAISED 8,000 -> 12,000 on 2026-08-21, from measurement rather than feel.
|
|
598
|
+
// 8,000 made the product's core promise impossible: forking a house template
|
|
599
|
+
// starts you at 7,967 (Olivia) / 7,931 (Carl) / 7,839 (Mia), so a user had
|
|
600
|
+
// 33 to 161 characters to write their own rules in. "Fork a template and make
|
|
601
|
+
// it yours" could not be done.
|
|
602
|
+
//
|
|
603
|
+
// The cap was justified by hosted inference cost. Measured over 8,060 LLM
|
|
604
|
+
// cycles in 24h on prod: average input is 9,038 tokens, of which the prose is
|
|
605
|
+
// only 12.6-40.2% (median ~25%) — the OBSERVATION is the other ~75%. Inputs
|
|
606
|
+
// already reached 17,065 tokens on Llama 3.1 8B and 16,437 on Nemotron 49B
|
|
607
|
+
// (measured on the since-retired NIM line; Nemotron 3 successors match), with
|
|
608
|
+
// ZERO rate-limit errors and estimated_cost_usd of 0.0000 (free NIM tier).
|
|
609
|
+
// +4,000 characters is ~+1,000 tokens/cycle (+11%), landing average input near
|
|
610
|
+
// 10,038 — still below what the fleet already handles at peak today.
|
|
611
|
+
//
|
|
612
|
+
// Self-host is deliberately NOT capped (runner.ts/prompt.ts enforce nothing):
|
|
613
|
+
// those agents run on the user's own model key, so their prompt size costs us
|
|
614
|
+
// nothing. This limit exists only where WE pay for the inference.
|
|
615
|
+
export const HOSTED_PROSE_MAX_CHARS = 12000;
|
|
596
616
|
/** PURE — exported for tests. Mirrors the backend's trim-then-measure. */
|
|
597
617
|
export const hostedProseBudget = (mergedProse) => {
|
|
598
618
|
const used = mergedProse.replace(/\r\n/g, "\n").trim().length;
|
package/dist/agent/runner.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CoinRithmClient, ProvenanceReport } from "./client.js";
|
|
2
2
|
import { Provider } from "./providers.js";
|
|
3
3
|
import { AgentSpec, RunState, CycleResult, Decision, ProposedAction, PmMarket, PostedOpportunity, QuoteEvidence } from "./types.js";
|
|
4
|
+
import { type DecisionInputRecord } from "./decisionReceipt.js";
|
|
4
5
|
export interface RunnerDeps {
|
|
5
6
|
client: CoinRithmClient;
|
|
6
7
|
provider: Provider;
|
|
@@ -10,13 +11,15 @@ export interface RunnerDeps {
|
|
|
10
11
|
live: boolean;
|
|
11
12
|
stateFile?: string;
|
|
12
13
|
log?: (line: string) => void;
|
|
14
|
+
/** Optional private storage hook. Its failure never changes cycle execution. */
|
|
15
|
+
onDecisionInputRecord?: (record: DecisionInputRecord) => void;
|
|
13
16
|
}
|
|
14
17
|
export declare function houseAgentForecastEnabled(): boolean;
|
|
15
18
|
export declare function agentOpportunityCaptureEnabled(): boolean;
|
|
16
19
|
export declare function runnerRuntimeKind(): ProvenanceReport["runtimeKind"];
|
|
17
20
|
export declare function buildRunnerProvenance(spec: AgentSpec): ProvenanceReport;
|
|
18
21
|
export declare function sanitizeForecastProbability(raw: unknown): number | undefined;
|
|
19
|
-
export declare function repairFuturesTakeProfit(action: ProposedAction, quote?: QuoteEvidence): {
|
|
22
|
+
export declare function repairFuturesTakeProfit(action: ProposedAction, quote?: QuoteEvidence, capitalMinimumRewardRisk?: number): {
|
|
20
23
|
action: ProposedAction;
|
|
21
24
|
repaired: boolean;
|
|
22
25
|
};
|