@blockrun/clawrouter 0.12.90 → 0.12.92
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/dist/cli.js +13 -9
- package/dist/cli.js.map +1 -1
- package/dist/index.js +13 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/scripts/e2e-multiturn.mjs +156 -0
- package/skills/imagegen/SKILL.md +9 -7
- package/skills/predexon/SKILL.md +45 -38
package/package.json
CHANGED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2E test: multi-turn chat with continue.dev scenario (issue #135)
|
|
3
|
+
*
|
|
4
|
+
* Simulates exactly what continue.dev does:
|
|
5
|
+
* Turn 1 (new chat) → [system, user]
|
|
6
|
+
* Turn 2 (existing chat) → [system, user1, assistant1, user2]
|
|
7
|
+
*
|
|
8
|
+
* Before fix: Turn 2 always failed with "Unexpected error" from the SSE stream
|
|
9
|
+
* because kimi-k2.5 (primary MEDIUM model) returned 400 for missing
|
|
10
|
+
* reasoning_content on the plain text assistant message.
|
|
11
|
+
* After fix: Both turns succeed.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const PROXY = "http://localhost:8402";
|
|
15
|
+
// Use free model so test doesn't need a funded wallet
|
|
16
|
+
const MODEL = "blockrun/eco";
|
|
17
|
+
|
|
18
|
+
function log(msg) {
|
|
19
|
+
console.log(`\x1b[36m[e2e]\x1b[0m ${msg}`);
|
|
20
|
+
}
|
|
21
|
+
function ok(msg) {
|
|
22
|
+
console.log(`\x1b[32m✓\x1b[0m ${msg}`);
|
|
23
|
+
}
|
|
24
|
+
function fail(msg) {
|
|
25
|
+
console.error(`\x1b[31m✗\x1b[0m ${msg}`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Consume an SSE streaming response and return the reconstructed assistant content.
|
|
31
|
+
* Also detects error events so we can fail fast.
|
|
32
|
+
*/
|
|
33
|
+
async function collectSSE(response) {
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
const body = await response.text();
|
|
36
|
+
throw new Error(`HTTP ${response.status}: ${body.slice(0, 200)}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const reader = response.body.getReader();
|
|
40
|
+
const decoder = new TextDecoder();
|
|
41
|
+
let buffer = "";
|
|
42
|
+
let content = "";
|
|
43
|
+
let errorFound = null;
|
|
44
|
+
let done = false;
|
|
45
|
+
|
|
46
|
+
while (!done) {
|
|
47
|
+
const { value, done: streamDone } = await reader.read();
|
|
48
|
+
if (streamDone) break;
|
|
49
|
+
buffer += decoder.decode(value, { stream: true });
|
|
50
|
+
|
|
51
|
+
const lines = buffer.split("\n");
|
|
52
|
+
buffer = lines.pop(); // keep partial line
|
|
53
|
+
|
|
54
|
+
for (const line of lines) {
|
|
55
|
+
if (!line.startsWith("data: ")) continue;
|
|
56
|
+
const raw = line.slice(6).trim();
|
|
57
|
+
if (raw === "[DONE]") { done = true; break; }
|
|
58
|
+
|
|
59
|
+
let chunk;
|
|
60
|
+
try {
|
|
61
|
+
chunk = JSON.parse(raw);
|
|
62
|
+
} catch {
|
|
63
|
+
continue; // SSE comments / heartbeats
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Detect error event
|
|
67
|
+
if (chunk.error) {
|
|
68
|
+
errorFound = chunk.error.message || JSON.stringify(chunk.error);
|
|
69
|
+
done = true;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Accumulate delta content
|
|
74
|
+
const delta = chunk.choices?.[0]?.delta;
|
|
75
|
+
if (delta?.content) content += delta.content;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (errorFound) throw new Error(`SSE error: ${errorFound}`);
|
|
80
|
+
return content;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function chatStream(messages) {
|
|
84
|
+
return fetch(`${PROXY}/v1/chat/completions`, {
|
|
85
|
+
method: "POST",
|
|
86
|
+
headers: { "Content-Type": "application/json" },
|
|
87
|
+
body: JSON.stringify({
|
|
88
|
+
model: MODEL,
|
|
89
|
+
stream: true,
|
|
90
|
+
messages,
|
|
91
|
+
}),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ─── Turn 1: new chat ────────────────────────────────────────────────────────
|
|
96
|
+
log("Turn 1 — new chat: [system, user]");
|
|
97
|
+
|
|
98
|
+
const turn1Messages = [
|
|
99
|
+
{ role: "system", content: "You are a helpful assistant. Keep answers brief." },
|
|
100
|
+
{ role: "user", content: "Say exactly: hello" },
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
let assistantContent;
|
|
104
|
+
try {
|
|
105
|
+
const resp1 = await chatStream(turn1Messages);
|
|
106
|
+
assistantContent = await collectSSE(resp1);
|
|
107
|
+
if (!assistantContent || assistantContent.trim().length === 0) {
|
|
108
|
+
fail("Turn 1 returned empty content");
|
|
109
|
+
}
|
|
110
|
+
ok(`Turn 1 succeeded. Assistant said: "${assistantContent.trim().slice(0, 80)}"`);
|
|
111
|
+
} catch (err) {
|
|
112
|
+
fail(`Turn 1 failed: ${err.message}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ─── Turn 2: existing chat (the scenario that broke) ─────────────────────────
|
|
116
|
+
log("Turn 2 — existing chat: [system, user1, assistant1, user2]");
|
|
117
|
+
|
|
118
|
+
const turn2Messages = [
|
|
119
|
+
...turn1Messages,
|
|
120
|
+
{ role: "assistant", content: assistantContent }, // ← plain text, no reasoning_content
|
|
121
|
+
{ role: "user", content: "Now say exactly: world" },
|
|
122
|
+
];
|
|
123
|
+
|
|
124
|
+
let assistantContent2;
|
|
125
|
+
try {
|
|
126
|
+
const resp2 = await chatStream(turn2Messages);
|
|
127
|
+
assistantContent2 = await collectSSE(resp2);
|
|
128
|
+
if (!assistantContent2 || assistantContent2.trim().length === 0) {
|
|
129
|
+
fail("Turn 2 returned empty content");
|
|
130
|
+
}
|
|
131
|
+
ok(`Turn 2 succeeded. Assistant said: "${assistantContent2.trim().slice(0, 80)}"`);
|
|
132
|
+
} catch (err) {
|
|
133
|
+
fail(`Turn 2 failed (issue #135 regression): ${err.message}`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ─── Turn 3: three-turn to be thorough ───────────────────────────────────────
|
|
137
|
+
log("Turn 3 — three-turn: [system, user1, assistant1, user2, assistant2, user3]");
|
|
138
|
+
|
|
139
|
+
const turn3Messages = [
|
|
140
|
+
...turn2Messages,
|
|
141
|
+
{ role: "assistant", content: assistantContent2 },
|
|
142
|
+
{ role: "user", content: "What did you say in your first reply?" },
|
|
143
|
+
];
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
const resp3 = await chatStream(turn3Messages);
|
|
147
|
+
const assistantContent3 = await collectSSE(resp3);
|
|
148
|
+
if (!assistantContent3 || assistantContent3.trim().length === 0) {
|
|
149
|
+
fail("Turn 3 returned empty content");
|
|
150
|
+
}
|
|
151
|
+
ok(`Turn 3 succeeded. Assistant said: "${assistantContent3.trim().slice(0, 80)}"`);
|
|
152
|
+
} catch (err) {
|
|
153
|
+
fail(`Turn 3 failed: ${err.message}`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
console.log("\n\x1b[32m✓ All 3 turns passed — multi-turn fix verified\x1b[0m");
|
package/skills/imagegen/SKILL.md
CHANGED
|
@@ -36,21 +36,23 @@ Display inline: ``
|
|
|
36
36
|
|
|
37
37
|
### Model Selection
|
|
38
38
|
|
|
39
|
-
| Model
|
|
40
|
-
|
|
41
|
-
| `nano-banana` | `google/nano-banana`
|
|
42
|
-
| `banana-pro`
|
|
43
|
-
| `dall-e-3`
|
|
44
|
-
| `gpt-image`
|
|
45
|
-
| `flux`
|
|
39
|
+
| Model | Full ID | Price | Sizes | Best for |
|
|
40
|
+
| ------------- | --------------------------- | ----- | ------------------------------- | ----------------------------------- |
|
|
41
|
+
| `nano-banana` | `google/nano-banana` | $0.05 | 1024×1024, 1216×832, 1024×1792 | Default — fast, cheap, good quality |
|
|
42
|
+
| `banana-pro` | `google/nano-banana-pro` | $0.10 | up to 4096×4096 | High-res, large format |
|
|
43
|
+
| `dall-e-3` | `openai/dall-e-3` | $0.04 | 1024×1024, 1792×1024, 1024×1792 | Photorealistic, complex scenes |
|
|
44
|
+
| `gpt-image` | `openai/gpt-image-1` | $0.02 | 1024×1024, 1536×1024, 1024×1536 | Budget option |
|
|
45
|
+
| `flux` | `black-forest/flux-1.1-pro` | $0.04 | 1024×1024, 1216×832, 832×1216 | Artistic styles, fewer restrictions |
|
|
46
46
|
|
|
47
47
|
**Choosing a model:**
|
|
48
|
+
|
|
48
49
|
- Default → `nano-banana`
|
|
49
50
|
- "high res" / "large" → `banana-pro`
|
|
50
51
|
- "photorealistic" / "dall-e" → `dall-e-3`
|
|
51
52
|
- "artistic" / flexible content → `flux`
|
|
52
53
|
|
|
53
54
|
**Choosing a size:**
|
|
55
|
+
|
|
54
56
|
- Default: `1024x1024`
|
|
55
57
|
- Portrait: `1024x1792`
|
|
56
58
|
- Landscape: `1792x1024` (dall-e-3) or `1216x832` (nano-banana / flux)
|
package/skills/predexon/SKILL.md
CHANGED
|
@@ -24,6 +24,7 @@ GET `http://localhost:8402/v1/pm/polymarket/events?limit=20`
|
|
|
24
24
|
Common params: `limit`, `offset`, `tag` (e.g. `crypto`, `politics`, `sports`)
|
|
25
25
|
|
|
26
26
|
Response fields in `data`:
|
|
27
|
+
|
|
27
28
|
- `events[].title` — market name
|
|
28
29
|
- `events[].outcomes` — array of `{ name, price }` (price = implied probability 0–1)
|
|
29
30
|
- `events[].volume` — total volume in USD
|
|
@@ -33,6 +34,7 @@ Response fields in `data`:
|
|
|
33
34
|
To search by keyword: `GET /v1/pm/polymarket/markets?search=bitcoin&limit=10`
|
|
34
35
|
|
|
35
36
|
Response fields in `data`:
|
|
37
|
+
|
|
36
38
|
- `markets[].question` — market question
|
|
37
39
|
- `markets[].conditionId`
|
|
38
40
|
- `markets[].outcomes[].price`
|
|
@@ -47,6 +49,7 @@ Find the `conditionId` first (from events/markets above), then:
|
|
|
47
49
|
GET `http://localhost:8402/v1/pm/polymarket/market/{conditionId}/smart-money`
|
|
48
50
|
|
|
49
51
|
Response fields in `data`:
|
|
52
|
+
|
|
50
53
|
- `positions[].wallet` — wallet address
|
|
51
54
|
- `positions[].side` — YES or NO
|
|
52
55
|
- `positions[].size` — position size in USD
|
|
@@ -60,6 +63,7 @@ Response fields in `data`:
|
|
|
60
63
|
GET `http://localhost:8402/v1/pm/polymarket/leaderboard?limit=20`
|
|
61
64
|
|
|
62
65
|
Response fields in `data`:
|
|
66
|
+
|
|
63
67
|
- `wallets[].address`
|
|
64
68
|
- `wallets[].profit` — total realized profit in USD
|
|
65
69
|
- `wallets[].volume`
|
|
@@ -73,6 +77,7 @@ Response fields in `data`:
|
|
|
73
77
|
GET `http://localhost:8402/v1/pm/polymarket/wallet/{walletAddress}`
|
|
74
78
|
|
|
75
79
|
Response fields in `data`:
|
|
80
|
+
|
|
76
81
|
- `profit` — total realized profit
|
|
77
82
|
- `volume` — total traded volume
|
|
78
83
|
- `winRate` — fraction of winning trades (0–1)
|
|
@@ -80,6 +85,7 @@ Response fields in `data`:
|
|
|
80
85
|
- `currentPositions[]` — open positions
|
|
81
86
|
|
|
82
87
|
For P&L over time: GET `/v1/pm/polymarket/wallet/pnl/{walletAddress}`
|
|
88
|
+
|
|
83
89
|
- `data.pnlSeries[]` — `{ date, cumulativePnl }`
|
|
84
90
|
- `data.totalProfit`, `data.totalLoss`
|
|
85
91
|
|
|
@@ -90,6 +96,7 @@ For P&L over time: GET `/v1/pm/polymarket/wallet/pnl/{walletAddress}`
|
|
|
90
96
|
GET `http://localhost:8402/v1/pm/matching-markets?limit=10`
|
|
91
97
|
|
|
92
98
|
Response fields in `data`:
|
|
99
|
+
|
|
93
100
|
- `pairs[].polymarketTitle`
|
|
94
101
|
- `pairs[].kalshiTitle`
|
|
95
102
|
- `pairs[].polymarketPrice` — YES price on Polymarket (0–1)
|
|
@@ -121,44 +128,44 @@ Response fields in `data`:
|
|
|
121
128
|
|
|
122
129
|
All endpoints are GET. Query params go in the URL.
|
|
123
130
|
|
|
124
|
-
| Endpoint
|
|
125
|
-
|
|
126
|
-
| `/v1/pm/polymarket/events`
|
|
127
|
-
| `/v1/pm/polymarket/markets`
|
|
128
|
-
| `/v1/pm/polymarket/crypto-updown`
|
|
129
|
-
| `/v1/pm/polymarket/leaderboard`
|
|
130
|
-
| `/v1/pm/polymarket/leaderboard/market/{conditionId}` | $0.001 | `limit`
|
|
131
|
-
| `/v1/pm/polymarket/market/{conditionId}/top-holders` | $0.001 | `limit`
|
|
132
|
-
| `/v1/pm/polymarket/cohorts/stats`
|
|
133
|
-
| `/v1/pm/polymarket/positions`
|
|
134
|
-
| `/v1/pm/polymarket/trades`
|
|
135
|
-
| `/v1/pm/polymarket/orderbooks`
|
|
136
|
-
| `/v1/pm/polymarket/market-price/{tokenId}`
|
|
137
|
-
| `/v1/pm/polymarket/candlesticks/{conditionId}`
|
|
138
|
-
| `/v1/pm/polymarket/volume-chart/{conditionId}`
|
|
139
|
-
| `/v1/pm/polymarket/wallet/{wallet}`
|
|
140
|
-
| `/v1/pm/polymarket/wallet/{wallet}/markets`
|
|
141
|
-
| `/v1/pm/polymarket/wallet/{wallet}/similar`
|
|
142
|
-
| `/v1/pm/polymarket/wallet/pnl/{wallet}`
|
|
143
|
-
| `/v1/pm/polymarket/wallet/positions/{wallet}`
|
|
144
|
-
| `/v1/pm/polymarket/wallet/volume-chart/{wallet}`
|
|
145
|
-
| `/v1/pm/polymarket/wallets/profiles`
|
|
146
|
-
| `/v1/pm/polymarket/wallets/filter`
|
|
147
|
-
| `/v1/pm/polymarket/market/{conditionId}/smart-money` | $0.005 | `limit`
|
|
148
|
-
| `/v1/pm/polymarket/markets/smart-activity`
|
|
149
|
-
| `/v1/pm/kalshi/markets`
|
|
150
|
-
| `/v1/pm/kalshi/trades`
|
|
151
|
-
| `/v1/pm/kalshi/orderbooks`
|
|
152
|
-
| `/v1/pm/dflow/trades`
|
|
153
|
-
| `/v1/pm/dflow/wallet/positions/{wallet}`
|
|
154
|
-
| `/v1/pm/dflow/wallet/pnl/{wallet}`
|
|
155
|
-
| `/v1/pm/binance/candles/{symbol}`
|
|
156
|
-
| `/v1/pm/binance/ticks/{symbol}`
|
|
157
|
-
| `/v1/pm/matching-markets`
|
|
158
|
-
| `/v1/pm/matching-markets/pairs`
|
|
159
|
-
| `/v1/pm/limitless/orderbooks`
|
|
160
|
-
| `/v1/pm/opinion/orderbooks`
|
|
161
|
-
| `/v1/pm/predictfun/orderbooks`
|
|
131
|
+
| Endpoint | Price | Key params |
|
|
132
|
+
| ---------------------------------------------------- | ------ | --------------------------------------- |
|
|
133
|
+
| `/v1/pm/polymarket/events` | $0.001 | `limit`, `offset`, `tag` |
|
|
134
|
+
| `/v1/pm/polymarket/markets` | $0.001 | `search`, `limit`, `offset` |
|
|
135
|
+
| `/v1/pm/polymarket/crypto-updown` | $0.001 | — |
|
|
136
|
+
| `/v1/pm/polymarket/leaderboard` | $0.001 | `limit`, `offset` |
|
|
137
|
+
| `/v1/pm/polymarket/leaderboard/market/{conditionId}` | $0.001 | `limit` |
|
|
138
|
+
| `/v1/pm/polymarket/market/{conditionId}/top-holders` | $0.001 | `limit` |
|
|
139
|
+
| `/v1/pm/polymarket/cohorts/stats` | $0.001 | — |
|
|
140
|
+
| `/v1/pm/polymarket/positions` | $0.001 | `wallet`, `limit` |
|
|
141
|
+
| `/v1/pm/polymarket/trades` | $0.001 | `wallet`, `limit`, `start_ts`, `end_ts` |
|
|
142
|
+
| `/v1/pm/polymarket/orderbooks` | $0.001 | `tokenId`, `limit` |
|
|
143
|
+
| `/v1/pm/polymarket/market-price/{tokenId}` | $0.001 | `startTs`, `endTs` |
|
|
144
|
+
| `/v1/pm/polymarket/candlesticks/{conditionId}` | $0.001 | `period`, `limit` |
|
|
145
|
+
| `/v1/pm/polymarket/volume-chart/{conditionId}` | $0.001 | — |
|
|
146
|
+
| `/v1/pm/polymarket/wallet/{wallet}` | $0.005 | — |
|
|
147
|
+
| `/v1/pm/polymarket/wallet/{wallet}/markets` | $0.005 | `limit` |
|
|
148
|
+
| `/v1/pm/polymarket/wallet/{wallet}/similar` | $0.005 | — |
|
|
149
|
+
| `/v1/pm/polymarket/wallet/pnl/{wallet}` | $0.005 | — |
|
|
150
|
+
| `/v1/pm/polymarket/wallet/positions/{wallet}` | $0.005 | — |
|
|
151
|
+
| `/v1/pm/polymarket/wallet/volume-chart/{wallet}` | $0.005 | — |
|
|
152
|
+
| `/v1/pm/polymarket/wallets/profiles` | $0.005 | `wallets` (comma-separated) |
|
|
153
|
+
| `/v1/pm/polymarket/wallets/filter` | $0.005 | `conditionId`, `side` |
|
|
154
|
+
| `/v1/pm/polymarket/market/{conditionId}/smart-money` | $0.005 | `limit` |
|
|
155
|
+
| `/v1/pm/polymarket/markets/smart-activity` | $0.005 | `limit` |
|
|
156
|
+
| `/v1/pm/kalshi/markets` | $0.001 | `search`, `limit` |
|
|
157
|
+
| `/v1/pm/kalshi/trades` | $0.001 | `limit` |
|
|
158
|
+
| `/v1/pm/kalshi/orderbooks` | $0.001 | `marketId` |
|
|
159
|
+
| `/v1/pm/dflow/trades` | $0.001 | `wallet`, `limit` |
|
|
160
|
+
| `/v1/pm/dflow/wallet/positions/{wallet}` | $0.005 | — |
|
|
161
|
+
| `/v1/pm/dflow/wallet/pnl/{wallet}` | $0.005 | — |
|
|
162
|
+
| `/v1/pm/binance/candles/{symbol}` | $0.005 | `interval`, `limit` |
|
|
163
|
+
| `/v1/pm/binance/ticks/{symbol}` | $0.005 | `limit` |
|
|
164
|
+
| `/v1/pm/matching-markets` | $0.005 | `limit`, `offset` |
|
|
165
|
+
| `/v1/pm/matching-markets/pairs` | $0.005 | — |
|
|
166
|
+
| `/v1/pm/limitless/orderbooks` | $0.001 | `marketId` |
|
|
167
|
+
| `/v1/pm/opinion/orderbooks` | $0.001 | `marketId` |
|
|
168
|
+
| `/v1/pm/predictfun/orderbooks` | $0.001 | `marketId` |
|
|
162
169
|
|
|
163
170
|
---
|
|
164
171
|
|