clawmoney 0.12.0 → 0.12.2
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 +67 -16
- package/dist/relay/types.d.ts +6 -0
- package/dist/relay/upstream/codex-api.d.ts +42 -0
- package/dist/relay/upstream/codex-api.js +771 -0
- package/dist/relay/upstream/gemini-api.d.ts +31 -0
- package/dist/relay/upstream/gemini-api.js +387 -0
- package/package.json +1 -1
- package/scripts/capture-codex-request.mjs +432 -0
- package/scripts/capture-gemini-request.mjs +281 -0
package/dist/relay/provider.js
CHANGED
|
@@ -4,7 +4,9 @@ import { homedir } from "node:os";
|
|
|
4
4
|
import YAML from "yaml";
|
|
5
5
|
import { RelayWsClient } from "./ws-client.js";
|
|
6
6
|
import { spawnCli, buildCliArgs, parseCliOutput, ensureEmptyMcpConfig, ensureSandboxDir, } from "./executor.js";
|
|
7
|
-
import { callClaudeApi, preflightClaudeApi } from "./upstream/claude-api.js";
|
|
7
|
+
import { callClaudeApi, preflightClaudeApi, getRateGuardSnapshot } from "./upstream/claude-api.js";
|
|
8
|
+
import { callCodexApi, preflightCodexApi } from "./upstream/codex-api.js";
|
|
9
|
+
import { callGeminiApi, preflightGeminiApi } from "./upstream/gemini-api.js";
|
|
8
10
|
import { calculateCost } from "./pricing.js";
|
|
9
11
|
import { relayLogger as logger } from "./logger.js";
|
|
10
12
|
const CONFIG_DIR = join(homedir(), ".clawmoney");
|
|
@@ -106,8 +108,10 @@ async function executeRelayRequest(request, config) {
|
|
|
106
108
|
const model = request.model ?? config.relay.model;
|
|
107
109
|
const stateful = request.stateful ?? false;
|
|
108
110
|
const cliSessionId = request.cli_session_id ?? undefined;
|
|
109
|
-
// api mode is
|
|
110
|
-
|
|
111
|
+
// api mode is supported for claude / codex / gemini; anything else falls
|
|
112
|
+
// back to spawning the local CLI subprocess.
|
|
113
|
+
const useApiMode = config.relay.execution_mode === "api" &&
|
|
114
|
+
(cliType === "claude" || cliType === "codex" || cliType === "gemini");
|
|
111
115
|
// Build prompt from messages
|
|
112
116
|
const prompt = request.messages
|
|
113
117
|
? messagesToPrompt(request.messages)
|
|
@@ -130,13 +134,32 @@ async function executeRelayRequest(request, config) {
|
|
|
130
134
|
const startMs = Date.now();
|
|
131
135
|
let parsed;
|
|
132
136
|
if (useApiMode) {
|
|
133
|
-
// Direct
|
|
134
|
-
// the only thing the upstream sees is the prompt text we pass in.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
137
|
+
// Direct upstream HTTPS call — no subprocess, no sandbox needed because
|
|
138
|
+
// the only thing the upstream sees is the prompt text we pass in. The
|
|
139
|
+
// right handler is picked by cli_type (claude → Anthropic, codex →
|
|
140
|
+
// chatgpt.com, gemini → cloudcode-pa). Each handler has its own
|
|
141
|
+
// fingerprint file and rate-guard instance.
|
|
142
|
+
if (cliType === "codex") {
|
|
143
|
+
parsed = await callCodexApi({
|
|
144
|
+
prompt,
|
|
145
|
+
model,
|
|
146
|
+
maxTokens: max_budget_usd ? undefined : 4096,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
else if (cliType === "gemini") {
|
|
150
|
+
parsed = await callGeminiApi({
|
|
151
|
+
prompt,
|
|
152
|
+
model,
|
|
153
|
+
maxTokens: max_budget_usd ? undefined : 8192,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
parsed = await callClaudeApi({
|
|
158
|
+
prompt,
|
|
159
|
+
model,
|
|
160
|
+
maxTokens: max_budget_usd ? undefined : 4096,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
140
163
|
}
|
|
141
164
|
else {
|
|
142
165
|
// In stateful mode, pass cli_session_id so buildCliArgs adds --resume
|
|
@@ -159,6 +182,22 @@ async function executeRelayRequest(request, config) {
|
|
|
159
182
|
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)}`);
|
|
160
183
|
logger.info(` │ Total: API $${cost.apiCost.toFixed(4)} → Relay $${cost.relayCost.toFixed(4)} → Earn $${cost.providerEarn.toFixed(4)}`);
|
|
161
184
|
logger.info(` └─ Done`);
|
|
185
|
+
// When we're running in api mode, piggy-back the provider's current 5h
|
|
186
|
+
// session-window snapshot onto the response so the Hub can use it for
|
|
187
|
+
// predictive claim scheduling (avoid routing fresh work to a provider
|
|
188
|
+
// whose window is already 90%+ saturated). Only populated if upstream
|
|
189
|
+
// actually surfaced the headers this turn.
|
|
190
|
+
let sessionWindowTelemetry;
|
|
191
|
+
if (useApiMode) {
|
|
192
|
+
const snap = getRateGuardSnapshot();
|
|
193
|
+
if (snap?.sessionWindow) {
|
|
194
|
+
sessionWindowTelemetry = {
|
|
195
|
+
reset_at_ms: snap.sessionWindow.endMs,
|
|
196
|
+
utilization: snap.sessionWindow.utilization,
|
|
197
|
+
status: snap.sessionWindow.status,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
}
|
|
162
201
|
return {
|
|
163
202
|
event: "relay_response",
|
|
164
203
|
request_id,
|
|
@@ -167,6 +206,7 @@ async function executeRelayRequest(request, config) {
|
|
|
167
206
|
usage: parsed.usage,
|
|
168
207
|
model_used: parsed.model || model,
|
|
169
208
|
cost_usd: parsed.costUsd || undefined,
|
|
209
|
+
session_window: sessionWindowTelemetry,
|
|
170
210
|
};
|
|
171
211
|
}
|
|
172
212
|
catch (err) {
|
|
@@ -192,12 +232,23 @@ export function runRelayProvider(cliOverride) {
|
|
|
192
232
|
ensureEmptyMcpConfig();
|
|
193
233
|
ensureSandboxDir();
|
|
194
234
|
// If the operator picked api mode, validate the OAuth token + fingerprint
|
|
195
|
-
// up-front so we fail fast instead of on the first inbound request.
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
235
|
+
// up-front so we fail fast instead of on the first inbound request. Each
|
|
236
|
+
// cli_type has its own preflight path (different credential file, different
|
|
237
|
+
// fingerprint schema, different rate-guard instance).
|
|
238
|
+
if (config.relay.execution_mode === "api") {
|
|
239
|
+
const preflightFn = config.relay.cli_type === "codex"
|
|
240
|
+
? preflightCodexApi
|
|
241
|
+
: config.relay.cli_type === "gemini"
|
|
242
|
+
? preflightGeminiApi
|
|
243
|
+
: config.relay.cli_type === "claude"
|
|
244
|
+
? preflightClaudeApi
|
|
245
|
+
: null;
|
|
246
|
+
if (preflightFn) {
|
|
247
|
+
preflightFn(config.relay.rate_guard).catch((err) => {
|
|
248
|
+
logger.error(`${config.relay.cli_type} API preflight failed — falling back to CLI mode: ${err.message}`);
|
|
249
|
+
config.relay.execution_mode = "cli";
|
|
250
|
+
});
|
|
251
|
+
}
|
|
201
252
|
}
|
|
202
253
|
const activeTasks = new Set();
|
|
203
254
|
// Create WS client
|
package/dist/relay/types.d.ts
CHANGED
|
@@ -24,6 +24,11 @@ export interface RelayErrorEvent {
|
|
|
24
24
|
message: string;
|
|
25
25
|
}
|
|
26
26
|
export type RelayIncomingEvent = RelayRequest | RelayConnectedEvent | RelayErrorEvent;
|
|
27
|
+
export interface RelayResponseSessionWindow {
|
|
28
|
+
reset_at_ms: number;
|
|
29
|
+
utilization?: number;
|
|
30
|
+
status?: string;
|
|
31
|
+
}
|
|
27
32
|
export interface RelayResponse {
|
|
28
33
|
event: "relay_response";
|
|
29
34
|
request_id: string;
|
|
@@ -39,6 +44,7 @@ export interface RelayResponse {
|
|
|
39
44
|
model_used?: string;
|
|
40
45
|
cost_usd?: number;
|
|
41
46
|
error?: string;
|
|
47
|
+
session_window?: RelayResponseSessionWindow;
|
|
42
48
|
}
|
|
43
49
|
export type RelayOutgoingEvent = RelayResponse;
|
|
44
50
|
export interface ParsedOutput {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Direct chatgpt.com upstream for Codex (ChatGPT Plus/Pro) OAuth subscriptions.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors claude-api.ts structure exactly: same export shape, same error types,
|
|
5
|
+
* same RateGuard integration, same OAuth refresh + persist-back pattern, same
|
|
6
|
+
* fingerprint file loading, same 5xx retry path, same preflight function.
|
|
7
|
+
*
|
|
8
|
+
* IMPORTANT — wire format: codex-cli 0.118+ migrated from HTTP POST+SSE to a
|
|
9
|
+
* WebSocket-based Responses API. The endpoint is accessed as
|
|
10
|
+
* wss://chatgpt.com/backend-api/codex/responses
|
|
11
|
+
* with the handshake headers shown below, and after the upgrade the client
|
|
12
|
+
* sends a single `{type:"response.create", ...}` JSON frame. The server
|
|
13
|
+
* replies with a stream of JSON frames that mirror the old SSE event names
|
|
14
|
+
* (`response.created`, `response.output_text.delta`, `response.completed`,
|
|
15
|
+
* `response.failed`, `response.error`, etc.). We accumulate text deltas +
|
|
16
|
+
* the terminal event, close cleanly, and return ParsedOutput — exactly the
|
|
17
|
+
* same contract the caller sees for HTTP Claude.
|
|
18
|
+
*
|
|
19
|
+
* Key differences from claude-api.ts:
|
|
20
|
+
* - Token source: ~/.codex/auth.json (written by the Codex CLI)
|
|
21
|
+
* - Upstream transport: WebSocket to chatgpt.com/backend-api/codex/responses
|
|
22
|
+
* - Handshake header `openai-beta: responses_websockets=2026-02-06`
|
|
23
|
+
* - Handshake header `version: <codex cli version>`
|
|
24
|
+
* - Handshake header `chatgpt-account-id` from ~/.codex/auth.json tokens.account_id
|
|
25
|
+
* - First frame is a JSON `response.create` — request body is OpenAI Responses
|
|
26
|
+
* API shape (input[], instructions, model, store, stream) with `type` added
|
|
27
|
+
* - Session headers: session_id + conversation_id (not x-claude-code-session-id)
|
|
28
|
+
* - Rate-limit headers surface on the upgrade response or via `rate_limits` /
|
|
29
|
+
* `response.failed` frames — we parse both
|
|
30
|
+
*/
|
|
31
|
+
import type { ParsedOutput, RelayRateGuardConfig } from "../types.js";
|
|
32
|
+
import { RateGuard, RateGuardBudgetExceededError, RateGuardCooldownError } from "./rate-guard.js";
|
|
33
|
+
export { RateGuardBudgetExceededError, RateGuardCooldownError };
|
|
34
|
+
export declare function configureRateGuard(config?: RelayRateGuardConfig): void;
|
|
35
|
+
export declare function getRateGuardSnapshot(): ReturnType<RateGuard["currentLoad"]> | null;
|
|
36
|
+
export declare function preflightCodexApi(config?: RelayRateGuardConfig): Promise<void>;
|
|
37
|
+
export interface CallCodexApiOptions {
|
|
38
|
+
prompt: string;
|
|
39
|
+
model: string;
|
|
40
|
+
maxTokens?: number;
|
|
41
|
+
}
|
|
42
|
+
export declare function callCodexApi(opts: CallCodexApiOptions): Promise<ParsedOutput>;
|