@coinrithm/mcp-trading 0.2.0 → 0.3.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.
Files changed (54) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +2 -2
  3. package/dist/agent/act.d.ts +4 -0
  4. package/dist/agent/act.js +58 -9
  5. package/dist/agent/capabilityGuard.d.ts +2 -0
  6. package/dist/agent/capabilityGuard.js +131 -0
  7. package/dist/agent/cli.d.ts +21 -0
  8. package/dist/agent/cli.js +28 -6
  9. package/dist/agent/client.d.ts +107 -0
  10. package/dist/agent/client.js +26 -0
  11. package/dist/agent/decision.d.ts +137 -0
  12. package/dist/agent/decision.js +37 -4
  13. package/dist/agent/decisionValidator.d.ts +16 -0
  14. package/dist/agent/decisionValidator.js +118 -10
  15. package/dist/agent/engine.d.ts +10 -0
  16. package/dist/agent/engine.js +16 -0
  17. package/dist/agent/extract.d.ts +4 -0
  18. package/dist/agent/frontmatter.d.ts +5 -0
  19. package/dist/agent/index.d.ts +2 -0
  20. package/dist/agent/indicators.d.ts +44 -0
  21. package/dist/agent/indicators.js +135 -0
  22. package/dist/agent/manifest.d.ts +15 -0
  23. package/dist/agent/mergeRules.d.ts +11 -0
  24. package/dist/agent/observe.d.ts +7 -0
  25. package/dist/agent/observe.js +142 -8
  26. package/dist/agent/prompt.d.ts +3 -0
  27. package/dist/agent/prompt.js +39 -8
  28. package/dist/agent/providers.d.ts +25 -0
  29. package/dist/agent/providers.js +10 -1
  30. package/dist/agent/resolve.d.ts +11 -0
  31. package/dist/agent/resolve.js +12 -3
  32. package/dist/agent/runEvidence.d.ts +6 -0
  33. package/dist/agent/runner.d.ts +19 -0
  34. package/dist/agent/runner.js +102 -19
  35. package/dist/agent/skill.d.ts +12 -0
  36. package/dist/agent/skill.js +6 -2
  37. package/dist/agent/skillValidator.d.ts +7 -0
  38. package/dist/agent/state.d.ts +7 -0
  39. package/dist/agent/state.js +3 -1
  40. package/dist/agent/strictLint.d.ts +3 -0
  41. package/dist/agent/strictLint.js +2 -1
  42. package/dist/agent/templates.d.ts +14 -0
  43. package/dist/agent/types.d.ts +286 -0
  44. package/dist/agent/types.js +39 -1
  45. package/dist/agent/util.d.ts +13 -0
  46. package/dist/agent/util.js +4 -0
  47. package/dist/agent/version.d.ts +11 -0
  48. package/dist/agent/version.js +1 -1
  49. package/dist/client.d.ts +162 -0
  50. package/dist/http.d.ts +2 -0
  51. package/dist/index.d.ts +2 -0
  52. package/dist/tools.d.ts +3 -0
  53. package/dist/version.d.ts +1 -0
  54. package/package.json +7 -2
package/CHANGELOG.md ADDED
@@ -0,0 +1,32 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@coinrithm/mcp-trading` are documented here. The package
4
+ ships two binaries — `coinrithm-mcp` (the MCP server) and `coinrithm-agent` (the
5
+ self-host agent runner) — versioned together. The CoinRithm **API contract** is
6
+ versioned separately (see `openapi.yaml` `info.version`, currently `1.4.0`).
7
+
8
+ ## 0.3.0
9
+
10
+ - **Agent risk config: coin deny-list (`blocklist`).** `risk.blocklist` lets an
11
+ agent name symbols it must never open, even if they are on the watchlist —
12
+ deny wins over allow. Enforced in the runner's decision validator (rejects
13
+ `futures_open` / `spot_order` on a denied symbol) and surfaced in the system
14
+ prompt, so the model is told the boundary and the runner re-checks it.
15
+ - **Docs: Open Knowledge Format positioning.** Clarified that a CoinRithm agent
16
+ is an OKF bundle — a portable directory of markdown + frontmatter that is
17
+ model-agnostic (run the same definition on any model). Develop and prove it
18
+ free on paper, then run it anywhere.
19
+ - npm keywords refreshed (`open-knowledge-format`, `okf`, `model-agnostic`,
20
+ `gemini`) for registry discovery.
21
+
22
+ ## 0.2.0
23
+
24
+ - Added the **`coinrithm-agent`** self-host runner binary alongside the MCP
25
+ server: a folder-as-architecture (OKF) agent you bring your own model key to,
26
+ with caps enforced by the runner (not the model), dry-run by default,
27
+ paper-only.
28
+
29
+ ## 0.1.x
30
+
31
+ - Initial `coinrithm-mcp` MCP server: reads, quotes, scoped spot/futures/PM
32
+ writes, ledger export, and Agent Arena integration over a user-minted API key.
package/README.md CHANGED
@@ -6,7 +6,7 @@ CoinRithm paper-trading toolkit. Two binaries:
6
6
  CoinRithm (spot, futures, prediction markets) using a personal API key.
7
7
  - **`coinrithm-agent`** — a self-host **agent runner**: author an agent as a
8
8
  folder and run an observe→decide→validate→act loop with your own model key
9
- (paper, futures v1). See [Agent runner](#agent-runner-coinrithm-agent) below.
9
+ (paper: spot, futures, prediction markets). See [Agent runner](#agent-runner-coinrithm-agent) below.
10
10
 
11
11
  > **Paper trading only** — virtual funds (50,000 mUSD). Not financial advice.
12
12
 
@@ -23,7 +23,7 @@ This package also ships a **self-host agent runner**. You write an agent as a
23
23
  folder (strategy + hard caps in markdown/YAML); the runner compiles it and runs
24
24
  an `observe → decide → validate → act` loop, asking *your* model (bring-your-own
25
25
  key) for structured decisions and executing only the ones that pass your caps —
26
- **dry-run by default**, paper-only, futures in v1.
26
+ **dry-run by default**, paper-only across spot, futures, and prediction markets.
27
27
 
28
28
  ```bash
29
29
  coinrithm-agent new my-agent --preset conservative
@@ -0,0 +1,4 @@
1
+ import { CoinRithmClient } from "./client.js";
2
+ import { ProposedAction, AgentTrace, ApiResult, Observation, QuoteEvidence } from "./types.js";
3
+ export declare function fetchQuote(client: CoinRithmClient, action: ProposedAction, observation: Observation, trace?: AgentTrace): Promise<QuoteEvidence | undefined>;
4
+ export declare function executeAction(client: CoinRithmClient, action: ProposedAction, observation: Observation, trace: AgentTrace, idempotencyKey: string): Promise<ApiResult>;
package/dist/agent/act.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // Act phase: fetch the quote evidence for an open (the runner does this, never
2
- // the model) and execute a validated futures action with an idempotency key.
2
+ // the model) and execute a validated action (futures / spot / PM) with an
3
+ // idempotency key.
3
4
  import { asObj, asNum, asStr } from "./extract.js";
4
5
  function coinIdFor(observation, symbol) {
5
6
  return observation.watch.find((w) => w.symbol.toUpperCase() === symbol.toUpperCase())?.coinId ?? undefined;
@@ -11,20 +12,40 @@ function freshnessOf(block) {
11
12
  }
12
13
  // Read-only quote BEFORE any open. Returns ineligible (never throws) on error.
13
14
  export async function fetchQuote(client, action, observation, trace) {
14
- if (action.type !== "futures_open")
15
- return undefined;
16
- const coinId = coinIdFor(observation, action.symbol);
17
- if (!coinId)
18
- return { eligible: false, blockReasons: ["unresolved_symbol"] };
19
- const r = await client.futuresQuote({ coinId, side: action.side, leverage: action.leverage, marginMusd: action.marginMusd }, trace);
15
+ let r;
16
+ if (action.type === "futures_open") {
17
+ const coinId = coinIdFor(observation, action.symbol);
18
+ if (!coinId)
19
+ return { eligible: false, blockReasons: ["unresolved_symbol"] };
20
+ r = await client.futuresQuote({ coinId, side: action.side, leverage: action.leverage, marginMusd: action.marginMusd }, trace);
21
+ }
22
+ else if (action.type === "spot_order") {
23
+ const coinId = coinIdFor(observation, action.symbol);
24
+ if (!coinId)
25
+ return { eligible: false, blockReasons: ["unresolved_symbol"] };
26
+ r = await client.spotQuote({ coinId, side: action.side, quantity: action.quantity }, trace);
27
+ }
28
+ else if (action.type === "pm_open") {
29
+ r = await client.pmQuote({
30
+ source: action.source,
31
+ slug: action.slug,
32
+ outcomeExternalMarketId: action.outcomeExternalMarketId,
33
+ stakeMusd: action.stakeMusd,
34
+ }, trace);
35
+ }
36
+ else {
37
+ return undefined; // close / set-sltp / cancel need no quote
38
+ }
20
39
  if (!r.ok)
21
40
  return { eligible: false, blockReasons: [`quote_http_${r.status}`] };
22
41
  const d = asObj(r.data);
23
42
  return {
24
43
  eligible: d.eligible === true,
25
44
  blockReasons: d.blockReasons,
26
- entryPrice: asNum(d.entryPrice),
27
- liquidationPrice: asNum(d.liquidationPrice),
45
+ entryPrice: asNum(d.entryPrice), // futures
46
+ liquidationPrice: asNum(d.liquidationPrice), // futures
47
+ executionPrice: asNum(d.executionPrice), // spot live fill price
48
+ estimatedCostMusd: asNum(d.estimatedCostMusd), // spot gross notional
28
49
  // Freshness lives in the response's `observation` block (anti-look-ahead).
29
50
  freshness: freshnessOf(asObj(d.observation)),
30
51
  };
@@ -61,5 +82,33 @@ export async function executeAction(client, action, observation, trace, idempote
61
82
  agentTrace: trace,
62
83
  });
63
84
  }
85
+ if (action.type === "spot_order") {
86
+ const coinId = coinIdFor(observation, action.symbol);
87
+ if (!coinId)
88
+ return { ok: false, status: 0, data: { error: "unresolved_symbol" } };
89
+ return client.placeSpotOrder({
90
+ coinId,
91
+ side: action.side,
92
+ orderType: action.orderType,
93
+ quantity: action.quantity,
94
+ limitPrice: action.limitPrice,
95
+ stopPrice: action.stopPrice,
96
+ idempotencyKey,
97
+ agentTrace: trace,
98
+ });
99
+ }
100
+ if (action.type === "spot_cancel") {
101
+ return client.cancelSpotOrder(action.orderId, trace);
102
+ }
103
+ if (action.type === "pm_open") {
104
+ return client.openPmPosition({
105
+ source: action.source,
106
+ slug: action.slug,
107
+ outcomeExternalMarketId: action.outcomeExternalMarketId,
108
+ stakeMusd: action.stakeMusd,
109
+ idempotencyKey,
110
+ agentTrace: trace,
111
+ });
112
+ }
64
113
  return { ok: false, status: 0, data: { error: "unsupported_action" } };
65
114
  }
@@ -0,0 +1,2 @@
1
+ import { AgentSpec, ResolvedAgent, ResolveIssue } from "./types.js";
2
+ export declare function checkCapabilityDrift(resolved: ResolvedAgent, spec: AgentSpec): ResolveIssue[];
@@ -0,0 +1,131 @@
1
+ // Capability drift guard.
2
+ //
3
+ // An agent's PROSE (thesis, persona, tactic skills) is authored separately from
4
+ // the runner's code, so a skill can name a venue, an action, or a cap that the
5
+ // runner does NOT support — and rot silently, exactly the way a stale doc drifts
6
+ // from a live enum. (We watched this happen in a sibling system: a knowledge file
7
+ // kept naming post types the live enum had dropped, and a test pinned the stale
8
+ // names so the suite stayed green while the model was fed a wrong taxonomy.)
9
+ //
10
+ // This guard asserts that every capability IDENTIFIER referenced in authored
11
+ // prose is in the runner's LIVE capability set AND — for actions/venues — in THIS
12
+ // agent's enabled venues. It is deliberately deterministic and low-false-positive:
13
+ // it inspects only identifier-shaped tokens (snake_case `<venue>_<verb>` actions
14
+ // scanned anywhere; cap/venue identifiers scanned only inside markdown code
15
+ // spans), never free English. Runtime memory (journal/notes.md) is excluded — it
16
+ // records history, and may legitimately recall a venue later disabled.
17
+ import { VENUES, ACTION_TYPES, } from "./types.js";
18
+ import { levenshtein } from "./strictLint.js";
19
+ import { RISK_CAPS, LIMIT_CAPS } from "./mergeRules.js";
20
+ const KNOWN_ACTIONS = new Set(ACTION_TYPES);
21
+ // Every enforced cap the runner knows: risk + limits + killSwitch + abstention.
22
+ const LIVE_CAPS = [
23
+ ...Object.keys(RISK_CAPS),
24
+ ...Object.keys(LIMIT_CAPS),
25
+ "maxDrawdownMusd",
26
+ "maxConsecutiveRejects",
27
+ "maxConsecutiveModelFailures",
28
+ "onRateLimitPressure",
29
+ "onStaleData",
30
+ "onWeakSignal",
31
+ "onMissingQuote",
32
+ "onInsufficientBalance",
33
+ "minConfidence",
34
+ ];
35
+ const LIVE_CAP_SET = new Set(LIVE_CAPS);
36
+ // `<venue>_<verb>` action references. The verb is restricted to the runner's
37
+ // real verbs, so descriptive tokens (spot_price, futures_curve) never match —
38
+ // only action-shaped references (real OR plausibly-fake) do.
39
+ const ACTION_RE = /\b(spot|futures|pm)_(open|close|order|cancel|set_sltp)\b/g;
40
+ // camelCase cap-shaped identifiers (maxLeverage, minConfidence, requireStopLoss…).
41
+ const CAP_RE = /\b(?:max|min|require|on|per)[A-Z][A-Za-z0-9]+\b/g;
42
+ // markdown inline code spans — where authors put identifiers.
43
+ const CODESPAN_RE = /`([^`\n]+)`/g;
44
+ // Levenshtein threshold for "this is a typo/rename of a real cap" — matches the
45
+ // strictLint "did you mean" threshold (< 3, i.e. edit distance <= 2).
46
+ const CAP_NEARMISS_MAX = 3;
47
+ function venueOf(action) {
48
+ return action.split("_")[0];
49
+ }
50
+ // Authored knowledge only: keystone + character/* + skills/*. journal/notes.md is
51
+ // runtime memory and is intentionally NOT checked.
52
+ function authoredProse(resolved) {
53
+ return resolved.proseParts.filter((p) => p.source !== "journal/notes.md");
54
+ }
55
+ // Returns one issue per (token, source) drift found. Empty = no drift.
56
+ export function checkCapabilityDrift(resolved, spec) {
57
+ const issues = [];
58
+ const enabled = new Set(spec.venues);
59
+ const seen = new Set(); // dedupe "kind|token|source"
60
+ const once = (key) => {
61
+ if (seen.has(key))
62
+ return false;
63
+ seen.add(key);
64
+ return true;
65
+ };
66
+ for (const part of authoredProse(resolved)) {
67
+ const where = part.source;
68
+ // ── action references (anywhere in the body) ──
69
+ for (const m of part.text.matchAll(ACTION_RE)) {
70
+ const action = m[0];
71
+ if (!once(`a|${action}|${where}`))
72
+ continue;
73
+ if (!KNOWN_ACTIONS.has(action)) {
74
+ issues.push({
75
+ code: "drift_unknown_action",
76
+ path: where,
77
+ message: `prose references action \`${action}\`, which the runner does not support (allowed: ${ACTION_TYPES.join(", ")})`,
78
+ });
79
+ }
80
+ else {
81
+ const venue = venueOf(action);
82
+ if (!enabled.has(venue)) {
83
+ issues.push({
84
+ code: "drift_venue_not_enabled",
85
+ path: where,
86
+ message: `prose references \`${action}\` but venue \`${venue}\` is not in this agent's venues [${spec.venues.join(", ")}]`,
87
+ });
88
+ }
89
+ }
90
+ }
91
+ // ── venue + cap references inside code spans only ──
92
+ for (const span of part.text.matchAll(CODESPAN_RE)) {
93
+ const inner = span[1];
94
+ // An exact venue identifier in a code span (e.g. `pm`) that the agent has
95
+ // not enabled — a prose promise the runner cannot keep.
96
+ if (VENUES.includes(inner) &&
97
+ !enabled.has(inner) &&
98
+ once(`v|${inner}|${where}`)) {
99
+ issues.push({
100
+ code: "drift_venue_not_enabled",
101
+ path: where,
102
+ message: `prose references venue \`${inner}\` but it is not in this agent's venues [${spec.venues.join(", ")}]`,
103
+ });
104
+ }
105
+ // cap-shaped identifiers — flag only a near-miss of a REAL cap (a typo or
106
+ // rename); unrelated identifiers are left alone to avoid false positives.
107
+ for (const cm of inner.matchAll(CAP_RE)) {
108
+ const token = cm[0];
109
+ if (LIVE_CAP_SET.has(token))
110
+ continue;
111
+ let nearest = null;
112
+ let best = CAP_NEARMISS_MAX;
113
+ for (const cap of LIVE_CAPS) {
114
+ const d = levenshtein(token.toLowerCase(), cap.toLowerCase());
115
+ if (d < best) {
116
+ best = d;
117
+ nearest = cap;
118
+ }
119
+ }
120
+ if (nearest && once(`c|${token}|${where}`)) {
121
+ issues.push({
122
+ code: "drift_unknown_cap",
123
+ path: where,
124
+ message: `prose references cap \`${token}\`, which is not a runner cap — did you mean \`${nearest}\`?`,
125
+ });
126
+ }
127
+ }
128
+ }
129
+ }
130
+ return issues;
131
+ }
@@ -0,0 +1,21 @@
1
+ import { SkillValidationMode } from "./skillValidator.js";
2
+ export interface CmdResult {
3
+ ok: boolean;
4
+ code: number;
5
+ lines: string[];
6
+ data?: unknown;
7
+ }
8
+ export declare function cmdNew(targetPath: string, opts?: {
9
+ template?: string;
10
+ preset?: string;
11
+ }): CmdResult;
12
+ export declare function cmdValidate(path: string, mode?: SkillValidationMode): CmdResult;
13
+ export declare function cmdLock(path: string): CmdResult;
14
+ export declare function cmdEject(path: string): CmdResult;
15
+ export declare function cmdInspect(path: string, json?: boolean): CmdResult;
16
+ export declare function cmdRun(path: string, opts?: {
17
+ once?: boolean;
18
+ live?: boolean;
19
+ stateFile?: string;
20
+ }): Promise<CmdResult>;
21
+ export declare function main(argv: string[]): Promise<number>;
package/dist/agent/cli.js CHANGED
@@ -7,15 +7,16 @@
7
7
  import { mkdirSync, writeFileSync, existsSync, statSync, readFileSync, openSync, closeSync, unlinkSync, } from "node:fs";
8
8
  import { resolve as resolvePath, dirname, join, basename } from "node:path";
9
9
  import { parse as parseYaml } from "yaml";
10
- import { resolveAgent, ResolveError } from "./resolve.js";
10
+ import { resolveAgent, ResolveError, mergeProseParts, isSkillProseSource } from "./resolve.js";
11
11
  import { buildSpec, loadAgent } from "./skill.js";
12
12
  import { validateSkill } from "./skillValidator.js";
13
13
  import { strictLint } from "./strictLint.js";
14
+ import { checkCapabilityDrift } from "./capabilityGuard.js";
14
15
  import { buildManifest, writeManifest } from "./manifest.js";
15
16
  import { parseFrontmatter } from "./frontmatter.js";
16
17
  import { renderFolderOfOne, ejectFiles, PRESET_NAMES } from "./templates.js";
17
18
  import { COINRITHM_API } from "./version.js";
18
- import { stableStringify } from "./util.js";
19
+ import { stableStringify, envFlag } from "./util.js";
19
20
  import { CoinRithmClient } from "./client.js";
20
21
  import { selectProvider } from "./providers.js";
21
22
  import { runLoop } from "./runner.js";
@@ -91,8 +92,8 @@ export function cmdValidate(path, mode = "self-host") {
91
92
  throw e;
92
93
  }
93
94
  const raw = resolved.rawFrontmatter;
94
- const lint = strictLint(raw);
95
95
  const spec = buildSpec(raw);
96
+ const lint = [...strictLint(raw), ...checkCapabilityDrift(resolved, spec)];
96
97
  const v = validateSkill({ spec, body: resolved.mergedProse, raw }, mode);
97
98
  const lintFatal = mode === "hosted";
98
99
  const lines = [];
@@ -114,7 +115,17 @@ export function cmdLock(path) {
114
115
  const spec = buildSpec(resolved.rawFrontmatter);
115
116
  const manifest = buildManifest(resolved, spec);
116
117
  const out = writeManifest(agentDirOf(path), manifest);
117
- return { ok: true, code: 0, lines: [`wrote ${out}`, `configHash ${manifest.configHash}`] };
118
+ // Self-host treats capability drift as advisory (not fatal), but locking past
119
+ // it silently would hide it — surface it so the author isn't surprised when
120
+ // `validate --hosted` later rejects the same folder.
121
+ const drift = (v.data?.lint ?? []).filter((i) => i.code.startsWith("drift_"));
122
+ const warn = drift.length
123
+ ? [
124
+ `⚠ locked with ${drift.length} advisory capability-drift note(s) — \`validate --hosted\` would reject these:`,
125
+ ...drift.map((i) => ` [${i.code}] ${i.path ? `${i.path}: ` : ""}${i.message}`),
126
+ ]
127
+ : [];
128
+ return { ok: true, code: 0, lines: [`wrote ${out}`, `configHash ${manifest.configHash}`, ...warn] };
118
129
  }
119
130
  export function cmdEject(path) {
120
131
  const agentDir = agentDirOf(path);
@@ -159,7 +170,7 @@ export function cmdInspect(path, json = false) {
159
170
  throw e;
160
171
  }
161
172
  const spec = buildSpec(resolved.rawFrontmatter);
162
- const lint = strictLint(resolved.rawFrontmatter);
173
+ const lint = [...strictLint(resolved.rawFrontmatter), ...checkCapabilityDrift(resolved, spec)];
163
174
  const v = validateSkill({ spec, body: resolved.mergedProse, raw: resolved.rawFrontmatter }, "self-host");
164
175
  const output = {
165
176
  resolvedConfig: resolved.rawFrontmatter,
@@ -258,11 +269,22 @@ export async function cmdRun(path, opts = {}) {
258
269
  const lines = [
259
270
  `run ${live ? "LIVE (paper trades WILL be placed)" : "DRY-RUN (no writes; set --live or LIVE=1)"} — ${loaded.spec.name}`,
260
271
  ];
272
+ // Skills ablation kill-switch: drop tactic-skill prose from the prompt for
273
+ // token-cost control or A/B testing. Affects ONLY the run-time prompt — the
274
+ // resolver, manifest, and caps are untouched (the spec is still enforced).
275
+ const disableSkills = envFlag(process.env.COINRITHM_AGENT_DISABLE_SKILLS);
276
+ const mergedProse = disableSkills
277
+ ? mergeProseParts(loaded.resolved.proseParts.filter((p) => !isSkillProseSource(p.source)))
278
+ : loaded.body;
279
+ if (disableSkills) {
280
+ const dropped = loaded.resolved.proseParts.filter((p) => isSkillProseSource(p.source)).length;
281
+ lines.push(`skills DISABLED via COINRITHM_AGENT_DISABLE_SKILLS — ${dropped} tactic skill(s) dropped from the prompt (caps unchanged)`);
282
+ }
261
283
  const deps = {
262
284
  client,
263
285
  provider,
264
286
  spec: loaded.spec,
265
- mergedProse: loaded.body,
287
+ mergedProse,
266
288
  state,
267
289
  live,
268
290
  stateFile,
@@ -0,0 +1,107 @@
1
+ import { AgentTrace, ApiResult } from "./types.js";
2
+ export declare const DEFAULT_BASE_URL = "https://api.coinrithm.com";
3
+ export interface ClientConfig {
4
+ apiKey: string;
5
+ baseUrl?: string;
6
+ fetchFn?: typeof fetch;
7
+ sleepFn?: (ms: number) => Promise<void>;
8
+ maxRetries?: number;
9
+ }
10
+ export declare class CoinRithmClient {
11
+ private readonly apiKey;
12
+ private readonly baseUrl;
13
+ private readonly fetchFn;
14
+ private readonly sleepFn;
15
+ private readonly maxRetries;
16
+ rateLimitHits: number;
17
+ constructor(cfg: ClientConfig);
18
+ private request;
19
+ me(trace?: AgentTrace): Promise<ApiResult>;
20
+ portfolio(trace?: AgentTrace): Promise<ApiResult>;
21
+ wallet(query?: {
22
+ coinId?: string;
23
+ }, trace?: AgentTrace): Promise<ApiResult>;
24
+ resolve(q: string, trace?: AgentTrace): Promise<ApiResult>;
25
+ market(coinId: string, trace?: AgentTrace): Promise<ApiResult>;
26
+ candles(coinId: string, range: string, trace?: AgentTrace): Promise<ApiResult>;
27
+ trades(query?: {
28
+ venue?: string;
29
+ limit?: number;
30
+ updatedSince?: string;
31
+ }, trace?: AgentTrace): Promise<ApiResult>;
32
+ futuresPositions(query?: {
33
+ updatedSince?: string;
34
+ }, trace?: AgentTrace): Promise<ApiResult>;
35
+ futuresQuote(body: {
36
+ coinId: string;
37
+ side: string;
38
+ leverage: number;
39
+ marginMusd: number;
40
+ }, trace?: AgentTrace): Promise<ApiResult>;
41
+ openOrders(query?: {
42
+ coinId?: string;
43
+ updatedSince?: string;
44
+ }, trace?: AgentTrace): Promise<ApiResult>;
45
+ spotQuote(body: {
46
+ coinId: string;
47
+ side: string;
48
+ quantity: number;
49
+ }, trace?: AgentTrace): Promise<ApiResult>;
50
+ discoverPmMarkets(query?: {
51
+ q?: string;
52
+ source?: string;
53
+ limit?: number;
54
+ }, trace?: AgentTrace): Promise<ApiResult>;
55
+ pmPositions(query?: {
56
+ updatedSince?: string;
57
+ }, trace?: AgentTrace): Promise<ApiResult>;
58
+ pmQuote(body: {
59
+ source: string;
60
+ slug: string;
61
+ outcomeExternalMarketId: string;
62
+ stakeMusd: number;
63
+ }, trace?: AgentTrace): Promise<ApiResult>;
64
+ openFutures(body: {
65
+ coinId: string;
66
+ side: string;
67
+ leverage: number;
68
+ marginMusd: number;
69
+ idempotencyKey: string;
70
+ stopLossPrice?: number | null;
71
+ takeProfitPrice?: number | null;
72
+ agentTrace?: AgentTrace;
73
+ }): Promise<ApiResult>;
74
+ closeFutures(body: {
75
+ positionId: number;
76
+ fraction?: number;
77
+ idempotencyKey: string;
78
+ agentTrace?: AgentTrace;
79
+ }): Promise<ApiResult>;
80
+ setFuturesSlTp(body: {
81
+ positionId: number;
82
+ stopLossPrice?: number | null;
83
+ takeProfitPrice?: number | null;
84
+ agentTrace?: AgentTrace;
85
+ }): Promise<ApiResult>;
86
+ placeSpotOrder(body: {
87
+ coinId: string;
88
+ side: string;
89
+ orderType: string;
90
+ quantity: number;
91
+ limitPrice?: number;
92
+ stopPrice?: number;
93
+ idempotencyKey: string;
94
+ agentTrace?: AgentTrace;
95
+ }): Promise<ApiResult>;
96
+ cancelSpotOrder(orderId: number, trace?: AgentTrace): Promise<ApiResult>;
97
+ openPmPosition(body: {
98
+ source: string;
99
+ slug: string;
100
+ outcomeExternalMarketId: string;
101
+ stakeMusd: number;
102
+ idempotencyKey: string;
103
+ agentTrace?: AgentTrace;
104
+ }): Promise<ApiResult>;
105
+ exportRunEvidence(runId: string): Promise<ApiResult>;
106
+ }
107
+ export declare function isFailClosed(status: number): boolean;
@@ -125,6 +125,23 @@ export class CoinRithmClient {
125
125
  futuresQuote(body, trace) {
126
126
  return this.request("POST", "/api/agent/futures/quote", { body: { ...body, agentTrace: trace } });
127
127
  }
128
+ // ── spot ─────────────────────────────────────────────────────────────────
129
+ openOrders(query, trace) {
130
+ return this.request("GET", "/api/agent/orders/open", { query, trace });
131
+ }
132
+ spotQuote(body, trace) {
133
+ return this.request("POST", "/api/agent/spot/quote", { body: { ...body, agentTrace: trace } });
134
+ }
135
+ // ── prediction markets ───────────────────────────────────────────────────
136
+ discoverPmMarkets(query, trace) {
137
+ return this.request("GET", "/api/agent/pm/discover", { query, trace });
138
+ }
139
+ pmPositions(query, trace) {
140
+ return this.request("GET", "/api/agent/positions/pm", { query, trace });
141
+ }
142
+ pmQuote(body, trace) {
143
+ return this.request("POST", "/api/agent/pm/quote", { body: { ...body, agentTrace: trace } });
144
+ }
128
145
  // ── writes ─────────────────────────────────────────────────────────────────
129
146
  openFutures(body) {
130
147
  return this.request("POST", "/api/agent/futures/open", { body });
@@ -135,6 +152,15 @@ export class CoinRithmClient {
135
152
  setFuturesSlTp(body) {
136
153
  return this.request("POST", "/api/agent/futures/sl-tp", { body });
137
154
  }
155
+ placeSpotOrder(body) {
156
+ return this.request("POST", "/api/agent/spot/order", { body });
157
+ }
158
+ cancelSpotOrder(orderId, trace) {
159
+ return this.request("POST", `/api/agent/spot/order/${orderId}/cancel`, { trace });
160
+ }
161
+ openPmPosition(body) {
162
+ return this.request("POST", "/api/agent/pm/open", { body });
163
+ }
138
164
  // Run-evidence export — runId is URL-encoded into the query.
139
165
  exportRunEvidence(runId) {
140
166
  return this.request("GET", "/api/agent/ledger/export", { query: { runId } });
@@ -0,0 +1,137 @@
1
+ import { z } from "zod";
2
+ import { Decision } from "./types.js";
3
+ export declare const actionSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
4
+ type: z.ZodLiteral<"futures_open">;
5
+ symbol: z.ZodString;
6
+ side: z.ZodEnum<["long", "short"]>;
7
+ leverage: z.ZodNumber;
8
+ marginMusd: z.ZodNumber;
9
+ stopLossPrice: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
10
+ takeProfitPrice: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
11
+ confidence: z.ZodOptional<z.ZodNumber>;
12
+ rationaleSummary: z.ZodOptional<z.ZodString>;
13
+ }, "strict", z.ZodTypeAny, {
14
+ symbol: string;
15
+ side: "long" | "short";
16
+ leverage: number;
17
+ marginMusd: number;
18
+ type: "futures_open";
19
+ stopLossPrice?: number | null | undefined;
20
+ takeProfitPrice?: number | null | undefined;
21
+ confidence?: number | undefined;
22
+ rationaleSummary?: string | undefined;
23
+ }, {
24
+ symbol: string;
25
+ side: "long" | "short";
26
+ leverage: number;
27
+ marginMusd: number;
28
+ type: "futures_open";
29
+ stopLossPrice?: number | null | undefined;
30
+ takeProfitPrice?: number | null | undefined;
31
+ confidence?: number | undefined;
32
+ rationaleSummary?: string | undefined;
33
+ }>, z.ZodObject<{
34
+ type: z.ZodLiteral<"futures_close">;
35
+ positionId: z.ZodNumber;
36
+ fraction: z.ZodOptional<z.ZodNumber>;
37
+ confidence: z.ZodOptional<z.ZodNumber>;
38
+ rationaleSummary: z.ZodOptional<z.ZodString>;
39
+ }, "strict", z.ZodTypeAny, {
40
+ positionId: number;
41
+ type: "futures_close";
42
+ fraction?: number | undefined;
43
+ confidence?: number | undefined;
44
+ rationaleSummary?: string | undefined;
45
+ }, {
46
+ positionId: number;
47
+ type: "futures_close";
48
+ fraction?: number | undefined;
49
+ confidence?: number | undefined;
50
+ rationaleSummary?: string | undefined;
51
+ }>, z.ZodObject<{
52
+ type: z.ZodLiteral<"futures_set_sltp">;
53
+ positionId: z.ZodNumber;
54
+ stopLossPrice: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
55
+ takeProfitPrice: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
56
+ }, "strict", z.ZodTypeAny, {
57
+ positionId: number;
58
+ type: "futures_set_sltp";
59
+ stopLossPrice?: number | null | undefined;
60
+ takeProfitPrice?: number | null | undefined;
61
+ }, {
62
+ positionId: number;
63
+ type: "futures_set_sltp";
64
+ stopLossPrice?: number | null | undefined;
65
+ takeProfitPrice?: number | null | undefined;
66
+ }>, z.ZodObject<{
67
+ type: z.ZodLiteral<"spot_order">;
68
+ symbol: z.ZodString;
69
+ side: z.ZodEnum<["buy", "sell"]>;
70
+ orderType: z.ZodEnum<["market", "limit", "stop"]>;
71
+ quantity: z.ZodNumber;
72
+ limitPrice: z.ZodOptional<z.ZodNumber>;
73
+ stopPrice: z.ZodOptional<z.ZodNumber>;
74
+ confidence: z.ZodOptional<z.ZodNumber>;
75
+ rationaleSummary: z.ZodOptional<z.ZodString>;
76
+ }, "strict", z.ZodTypeAny, {
77
+ symbol: string;
78
+ side: "buy" | "sell";
79
+ orderType: "limit" | "market" | "stop";
80
+ quantity: number;
81
+ type: "spot_order";
82
+ limitPrice?: number | undefined;
83
+ stopPrice?: number | undefined;
84
+ confidence?: number | undefined;
85
+ rationaleSummary?: string | undefined;
86
+ }, {
87
+ symbol: string;
88
+ side: "buy" | "sell";
89
+ orderType: "limit" | "market" | "stop";
90
+ quantity: number;
91
+ type: "spot_order";
92
+ limitPrice?: number | undefined;
93
+ stopPrice?: number | undefined;
94
+ confidence?: number | undefined;
95
+ rationaleSummary?: string | undefined;
96
+ }>, z.ZodObject<{
97
+ type: z.ZodLiteral<"spot_cancel">;
98
+ orderId: z.ZodNumber;
99
+ }, "strict", z.ZodTypeAny, {
100
+ type: "spot_cancel";
101
+ orderId: number;
102
+ }, {
103
+ type: "spot_cancel";
104
+ orderId: number;
105
+ }>, z.ZodObject<{
106
+ type: z.ZodLiteral<"pm_open">;
107
+ source: z.ZodString;
108
+ slug: z.ZodString;
109
+ outcomeExternalMarketId: z.ZodString;
110
+ stakeMusd: z.ZodNumber;
111
+ confidence: z.ZodOptional<z.ZodNumber>;
112
+ rationaleSummary: z.ZodOptional<z.ZodString>;
113
+ }, "strict", z.ZodTypeAny, {
114
+ source: string;
115
+ slug: string;
116
+ outcomeExternalMarketId: string;
117
+ stakeMusd: number;
118
+ type: "pm_open";
119
+ confidence?: number | undefined;
120
+ rationaleSummary?: string | undefined;
121
+ }, {
122
+ source: string;
123
+ slug: string;
124
+ outcomeExternalMarketId: string;
125
+ stakeMusd: number;
126
+ type: "pm_open";
127
+ confidence?: number | undefined;
128
+ rationaleSummary?: string | undefined;
129
+ }>]>;
130
+ export type ParseDecisionResult = {
131
+ ok: true;
132
+ decision: Decision;
133
+ } | {
134
+ ok: false;
135
+ error: string;
136
+ };
137
+ export declare function parseDecision(text: string): ParseDecisionResult;