@onride/agent 0.0.0-stage → 0.5.1-rc.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 (64) hide show
  1. package/.env.example +31 -0
  2. package/LICENSE +21 -0
  3. package/README.md +174 -2
  4. package/dist/account_balances.js +435 -0
  5. package/dist/account_reads.js +199 -0
  6. package/dist/agent/adapter.js +115 -0
  7. package/dist/agent/analytics.js +174 -0
  8. package/dist/agent/backend.js +237 -0
  9. package/dist/agent/card_hosts.js +9 -0
  10. package/dist/agent/contracts.js +276 -0
  11. package/dist/agent/conversational.js +86 -0
  12. package/dist/agent/core_bridge.js +1155 -0
  13. package/dist/agent/deposits.js +309 -0
  14. package/dist/agent/discovery.js +205 -0
  15. package/dist/agent/domain.js +507 -0
  16. package/dist/agent/firebase.js +34 -0
  17. package/dist/agent/funding.js +230 -0
  18. package/dist/agent/http.js +40 -0
  19. package/dist/agent/human.js +298 -0
  20. package/dist/agent/human_handoffs.js +39 -0
  21. package/dist/agent/ledger.js +137 -0
  22. package/dist/agent/mcp.js +245 -0
  23. package/dist/agent/order_rules.js +61 -0
  24. package/dist/agent/perps_account.js +41 -0
  25. package/dist/agent/prediction.js +607 -0
  26. package/dist/agent/prediction_equity_history.js +910 -0
  27. package/dist/agent/prediction_history.js +336 -0
  28. package/dist/agent/preferences.js +114 -0
  29. package/dist/agent/repository.js +106 -0
  30. package/dist/agent/runner.js +683 -0
  31. package/dist/agent/service.js +1347 -0
  32. package/dist/backend.js +6 -0
  33. package/dist/bridge.js +6 -0
  34. package/dist/cli/clients.js +99 -0
  35. package/dist/cli/commands.js +332 -0
  36. package/dist/cli/onboarding.js +53 -0
  37. package/dist/cli/session.js +224 -0
  38. package/dist/cli/setup.js +256 -0
  39. package/dist/cli.js +16 -0
  40. package/dist/guide.js +92 -0
  41. package/dist/http_security.js +79 -0
  42. package/dist/mcp-cli.js +7 -0
  43. package/dist/oauth.js +1032 -0
  44. package/dist/server.js +171 -0
  45. package/dist/start.js +18 -0
  46. package/dist/version.js +25 -0
  47. package/docs/confirmation-cards.md +118 -0
  48. package/docs/conversation-v050.md +30 -0
  49. package/docs/conversational-v17.md +48 -0
  50. package/docs/deployment.md +58 -0
  51. package/docs/perps-leverage.md +43 -0
  52. package/docs/prediction-history.md +28 -0
  53. package/docs/preferences-funding-v042.md +17 -0
  54. package/docs/ride-agent-migration.md +127 -0
  55. package/docs/v17-completion-checklist.md +31 -0
  56. package/docs/v17-rollout.md +44 -0
  57. package/docs/widget-account-v043.md +17 -0
  58. package/package.json +75 -4
  59. package/public/agent.html +2313 -0
  60. package/public/confirm.html +2373 -0
  61. package/public/fund.html +563 -0
  62. package/skills/ride/SKILL.md +18 -0
  63. package/skills/ride/references/commands.md +47 -0
  64. package/skills/ride/references/workflow.md +47 -0
@@ -0,0 +1,276 @@
1
+ import { z } from "zod";
2
+ // JSON-safe decimals: six-place USDC precision; no floating point accounting.
3
+ export const money = z.union([
4
+ z.string().regex(/^(0|[1-9]\d{0,11})(\.\d{1,6})?$/),
5
+ z
6
+ .number()
7
+ .finite()
8
+ .nonnegative()
9
+ .max(999999999999)
10
+ .refine((v) => /^\d+(\.\d{1,6})?$/.test(String(v))),
11
+ ]);
12
+ export const positiveMoney = money.refine((v) => Number(v) > 0, "Amount must be positive");
13
+ export const id = z.string().regex(/^[a-zA-Z0-9_-]{8,100}$/);
14
+ export const market = z.enum(["perps", "prediction", "both"]);
15
+ export const copySettings = {
16
+ copy_mode: z.enum(["ride_recommends", "fixed", "proportional"]).optional(),
17
+ fixed_margin_usdc: positiveMoney.optional(),
18
+ proportional_multiplier: z.number().positive().max(10).optional(),
19
+ loss_policy: z.enum(["exit", "pause_then_exit"]).optional(),
20
+ emergency_exit_pct: z.number().min(1).max(100).optional(),
21
+ };
22
+ export const preferences = z
23
+ .object({
24
+ budget_usdc: positiveMoney,
25
+ loss_trigger_pct: z.number().min(1).max(100),
26
+ market: market.default("perps"),
27
+ assets: z
28
+ .array(z.string().regex(/^[A-Za-z][A-Za-z0-9:_-]{0,30}$/))
29
+ .max(20)
30
+ .default([]),
31
+ allow_altcoins: z.boolean().default(true),
32
+ portfolio_kind: z.enum(["single", "basket"]).optional(),
33
+ basket_size: z.number().int().min(1).max(8).optional(),
34
+ risk_tolerance: z.enum(["low", "medium", "high"]).optional(),
35
+ asset_category: z.enum(["crypto", "rwa", "any"]).optional(),
36
+ ...copySettings,
37
+ })
38
+ .strict();
39
+ const portfolioInput = z.object({ portfolio_id: id.optional() }).strict();
40
+ // Partial answers must not silently acquire defaults before the user chooses.
41
+ export const preferenceAnswers = z
42
+ .object({
43
+ ...preferences.shape,
44
+ market: market,
45
+ assets: preferences.shape.assets.unwrap(),
46
+ allow_altcoins: z.boolean(),
47
+ })
48
+ .partial()
49
+ .strict();
50
+ export const inputs = {
51
+ get_preferences: z.object({}).strict(),
52
+ prepare_deposit: z
53
+ .object({ deposit_id: id, amount_usdc: positiveMoney })
54
+ .strict(),
55
+ get_deposit_options: z.object({}).strict(),
56
+ get_deposit_address: z
57
+ .object({
58
+ market: z.enum(["perps", "prediction"]),
59
+ asset: z.enum(["USDC", "USDT"]),
60
+ network: z.enum(["Arbitrum", "Base", "Ethereum", "Polygon"]),
61
+ amount_usdc: positiveMoney.optional(),
62
+ })
63
+ .strict(),
64
+ check_deposit_status: z.object({ deposit_id: id }).strict(),
65
+ get_daily_checkin: z
66
+ .object({ timezone: z.string().max(80).optional() })
67
+ .strict(),
68
+ update_portfolio: z
69
+ .object({ portfolio_id: id, loss_trigger_pct: z.number().min(1).max(100) })
70
+ .strict(),
71
+ set_preferences: preferenceAnswers,
72
+ recommend_traders: z
73
+ .object({
74
+ preferences: preferenceAnswers.optional(),
75
+ batch_action: z.enum(["first", "next", "saved"]).optional(),
76
+ batch_index: z.number().int().min(0).max(1).optional(),
77
+ trader_ids: z
78
+ .array(id)
79
+ .min(1)
80
+ .max(8)
81
+ .refine((v) => new Set(v).size === v.length, "Select each trader only once")
82
+ .optional(),
83
+ selection: z.enum(["system"]).optional(),
84
+ })
85
+ .strict(),
86
+ browse_traders: z
87
+ .object({
88
+ sort_by: z.enum(["score", "drawdown", "roi"]).default("score"),
89
+ direction: z.enum(["asc", "desc"]).optional(),
90
+ limit: z.number().int().min(1).max(20).default(5),
91
+ cursor: z.string().max(1024).optional(),
92
+ compare_ids: z.array(id).min(1).max(8).optional(),
93
+ })
94
+ .strict(),
95
+ get_trader_profile: z
96
+ .object({
97
+ trader_id: id,
98
+ market: z.enum(["perps", "prediction"]).optional(),
99
+ })
100
+ .strict(),
101
+ start_copy: z
102
+ .object({
103
+ plan_id: id,
104
+ compatibility_ack: z.string().length(64).optional(),
105
+ })
106
+ .strict(),
107
+ recalculate_plan: z
108
+ .object({
109
+ plan_id: id,
110
+ preferences: preferenceAnswers.optional(),
111
+ allocations: z
112
+ .array(z
113
+ .object({
114
+ trader_id: id,
115
+ amount_usdc: positiveMoney,
116
+ leverage_cap: z.number().int().min(1).max(40).nullable().optional(),
117
+ stop_loss_pct: z.number().min(1).max(100).optional(),
118
+ ...copySettings,
119
+ })
120
+ .strict())
121
+ .min(1)
122
+ .max(8)
123
+ .optional(),
124
+ })
125
+ .strict(),
126
+ get_performance: z
127
+ .object({
128
+ portfolio_id: id.optional(),
129
+ period: z.enum(["daily", "weekly", "inception"]).default("weekly"),
130
+ })
131
+ .strict(),
132
+ diagnose_copy: z
133
+ .object({
134
+ copy_id: id,
135
+ start_at: z.number().int().positive(),
136
+ end_at: z.number().int().positive(),
137
+ })
138
+ .strict()
139
+ .refine((v) => v.end_at > v.start_at && v.end_at - v.start_at <= 31 * 86400_000, "Use a time window of at most 31 days"),
140
+ get_notification_preferences: z.object({}).strict(),
141
+ set_notification_preferences: z
142
+ .object({
143
+ daily_digest: z.boolean(),
144
+ local_time: z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/),
145
+ timezone: z
146
+ .string()
147
+ .max(80)
148
+ .refine((v) => {
149
+ try {
150
+ new Intl.DateTimeFormat("en", { timeZone: v });
151
+ return true;
152
+ }
153
+ catch {
154
+ return false;
155
+ }
156
+ }, "Use an IANA timezone"),
157
+ })
158
+ .strict(),
159
+ get_portfolio: portfolioInput,
160
+ update_copy: z
161
+ .object({
162
+ copy_id: id,
163
+ amount_usdc: positiveMoney.optional(),
164
+ leverage_cap: z.number().int().min(1).max(40).nullable().optional(),
165
+ stop_loss_pct: z.number().min(1).max(100).optional(),
166
+ replacement_trader_id: id.optional(),
167
+ resume_new_trades: z.boolean().optional(),
168
+ ...copySettings,
169
+ })
170
+ .strict()
171
+ .refine((v) => Object.keys(v).length > 1, "Specify a change"),
172
+ stop_copy: z
173
+ .object({
174
+ copy_id: id,
175
+ mode: z.enum(["wind_down", "close_now"]).default("wind_down"),
176
+ })
177
+ .strict(),
178
+ review_portfolio: portfolioInput,
179
+ get_updates: z
180
+ .object({
181
+ cursor: z.string().max(256).optional(),
182
+ limit: z.number().int().min(1).max(100).default(20),
183
+ })
184
+ .strict(),
185
+ close_position: z.object({ position_id: id }).strict(),
186
+ };
187
+ export const mutationTools = new Set([
188
+ "start_copy",
189
+ "update_portfolio",
190
+ "update_copy",
191
+ "stop_copy",
192
+ "close_position",
193
+ ]);
194
+ // State edits are distinct from proposals that can move funds.
195
+ export const stateTools = new Set([
196
+ "prepare_deposit",
197
+ "set_preferences",
198
+ "recalculate_plan",
199
+ "recommend_traders",
200
+ "set_notification_preferences",
201
+ "get_deposit_address",
202
+ "get_daily_checkin",
203
+ ]);
204
+ export const TOOL_NAMES = Object.keys(inputs);
205
+ export const output = z
206
+ .object({
207
+ version: z.literal("3"),
208
+ status: z.enum([
209
+ "ok",
210
+ "requires_preferences",
211
+ "empty",
212
+ "requires_confirmation",
213
+ "queued",
214
+ "executing",
215
+ "partially_filled",
216
+ "settling",
217
+ "completed",
218
+ "failed",
219
+ "uncertain",
220
+ "stale",
221
+ ]),
222
+ data: z.record(z.string(), z.unknown()),
223
+ warnings: z.array(z.string()),
224
+ as_of: z.string(),
225
+ error: z
226
+ .object({ code: z.string(), message: z.string(), retryable: z.boolean() })
227
+ .strict()
228
+ .optional(),
229
+ })
230
+ .strict();
231
+ export class AgentError extends Error {
232
+ code;
233
+ retryable;
234
+ httpStatus;
235
+ constructor(code, message, retryable = false, httpStatus = 409) {
236
+ super(message);
237
+ this.code = code;
238
+ this.retryable = retryable;
239
+ this.httpStatus = httpStatus;
240
+ }
241
+ }
242
+ export function result(data, status = "ok", warnings = []) {
243
+ return {
244
+ version: "3",
245
+ status,
246
+ data,
247
+ warnings,
248
+ as_of: new Date().toISOString(),
249
+ };
250
+ }
251
+ export function failure(error) {
252
+ const e = error instanceof AgentError
253
+ ? error
254
+ : error instanceof z.ZodError
255
+ ? new AgentError("INVALID_INPUT", "Input does not match the Ride tool contract.", false, 400)
256
+ : new AgentError("INTERNAL_ERROR", "Ride could not complete this request.", true, 500);
257
+ return {
258
+ ...result({}, "failed"),
259
+ error: { code: e.code, message: e.message, retryable: e.retryable },
260
+ };
261
+ }
262
+ // No raw upstream objects cross the MCP boundary. Reject accidental addresses
263
+ // anywhere, including explanations and nested arrays, instead of leaking them.
264
+ export function publicResult(value) {
265
+ const checked = output.parse(value);
266
+ const scan = structuredClone(checked);
267
+ const own = scan.data.deposit;
268
+ if (own &&
269
+ /^deposit_[a-f0-9]{32}$/.test(own.id) &&
270
+ ["wallet", "polymarket_bridge"].includes(own.provider) &&
271
+ /^0x[a-fA-F0-9]{40}$/.test(own.address))
272
+ own.address = "owned deposit address";
273
+ if (/0x[a-fA-F0-9]{40}\b/.test(JSON.stringify(scan)))
274
+ throw new AgentError("PRIVATE_DATA", "Response contained a private trading identity.", false, 502);
275
+ return checked;
276
+ }
@@ -0,0 +1,86 @@
1
+ import { AgentError } from "./contracts.js";
2
+ import { D, cash, fingerprint, } from "./domain.js";
3
+ export function validateCopySettings(a) {
4
+ if (a.copy_mode === "fixed" &&
5
+ (!a.fixed_margin_usdc || D(a.fixed_margin_usdc).gt(a.amount_usdc)))
6
+ throw new AgentError("INVALID_COPY_MODE", "Fixed margin must be positive and within the trader allocation.", false, 400);
7
+ if (a.loss_policy === "pause_then_exit" &&
8
+ (a.emergency_exit_pct ?? 70) <= a.stop_loss_pct)
9
+ throw new AgentError("INVALID_LOSS_POLICY", "Emergency exit must be above the pause threshold.", false, 400);
10
+ if (a.trader.market === "prediction" && a.copy_mode === "fixed")
11
+ throw new AgentError("INVALID_COPY_MODE", "Fixed margin is a Perps mode; prediction copies use proportional exposure.", false, 400);
12
+ }
13
+ export function compatibility(plan) {
14
+ const allocations = plan.allocations, total = allocations.reduce((n, a) => n.plus(a.amount_usdc), D(0));
15
+ const pairs = [];
16
+ for (let i = 0; i < allocations.length; i++)
17
+ for (let j = i + 1; j < allocations.length; j++) {
18
+ const a = allocations[i].trader, b = allocations[j].trader;
19
+ const assets = a.assets.filter((x) => b.assets.includes(x));
20
+ const sameDirection = a.direction !== "mixed" && a.direction === b.direction;
21
+ const holdingOverlap = a.median_hold_hours == null || b.median_hold_hours == null
22
+ ? null
23
+ : Math.max(a.median_hold_hours, b.median_hold_hours) /
24
+ Math.max(1, Math.min(a.median_hold_hours, b.median_hold_hours)) <
25
+ 2;
26
+ const flags = [
27
+ assets.length && sameDirection ? "same_asset_and_direction" : null,
28
+ holdingOverlap && a.style === b.style
29
+ ? "similar_holding_and_style"
30
+ : null,
31
+ ].filter(Boolean);
32
+ pairs.push({
33
+ traders: [a.id, b.id],
34
+ shared_assets: assets,
35
+ same_direction: sameDirection,
36
+ similar_holding_period: holdingOverlap,
37
+ flags,
38
+ });
39
+ }
40
+ const warnings = pairs.reduce((n, p) => n + p.flags.length, 0);
41
+ return {
42
+ fingerprint: fingerprint(allocations),
43
+ warnings,
44
+ pairs,
45
+ capital_weights: allocations.map((a) => ({
46
+ trader_id: a.trader.id,
47
+ weight_pct: total.isZero()
48
+ ? null
49
+ : cash(D(a.amount_usdc).div(total).times(100)),
50
+ })),
51
+ portfolio_historical_drawdown_pct: null,
52
+ historical_loss_correlation: null,
53
+ missing_evidence: "Historical overlap of losses is unavailable. Shared assets and current direction do not prove future correlation.",
54
+ };
55
+ }
56
+ export function alternatives(plan, candidates) {
57
+ const current = compatibility(plan).warnings;
58
+ const suggestions = [];
59
+ for (const out of plan.allocations)
60
+ for (const candidate of candidates) {
61
+ if (plan.allocations.some((a) => a.trader.id === candidate.id))
62
+ continue;
63
+ const trial = {
64
+ ...plan,
65
+ allocations: plan.allocations.map((a) => a === out ? { ...a, trader: candidate } : a),
66
+ };
67
+ const improvement = current - compatibility(trial).warnings;
68
+ if (improvement > 0)
69
+ suggestions.push({
70
+ outgoing_trader_id: out.trader.id,
71
+ incoming_trader: candidate,
72
+ fewer_flags: improvement,
73
+ });
74
+ }
75
+ return suggestions
76
+ .sort((a, b) => b.fewer_flags - a.fewer_flags ||
77
+ b.incoming_trader.score - a.incoming_trader.score)
78
+ .slice(0, 4);
79
+ }
80
+ export function basketLimits(p) {
81
+ return p.portfolio_kind === "single"
82
+ ? [1, 1]
83
+ : p.portfolio_kind === "basket"
84
+ ? [2, 8]
85
+ : [3, 6];
86
+ }