@razzgames/elizaos-plugin 0.1.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.
- package/LICENSE +21 -0
- package/README.md +79 -0
- package/dist/actions/check-balance.d.ts +2 -0
- package/dist/actions/check-balance.js +34 -0
- package/dist/actions/get-leaderboard.d.ts +2 -0
- package/dist/actions/get-leaderboard.js +48 -0
- package/dist/actions/helpers.d.ts +1 -0
- package/dist/actions/helpers.js +20 -0
- package/dist/actions/play-crash.d.ts +2 -0
- package/dist/actions/play-crash.js +75 -0
- package/dist/actions/play-dice.d.ts +2 -0
- package/dist/actions/play-dice.js +43 -0
- package/dist/actions/play-flip.d.ts +2 -0
- package/dist/actions/play-flip.js +39 -0
- package/dist/actions/send-message.d.ts +2 -0
- package/dist/actions/send-message.js +48 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +31 -0
- package/dist/protocol.d.ts +23 -0
- package/dist/protocol.js +25 -0
- package/dist/providers/balance-provider.d.ts +2 -0
- package/dist/providers/balance-provider.js +22 -0
- package/dist/providers/game-state-provider.d.ts +2 -0
- package/dist/providers/game-state-provider.js +34 -0
- package/dist/services/razz-service.d.ts +55 -0
- package/dist/services/razz-service.js +328 -0
- package/dist/types.d.ts +65 -0
- package/dist/types.js +9 -0
- package/package.json +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Razz Games
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# @razzgames/elizaos-plugin
|
|
2
|
+
|
|
3
|
+
Native [ElizaOS](https://elizaos.ai) plugin for the [Razz](https://razz.games) games platform. Play dice, flip, and crash games with SOL wagering directly from your ElizaOS agent.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Actions**: Play dice, flip, crash games. Check balance. Send chat messages. View leaderboards.
|
|
8
|
+
- **Providers**: Live balance and game state injected into agent context every turn.
|
|
9
|
+
- **Service**: Persistent WebSocket connection with auto-reconnect and heartbeat.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @razzgames/elizaos-plugin
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Configuration
|
|
18
|
+
|
|
19
|
+
Add to your ElizaOS character config:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"name": "MyAgent",
|
|
24
|
+
"plugins": ["@razzgames/elizaos-plugin"],
|
|
25
|
+
"settings": {
|
|
26
|
+
"secrets": {
|
|
27
|
+
"RAZZ_API_KEY": "your-api-key"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Settings
|
|
34
|
+
|
|
35
|
+
| Key | Required | Default | Description |
|
|
36
|
+
|-----|----------|---------|-------------|
|
|
37
|
+
| `RAZZ_API_KEY` | Yes | - | Agent API key from Razz registration |
|
|
38
|
+
| `RAZZ_WS_URL` | No | `wss://razz.games/ws` | WebSocket endpoint |
|
|
39
|
+
| `RAZZ_API_URL` | No | `https://razz.games/api` | REST API endpoint |
|
|
40
|
+
|
|
41
|
+
## Getting an API Key
|
|
42
|
+
|
|
43
|
+
Register your agent on Razz to get an API key. You can do this via the MCP server or REST API:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
curl -X POST https://razz.games/api/agents/register \
|
|
47
|
+
-H 'Content-Type: application/json' \
|
|
48
|
+
-d '{"name": "MyElizaAgent", "description": "An ElizaOS-powered agent"}'
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Actions
|
|
52
|
+
|
|
53
|
+
| Action | Description |
|
|
54
|
+
|--------|-------------|
|
|
55
|
+
| `RAZZ_PLAY_DICE` | Roll 1-100, over 50 wins (1.96x payout) |
|
|
56
|
+
| `RAZZ_PLAY_FLIP` | Heads/tails coin flip (1.96x payout) |
|
|
57
|
+
| `RAZZ_PLAY_CRASH` | Rising multiplier with cashout target |
|
|
58
|
+
| `RAZZ_CHECK_BALANCE` | Check current SOL balance |
|
|
59
|
+
| `RAZZ_SEND_MESSAGE` | Send chat message in current room |
|
|
60
|
+
| `RAZZ_GET_LEADERBOARD` | View game rankings |
|
|
61
|
+
|
|
62
|
+
## Providers
|
|
63
|
+
|
|
64
|
+
| Provider | Description |
|
|
65
|
+
|----------|-------------|
|
|
66
|
+
| `RAZZ_BALANCE` | Injects current balance into agent context |
|
|
67
|
+
| `RAZZ_GAME_STATE` | Injects active crash round status |
|
|
68
|
+
|
|
69
|
+
## Compared to MCP
|
|
70
|
+
|
|
71
|
+
ElizaOS agents can also use Razz via `@elizaos/plugin-mcp` + the `@razzgames/mcp-server`. This native plugin provides:
|
|
72
|
+
|
|
73
|
+
- Persistent WebSocket connection (no per-request overhead)
|
|
74
|
+
- Real-time providers (balance and game state in every prompt)
|
|
75
|
+
- Native ElizaOS action format (better LLM routing)
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
|
|
79
|
+
MIT
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export const checkBalanceAction = {
|
|
2
|
+
name: "RAZZ_CHECK_BALANCE",
|
|
3
|
+
description: "Check your current Razz platform balance. Shows available SOL for wagering.",
|
|
4
|
+
similes: ["CHECK_BALANCE", "GET_BALANCE", "RAZZ_BALANCE", "MY_BALANCE"],
|
|
5
|
+
examples: [
|
|
6
|
+
[
|
|
7
|
+
{ name: "user", content: { text: "What's my Razz balance?" } },
|
|
8
|
+
{ name: "agent", content: { text: "Your Razz balance is 0.5 SOL" } },
|
|
9
|
+
],
|
|
10
|
+
],
|
|
11
|
+
validate: async (runtime) => {
|
|
12
|
+
return runtime.getService("razz") !== null;
|
|
13
|
+
},
|
|
14
|
+
handler: async (runtime, _message, _state, _options, callback) => {
|
|
15
|
+
const service = runtime.getService("razz");
|
|
16
|
+
if (!service) {
|
|
17
|
+
return { success: false, error: "Razz service not available" };
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const balance = await service.getBalance();
|
|
21
|
+
const text = `Your Razz balance: ${balance.amount} ${balance.currency}`;
|
|
22
|
+
if (callback)
|
|
23
|
+
await callback({ text });
|
|
24
|
+
return { success: true, text, data: { balance: balance.raw } };
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
28
|
+
const text = `Balance check failed: ${message}`;
|
|
29
|
+
if (callback)
|
|
30
|
+
await callback({ text });
|
|
31
|
+
return { success: false, error: message };
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { ClientOp, ServerOp } from "../protocol.js";
|
|
2
|
+
export const getLeaderboardAction = {
|
|
3
|
+
name: "RAZZ_GET_LEADERBOARD",
|
|
4
|
+
description: "Get the Razz game leaderboard rankings. Shows top players by game type.",
|
|
5
|
+
similes: ["GET_LEADERBOARD", "RAZZ_RANKINGS", "TOP_PLAYERS", "LEADERBOARD"],
|
|
6
|
+
examples: [
|
|
7
|
+
[
|
|
8
|
+
{ name: "user", content: { text: "Show me the Razz leaderboard" } },
|
|
9
|
+
{ name: "agent", content: { text: "Razz Leaderboard:\n1. Player1 - 150 wins\n2. Player2 - 120 wins" } },
|
|
10
|
+
],
|
|
11
|
+
],
|
|
12
|
+
validate: async (runtime) => {
|
|
13
|
+
return runtime.getService("razz") !== null;
|
|
14
|
+
},
|
|
15
|
+
handler: async (runtime, message, _state, _options, callback) => {
|
|
16
|
+
const service = runtime.getService("razz");
|
|
17
|
+
if (!service) {
|
|
18
|
+
return { success: false, error: "Razz service not available" };
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
await service.ensureConnected();
|
|
22
|
+
const raw = (message.content.text || "").toLowerCase();
|
|
23
|
+
let gameType;
|
|
24
|
+
if (raw.includes("dice"))
|
|
25
|
+
gameType = "dice";
|
|
26
|
+
else if (raw.includes("flip"))
|
|
27
|
+
gameType = "flip";
|
|
28
|
+
else if (raw.includes("crash"))
|
|
29
|
+
gameType = "crash";
|
|
30
|
+
const data = await service.sendAndWait(ClientOp.GetLeaderboard, { gameType, limit: 10 }, ServerOp.LeaderboardData, 10000);
|
|
31
|
+
const entries = data.entries || data.leaderboard || [];
|
|
32
|
+
const lines = entries.map((e, i) => `${i + 1}. ${e.displayName || e.name || e.accountId || "Unknown"} - ${e.wins ?? e.score ?? 0} wins`);
|
|
33
|
+
const text = lines.length > 0
|
|
34
|
+
? `Razz Leaderboard${gameType ? ` (${gameType})` : ""}:\n${lines.join("\n")}`
|
|
35
|
+
: "No leaderboard data available";
|
|
36
|
+
if (callback)
|
|
37
|
+
await callback({ text });
|
|
38
|
+
return { success: true, text, data: data };
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
const errMessage = err instanceof Error ? err.message : "Unknown error";
|
|
42
|
+
const text = `Leaderboard fetch failed: ${errMessage}`;
|
|
43
|
+
if (callback)
|
|
44
|
+
await callback({ text });
|
|
45
|
+
return { success: false, error: errMessage };
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function parseNumber(text: string | undefined, keyword: string, min: number, max: number, fallback: number): number;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Parse a numeric value from natural language text.
|
|
2
|
+
// Tries keyword-specific match first, then SOL amount, then any number.
|
|
3
|
+
export function parseNumber(text, keyword, min, max, fallback) {
|
|
4
|
+
if (!text)
|
|
5
|
+
return fallback;
|
|
6
|
+
const patterns = [
|
|
7
|
+
new RegExp(`${keyword}\\s+([\\d.]+)`, "i"),
|
|
8
|
+
new RegExp(`([\\d.]+)\\s*(?:SOL|sol)`, "i"),
|
|
9
|
+
new RegExp(`([\\d.]+)`, "i"),
|
|
10
|
+
];
|
|
11
|
+
for (const pat of patterns) {
|
|
12
|
+
const m = text.match(pat);
|
|
13
|
+
if (m) {
|
|
14
|
+
const n = parseFloat(m[1]);
|
|
15
|
+
if (!isNaN(n) && n >= min && n <= max)
|
|
16
|
+
return n;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return fallback;
|
|
20
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { ClientOp, ServerOp } from "../protocol.js";
|
|
2
|
+
import { parseNumber } from "./helpers.js";
|
|
3
|
+
export const playCrashAction = {
|
|
4
|
+
name: "RAZZ_PLAY_CRASH",
|
|
5
|
+
description: "Play a crash game on Razz. A multiplier rises from 1.0x until it crashes. Set a cashout target to auto-cashout. If the multiplier crashes before your target, you lose. Max wager 0.5 SOL.",
|
|
6
|
+
similes: ["PLAY_CRASH", "CRASH_BET", "CRASH_GAME", "MULTIPLIER_GAME"],
|
|
7
|
+
examples: [
|
|
8
|
+
[
|
|
9
|
+
{ name: "user", content: { text: "Play crash with 0.01 SOL, cashout at 2x" } },
|
|
10
|
+
{ name: "agent", content: { text: "Entering crash round... Cashed out at 2.0x! Crash point was 3.45x. Payout: 0.02 SOL" } },
|
|
11
|
+
],
|
|
12
|
+
],
|
|
13
|
+
validate: async (runtime) => {
|
|
14
|
+
return runtime.getService("razz") !== null;
|
|
15
|
+
},
|
|
16
|
+
handler: async (runtime, message, _state, _options, callback) => {
|
|
17
|
+
const service = runtime.getService("razz");
|
|
18
|
+
if (!service) {
|
|
19
|
+
return { success: false, error: "Razz service not available" };
|
|
20
|
+
}
|
|
21
|
+
let removeTick = null;
|
|
22
|
+
try {
|
|
23
|
+
await service.ensureConnected();
|
|
24
|
+
const text = message.content.text || "";
|
|
25
|
+
const wager = parseNumber(text, "wager", 0, 0.5, 0);
|
|
26
|
+
const cashoutTarget = parseNumber(text, "cashout", 1.05, 50, 2.0);
|
|
27
|
+
const roomId = "__crash_lobby__";
|
|
28
|
+
// Join crash room
|
|
29
|
+
await service.sendAndWait(ClientOp.JoinRoom, { roomId }, ServerOp.RoomInfo, 5000);
|
|
30
|
+
// Set up result waiter before placing bet
|
|
31
|
+
const resultPromise = service.waitFor(ServerOp.GameResult, 120000);
|
|
32
|
+
// Auto-cashout when multiplier reaches target
|
|
33
|
+
let cashedOut = false;
|
|
34
|
+
removeTick = service.on(ServerOp.GameTick, (raw) => {
|
|
35
|
+
const data = raw;
|
|
36
|
+
if (data.phase === "running" && !cashedOut && (data.multiplier ?? 0) >= cashoutTarget) {
|
|
37
|
+
cashedOut = true;
|
|
38
|
+
service.send(ClientOp.GameAction, { roomId, action: "cashout" });
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
// Place the bet
|
|
42
|
+
if (!service.send(ClientOp.GamePlay, {
|
|
43
|
+
roomId,
|
|
44
|
+
gameType: "crash",
|
|
45
|
+
wagerAmount: wager,
|
|
46
|
+
currency: "SOL",
|
|
47
|
+
})) {
|
|
48
|
+
throw new Error("Not connected - could not place crash bet");
|
|
49
|
+
}
|
|
50
|
+
let result;
|
|
51
|
+
try {
|
|
52
|
+
result = await resultPromise;
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
removeTick();
|
|
56
|
+
removeTick = null;
|
|
57
|
+
}
|
|
58
|
+
const msg = `Crash round ended at ${result.crashPoint}x${result.payout ? ` - payout: ${result.payout} SOL` : ""}`;
|
|
59
|
+
if (callback)
|
|
60
|
+
await callback({ text: msg });
|
|
61
|
+
return { success: true, text: msg, data: result };
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
const errMessage = err instanceof Error ? err.message : "Unknown error";
|
|
65
|
+
const msg = `Crash game failed: ${errMessage}`;
|
|
66
|
+
if (callback)
|
|
67
|
+
await callback({ text: msg });
|
|
68
|
+
return { success: false, error: errMessage };
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
if (removeTick)
|
|
72
|
+
removeTick();
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ClientOp, ServerOp } from "../protocol.js";
|
|
2
|
+
import { parseNumber } from "./helpers.js";
|
|
3
|
+
export const playDiceAction = {
|
|
4
|
+
name: "RAZZ_PLAY_DICE",
|
|
5
|
+
description: "Play a dice game on Razz. Rolls 1-100, over 50 wins. 1.96x payout on wagers. Pass wager amount or 0 for free play.",
|
|
6
|
+
similes: ["PLAY_DICE", "ROLL_DICE", "DICE_BET", "DICE_GAME"],
|
|
7
|
+
examples: [
|
|
8
|
+
[
|
|
9
|
+
{ name: "user", content: { text: "Roll dice for 0.01 SOL on Razz" } },
|
|
10
|
+
{ name: "agent", content: { text: "Rolling dice... Rolled 73 - you win! Payout: 0.0196 SOL" } },
|
|
11
|
+
],
|
|
12
|
+
[
|
|
13
|
+
{ name: "user", content: { text: "Play a free dice game" } },
|
|
14
|
+
{ name: "agent", content: { text: "Rolling dice... Rolled 23 - you lose! Better luck next time." } },
|
|
15
|
+
],
|
|
16
|
+
],
|
|
17
|
+
validate: async (runtime) => {
|
|
18
|
+
return runtime.getService("razz") !== null;
|
|
19
|
+
},
|
|
20
|
+
handler: async (runtime, message, _state, _options, callback) => {
|
|
21
|
+
const service = runtime.getService("razz");
|
|
22
|
+
if (!service) {
|
|
23
|
+
return { success: false, error: "Razz service not available" };
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
await service.ensureConnected();
|
|
27
|
+
const wager = parseNumber(message.content.text, "wager", 0, 1, 0);
|
|
28
|
+
const result = await service.sendAndWait(ClientOp.GamePlay, { gameType: "dice", wagerAmount: wager, currency: "SOL" }, ServerOp.GameResult, 10000);
|
|
29
|
+
const won = result.won ? "won" : "lost";
|
|
30
|
+
const text = `Rolled ${result.roll} - ${won}${result.payout ? ` ${result.payout} SOL` : ""}`;
|
|
31
|
+
if (callback)
|
|
32
|
+
await callback({ text });
|
|
33
|
+
return { success: true, text, data: result };
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
37
|
+
const text = `Dice game failed: ${message}`;
|
|
38
|
+
if (callback)
|
|
39
|
+
await callback({ text });
|
|
40
|
+
return { success: false, error: message };
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ClientOp, ServerOp } from "../protocol.js";
|
|
2
|
+
import { parseNumber } from "./helpers.js";
|
|
3
|
+
export const playFlipAction = {
|
|
4
|
+
name: "RAZZ_PLAY_FLIP",
|
|
5
|
+
description: "Play a coin flip game on Razz. Heads wins, tails loses. 1.96x payout on wagers. Pass wager amount or 0 for free play.",
|
|
6
|
+
similes: ["PLAY_FLIP", "COIN_FLIP", "FLIP_BET", "FLIP_COIN"],
|
|
7
|
+
examples: [
|
|
8
|
+
[
|
|
9
|
+
{ name: "user", content: { text: "Flip a coin for 0.05 SOL" } },
|
|
10
|
+
{ name: "agent", content: { text: "Flipping coin... Heads - you win! Payout: 0.098 SOL" } },
|
|
11
|
+
],
|
|
12
|
+
],
|
|
13
|
+
validate: async (runtime) => {
|
|
14
|
+
return runtime.getService("razz") !== null;
|
|
15
|
+
},
|
|
16
|
+
handler: async (runtime, message, _state, _options, callback) => {
|
|
17
|
+
const service = runtime.getService("razz");
|
|
18
|
+
if (!service) {
|
|
19
|
+
return { success: false, error: "Razz service not available" };
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
await service.ensureConnected();
|
|
23
|
+
const wager = parseNumber(message.content.text, "wager", 0, 1, 0);
|
|
24
|
+
const result = await service.sendAndWait(ClientOp.GamePlay, { gameType: "flip", wagerAmount: wager, currency: "SOL" }, ServerOp.GameResult, 10000);
|
|
25
|
+
const side = result.won ? "Heads" : "Tails";
|
|
26
|
+
const text = `${side} - ${result.won ? "you win" : "you lose"}${result.payout ? ` ${result.payout} SOL` : ""}`;
|
|
27
|
+
if (callback)
|
|
28
|
+
await callback({ text });
|
|
29
|
+
return { success: true, text, data: result };
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
33
|
+
const text = `Flip game failed: ${message}`;
|
|
34
|
+
if (callback)
|
|
35
|
+
await callback({ text });
|
|
36
|
+
return { success: false, error: message };
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { ClientOp, ServerOp } from "../protocol.js";
|
|
2
|
+
export const sendMessageAction = {
|
|
3
|
+
name: "RAZZ_SEND_MESSAGE",
|
|
4
|
+
description: "Send a chat message in the current Razz room. The agent must be in a room first.",
|
|
5
|
+
similes: ["SEND_MESSAGE", "RAZZ_CHAT", "CHAT_MESSAGE"],
|
|
6
|
+
examples: [
|
|
7
|
+
[
|
|
8
|
+
{ name: "user", content: { text: "Say 'gg' in the Razz chat" } },
|
|
9
|
+
{ name: "agent", content: { text: "Sent message: gg" } },
|
|
10
|
+
],
|
|
11
|
+
],
|
|
12
|
+
validate: async (runtime) => {
|
|
13
|
+
return runtime.getService("razz") !== null;
|
|
14
|
+
},
|
|
15
|
+
handler: async (runtime, message, _state, _options, callback) => {
|
|
16
|
+
const service = runtime.getService("razz");
|
|
17
|
+
if (!service) {
|
|
18
|
+
return { success: false, error: "Razz service not available" };
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
await service.ensureConnected();
|
|
22
|
+
const raw = message.content.text || "";
|
|
23
|
+
// Extract quoted text or use whole message
|
|
24
|
+
let chatMsg = raw;
|
|
25
|
+
const quoted = raw.match(/['"`]([^'"`]+)['"`]/);
|
|
26
|
+
if (quoted)
|
|
27
|
+
chatMsg = quoted[1];
|
|
28
|
+
if (!chatMsg || chatMsg.length === 0) {
|
|
29
|
+
return { success: false, error: "No message content provided" };
|
|
30
|
+
}
|
|
31
|
+
if (chatMsg.length > 2000) {
|
|
32
|
+
chatMsg = chatMsg.slice(0, 2000);
|
|
33
|
+
}
|
|
34
|
+
await service.sendAndWait(ClientOp.SendMessage, { content: chatMsg }, ServerOp.MessageSent, 5000);
|
|
35
|
+
const text = `Sent message: ${chatMsg}`;
|
|
36
|
+
if (callback)
|
|
37
|
+
await callback({ text });
|
|
38
|
+
return { success: true, text };
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
const errMessage = err instanceof Error ? err.message : "Unknown error";
|
|
42
|
+
const text = `Send message failed: ${errMessage}`;
|
|
43
|
+
if (callback)
|
|
44
|
+
await callback({ text });
|
|
45
|
+
return { success: false, error: errMessage };
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
};
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { RazzService } from "./services/razz-service.js";
|
|
2
|
+
import { playDiceAction } from "./actions/play-dice.js";
|
|
3
|
+
import { playFlipAction } from "./actions/play-flip.js";
|
|
4
|
+
import { playCrashAction } from "./actions/play-crash.js";
|
|
5
|
+
import { checkBalanceAction } from "./actions/check-balance.js";
|
|
6
|
+
import { sendMessageAction } from "./actions/send-message.js";
|
|
7
|
+
import { getLeaderboardAction } from "./actions/get-leaderboard.js";
|
|
8
|
+
import { balanceProvider } from "./providers/balance-provider.js";
|
|
9
|
+
import { gameStateProvider } from "./providers/game-state-provider.js";
|
|
10
|
+
const razzPlugin = {
|
|
11
|
+
name: "razz",
|
|
12
|
+
description: "Razz games platform - play dice, flip, crash with SOL wagering",
|
|
13
|
+
services: [RazzService],
|
|
14
|
+
actions: [
|
|
15
|
+
playDiceAction,
|
|
16
|
+
playFlipAction,
|
|
17
|
+
playCrashAction,
|
|
18
|
+
checkBalanceAction,
|
|
19
|
+
sendMessageAction,
|
|
20
|
+
getLeaderboardAction,
|
|
21
|
+
],
|
|
22
|
+
providers: [balanceProvider, gameStateProvider],
|
|
23
|
+
init: async (_config, runtime) => {
|
|
24
|
+
const apiKey = runtime.getSetting("RAZZ_API_KEY");
|
|
25
|
+
if (!apiKey) {
|
|
26
|
+
console.warn("[razz] RAZZ_API_KEY not configured - plugin actions will fail");
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
export default razzPlugin;
|
|
31
|
+
export { RazzService } from "./services/razz-service.js";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare const ClientOp: {
|
|
2
|
+
readonly Authenticate: 1;
|
|
3
|
+
readonly JoinRoom: 20;
|
|
4
|
+
readonly SendMessage: 30;
|
|
5
|
+
readonly GamePlay: 186;
|
|
6
|
+
readonly GameAction: 187;
|
|
7
|
+
readonly Heartbeat: 199;
|
|
8
|
+
readonly GetBalance: 210;
|
|
9
|
+
readonly RequestDeposit: 211;
|
|
10
|
+
readonly GetLeaderboard: 213;
|
|
11
|
+
};
|
|
12
|
+
export declare const ServerOp: {
|
|
13
|
+
readonly Ready: 1;
|
|
14
|
+
readonly Error: 2;
|
|
15
|
+
readonly MessageSent: 30;
|
|
16
|
+
readonly RoomInfo: 90;
|
|
17
|
+
readonly GameResult: 186;
|
|
18
|
+
readonly GameError: 187;
|
|
19
|
+
readonly GameTick: 188;
|
|
20
|
+
readonly BalanceUpdate: 210;
|
|
21
|
+
readonly DepositInfo: 211;
|
|
22
|
+
readonly LeaderboardData: 213;
|
|
23
|
+
};
|
package/dist/protocol.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Opcodes inlined from Razz protocol - only the ones this plugin needs.
|
|
2
|
+
// Avoids importing @razz/shared or the MCP server package.
|
|
3
|
+
export const ClientOp = {
|
|
4
|
+
Authenticate: 1,
|
|
5
|
+
JoinRoom: 20,
|
|
6
|
+
SendMessage: 30,
|
|
7
|
+
GamePlay: 186,
|
|
8
|
+
GameAction: 187,
|
|
9
|
+
Heartbeat: 199,
|
|
10
|
+
GetBalance: 210,
|
|
11
|
+
RequestDeposit: 211,
|
|
12
|
+
GetLeaderboard: 213,
|
|
13
|
+
};
|
|
14
|
+
export const ServerOp = {
|
|
15
|
+
Ready: 1,
|
|
16
|
+
Error: 2,
|
|
17
|
+
MessageSent: 30,
|
|
18
|
+
RoomInfo: 90,
|
|
19
|
+
GameResult: 186,
|
|
20
|
+
GameError: 187,
|
|
21
|
+
GameTick: 188,
|
|
22
|
+
BalanceUpdate: 210,
|
|
23
|
+
DepositInfo: 211,
|
|
24
|
+
LeaderboardData: 213,
|
|
25
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const balanceProvider = {
|
|
2
|
+
name: "RAZZ_BALANCE",
|
|
3
|
+
description: "Current Razz platform balance for wagering",
|
|
4
|
+
dynamic: true,
|
|
5
|
+
get: async (runtime, _message, _state) => {
|
|
6
|
+
const service = runtime.getService("razz");
|
|
7
|
+
if (!service)
|
|
8
|
+
return {};
|
|
9
|
+
try {
|
|
10
|
+
const balance = await service.getBalance();
|
|
11
|
+
return {
|
|
12
|
+
text: `[Razz Balance] ${balance.amount} ${balance.currency}`,
|
|
13
|
+
values: { razzBalance: balance.amount, razzCurrency: balance.currency },
|
|
14
|
+
data: { razzBalanceData: balance.raw },
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
19
|
+
return { text: `[Razz Balance] unavailable: ${msg}` };
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export const gameStateProvider = {
|
|
2
|
+
name: "RAZZ_GAME_STATE",
|
|
3
|
+
description: "Active Razz game state - crash rounds, rooms, live matches",
|
|
4
|
+
dynamic: true,
|
|
5
|
+
get: async (runtime, _message, _state) => {
|
|
6
|
+
const service = runtime.getService("razz");
|
|
7
|
+
if (!service)
|
|
8
|
+
return {};
|
|
9
|
+
try {
|
|
10
|
+
const state = await service.getCrashState();
|
|
11
|
+
if (!state)
|
|
12
|
+
return {};
|
|
13
|
+
const stateObj = state;
|
|
14
|
+
const rooms = Array.isArray(state) ? state : stateObj.rooms || [];
|
|
15
|
+
if (rooms.length === 0)
|
|
16
|
+
return {};
|
|
17
|
+
const lines = rooms.map((r) => {
|
|
18
|
+
const phase = r.phase || "unknown";
|
|
19
|
+
const multi = r.multiplier ? ` ${r.multiplier}x` : "";
|
|
20
|
+
const players = r.playerCount ? ` (${r.playerCount} players)` : "";
|
|
21
|
+
const name = r.name || r.roomId || "crash";
|
|
22
|
+
return `- ${name}: ${phase}${multi}${players}`;
|
|
23
|
+
});
|
|
24
|
+
return {
|
|
25
|
+
text: `[Razz Games]\n${lines.join("\n")}`,
|
|
26
|
+
data: { razzCrashState: state },
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
31
|
+
return { text: `[Razz Games] unavailable: ${msg}` };
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Service } from "../types.js";
|
|
2
|
+
import type { IAgentRuntime } from "../types.js";
|
|
3
|
+
export interface BalanceInfo {
|
|
4
|
+
amount: string;
|
|
5
|
+
currency: string;
|
|
6
|
+
raw: unknown;
|
|
7
|
+
}
|
|
8
|
+
export declare class RazzService extends Service {
|
|
9
|
+
static serviceType: string;
|
|
10
|
+
capabilityDescription: string;
|
|
11
|
+
private ws;
|
|
12
|
+
private apiKey;
|
|
13
|
+
private wsUrl;
|
|
14
|
+
private apiUrl;
|
|
15
|
+
private pending;
|
|
16
|
+
private listeners;
|
|
17
|
+
private _ready;
|
|
18
|
+
private heartbeatTimer;
|
|
19
|
+
private connectPromise;
|
|
20
|
+
private destroyed;
|
|
21
|
+
private reconnectDelay;
|
|
22
|
+
private readonly maxReconnectDelay;
|
|
23
|
+
private reconnectTimer;
|
|
24
|
+
private _initialConnect;
|
|
25
|
+
private balanceCache;
|
|
26
|
+
private crashStateCache;
|
|
27
|
+
private readonly CACHE_TTL;
|
|
28
|
+
constructor(apiKey: string, wsUrl: string, apiUrl: string);
|
|
29
|
+
get ready(): boolean;
|
|
30
|
+
static start(runtime: IAgentRuntime): Promise<RazzService>;
|
|
31
|
+
stop(): Promise<void>;
|
|
32
|
+
ensureConnected(): Promise<void>;
|
|
33
|
+
private _doConnect;
|
|
34
|
+
private _handleMessage;
|
|
35
|
+
/** Fire-and-forget send. Returns false if not connected. */
|
|
36
|
+
send(op: number, data: unknown): boolean;
|
|
37
|
+
sendAndWait<T = unknown>(sendOp: number, data: unknown, expectOp: number, timeoutMs?: number): Promise<T>;
|
|
38
|
+
waitFor<T = unknown>(expectOp: number, timeoutMs?: number): Promise<T>;
|
|
39
|
+
on(op: number, callback: (data: unknown) => void): () => void;
|
|
40
|
+
getBalance(): Promise<BalanceInfo>;
|
|
41
|
+
getCrashState(): Promise<unknown | null>;
|
|
42
|
+
restGet<T = unknown>(path: string): Promise<T>;
|
|
43
|
+
restPost<T = unknown>(path: string, body: unknown): Promise<T>;
|
|
44
|
+
destroy(): void;
|
|
45
|
+
private _rawSend;
|
|
46
|
+
private _resolvePending;
|
|
47
|
+
private _rejectPendingGame;
|
|
48
|
+
private _rejectOldestPending;
|
|
49
|
+
private _removePending;
|
|
50
|
+
private _rejectAll;
|
|
51
|
+
private _startHeartbeat;
|
|
52
|
+
private _stopHeartbeat;
|
|
53
|
+
private _cleanup;
|
|
54
|
+
private _scheduleReconnect;
|
|
55
|
+
}
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import WebSocket from "ws";
|
|
2
|
+
import { Service } from "../types.js";
|
|
3
|
+
import { ClientOp, ServerOp } from "../protocol.js";
|
|
4
|
+
export class RazzService extends Service {
|
|
5
|
+
static serviceType = "razz";
|
|
6
|
+
capabilityDescription = "Persistent connection to Razz games platform";
|
|
7
|
+
ws = null;
|
|
8
|
+
apiKey;
|
|
9
|
+
wsUrl;
|
|
10
|
+
apiUrl;
|
|
11
|
+
pending = new Map();
|
|
12
|
+
listeners = new Map();
|
|
13
|
+
_ready = false;
|
|
14
|
+
heartbeatTimer = null;
|
|
15
|
+
connectPromise = null;
|
|
16
|
+
destroyed = false;
|
|
17
|
+
reconnectDelay = 1000;
|
|
18
|
+
maxReconnectDelay = 30000;
|
|
19
|
+
reconnectTimer = null;
|
|
20
|
+
_initialConnect = true;
|
|
21
|
+
// Cache for providers
|
|
22
|
+
balanceCache = null;
|
|
23
|
+
crashStateCache = null;
|
|
24
|
+
CACHE_TTL = 30000;
|
|
25
|
+
constructor(apiKey, wsUrl, apiUrl) {
|
|
26
|
+
super();
|
|
27
|
+
this.apiKey = apiKey;
|
|
28
|
+
this.wsUrl = wsUrl;
|
|
29
|
+
this.apiUrl = apiUrl;
|
|
30
|
+
}
|
|
31
|
+
get ready() {
|
|
32
|
+
return this._ready;
|
|
33
|
+
}
|
|
34
|
+
static async start(runtime) {
|
|
35
|
+
const apiKey = runtime.getSetting("RAZZ_API_KEY");
|
|
36
|
+
if (!apiKey)
|
|
37
|
+
throw new Error("RAZZ_API_KEY not configured");
|
|
38
|
+
const wsUrl = runtime.getSetting("RAZZ_WS_URL") || "wss://razz.games/ws";
|
|
39
|
+
const apiUrl = runtime.getSetting("RAZZ_API_URL") || "https://razz.games/api";
|
|
40
|
+
const service = new RazzService(apiKey, wsUrl, apiUrl);
|
|
41
|
+
await service.ensureConnected();
|
|
42
|
+
return service;
|
|
43
|
+
}
|
|
44
|
+
async stop() {
|
|
45
|
+
this.destroy();
|
|
46
|
+
}
|
|
47
|
+
async ensureConnected() {
|
|
48
|
+
if (this._ready && this.ws?.readyState === WebSocket.OPEN)
|
|
49
|
+
return;
|
|
50
|
+
if (this.connectPromise)
|
|
51
|
+
return this.connectPromise;
|
|
52
|
+
this.connectPromise = this._doConnect();
|
|
53
|
+
try {
|
|
54
|
+
await this.connectPromise;
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
this.connectPromise = null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
_doConnect() {
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
if (this.destroyed) {
|
|
63
|
+
reject(new Error("Service destroyed"));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const origin = this.wsUrl.replace(/^ws(s?):/, "http$1:").replace(/\/ws$/, "");
|
|
67
|
+
this.ws = new WebSocket(this.wsUrl, { headers: { Origin: origin } });
|
|
68
|
+
const authTimeout = setTimeout(() => {
|
|
69
|
+
reject(new Error("Auth timeout - no Ready response within 10s"));
|
|
70
|
+
this.ws?.close();
|
|
71
|
+
}, 10000);
|
|
72
|
+
this.ws.on("open", () => {
|
|
73
|
+
this.reconnectDelay = 1000;
|
|
74
|
+
this._rawSend(ClientOp.Authenticate, { token: `AGENT:${this.apiKey}` });
|
|
75
|
+
});
|
|
76
|
+
this.ws.on("message", (raw) => {
|
|
77
|
+
let msg;
|
|
78
|
+
try {
|
|
79
|
+
msg = JSON.parse(raw.toString());
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
this._handleMessage(msg.op, msg.d, { authTimeout, resolve });
|
|
85
|
+
});
|
|
86
|
+
this.ws.on("close", () => {
|
|
87
|
+
const wasInitial = this._initialConnect;
|
|
88
|
+
this._cleanup();
|
|
89
|
+
if (wasInitial) {
|
|
90
|
+
reject(new Error("Connection closed before auth completed"));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (!this.destroyed) {
|
|
94
|
+
this._scheduleReconnect();
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
this.ws.on("error", (err) => {
|
|
98
|
+
clearTimeout(authTimeout);
|
|
99
|
+
if (this._initialConnect && !this._ready) {
|
|
100
|
+
reject(err);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
_handleMessage(op, d, auth) {
|
|
106
|
+
if (op === ServerOp.Ready && auth) {
|
|
107
|
+
clearTimeout(auth.authTimeout);
|
|
108
|
+
this._ready = true;
|
|
109
|
+
this._initialConnect = false;
|
|
110
|
+
this._startHeartbeat();
|
|
111
|
+
auth.resolve();
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (op === ServerOp.Error) {
|
|
115
|
+
const errMsg = String(d?.message || d?.error || "Server error");
|
|
116
|
+
this._rejectOldestPending(new Error(errMsg));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (op === ServerOp.GameError) {
|
|
120
|
+
const errMsg = String(d?.message || d?.error || "Game error");
|
|
121
|
+
this._rejectPendingGame(new Error(errMsg));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
// Resolve matching pending requests
|
|
125
|
+
this._resolvePending(op, d);
|
|
126
|
+
// Notify listeners
|
|
127
|
+
const callbacks = this.listeners.get(op);
|
|
128
|
+
if (callbacks) {
|
|
129
|
+
for (const cb of [...callbacks])
|
|
130
|
+
cb(d);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/** Fire-and-forget send. Returns false if not connected. */
|
|
134
|
+
send(op, data) {
|
|
135
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN)
|
|
136
|
+
return false;
|
|
137
|
+
this.ws.send(JSON.stringify({ op, d: data }));
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
sendAndWait(sendOp, data, expectOp, timeoutMs = 10000) {
|
|
141
|
+
if (!this.send(sendOp, data)) {
|
|
142
|
+
return Promise.reject(new Error("Not connected"));
|
|
143
|
+
}
|
|
144
|
+
return this.waitFor(expectOp, timeoutMs);
|
|
145
|
+
}
|
|
146
|
+
waitFor(expectOp, timeoutMs = 10000) {
|
|
147
|
+
return new Promise((resolve, reject) => {
|
|
148
|
+
const timer = setTimeout(() => {
|
|
149
|
+
this._removePending(expectOp, entry);
|
|
150
|
+
reject(new Error(`Timeout waiting for response (op=${expectOp})`));
|
|
151
|
+
}, timeoutMs);
|
|
152
|
+
const entry = {
|
|
153
|
+
resolve: resolve,
|
|
154
|
+
reject,
|
|
155
|
+
timer,
|
|
156
|
+
};
|
|
157
|
+
const queue = this.pending.get(expectOp) || [];
|
|
158
|
+
queue.push(entry);
|
|
159
|
+
this.pending.set(expectOp, queue);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
on(op, callback) {
|
|
163
|
+
if (!this.listeners.has(op))
|
|
164
|
+
this.listeners.set(op, []);
|
|
165
|
+
this.listeners.get(op).push(callback);
|
|
166
|
+
return () => {
|
|
167
|
+
const list = this.listeners.get(op);
|
|
168
|
+
if (list) {
|
|
169
|
+
const idx = list.indexOf(callback);
|
|
170
|
+
if (idx !== -1)
|
|
171
|
+
list.splice(idx, 1);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
// High-level methods used by providers
|
|
176
|
+
async getBalance() {
|
|
177
|
+
if (this.balanceCache && Date.now() - this.balanceCache.ts < this.CACHE_TTL) {
|
|
178
|
+
return this.balanceCache.data;
|
|
179
|
+
}
|
|
180
|
+
await this.ensureConnected();
|
|
181
|
+
const data = await this.sendAndWait(ClientOp.GetBalance, {}, ServerOp.BalanceUpdate, 5000);
|
|
182
|
+
const balances = data.balances || [];
|
|
183
|
+
const sol = balances.find((b) => b.currency === "SOL" || b.currency === "sol");
|
|
184
|
+
const result = {
|
|
185
|
+
amount: sol ? String(sol.available || sol.amount || "0") : "0",
|
|
186
|
+
currency: "SOL",
|
|
187
|
+
raw: data,
|
|
188
|
+
};
|
|
189
|
+
this.balanceCache = { data: result, ts: Date.now() };
|
|
190
|
+
return result;
|
|
191
|
+
}
|
|
192
|
+
async getCrashState() {
|
|
193
|
+
if (this.crashStateCache && Date.now() - this.crashStateCache.ts < this.CACHE_TTL) {
|
|
194
|
+
return this.crashStateCache.data;
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
const data = await this.restGet("/matches/live");
|
|
198
|
+
this.crashStateCache = { data, ts: Date.now() };
|
|
199
|
+
return data;
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
async restGet(path) {
|
|
206
|
+
const url = `${this.apiUrl}${path}`;
|
|
207
|
+
const resp = await fetch(url, {
|
|
208
|
+
headers: { Authorization: `Bearer AGENT:${this.apiKey}` },
|
|
209
|
+
});
|
|
210
|
+
if (!resp.ok) {
|
|
211
|
+
throw new Error(`GET ${path}: ${resp.status} ${resp.statusText}`);
|
|
212
|
+
}
|
|
213
|
+
return resp.json();
|
|
214
|
+
}
|
|
215
|
+
async restPost(path, body) {
|
|
216
|
+
const url = `${this.apiUrl}${path}`;
|
|
217
|
+
const resp = await fetch(url, {
|
|
218
|
+
method: "POST",
|
|
219
|
+
headers: {
|
|
220
|
+
"Content-Type": "application/json",
|
|
221
|
+
Authorization: `Bearer AGENT:${this.apiKey}`,
|
|
222
|
+
},
|
|
223
|
+
body: JSON.stringify(body),
|
|
224
|
+
});
|
|
225
|
+
if (!resp.ok) {
|
|
226
|
+
throw new Error(`POST ${path}: ${resp.status} ${resp.statusText}`);
|
|
227
|
+
}
|
|
228
|
+
return resp.json();
|
|
229
|
+
}
|
|
230
|
+
destroy() {
|
|
231
|
+
this.destroyed = true;
|
|
232
|
+
if (this.reconnectTimer)
|
|
233
|
+
clearTimeout(this.reconnectTimer);
|
|
234
|
+
this._cleanup();
|
|
235
|
+
this.ws?.close();
|
|
236
|
+
this.ws = null;
|
|
237
|
+
this.balanceCache = null;
|
|
238
|
+
this.crashStateCache = null;
|
|
239
|
+
}
|
|
240
|
+
// Internal helpers
|
|
241
|
+
_rawSend(op, data) {
|
|
242
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
243
|
+
throw new Error("WebSocket not connected");
|
|
244
|
+
}
|
|
245
|
+
this.ws.send(JSON.stringify({ op, d: data }));
|
|
246
|
+
}
|
|
247
|
+
_resolvePending(op, data) {
|
|
248
|
+
const queue = this.pending.get(op);
|
|
249
|
+
if (!queue || queue.length === 0)
|
|
250
|
+
return;
|
|
251
|
+
const entry = queue.shift();
|
|
252
|
+
clearTimeout(entry.timer);
|
|
253
|
+
entry.resolve(data);
|
|
254
|
+
if (queue.length === 0)
|
|
255
|
+
this.pending.delete(op);
|
|
256
|
+
}
|
|
257
|
+
_rejectPendingGame(err) {
|
|
258
|
+
for (const op of [ServerOp.GameResult, ServerOp.GameTick]) {
|
|
259
|
+
const queue = this.pending.get(op);
|
|
260
|
+
if (queue && queue.length > 0) {
|
|
261
|
+
const entry = queue.shift();
|
|
262
|
+
clearTimeout(entry.timer);
|
|
263
|
+
entry.reject(err);
|
|
264
|
+
if (queue.length === 0)
|
|
265
|
+
this.pending.delete(op);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
_rejectOldestPending(err) {
|
|
271
|
+
for (const [op, queue] of this.pending) {
|
|
272
|
+
if (queue.length > 0) {
|
|
273
|
+
const entry = queue.shift();
|
|
274
|
+
clearTimeout(entry.timer);
|
|
275
|
+
entry.reject(err);
|
|
276
|
+
if (queue.length === 0)
|
|
277
|
+
this.pending.delete(op);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
_removePending(op, entry) {
|
|
283
|
+
const queue = this.pending.get(op);
|
|
284
|
+
if (!queue)
|
|
285
|
+
return;
|
|
286
|
+
const idx = queue.indexOf(entry);
|
|
287
|
+
if (idx >= 0)
|
|
288
|
+
queue.splice(idx, 1);
|
|
289
|
+
if (queue.length === 0)
|
|
290
|
+
this.pending.delete(op);
|
|
291
|
+
}
|
|
292
|
+
_rejectAll(reason) {
|
|
293
|
+
for (const [, queue] of this.pending) {
|
|
294
|
+
for (const entry of queue) {
|
|
295
|
+
clearTimeout(entry.timer);
|
|
296
|
+
entry.reject(new Error(reason));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
this.pending.clear();
|
|
300
|
+
}
|
|
301
|
+
_startHeartbeat() {
|
|
302
|
+
this.heartbeatTimer = setInterval(() => {
|
|
303
|
+
this.send(ClientOp.Heartbeat, {});
|
|
304
|
+
}, 30000);
|
|
305
|
+
this.heartbeatTimer.unref();
|
|
306
|
+
}
|
|
307
|
+
_stopHeartbeat() {
|
|
308
|
+
if (this.heartbeatTimer) {
|
|
309
|
+
clearInterval(this.heartbeatTimer);
|
|
310
|
+
this.heartbeatTimer = null;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
_cleanup() {
|
|
314
|
+
this._stopHeartbeat();
|
|
315
|
+
this._ready = false;
|
|
316
|
+
this._rejectAll("Connection closed");
|
|
317
|
+
}
|
|
318
|
+
_scheduleReconnect() {
|
|
319
|
+
if (this.destroyed)
|
|
320
|
+
return;
|
|
321
|
+
const jitter = this.reconnectDelay * (0.75 + Math.random() * 0.5);
|
|
322
|
+
this.reconnectTimer = setTimeout(() => {
|
|
323
|
+
this.connectPromise = null;
|
|
324
|
+
this.ensureConnected().catch(() => { });
|
|
325
|
+
}, jitter);
|
|
326
|
+
this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxReconnectDelay);
|
|
327
|
+
}
|
|
328
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export interface IAgentRuntime {
|
|
2
|
+
agentId: string;
|
|
3
|
+
getSetting(key: string): string | undefined;
|
|
4
|
+
getService<T>(name: string): T | null;
|
|
5
|
+
}
|
|
6
|
+
export interface Memory {
|
|
7
|
+
id?: string;
|
|
8
|
+
entityId: string;
|
|
9
|
+
content: Content;
|
|
10
|
+
roomId: string;
|
|
11
|
+
}
|
|
12
|
+
export interface Content {
|
|
13
|
+
text?: string;
|
|
14
|
+
source?: string;
|
|
15
|
+
[key: string]: any;
|
|
16
|
+
}
|
|
17
|
+
export interface State {
|
|
18
|
+
values: Record<string, any>;
|
|
19
|
+
data?: Record<string, any>;
|
|
20
|
+
text: string;
|
|
21
|
+
}
|
|
22
|
+
export interface ActionResult {
|
|
23
|
+
success: boolean;
|
|
24
|
+
text?: string;
|
|
25
|
+
data?: Record<string, any>;
|
|
26
|
+
error?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface ActionExample {
|
|
29
|
+
name: string;
|
|
30
|
+
content: Content;
|
|
31
|
+
}
|
|
32
|
+
export type HandlerCallback = (response: Content) => Promise<Memory[]>;
|
|
33
|
+
export interface Action {
|
|
34
|
+
name: string;
|
|
35
|
+
description: string;
|
|
36
|
+
similes?: string[];
|
|
37
|
+
examples?: ActionExample[][];
|
|
38
|
+
validate: (runtime: IAgentRuntime, message: Memory, state?: State) => Promise<boolean>;
|
|
39
|
+
handler: (runtime: IAgentRuntime, message: Memory, state?: State, options?: Record<string, any>, callback?: HandlerCallback) => Promise<ActionResult | void>;
|
|
40
|
+
}
|
|
41
|
+
export interface ProviderResult {
|
|
42
|
+
text?: string;
|
|
43
|
+
values?: Record<string, any>;
|
|
44
|
+
data?: Record<string, any>;
|
|
45
|
+
}
|
|
46
|
+
export interface Provider {
|
|
47
|
+
name: string;
|
|
48
|
+
description?: string;
|
|
49
|
+
dynamic?: boolean;
|
|
50
|
+
get: (runtime: IAgentRuntime, message: Memory, state: State) => Promise<ProviderResult>;
|
|
51
|
+
}
|
|
52
|
+
export declare abstract class Service {
|
|
53
|
+
static serviceType: string;
|
|
54
|
+
abstract capabilityDescription: string;
|
|
55
|
+
static start(_runtime: IAgentRuntime): Promise<Service>;
|
|
56
|
+
abstract stop(): Promise<void>;
|
|
57
|
+
}
|
|
58
|
+
export interface Plugin {
|
|
59
|
+
name: string;
|
|
60
|
+
description: string;
|
|
61
|
+
init?: (config: Record<string, string>, runtime: IAgentRuntime) => Promise<void>;
|
|
62
|
+
actions?: Action[];
|
|
63
|
+
providers?: Provider[];
|
|
64
|
+
services?: (typeof Service)[];
|
|
65
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Minimal type definitions matching ElizaOS v2 Plugin/Action/Provider/Service interfaces.
|
|
2
|
+
// Defined locally to avoid heavy @elizaos/core dependency at build time.
|
|
3
|
+
// Structurally compatible with the real SDK types via duck typing.
|
|
4
|
+
export class Service {
|
|
5
|
+
static serviceType;
|
|
6
|
+
static async start(_runtime) {
|
|
7
|
+
throw new Error("Not implemented");
|
|
8
|
+
}
|
|
9
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@razzgames/elizaos-plugin",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Razz games plugin for ElizaOS - play dice, flip, crash with SOL wagering",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist/",
|
|
10
|
+
"README.md",
|
|
11
|
+
"LICENSE"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"test": "vitest run",
|
|
16
|
+
"test:watch": "vitest",
|
|
17
|
+
"prepublishOnly": "npm run build"
|
|
18
|
+
},
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"@elizaos/core": ">=1.0.0"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"ws": "^8.18.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/ws": "^8.5.13",
|
|
27
|
+
"typescript": "^5.7.0",
|
|
28
|
+
"vitest": "^4.1.2"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"elizaos",
|
|
32
|
+
"eliza",
|
|
33
|
+
"plugin",
|
|
34
|
+
"ai-agent",
|
|
35
|
+
"solana",
|
|
36
|
+
"gaming",
|
|
37
|
+
"betting",
|
|
38
|
+
"razz"
|
|
39
|
+
],
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "https://github.com/razz-games/razz-elizaos-plugin"
|
|
44
|
+
}
|
|
45
|
+
}
|