clawmoney 0.14.8 → 0.15.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/dist/relay/provider.js +60 -117
- package/dist/relay/types.d.ts +0 -1
- package/package.json +1 -1
package/dist/relay/provider.js
CHANGED
|
@@ -3,7 +3,6 @@ import { join } from "node:path";
|
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import YAML from "yaml";
|
|
5
5
|
import { RelayWsClient } from "./ws-client.js";
|
|
6
|
-
import { spawnCli, buildCliArgs, parseCliOutput, ensureEmptyMcpConfig, ensureSandboxDir, } from "./executor.js";
|
|
7
6
|
import { callClaudeApi, preflightClaudeApi, getRateGuardSnapshot as getClaudeRateGuardSnapshot, } from "./upstream/claude-api.js";
|
|
8
7
|
import { callCodexApi, preflightCodexApi, getRateGuardSnapshot as getCodexRateGuardSnapshot, } from "./upstream/codex-api.js";
|
|
9
8
|
import { callGeminiApi, preflightGeminiApi, getGeminiRateGuardSnapshot, } from "./upstream/gemini-api.js";
|
|
@@ -32,25 +31,8 @@ import { relayLogger as logger } from "./logger.js";
|
|
|
32
31
|
const CONFIG_DIR = join(homedir(), ".clawmoney");
|
|
33
32
|
const CONFIG_FILE = join(CONFIG_DIR, "config.yaml");
|
|
34
33
|
const PID_FILE = join(CONFIG_DIR, "relay.pid");
|
|
35
|
-
// Default execution mode is `api` as of 0.14.7. The `cli` fallback is still
|
|
36
|
-
// supported — set `relay.execution_mode: cli` in ~/.clawmoney/config.yaml
|
|
37
|
-
// or export CLAWMONEY_RELAY_EXECUTION_MODE=cli at launch — but new
|
|
38
|
-
// providers get the direct-API path by default because:
|
|
39
|
-
// - Every spawnCli() round-trip burns 2-5 seconds of cold start, which
|
|
40
|
-
// is far too much for a request/response relay where buyers expect
|
|
41
|
-
// sub-second handoff.
|
|
42
|
-
// - Each subprocess consumes its own RAM + file handles; API mode runs
|
|
43
|
-
// hundreds of concurrent calls out of one Node process.
|
|
44
|
-
// - The fingerprint gap that used to make CLI mode "safer" is now
|
|
45
|
-
// closed — 0.14.0–0.14.6 ported the real CLI's attribution hash,
|
|
46
|
-
// streaming transport, thinking config, dynamic beta header, session
|
|
47
|
-
// masking, Gemini startup warmup, and Codex per-turn prewarm. API
|
|
48
|
-
// mode now matches real-CLI wire shape on every upstream.
|
|
49
|
-
// CLI mode will be removed entirely in 0.15.0 once we've observed a
|
|
50
|
-
// week of API-mode-default in production.
|
|
51
34
|
const DEFAULT_RELAY = {
|
|
52
35
|
cli_type: "claude",
|
|
53
|
-
execution_mode: "api",
|
|
54
36
|
model: "claude-opus-4-6",
|
|
55
37
|
mode: "chat",
|
|
56
38
|
concurrency: 5,
|
|
@@ -109,13 +91,8 @@ function loadRelayConfig(cliOverride) {
|
|
|
109
91
|
process.exit(1);
|
|
110
92
|
}
|
|
111
93
|
const userRelay = (raw.relay ?? {});
|
|
112
|
-
// Env override lets `CLAWMONEY_RELAY_EXECUTION_MODE=api` flip the mode
|
|
113
|
-
// without editing config.yaml — useful for quick A/B and testing.
|
|
114
|
-
const envMode = process.env.CLAWMONEY_RELAY_EXECUTION_MODE;
|
|
115
|
-
const executionMode = envMode ?? userRelay.execution_mode ?? DEFAULT_RELAY.execution_mode;
|
|
116
94
|
const relay = {
|
|
117
95
|
cli_type: cliOverride ?? userRelay.cli_type ?? DEFAULT_RELAY.cli_type,
|
|
118
|
-
execution_mode: executionMode,
|
|
119
96
|
model: userRelay.model ?? DEFAULT_RELAY.model,
|
|
120
97
|
mode: userRelay.mode ?? DEFAULT_RELAY.mode,
|
|
121
98
|
concurrency: userRelay.concurrency ?? DEFAULT_RELAY.concurrency,
|
|
@@ -166,13 +143,6 @@ async function executeRelayRequest(request, config) {
|
|
|
166
143
|
const model = request.model ?? config.relay.model;
|
|
167
144
|
const stateful = request.stateful ?? false;
|
|
168
145
|
const cliSessionId = request.cli_session_id ?? undefined;
|
|
169
|
-
// api mode is supported for claude / codex / gemini / antigravity; anything
|
|
170
|
-
// else falls back to spawning the local CLI subprocess. Antigravity is
|
|
171
|
-
// api-only (there is no local CLI to spawn) so execution_mode is ignored
|
|
172
|
-
// for it and we always route through the direct upstream handler.
|
|
173
|
-
const useApiMode = (config.relay.execution_mode === "api" &&
|
|
174
|
-
(cliType === "claude" || cliType === "codex" || cliType === "gemini")) ||
|
|
175
|
-
cliType === "antigravity";
|
|
176
146
|
// Build prompt from messages
|
|
177
147
|
const prompt = request.messages
|
|
178
148
|
? messagesToPrompt(request.messages)
|
|
@@ -186,58 +156,44 @@ async function executeRelayRequest(request, config) {
|
|
|
186
156
|
const modeLabel = stateful
|
|
187
157
|
? (cliSessionId ? `stateful[resume ${cliSessionId.slice(0, 8)}]` : "stateful[new]")
|
|
188
158
|
: "stateless";
|
|
189
|
-
const execLabel = useApiMode ? "api" : "cli";
|
|
190
159
|
logger.info(` ┌─ Request ${request_id.slice(0, 8)}`);
|
|
191
|
-
logger.info(` │ CLI: ${cliType} / ${model} (${modeLabel}
|
|
160
|
+
logger.info(` │ CLI: ${cliType} / ${model} (${modeLabel})`);
|
|
192
161
|
logger.info(` │ Turns: ${turns}`);
|
|
193
162
|
logger.info(` │ Prompt: ${String(lastUserMsg).slice(0, 80)}`);
|
|
194
163
|
try {
|
|
195
164
|
const startMs = Date.now();
|
|
196
165
|
let parsed;
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
maxTokens: max_budget_usd ? undefined : 8192,
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
else {
|
|
225
|
-
parsed = await callClaudeApi({
|
|
226
|
-
prompt,
|
|
227
|
-
model,
|
|
228
|
-
maxTokens: max_budget_usd ? undefined : 4096,
|
|
229
|
-
});
|
|
230
|
-
}
|
|
166
|
+
// Direct upstream API call — the right handler is picked by cli_type
|
|
167
|
+
// (claude → Anthropic, codex → chatgpt.com WS, gemini → cloudcode-pa,
|
|
168
|
+
// antigravity → daily-cloudcode-pa). Each handler has its own
|
|
169
|
+
// fingerprint file and rate-guard instance.
|
|
170
|
+
if (cliType === "codex") {
|
|
171
|
+
parsed = await callCodexApi({
|
|
172
|
+
prompt,
|
|
173
|
+
model,
|
|
174
|
+
maxTokens: max_budget_usd ? undefined : 4096,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
else if (cliType === "gemini") {
|
|
178
|
+
parsed = await callGeminiApi({
|
|
179
|
+
prompt,
|
|
180
|
+
model,
|
|
181
|
+
maxTokens: max_budget_usd ? undefined : 8192,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
else if (cliType === "antigravity") {
|
|
185
|
+
parsed = await callAntigravityApi({
|
|
186
|
+
prompt,
|
|
187
|
+
model,
|
|
188
|
+
maxTokens: max_budget_usd ? undefined : 8192,
|
|
189
|
+
});
|
|
231
190
|
}
|
|
232
191
|
else {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
const sandbox = ensureSandboxDir();
|
|
239
|
-
const raw = await spawnCli(cliType, args, undefined, sandbox);
|
|
240
|
-
parsed = parseCliOutput(cliType, raw);
|
|
192
|
+
parsed = await callClaudeApi({
|
|
193
|
+
prompt,
|
|
194
|
+
model,
|
|
195
|
+
maxTokens: max_budget_usd ? undefined : 4096,
|
|
196
|
+
});
|
|
241
197
|
}
|
|
242
198
|
const elapsedMs = Date.now() - startMs;
|
|
243
199
|
const answer = parsed.text.replace(/\n/g, " ").slice(0, 80);
|
|
@@ -250,21 +206,18 @@ async function executeRelayRequest(request, config) {
|
|
|
250
206
|
logger.info(` │ Cost: input=$${cost.inputCost.toFixed(4)} cache_w=$${cost.cacheCreationCost.toFixed(4)} cache_r=$${cost.cacheReadCost.toFixed(4)} output=$${cost.outputCost.toFixed(4)}`);
|
|
251
207
|
logger.info(` │ Total: API $${cost.apiCost.toFixed(4)} → Relay $${cost.relayCost.toFixed(4)} → Earn $${cost.providerEarn.toFixed(4)}`);
|
|
252
208
|
logger.info(` └─ Done`);
|
|
253
|
-
//
|
|
254
|
-
//
|
|
255
|
-
//
|
|
256
|
-
//
|
|
257
|
-
// actually surfaced the headers this turn.
|
|
209
|
+
// Piggy-back the provider's current 5h session-window snapshot onto
|
|
210
|
+
// the response so the Hub can use it for predictive claim scheduling
|
|
211
|
+
// (avoid routing fresh work to a provider whose window is already
|
|
212
|
+
// 90%+ saturated).
|
|
258
213
|
let sessionWindowTelemetry;
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
};
|
|
267
|
-
}
|
|
214
|
+
const snap = getRateGuardSnapshotForCli(cliType);
|
|
215
|
+
if (snap?.sessionWindow) {
|
|
216
|
+
sessionWindowTelemetry = {
|
|
217
|
+
reset_at_ms: snap.sessionWindow.endMs,
|
|
218
|
+
utilization: snap.sessionWindow.utilization,
|
|
219
|
+
status: snap.sessionWindow.status,
|
|
220
|
+
};
|
|
268
221
|
}
|
|
269
222
|
return {
|
|
270
223
|
event: "relay_response",
|
|
@@ -300,34 +253,24 @@ export function runRelayProvider(cliOverride) {
|
|
|
300
253
|
// process.env.HTTPS_PROXY / http_proxy at init time. Must run BEFORE any
|
|
301
254
|
// preflight call so the first outbound request already goes through it.
|
|
302
255
|
applyProxyFromConfig(config);
|
|
303
|
-
//
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
//
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
? preflightClaudeApi
|
|
322
|
-
: config.relay.cli_type === "antigravity"
|
|
323
|
-
? preflightAntigravityApi
|
|
324
|
-
: null;
|
|
325
|
-
if (preflightFn) {
|
|
326
|
-
preflightFn(config.relay.rate_guard).catch((err) => {
|
|
327
|
-
logger.error(`${config.relay.cli_type} API preflight failed — falling back to CLI mode: ${err.message}`);
|
|
328
|
-
config.relay.execution_mode = "cli";
|
|
329
|
-
});
|
|
330
|
-
}
|
|
256
|
+
// Validate the OAuth token + fingerprint up-front so we fail fast instead
|
|
257
|
+
// of on the first inbound request. Each cli_type has its own preflight
|
|
258
|
+
// path (different credential file, different fingerprint schema, different
|
|
259
|
+
// rate-guard instance).
|
|
260
|
+
const preflightFn = config.relay.cli_type === "codex"
|
|
261
|
+
? preflightCodexApi
|
|
262
|
+
: config.relay.cli_type === "gemini"
|
|
263
|
+
? preflightGeminiApi
|
|
264
|
+
: config.relay.cli_type === "claude"
|
|
265
|
+
? preflightClaudeApi
|
|
266
|
+
: config.relay.cli_type === "antigravity"
|
|
267
|
+
? preflightAntigravityApi
|
|
268
|
+
: null;
|
|
269
|
+
if (preflightFn) {
|
|
270
|
+
preflightFn(config.relay.rate_guard).catch((err) => {
|
|
271
|
+
logger.error(`${config.relay.cli_type} API preflight failed: ${err.message}`);
|
|
272
|
+
process.exit(1);
|
|
273
|
+
});
|
|
331
274
|
}
|
|
332
275
|
const activeTasks = new Set();
|
|
333
276
|
// Create WS client
|
|
@@ -398,5 +341,5 @@ export function runRelayProvider(cliOverride) {
|
|
|
398
341
|
writeRelayPid();
|
|
399
342
|
wsClient.start();
|
|
400
343
|
logger.info("Relay Provider running. Listening for relay requests...");
|
|
401
|
-
logger.info(`Config: cli=${config.relay.cli_type},
|
|
344
|
+
logger.info(`Config: cli=${config.relay.cli_type}, model=${config.relay.model}, mode=${config.relay.mode}, concurrency=${config.relay.concurrency}`);
|
|
402
345
|
}
|
package/dist/relay/types.d.ts
CHANGED