@token2chat/t2c 0.2.0-beta.1 → 0.2.1

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 (40) hide show
  1. package/README.md +117 -144
  2. package/dist/cashu-store.d.ts +1 -1
  3. package/dist/cashu-store.js +4 -4
  4. package/dist/commands/audit.d.ts +65 -0
  5. package/dist/commands/audit.js +12 -12
  6. package/dist/commands/balance.js +2 -2
  7. package/dist/commands/doctor.js +2 -2
  8. package/dist/commands/init.js +2 -2
  9. package/dist/commands/mint.js +14 -14
  10. package/dist/commands/monitor.d.ts +51 -0
  11. package/dist/commands/monitor.js +353 -0
  12. package/dist/commands/recover.js +4 -4
  13. package/dist/commands/setup.js +2 -2
  14. package/dist/commands/status.js +2 -3
  15. package/dist/config.d.ts +5 -0
  16. package/dist/config.js +17 -0
  17. package/dist/connectors/cursor.js +44 -15
  18. package/dist/connectors/openclaw.js +32 -24
  19. package/dist/index.js +8 -1
  20. package/dist/proxy/auth.d.ts +20 -0
  21. package/dist/proxy/auth.js +28 -0
  22. package/dist/proxy/errors.d.ts +58 -0
  23. package/dist/proxy/errors.js +95 -0
  24. package/dist/proxy/gate-client.d.ts +34 -0
  25. package/dist/proxy/gate-client.js +81 -0
  26. package/dist/proxy/index.d.ts +10 -0
  27. package/dist/proxy/index.js +17 -0
  28. package/dist/proxy/payment-service.d.ts +65 -0
  29. package/dist/proxy/payment-service.js +101 -0
  30. package/dist/proxy/pricing.d.ts +37 -0
  31. package/dist/proxy/pricing.js +90 -0
  32. package/dist/proxy/response.d.ts +24 -0
  33. package/dist/proxy/response.js +48 -0
  34. package/dist/proxy/sse-parser.d.ts +19 -0
  35. package/dist/proxy/sse-parser.js +80 -0
  36. package/dist/proxy/types.d.ts +113 -0
  37. package/dist/proxy/types.js +74 -0
  38. package/dist/proxy.d.ts +2 -9
  39. package/dist/proxy.js +74 -186
  40. package/package.json +5 -2
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Response utilities and error handler for proxy.
3
+ */
4
+ import { type Logger } from "./types.js";
5
+ /**
6
+ * Minimal response writer interface.
7
+ */
8
+ export interface ResponseWriter {
9
+ writeHead(status: number, headers?: Record<string, string>): void;
10
+ end(body?: string): void;
11
+ }
12
+ /**
13
+ * Send a JSON response.
14
+ */
15
+ export declare function sendJsonResponse(res: ResponseWriter, status: number, body?: unknown): void;
16
+ /**
17
+ * Send an error response in OpenAI-compatible format.
18
+ */
19
+ export declare function sendError(res: ResponseWriter, status: number, code: string, message: string, type?: string): void;
20
+ /**
21
+ * Handle an error and send appropriate response.
22
+ * Supports ProxyError subclasses and standard Errors.
23
+ */
24
+ export declare function handleError(res: ResponseWriter, error: unknown, logger?: Logger): void;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Response utilities and error handler for proxy.
3
+ */
4
+ import { defaultLogger } from "./types.js";
5
+ import { ProxyError } from "./errors.js";
6
+ /**
7
+ * Send a JSON response.
8
+ */
9
+ export function sendJsonResponse(res, status, body) {
10
+ res.writeHead(status, { "Content-Type": "application/json" });
11
+ res.end(JSON.stringify(body ?? {}));
12
+ }
13
+ /**
14
+ * Send an error response in OpenAI-compatible format.
15
+ */
16
+ export function sendError(res, status, code, message, type) {
17
+ const error = { code, message };
18
+ if (type)
19
+ error.type = type;
20
+ sendJsonResponse(res, status, { error });
21
+ }
22
+ /**
23
+ * Handle an error and send appropriate response.
24
+ * Supports ProxyError subclasses and standard Errors.
25
+ */
26
+ export function handleError(res, error, logger = defaultLogger) {
27
+ // Handle ProxyError (and subclasses)
28
+ if (error instanceof ProxyError) {
29
+ logger.error("Proxy error:", error);
30
+ sendJsonResponse(res, error.httpStatus, error.toJSON());
31
+ return;
32
+ }
33
+ // Handle standard Error
34
+ if (error instanceof Error) {
35
+ logger.error("Proxy error:", error);
36
+ // Special case for body too large
37
+ if (error.message === "Request body too large") {
38
+ sendError(res, 413, "payload_too_large", "Request body too large");
39
+ return;
40
+ }
41
+ // Generic internal error
42
+ sendError(res, 500, "proxy_error", "Internal proxy error");
43
+ return;
44
+ }
45
+ // Handle unknown error types
46
+ logger.error("Proxy error:", error);
47
+ sendError(res, 500, "proxy_error", "Internal proxy error");
48
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * SSE stream parser that extracts cashu-change events.
3
+ *
4
+ * The Gate emits change tokens via `event: cashu-change` SSE events
5
+ * during streaming responses. This parser intercepts those events,
6
+ * extracts the token, and filters them out so they are not forwarded
7
+ * to the AI tool.
8
+ */
9
+ export interface SSEFilterResult {
10
+ /** The filtered stream with cashu-change events removed. */
11
+ filtered: ReadableStream<Uint8Array>;
12
+ /** Returns the extracted change token (available after stream is consumed). */
13
+ changeToken: () => string | undefined;
14
+ }
15
+ /**
16
+ * Create a TransformStream that filters out `event: cashu-change` SSE events
17
+ * and captures the token data.
18
+ */
19
+ export declare function extractCashuChangeFromSSE(input: ReadableStream<Uint8Array>): SSEFilterResult;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * SSE stream parser that extracts cashu-change events.
3
+ *
4
+ * The Gate emits change tokens via `event: cashu-change` SSE events
5
+ * during streaming responses. This parser intercepts those events,
6
+ * extracts the token, and filters them out so they are not forwarded
7
+ * to the AI tool.
8
+ */
9
+ /**
10
+ * Create a TransformStream that filters out `event: cashu-change` SSE events
11
+ * and captures the token data.
12
+ */
13
+ export function extractCashuChangeFromSSE(input) {
14
+ let token;
15
+ const decoder = new TextDecoder();
16
+ let buffer = "";
17
+ const filtered = new ReadableStream({
18
+ async start() { },
19
+ async pull(controller) {
20
+ // This is handled via piping below
21
+ },
22
+ cancel() { },
23
+ });
24
+ // Use a TransformStream approach
25
+ const encoder = new TextEncoder();
26
+ let resolveReady = null;
27
+ const transform = new TransformStream({
28
+ transform(chunk, controller) {
29
+ buffer += decoder.decode(chunk, { stream: true });
30
+ // Process complete SSE blocks (separated by \n\n)
31
+ while (true) {
32
+ const blockEnd = buffer.indexOf("\n\n");
33
+ if (blockEnd === -1)
34
+ break;
35
+ const block = buffer.slice(0, blockEnd + 2);
36
+ buffer = buffer.slice(blockEnd + 2);
37
+ // Check if this block is a cashu-change event
38
+ if (isCashuChangeBlock(block)) {
39
+ // Extract the token from "data: <token>\n"
40
+ const dataMatch = block.match(/^data:\s*(.+)$/m);
41
+ if (dataMatch && !token) {
42
+ token = dataMatch[1].trim();
43
+ }
44
+ // Don't forward this block
45
+ continue;
46
+ }
47
+ // Forward non-cashu-change blocks
48
+ controller.enqueue(encoder.encode(block));
49
+ }
50
+ },
51
+ flush(controller) {
52
+ // Flush any remaining buffer
53
+ if (buffer.length > 0) {
54
+ // Check if remaining buffer is a cashu-change event
55
+ if (isCashuChangeBlock(buffer)) {
56
+ const dataMatch = buffer.match(/^data:\s*(.+)$/m);
57
+ if (dataMatch && !token) {
58
+ token = dataMatch[1].trim();
59
+ }
60
+ }
61
+ else {
62
+ controller.enqueue(encoder.encode(buffer));
63
+ }
64
+ buffer = "";
65
+ }
66
+ },
67
+ });
68
+ const outputStream = input.pipeThrough(transform);
69
+ return {
70
+ filtered: outputStream,
71
+ changeToken: () => token,
72
+ };
73
+ }
74
+ /**
75
+ * Check if an SSE block is a cashu-change event.
76
+ */
77
+ function isCashuChangeBlock(block) {
78
+ // An SSE block with event: cashu-change
79
+ return /^event:\s*cashu-change\s*$/m.test(block);
80
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Shared type definitions for the proxy module.
3
+ */
4
+ /**
5
+ * Logger interface for proxy operations.
6
+ */
7
+ export interface Logger {
8
+ info: (...args: unknown[]) => void;
9
+ warn: (...args: unknown[]) => void;
10
+ error: (...args: unknown[]) => void;
11
+ }
12
+ /**
13
+ * Default console logger.
14
+ */
15
+ export declare const defaultLogger: Logger;
16
+ /**
17
+ * Known provider prefixes for model ID transformation.
18
+ * We use `-` as separator in OpenClaw to avoid double-slash issue,
19
+ * but Gate/OpenRouter expects `/` as separator.
20
+ */
21
+ export declare const MODEL_PROVIDER_PREFIXES: readonly ["openai", "anthropic", "google", "deepseek", "qwen", "moonshotai", "mistralai", "meta-llama", "nvidia", "cohere", "perplexity"];
22
+ /**
23
+ * Transform model ID from dash format to slash format.
24
+ * e.g., "anthropic-claude-sonnet-4.5" → "anthropic/claude-sonnet-4.5"
25
+ */
26
+ export declare function transformModelId(model: string): string;
27
+ /**
28
+ * Parse Retry-After header value.
29
+ * Returns milliseconds to wait, or null if invalid.
30
+ */
31
+ export declare function parseRetryAfter(value: string | null): number | null;
32
+ /**
33
+ * Result from a proxy request.
34
+ */
35
+ export interface ProxyResult {
36
+ status: number;
37
+ headers: Record<string, string>;
38
+ body: ReadableStream<Uint8Array> | string;
39
+ }
40
+ /**
41
+ * OpenAI chat completion request (minimal).
42
+ */
43
+ export interface CompletionRequest {
44
+ model: string;
45
+ messages: Array<{
46
+ role: string;
47
+ content: string | unknown[];
48
+ }>;
49
+ stream?: boolean;
50
+ [key: string]: unknown;
51
+ }
52
+ /**
53
+ * Model info in OpenAI format.
54
+ */
55
+ export interface ModelInfo {
56
+ id: string;
57
+ object: "model";
58
+ created: number;
59
+ owned_by: string;
60
+ }
61
+ /**
62
+ * Proxy handle returned by startProxy.
63
+ */
64
+ export interface ProxyHandle {
65
+ stop: () => void;
66
+ proxySecret: string;
67
+ }
68
+ /**
69
+ * Payment result from token selection.
70
+ */
71
+ export interface PaymentResult {
72
+ token: string;
73
+ priceSpent: number;
74
+ balanceAfter: number;
75
+ }
76
+ /**
77
+ * Change/refund received from Gate.
78
+ */
79
+ export interface TokenReceiveResult {
80
+ amount: number;
81
+ type: "change" | "refund";
82
+ }
83
+ /**
84
+ * Proxy request metrics for a single request.
85
+ */
86
+ export interface RequestMetrics {
87
+ txId: string;
88
+ model: string;
89
+ priceSat: number;
90
+ changeSat: number;
91
+ refundSat: number;
92
+ gateStatus: number;
93
+ balanceBefore: number;
94
+ balanceAfter: number;
95
+ durationMs: number;
96
+ error?: string;
97
+ }
98
+ /**
99
+ * Maximum request body size (10 MB).
100
+ */
101
+ export declare const MAX_BODY_SIZE: number;
102
+ /**
103
+ * Maximum retry delay (30 seconds).
104
+ */
105
+ export declare const MAX_RETRY_DELAY_MS = 30000;
106
+ /**
107
+ * Default retry configuration.
108
+ */
109
+ export declare const DEFAULT_RETRY_CONFIG: {
110
+ readonly maxRetries: 2;
111
+ readonly baseDelayMs: 2000;
112
+ readonly maxDelayMs: 30000;
113
+ };
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Shared type definitions for the proxy module.
3
+ */
4
+ /**
5
+ * Default console logger.
6
+ */
7
+ export const defaultLogger = {
8
+ info: (...args) => console.log("[t2c]", ...args),
9
+ warn: (...args) => console.warn("[t2c]", ...args),
10
+ error: (...args) => console.error("[t2c]", ...args),
11
+ };
12
+ /**
13
+ * Known provider prefixes for model ID transformation.
14
+ * We use `-` as separator in OpenClaw to avoid double-slash issue,
15
+ * but Gate/OpenRouter expects `/` as separator.
16
+ */
17
+ export const MODEL_PROVIDER_PREFIXES = [
18
+ "openai",
19
+ "anthropic",
20
+ "google",
21
+ "deepseek",
22
+ "qwen",
23
+ "moonshotai",
24
+ "mistralai",
25
+ "meta-llama",
26
+ "nvidia",
27
+ "cohere",
28
+ "perplexity",
29
+ ];
30
+ /**
31
+ * Transform model ID from dash format to slash format.
32
+ * e.g., "anthropic-claude-sonnet-4.5" → "anthropic/claude-sonnet-4.5"
33
+ */
34
+ export function transformModelId(model) {
35
+ for (const prefix of MODEL_PROVIDER_PREFIXES) {
36
+ if (model.startsWith(`${prefix}-`)) {
37
+ return `${prefix}/${model.slice(prefix.length + 1)}`;
38
+ }
39
+ }
40
+ return model;
41
+ }
42
+ /**
43
+ * Parse Retry-After header value.
44
+ * Returns milliseconds to wait, or null if invalid.
45
+ */
46
+ export function parseRetryAfter(value) {
47
+ if (!value)
48
+ return null;
49
+ const seconds = parseFloat(value);
50
+ if (!isNaN(seconds) && isFinite(seconds)) {
51
+ return Math.max(0, Math.ceil(seconds * 1000));
52
+ }
53
+ const date = new Date(value);
54
+ if (!isNaN(date.getTime())) {
55
+ return Math.max(0, date.getTime() - Date.now());
56
+ }
57
+ return null;
58
+ }
59
+ /**
60
+ * Maximum request body size (10 MB).
61
+ */
62
+ export const MAX_BODY_SIZE = 10 * 1024 * 1024;
63
+ /**
64
+ * Maximum retry delay (30 seconds).
65
+ */
66
+ export const MAX_RETRY_DELAY_MS = 30_000;
67
+ /**
68
+ * Default retry configuration.
69
+ */
70
+ export const DEFAULT_RETRY_CONFIG = {
71
+ maxRetries: 2,
72
+ baseDelayMs: 2000,
73
+ maxDelayMs: MAX_RETRY_DELAY_MS,
74
+ };
package/dist/proxy.d.ts CHANGED
@@ -1,11 +1,4 @@
1
1
  import { type T2CConfig } from "./config.js";
2
- export interface Logger {
3
- info: (...args: unknown[]) => void;
4
- warn: (...args: unknown[]) => void;
5
- error: (...args: unknown[]) => void;
6
- }
7
- export interface ProxyHandle {
8
- stop: () => void;
9
- proxySecret: string;
10
- }
2
+ import { type Logger, type ProxyHandle, transformModelId, parseRetryAfter } from "./proxy/index.js";
3
+ export { transformModelId, parseRetryAfter, type Logger, type ProxyHandle };
11
4
  export declare function startProxy(config: T2CConfig, logger?: Logger): Promise<ProxyHandle>;