@t54-labs/clawcredit-blockrun-sdk 0.1.2 → 0.1.4
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.
|
@@ -108,6 +108,34 @@ function createClawCreditFetch(config) {
|
|
|
108
108
|
|
|
109
109
|
// src/server.ts
|
|
110
110
|
import { createServer } from "http";
|
|
111
|
+
import { appendFileSync, mkdirSync } from "fs";
|
|
112
|
+
import { dirname } from "path";
|
|
113
|
+
import { randomUUID } from "crypto";
|
|
114
|
+
var VALID_ROLES = /* @__PURE__ */ new Set(["system", "user", "assistant", "tool", "function"]);
|
|
115
|
+
var ROLE_MAPPINGS = {
|
|
116
|
+
developer: "system",
|
|
117
|
+
model: "assistant"
|
|
118
|
+
};
|
|
119
|
+
function normalizeMessageRoles(messages) {
|
|
120
|
+
if (messages.length === 0) return messages;
|
|
121
|
+
let hasChanges = false;
|
|
122
|
+
const normalized = messages.map((msg) => {
|
|
123
|
+
const role = msg.role;
|
|
124
|
+
if (typeof role !== "string") {
|
|
125
|
+
hasChanges = true;
|
|
126
|
+
return { ...msg, role: "user" };
|
|
127
|
+
}
|
|
128
|
+
if (VALID_ROLES.has(role)) return msg;
|
|
129
|
+
const mappedRole = ROLE_MAPPINGS[role];
|
|
130
|
+
if (mappedRole) {
|
|
131
|
+
hasChanges = true;
|
|
132
|
+
return { ...msg, role: mappedRole };
|
|
133
|
+
}
|
|
134
|
+
hasChanges = true;
|
|
135
|
+
return { ...msg, role: "user" };
|
|
136
|
+
});
|
|
137
|
+
return hasChanges ? normalized : messages;
|
|
138
|
+
}
|
|
111
139
|
function readBody(req) {
|
|
112
140
|
return new Promise((resolve, reject) => {
|
|
113
141
|
const chunks = [];
|
|
@@ -135,18 +163,128 @@ function extractMaxTokens(body) {
|
|
|
135
163
|
function normalizePayloadForGateway(body) {
|
|
136
164
|
try {
|
|
137
165
|
const parsed = JSON.parse(body.toString("utf-8"));
|
|
166
|
+
const requestedStream = parsed.stream === true;
|
|
138
167
|
if (parsed.stream === true) parsed.stream = false;
|
|
139
|
-
|
|
168
|
+
if (Array.isArray(parsed.messages)) {
|
|
169
|
+
parsed.messages = normalizeMessageRoles(parsed.messages);
|
|
170
|
+
}
|
|
171
|
+
return { body: Buffer.from(JSON.stringify(parsed)), requestedStream };
|
|
140
172
|
} catch {
|
|
141
|
-
return body;
|
|
173
|
+
return { body, requestedStream: false };
|
|
142
174
|
}
|
|
143
175
|
}
|
|
176
|
+
function convertJsonCompletionToSseBody(responseBody) {
|
|
177
|
+
let payload = null;
|
|
178
|
+
try {
|
|
179
|
+
payload = JSON.parse(responseBody);
|
|
180
|
+
} catch {
|
|
181
|
+
payload = null;
|
|
182
|
+
}
|
|
183
|
+
if (!payload || !Array.isArray(payload.choices) || payload.choices.length === 0) {
|
|
184
|
+
return `data: ${responseBody}
|
|
185
|
+
|
|
186
|
+
data: [DONE]
|
|
187
|
+
|
|
188
|
+
`;
|
|
189
|
+
}
|
|
190
|
+
const created = payload.created ?? Math.floor(Date.now() / 1e3);
|
|
191
|
+
const baseChunk = {
|
|
192
|
+
id: payload.id ?? `chatcmpl-${Date.now()}`,
|
|
193
|
+
object: "chat.completion.chunk",
|
|
194
|
+
created,
|
|
195
|
+
model: payload.model ?? "unknown",
|
|
196
|
+
system_fingerprint: null
|
|
197
|
+
};
|
|
198
|
+
const out = [];
|
|
199
|
+
for (const choice of payload.choices) {
|
|
200
|
+
const index = Number.isFinite(choice.index) ? Number(choice.index) : 0;
|
|
201
|
+
const role = choice.message?.role ?? choice.delta?.role ?? "assistant";
|
|
202
|
+
const content = choice.message?.content ?? choice.delta?.content ?? "";
|
|
203
|
+
const toolCalls = choice.message?.tool_calls ?? choice.delta?.tool_calls;
|
|
204
|
+
out.push(
|
|
205
|
+
`data: ${JSON.stringify({
|
|
206
|
+
...baseChunk,
|
|
207
|
+
choices: [{ index, delta: { role }, logprobs: null, finish_reason: null }]
|
|
208
|
+
})}
|
|
209
|
+
|
|
210
|
+
`
|
|
211
|
+
);
|
|
212
|
+
if (typeof content === "string" && content.length > 0) {
|
|
213
|
+
out.push(
|
|
214
|
+
`data: ${JSON.stringify({
|
|
215
|
+
...baseChunk,
|
|
216
|
+
choices: [{ index, delta: { content }, logprobs: null, finish_reason: null }]
|
|
217
|
+
})}
|
|
218
|
+
|
|
219
|
+
`
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
if (Array.isArray(toolCalls) && toolCalls.length > 0) {
|
|
223
|
+
out.push(
|
|
224
|
+
`data: ${JSON.stringify({
|
|
225
|
+
...baseChunk,
|
|
226
|
+
choices: [{ index, delta: { tool_calls: toolCalls }, logprobs: null, finish_reason: null }]
|
|
227
|
+
})}
|
|
228
|
+
|
|
229
|
+
`
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
out.push(
|
|
233
|
+
`data: ${JSON.stringify({
|
|
234
|
+
...baseChunk,
|
|
235
|
+
choices: [
|
|
236
|
+
{
|
|
237
|
+
index,
|
|
238
|
+
delta: {},
|
|
239
|
+
logprobs: null,
|
|
240
|
+
finish_reason: Array.isArray(toolCalls) && toolCalls.length > 0 ? "tool_calls" : choice.finish_reason ?? "stop"
|
|
241
|
+
}
|
|
242
|
+
]
|
|
243
|
+
})}
|
|
244
|
+
|
|
245
|
+
`
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
out.push("data: [DONE]\n\n");
|
|
249
|
+
return out.join("");
|
|
250
|
+
}
|
|
251
|
+
function parseJsonIfPossible(text) {
|
|
252
|
+
try {
|
|
253
|
+
return JSON.parse(text);
|
|
254
|
+
} catch {
|
|
255
|
+
return text;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
function safeHeadersObject(headers) {
|
|
259
|
+
const out = {};
|
|
260
|
+
headers.forEach((value, key) => {
|
|
261
|
+
out[key] = value;
|
|
262
|
+
});
|
|
263
|
+
return out;
|
|
264
|
+
}
|
|
265
|
+
function createCaptureWriter() {
|
|
266
|
+
const enabled = process.env.GATEWAY_CAPTURE === "1" || process.env.GATEWAY_CAPTURE === "true";
|
|
267
|
+
const file = (process.env.GATEWAY_CAPTURE_FILE || "/tmp/clawcredit-blockrun-gateway/.run/capture.jsonl").trim();
|
|
268
|
+
if (!enabled) {
|
|
269
|
+
return (_entry) => {
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
273
|
+
return (entry) => {
|
|
274
|
+
try {
|
|
275
|
+
appendFileSync(file, `${JSON.stringify(entry)}
|
|
276
|
+
`, "utf-8");
|
|
277
|
+
} catch {
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
}
|
|
144
281
|
async function startGateway(options) {
|
|
145
282
|
const host = options.host ?? "127.0.0.1";
|
|
146
283
|
const port = options.port ?? 3402;
|
|
147
284
|
const apiBase = (options.blockrunApiBase ?? "https://blockrun.ai/api").replace(/\/+$/, "");
|
|
148
285
|
const defaultAmountUsd = Number.isFinite(options.defaultAmountUsd) ? Number(options.defaultAmountUsd) : 0.1;
|
|
149
286
|
const payFetch = createClawCreditFetch(options.clawCredit);
|
|
287
|
+
const writeCapture = createCaptureWriter();
|
|
150
288
|
const server = createServer(async (req, res) => {
|
|
151
289
|
if (req.url === "/health") {
|
|
152
290
|
return sendJson(res, 200, {
|
|
@@ -159,8 +297,13 @@ async function startGateway(options) {
|
|
|
159
297
|
return sendJson(res, 404, { error: "Not found" });
|
|
160
298
|
}
|
|
161
299
|
try {
|
|
300
|
+
const requestId = randomUUID();
|
|
301
|
+
const startedAt = Date.now();
|
|
162
302
|
const body = await readBody(req);
|
|
163
|
-
const
|
|
303
|
+
const normalizedReq = normalizePayloadForGateway(body);
|
|
304
|
+
const normalized = normalizedReq.body;
|
|
305
|
+
const requestedStream = normalizedReq.requestedStream;
|
|
306
|
+
const normalizedText = normalized.toString("utf-8");
|
|
164
307
|
const maxTokens = extractMaxTokens(normalized);
|
|
165
308
|
const estimatedMicros = String(
|
|
166
309
|
Math.max(1e4, Math.round(defaultAmountUsd * 1e6 + maxTokens * 8))
|
|
@@ -176,6 +319,20 @@ async function startGateway(options) {
|
|
|
176
319
|
if (!headers.has("content-type")) {
|
|
177
320
|
headers.set("content-type", "application/json");
|
|
178
321
|
}
|
|
322
|
+
writeCapture({
|
|
323
|
+
kind: "request",
|
|
324
|
+
requestId,
|
|
325
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
326
|
+
source: {
|
|
327
|
+
userAgent: req.headers["user-agent"] || null,
|
|
328
|
+
xOpenClawSession: req.headers["x-openclaw-session-id"] || null
|
|
329
|
+
},
|
|
330
|
+
method: "POST",
|
|
331
|
+
target: upstreamUrl,
|
|
332
|
+
estimatedMicros,
|
|
333
|
+
headers: safeHeadersObject(headers),
|
|
334
|
+
body: parseJsonIfPossible(normalizedText)
|
|
335
|
+
});
|
|
179
336
|
const upstream = await payFetch(
|
|
180
337
|
upstreamUrl,
|
|
181
338
|
{
|
|
@@ -186,12 +343,36 @@ async function startGateway(options) {
|
|
|
186
343
|
{ estimatedAmount: estimatedMicros }
|
|
187
344
|
);
|
|
188
345
|
const responseBody = await upstream.text();
|
|
346
|
+
writeCapture({
|
|
347
|
+
kind: "response",
|
|
348
|
+
requestId,
|
|
349
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
350
|
+
durationMs: Date.now() - startedAt,
|
|
351
|
+
status: upstream.status,
|
|
352
|
+
headers: safeHeadersObject(upstream.headers),
|
|
353
|
+
body: parseJsonIfPossible(responseBody)
|
|
354
|
+
});
|
|
355
|
+
if (requestedStream && upstream.ok) {
|
|
356
|
+
const sseBody = convertJsonCompletionToSseBody(responseBody);
|
|
357
|
+
res.writeHead(200, {
|
|
358
|
+
"content-type": "text/event-stream",
|
|
359
|
+
"cache-control": "no-cache",
|
|
360
|
+
connection: "keep-alive"
|
|
361
|
+
});
|
|
362
|
+
res.end(sseBody);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
189
365
|
res.writeHead(upstream.status, {
|
|
190
366
|
"content-type": upstream.headers.get("content-type") || "application/json"
|
|
191
367
|
});
|
|
192
368
|
res.end(responseBody);
|
|
193
369
|
} catch (err) {
|
|
194
370
|
const msg = err instanceof Error ? err.message : String(err);
|
|
371
|
+
writeCapture({
|
|
372
|
+
kind: "error",
|
|
373
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
374
|
+
message: msg
|
|
375
|
+
});
|
|
195
376
|
sendJson(res, 502, { error: msg });
|
|
196
377
|
}
|
|
197
378
|
});
|
|
@@ -214,4 +395,4 @@ export {
|
|
|
214
395
|
createClawCreditFetch,
|
|
215
396
|
startGateway
|
|
216
397
|
};
|
|
217
|
-
//# sourceMappingURL=chunk-
|
|
398
|
+
//# sourceMappingURL=chunk-ZMPGMK4C.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/clawcredit.ts","../src/server.ts"],"sourcesContent":["import { ClawCredit, withTrace } from \"@t54-labs/clawcredit-sdk\";\n\nconst DEFAULT_SERVICE_URL = \"https://api.claw.credit\";\n\nexport type ClawCreditConfig = {\n baseUrl?: string;\n apiToken: string;\n chain: string;\n asset: string;\n agent?: string;\n agentId?: string;\n};\n\nexport type PreAuthParams = {\n estimatedAmount: string;\n};\n\ntype SdkClient = {\n pay: (args: {\n transaction: {\n recipient: string;\n amount: number;\n chain: string;\n asset: string;\n amount_unit?: \"human\" | \"atomic\";\n };\n request_body: Record<string, unknown>;\n context?: {\n reasoning_process?: string;\n current_task?: string;\n };\n idempotencyKey?: string;\n }) => Promise<{ merchant_response?: unknown }>;\n};\n\nfunction headersToObject(headersInit?: HeadersInit): Record<string, string> {\n if (!headersInit) return {};\n const headers = new Headers(headersInit);\n const out: Record<string, string> = {};\n headers.forEach((value, key) => {\n const lower = key.toLowerCase();\n if (lower === \"host\" || lower === \"content-length\" || lower === \"connection\") return;\n out[key] = value;\n });\n return out;\n}\n\nfunction parseJsonBody(body: RequestInit[\"body\"]): unknown {\n if (body == null) return undefined;\n\n let raw = \"\";\n if (typeof body === \"string\") {\n raw = body;\n } else if (body instanceof Uint8Array) {\n raw = Buffer.from(body).toString(\"utf-8\");\n } else if (body instanceof ArrayBuffer) {\n raw = Buffer.from(body).toString(\"utf-8\");\n } else {\n return undefined;\n }\n\n if (!raw.trim()) return undefined;\n try {\n return JSON.parse(raw);\n } catch {\n return undefined;\n }\n}\n\nfunction microsToUsd(estimatedAmount?: string): number {\n const micros = Number(estimatedAmount ?? \"\");\n if (!Number.isFinite(micros) || micros <= 0) return 0.01;\n return Number((micros / 1_000_000).toFixed(6));\n}\n\nfunction inferStatusCode(err: unknown): number {\n const msg = err instanceof Error ? err.message : String(err);\n const match = msg.match(/ClawCredit API Error:\\s*(\\d{3})\\s*-/i);\n if (match) return parseInt(match[1], 10);\n if (/payment required/i.test(msg)) return 402;\n if (/prequalification_pending/i.test(msg)) return 403;\n if (/unauthorized/i.test(msg)) return 401;\n return 502;\n}\n\nexport function createClawCreditFetch(config: ClawCreditConfig) {\n const serviceUrl = (config.baseUrl || DEFAULT_SERVICE_URL).replace(/\\/+$/, \"\");\n const chain = config.chain.toUpperCase();\n const asset = config.asset;\n const apiToken = config.apiToken.trim();\n\n if (!apiToken) {\n throw new Error(\"CLAWCREDIT_API_TOKEN is required for claw.credit payment mode\");\n }\n\n const credit = new ClawCredit({\n serviceUrl,\n apiToken,\n agent: config.agent,\n agentId: config.agentId,\n }) as SdkClient;\n\n return async (\n input: RequestInfo | URL,\n init?: RequestInit,\n preAuth?: PreAuthParams,\n ): Promise<Response> => {\n const upstreamUrl =\n typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url;\n const method = (init?.method || \"POST\").toUpperCase();\n const headers = headersToObject(init?.headers);\n const idempotencyKey = new Headers(init?.headers).get(\"idempotency-key\") || undefined;\n const requestBody = parseJsonBody(init?.body);\n const amountUsd = microsToUsd(preAuth?.estimatedAmount);\n\n try {\n const result = await withTrace(async () =>\n credit.pay({\n transaction: {\n recipient: upstreamUrl,\n amount: amountUsd,\n chain,\n asset,\n },\n request_body: {\n http: {\n url: upstreamUrl,\n method,\n headers,\n },\n body: requestBody,\n },\n context: {\n current_task: \"blockrun_inference_via_clawcredit_blockrun_gateway\",\n reasoning_process: \"Pay BlockRun inference through claw.credit SDK\",\n },\n idempotencyKey,\n }),\n );\n\n const merchantResponse =\n result && typeof result === \"object\" && \"merchant_response\" in result\n ? (result as { merchant_response?: unknown }).merchant_response\n : result;\n\n return new Response(JSON.stringify(merchantResponse ?? {}), {\n status: 200,\n headers: { \"content-type\": \"application/json\" },\n });\n } catch (err) {\n const status = inferStatusCode(err);\n const message = err instanceof Error ? err.message : String(err);\n return new Response(JSON.stringify({ error: message }), {\n status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n };\n}\n","import { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport type { AddressInfo } from \"node:net\";\nimport { appendFileSync, mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { randomUUID } from \"node:crypto\";\nimport { createClawCreditFetch, type ClawCreditConfig } from \"./clawcredit.js\";\n\nexport type GatewayOptions = {\n port?: number;\n host?: string;\n blockrunApiBase?: string;\n clawCredit: ClawCreditConfig;\n defaultAmountUsd?: number;\n};\n\nexport type GatewayInstance = {\n port: number;\n baseUrl: string;\n close: () => Promise<void>;\n};\n\ntype CompletionRequest = {\n model?: string;\n stream?: boolean;\n max_tokens?: number;\n messages?: Array<Record<string, unknown>>;\n};\n\ntype NormalizedGatewayRequest = {\n body: Buffer;\n requestedStream: boolean;\n};\n\ntype CompletionResponseChoice = {\n index?: number;\n message?: {\n role?: string;\n content?: string;\n tool_calls?: unknown[];\n };\n delta?: {\n role?: string;\n content?: string;\n tool_calls?: unknown[];\n };\n finish_reason?: string | null;\n};\n\ntype CompletionResponse = {\n id?: string;\n object?: string;\n created?: number;\n model?: string;\n choices?: CompletionResponseChoice[];\n};\n\nconst VALID_ROLES = new Set([\"system\", \"user\", \"assistant\", \"tool\", \"function\"]);\nconst ROLE_MAPPINGS: Record<string, string> = {\n developer: \"system\",\n model: \"assistant\",\n};\n\nfunction normalizeMessageRoles(messages: Array<Record<string, unknown>>): Array<Record<string, unknown>> {\n if (messages.length === 0) return messages;\n\n let hasChanges = false;\n const normalized = messages.map((msg) => {\n const role = msg.role;\n if (typeof role !== \"string\") {\n hasChanges = true;\n return { ...msg, role: \"user\" };\n }\n if (VALID_ROLES.has(role)) return msg;\n\n const mappedRole = ROLE_MAPPINGS[role];\n if (mappedRole) {\n hasChanges = true;\n return { ...msg, role: mappedRole };\n }\n\n hasChanges = true;\n return { ...msg, role: \"user\" };\n });\n\n return hasChanges ? normalized : messages;\n}\n\nfunction readBody(req: IncomingMessage): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on(\"data\", (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));\n req.on(\"end\", () => resolve(Buffer.concat(chunks)));\n req.on(\"error\", reject);\n });\n}\n\nfunction sendJson(res: ServerResponse, status: number, payload: unknown): void {\n const text = JSON.stringify(payload);\n res.writeHead(status, {\n \"content-type\": \"application/json\",\n \"content-length\": Buffer.byteLength(text).toString(),\n });\n res.end(text);\n}\n\nfunction extractMaxTokens(body: Buffer): number {\n try {\n const parsed = JSON.parse(body.toString(\"utf-8\")) as CompletionRequest;\n return parsed.max_tokens && Number.isFinite(parsed.max_tokens) ? parsed.max_tokens : 512;\n } catch {\n return 512;\n }\n}\n\nfunction normalizePayloadForGateway(body: Buffer): NormalizedGatewayRequest {\n try {\n const parsed = JSON.parse(body.toString(\"utf-8\")) as CompletionRequest;\n const requestedStream = parsed.stream === true;\n if (parsed.stream === true) parsed.stream = false;\n if (Array.isArray(parsed.messages)) {\n parsed.messages = normalizeMessageRoles(parsed.messages);\n }\n return { body: Buffer.from(JSON.stringify(parsed)), requestedStream };\n } catch {\n return { body, requestedStream: false };\n }\n}\n\nfunction convertJsonCompletionToSseBody(responseBody: string): string {\n let payload: CompletionResponse | null = null;\n try {\n payload = JSON.parse(responseBody) as CompletionResponse;\n } catch {\n payload = null;\n }\n\n if (!payload || !Array.isArray(payload.choices) || payload.choices.length === 0) {\n return `data: ${responseBody}\\n\\ndata: [DONE]\\n\\n`;\n }\n\n const created = payload.created ?? Math.floor(Date.now() / 1000);\n const baseChunk = {\n id: payload.id ?? `chatcmpl-${Date.now()}`,\n object: \"chat.completion.chunk\",\n created,\n model: payload.model ?? \"unknown\",\n system_fingerprint: null,\n };\n\n const out: string[] = [];\n for (const choice of payload.choices) {\n const index = Number.isFinite(choice.index) ? Number(choice.index) : 0;\n const role = choice.message?.role ?? choice.delta?.role ?? \"assistant\";\n const content = choice.message?.content ?? choice.delta?.content ?? \"\";\n const toolCalls = choice.message?.tool_calls ?? choice.delta?.tool_calls;\n\n out.push(\n `data: ${JSON.stringify({\n ...baseChunk,\n choices: [{ index, delta: { role }, logprobs: null, finish_reason: null }],\n })}\\n\\n`,\n );\n\n if (typeof content === \"string\" && content.length > 0) {\n out.push(\n `data: ${JSON.stringify({\n ...baseChunk,\n choices: [{ index, delta: { content }, logprobs: null, finish_reason: null }],\n })}\\n\\n`,\n );\n }\n\n if (Array.isArray(toolCalls) && toolCalls.length > 0) {\n out.push(\n `data: ${JSON.stringify({\n ...baseChunk,\n choices: [{ index, delta: { tool_calls: toolCalls }, logprobs: null, finish_reason: null }],\n })}\\n\\n`,\n );\n }\n\n out.push(\n `data: ${JSON.stringify({\n ...baseChunk,\n choices: [\n {\n index,\n delta: {},\n logprobs: null,\n finish_reason:\n Array.isArray(toolCalls) && toolCalls.length > 0\n ? \"tool_calls\"\n : (choice.finish_reason ?? \"stop\"),\n },\n ],\n })}\\n\\n`,\n );\n }\n\n out.push(\"data: [DONE]\\n\\n\");\n return out.join(\"\");\n}\n\nfunction parseJsonIfPossible(text: string): unknown {\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n}\n\nfunction safeHeadersObject(headers: Headers): Record<string, string> {\n const out: Record<string, string> = {};\n headers.forEach((value, key) => {\n out[key] = value;\n });\n return out;\n}\n\nfunction createCaptureWriter() {\n const enabled =\n process.env.GATEWAY_CAPTURE === \"1\" || process.env.GATEWAY_CAPTURE === \"true\";\n const file =\n (process.env.GATEWAY_CAPTURE_FILE || \"/tmp/clawcredit-blockrun-gateway/.run/capture.jsonl\").trim();\n\n if (!enabled) {\n return (_entry: unknown) => {};\n }\n\n mkdirSync(dirname(file), { recursive: true });\n return (entry: unknown) => {\n try {\n appendFileSync(file, `${JSON.stringify(entry)}\\n`, \"utf-8\");\n } catch {\n // Best-effort debug capture only.\n }\n };\n}\n\nexport async function startGateway(options: GatewayOptions): Promise<GatewayInstance> {\n const host = options.host ?? \"127.0.0.1\";\n const port = options.port ?? 3402;\n const apiBase = (options.blockrunApiBase ?? \"https://blockrun.ai/api\").replace(/\\/+$/, \"\");\n const defaultAmountUsd = Number.isFinite(options.defaultAmountUsd)\n ? Number(options.defaultAmountUsd)\n : 0.1;\n const payFetch = createClawCreditFetch(options.clawCredit);\n const writeCapture = createCaptureWriter();\n\n const server = createServer(async (req, res) => {\n if (req.url === \"/health\") {\n return sendJson(res, 200, {\n status: \"ok\",\n service: \"clawcredit-blockrun-gateway\",\n payment_mode: \"clawcredit\",\n });\n }\n\n if (req.url !== \"/v1/chat/completions\" || req.method !== \"POST\") {\n return sendJson(res, 404, { error: \"Not found\" });\n }\n\n try {\n const requestId = randomUUID();\n const startedAt = Date.now();\n const body = await readBody(req);\n const normalizedReq = normalizePayloadForGateway(body);\n const normalized = normalizedReq.body;\n const requestedStream = normalizedReq.requestedStream;\n const normalizedText = normalized.toString(\"utf-8\");\n const maxTokens = extractMaxTokens(normalized);\n\n const estimatedMicros = String(\n Math.max(10_000, Math.round(defaultAmountUsd * 1_000_000 + maxTokens * 8)),\n );\n\n const upstreamUrl = `${apiBase}/v1/chat/completions`;\n\n const headers = new Headers();\n for (const [key, value] of Object.entries(req.headers)) {\n if (typeof value !== \"string\") continue;\n const lower = key.toLowerCase();\n if (lower === \"host\" || lower === \"content-length\" || lower === \"connection\") continue;\n headers.set(key, value);\n }\n if (!headers.has(\"content-type\")) {\n headers.set(\"content-type\", \"application/json\");\n }\n\n writeCapture({\n kind: \"request\",\n requestId,\n at: new Date().toISOString(),\n source: {\n userAgent: req.headers[\"user-agent\"] || null,\n xOpenClawSession: req.headers[\"x-openclaw-session-id\"] || null,\n },\n method: \"POST\",\n target: upstreamUrl,\n estimatedMicros,\n headers: safeHeadersObject(headers),\n body: parseJsonIfPossible(normalizedText),\n });\n\n const upstream = await payFetch(\n upstreamUrl,\n {\n method: \"POST\",\n headers,\n body: new Uint8Array(normalized),\n },\n { estimatedAmount: estimatedMicros },\n );\n\n const responseBody = await upstream.text();\n writeCapture({\n kind: \"response\",\n requestId,\n at: new Date().toISOString(),\n durationMs: Date.now() - startedAt,\n status: upstream.status,\n headers: safeHeadersObject(upstream.headers),\n body: parseJsonIfPossible(responseBody),\n });\n if (requestedStream && upstream.ok) {\n const sseBody = convertJsonCompletionToSseBody(responseBody);\n res.writeHead(200, {\n \"content-type\": \"text/event-stream\",\n \"cache-control\": \"no-cache\",\n connection: \"keep-alive\",\n });\n res.end(sseBody);\n return;\n }\n\n res.writeHead(upstream.status, {\n \"content-type\": upstream.headers.get(\"content-type\") || \"application/json\",\n });\n res.end(responseBody);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n writeCapture({\n kind: \"error\",\n at: new Date().toISOString(),\n message: msg,\n });\n sendJson(res, 502, { error: msg });\n }\n });\n\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(port, host, () => {\n server.removeListener(\"error\", reject);\n resolve();\n });\n });\n\n const actualPort = (server.address() as AddressInfo).port;\n return {\n port: actualPort,\n baseUrl: `http://${host}:${actualPort}`,\n close: () => new Promise<void>((resolve, reject) => server.close((err) => (err ? reject(err) : resolve()))),\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/clawcredit.ts","../src/server.ts"],"sourcesContent":["import { ClawCredit, withTrace } from \"@t54-labs/clawcredit-sdk\";\n\nconst DEFAULT_SERVICE_URL = \"https://api.claw.credit\";\n\nexport type ClawCreditConfig = {\n baseUrl?: string;\n apiToken: string;\n chain: string;\n asset: string;\n agent?: string;\n agentId?: string;\n};\n\nexport type PreAuthParams = {\n estimatedAmount: string;\n};\n\ntype SdkClient = {\n pay: (args: {\n transaction: {\n recipient: string;\n amount: number;\n chain: string;\n asset: string;\n amount_unit?: \"human\" | \"atomic\";\n };\n request_body: Record<string, unknown>;\n context?: {\n reasoning_process?: string;\n current_task?: string;\n };\n idempotencyKey?: string;\n }) => Promise<{ merchant_response?: unknown }>;\n};\n\nfunction headersToObject(headersInit?: HeadersInit): Record<string, string> {\n if (!headersInit) return {};\n const headers = new Headers(headersInit);\n const out: Record<string, string> = {};\n headers.forEach((value, key) => {\n const lower = key.toLowerCase();\n if (lower === \"host\" || lower === \"content-length\" || lower === \"connection\") return;\n out[key] = value;\n });\n return out;\n}\n\nfunction parseJsonBody(body: RequestInit[\"body\"]): unknown {\n if (body == null) return undefined;\n\n let raw = \"\";\n if (typeof body === \"string\") {\n raw = body;\n } else if (body instanceof Uint8Array) {\n raw = Buffer.from(body).toString(\"utf-8\");\n } else if (body instanceof ArrayBuffer) {\n raw = Buffer.from(body).toString(\"utf-8\");\n } else {\n return undefined;\n }\n\n if (!raw.trim()) return undefined;\n try {\n return JSON.parse(raw);\n } catch {\n return undefined;\n }\n}\n\nfunction microsToUsd(estimatedAmount?: string): number {\n const micros = Number(estimatedAmount ?? \"\");\n if (!Number.isFinite(micros) || micros <= 0) return 0.01;\n return Number((micros / 1_000_000).toFixed(6));\n}\n\nfunction inferStatusCode(err: unknown): number {\n const msg = err instanceof Error ? err.message : String(err);\n const match = msg.match(/ClawCredit API Error:\\s*(\\d{3})\\s*-/i);\n if (match) return parseInt(match[1], 10);\n if (/payment required/i.test(msg)) return 402;\n if (/prequalification_pending/i.test(msg)) return 403;\n if (/unauthorized/i.test(msg)) return 401;\n return 502;\n}\n\nexport function createClawCreditFetch(config: ClawCreditConfig) {\n const serviceUrl = (config.baseUrl || DEFAULT_SERVICE_URL).replace(/\\/+$/, \"\");\n const chain = config.chain.toUpperCase();\n const asset = config.asset;\n const apiToken = config.apiToken.trim();\n\n if (!apiToken) {\n throw new Error(\"CLAWCREDIT_API_TOKEN is required for claw.credit payment mode\");\n }\n\n const credit = new ClawCredit({\n serviceUrl,\n apiToken,\n agent: config.agent,\n agentId: config.agentId,\n }) as SdkClient;\n\n return async (\n input: RequestInfo | URL,\n init?: RequestInit,\n preAuth?: PreAuthParams,\n ): Promise<Response> => {\n const upstreamUrl =\n typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url;\n const method = (init?.method || \"POST\").toUpperCase();\n const headers = headersToObject(init?.headers);\n const idempotencyKey = new Headers(init?.headers).get(\"idempotency-key\") || undefined;\n const requestBody = parseJsonBody(init?.body);\n const amountUsd = microsToUsd(preAuth?.estimatedAmount);\n\n try {\n const result = await withTrace(async () =>\n credit.pay({\n transaction: {\n recipient: upstreamUrl,\n amount: amountUsd,\n chain,\n asset,\n },\n request_body: {\n http: {\n url: upstreamUrl,\n method,\n headers,\n },\n body: requestBody,\n },\n context: {\n current_task: \"blockrun_inference_via_clawcredit_blockrun_gateway\",\n reasoning_process: \"Pay BlockRun inference through claw.credit SDK\",\n },\n idempotencyKey,\n }),\n );\n\n const merchantResponse =\n result && typeof result === \"object\" && \"merchant_response\" in result\n ? (result as { merchant_response?: unknown }).merchant_response\n : result;\n\n return new Response(JSON.stringify(merchantResponse ?? {}), {\n status: 200,\n headers: { \"content-type\": \"application/json\" },\n });\n } catch (err) {\n const status = inferStatusCode(err);\n const message = err instanceof Error ? err.message : String(err);\n return new Response(JSON.stringify({ error: message }), {\n status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n };\n}\n","import { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport type { AddressInfo } from \"node:net\";\nimport { createClawCreditFetch, type ClawCreditConfig } from \"./clawcredit.js\";\n\nexport type GatewayOptions = {\n port?: number;\n host?: string;\n blockrunApiBase?: string;\n clawCredit: ClawCreditConfig;\n defaultAmountUsd?: number;\n};\n\nexport type GatewayInstance = {\n port: number;\n baseUrl: string;\n close: () => Promise<void>;\n};\n\ntype CompletionRequest = {\n model?: string;\n stream?: boolean;\n max_tokens?: number;\n};\n\nfunction readBody(req: IncomingMessage): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on(\"data\", (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));\n req.on(\"end\", () => resolve(Buffer.concat(chunks)));\n req.on(\"error\", reject);\n });\n}\n\nfunction sendJson(res: ServerResponse, status: number, payload: unknown): void {\n const text = JSON.stringify(payload);\n res.writeHead(status, {\n \"content-type\": \"application/json\",\n \"content-length\": Buffer.byteLength(text).toString(),\n });\n res.end(text);\n}\n\nfunction extractMaxTokens(body: Buffer): number {\n try {\n const parsed = JSON.parse(body.toString(\"utf-8\")) as CompletionRequest;\n return parsed.max_tokens && Number.isFinite(parsed.max_tokens) ? parsed.max_tokens : 512;\n } catch {\n return 512;\n }\n}\n\nfunction normalizePayloadForGateway(body: Buffer): Buffer {\n try {\n const parsed = JSON.parse(body.toString(\"utf-8\")) as CompletionRequest;\n if (parsed.stream === true) parsed.stream = false;\n return Buffer.from(JSON.stringify(parsed));\n } catch {\n return body;\n }\n}\n\nexport async function startGateway(options: GatewayOptions): Promise<GatewayInstance> {\n const host = options.host ?? \"127.0.0.1\";\n const port = options.port ?? 3402;\n const apiBase = (options.blockrunApiBase ?? \"https://blockrun.ai/api\").replace(/\\/+$/, \"\");\n const defaultAmountUsd = Number.isFinite(options.defaultAmountUsd)\n ? Number(options.defaultAmountUsd)\n : 0.1;\n const payFetch = createClawCreditFetch(options.clawCredit);\n\n const server = createServer(async (req, res) => {\n if (req.url === \"/health\") {\n return sendJson(res, 200, {\n status: \"ok\",\n service: \"clawcredit-blockrun-gateway\",\n payment_mode: \"clawcredit\",\n });\n }\n\n if (req.url !== \"/v1/chat/completions\" || req.method !== \"POST\") {\n return sendJson(res, 404, { error: \"Not found\" });\n }\n\n try {\n const body = await readBody(req);\n const normalized = normalizePayloadForGateway(body);\n const maxTokens = extractMaxTokens(normalized);\n\n const estimatedMicros = String(\n Math.max(10_000, Math.round(defaultAmountUsd * 1_000_000 + maxTokens * 8)),\n );\n\n const upstreamUrl = `${apiBase}/v1/chat/completions`;\n\n const headers = new Headers();\n for (const [key, value] of Object.entries(req.headers)) {\n if (typeof value !== \"string\") continue;\n const lower = key.toLowerCase();\n if (lower === \"host\" || lower === \"content-length\" || lower === \"connection\") continue;\n headers.set(key, value);\n }\n if (!headers.has(\"content-type\")) {\n headers.set(\"content-type\", \"application/json\");\n }\n\n const upstream = await payFetch(\n upstreamUrl,\n {\n method: \"POST\",\n headers,\n body: new Uint8Array(normalized),\n },\n { estimatedAmount: estimatedMicros },\n );\n\n const responseBody = await upstream.text();\n res.writeHead(upstream.status, {\n \"content-type\": upstream.headers.get(\"content-type\") || \"application/json\",\n });\n res.end(responseBody);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n sendJson(res, 502, { error: msg });\n }\n });\n\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(port, host, () => {\n server.removeListener(\"error\", reject);\n resolve();\n });\n });\n\n const actualPort = (server.address() as AddressInfo).port;\n return {\n port: actualPort,\n baseUrl: `http://${host}:${actualPort}`,\n close: () => new Promise<void>((resolve, reject) => server.close((err) => (err ? reject(err) : resolve()))),\n };\n}\n"],"mappings":";AAAA,SAAS,YAAY,iBAAiB;AAEtC,IAAM,sBAAsB;AAiC5B,SAAS,gBAAgB,aAAmD;AAC1E,MAAI,CAAC,YAAa,QAAO,CAAC;AAC1B,QAAM,UAAU,IAAI,QAAQ,WAAW;AACvC,QAAM,MAA8B,CAAC;AACrC,UAAQ,QAAQ,CAAC,OAAO,QAAQ;AAC9B,UAAM,QAAQ,IAAI,YAAY;AAC9B,QAAI,UAAU,UAAU,UAAU,oBAAoB,UAAU,aAAc;AAC9E,QAAI,GAAG,IAAI;AAAA,EACb,CAAC;AACD,SAAO;AACT;AAEA,SAAS,cAAc,MAAoC;AACzD,MAAI,QAAQ,KAAM,QAAO;AAEzB,MAAI,MAAM;AACV,MAAI,OAAO,SAAS,UAAU;AAC5B,UAAM;AAAA,EACR,WAAW,gBAAgB,YAAY;AACrC,UAAM,OAAO,KAAK,IAAI,EAAE,SAAS,OAAO;AAAA,EAC1C,WAAW,gBAAgB,aAAa;AACtC,UAAM,OAAO,KAAK,IAAI,EAAE,SAAS,OAAO;AAAA,EAC1C,OAAO;AACL,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,IAAI,KAAK,EAAG,QAAO;AACxB,MAAI;AACF,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,YAAY,iBAAkC;AACrD,QAAM,SAAS,OAAO,mBAAmB,EAAE;AAC3C,MAAI,CAAC,OAAO,SAAS,MAAM,KAAK,UAAU,EAAG,QAAO;AACpD,SAAO,QAAQ,SAAS,KAAW,QAAQ,CAAC,CAAC;AAC/C;AAEA,SAAS,gBAAgB,KAAsB;AAC7C,QAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,QAAM,QAAQ,IAAI,MAAM,sCAAsC;AAC9D,MAAI,MAAO,QAAO,SAAS,MAAM,CAAC,GAAG,EAAE;AACvC,MAAI,oBAAoB,KAAK,GAAG,EAAG,QAAO;AAC1C,MAAI,4BAA4B,KAAK,GAAG,EAAG,QAAO;AAClD,MAAI,gBAAgB,KAAK,GAAG,EAAG,QAAO;AACtC,SAAO;AACT;AAEO,SAAS,sBAAsB,QAA0B;AAC9D,QAAM,cAAc,OAAO,WAAW,qBAAqB,QAAQ,QAAQ,EAAE;AAC7E,QAAM,QAAQ,OAAO,MAAM,YAAY;AACvC,QAAM,QAAQ,OAAO;AACrB,QAAM,WAAW,OAAO,SAAS,KAAK;AAEtC,MAAI,CAAC,UAAU;AACb,UAAM,IAAI,MAAM,+DAA+D;AAAA,EACjF;AAEA,QAAM,SAAS,IAAI,WAAW;AAAA,IAC5B;AAAA,IACA;AAAA,IACA,OAAO,OAAO;AAAA,IACd,SAAS,OAAO;AAAA,EAClB,CAAC;AAED,SAAO,OACL,OACA,MACA,YACsB;AACtB,UAAM,cACJ,OAAO,UAAU,WAAW,QAAQ,iBAAiB,MAAM,MAAM,OAAO,MAAM;AAChF,UAAM,UAAU,MAAM,UAAU,QAAQ,YAAY;AACpD,UAAM,UAAU,gBAAgB,MAAM,OAAO;AAC7C,UAAM,iBAAiB,IAAI,QAAQ,MAAM,OAAO,EAAE,IAAI,iBAAiB,KAAK;AAC5E,UAAM,cAAc,cAAc,MAAM,IAAI;AAC5C,UAAM,YAAY,YAAY,SAAS,eAAe;AAEtD,QAAI;AACF,YAAM,SAAS,MAAM;AAAA,QAAU,YAC7B,OAAO,IAAI;AAAA,UACT,aAAa;AAAA,YACX,WAAW;AAAA,YACX,QAAQ;AAAA,YACR;AAAA,YACA;AAAA,UACF;AAAA,UACA,cAAc;AAAA,YACZ,MAAM;AAAA,cACJ,KAAK;AAAA,cACL;AAAA,cACA;AAAA,YACF;AAAA,YACA,MAAM;AAAA,UACR;AAAA,UACA,SAAS;AAAA,YACP,cAAc;AAAA,YACd,mBAAmB;AAAA,UACrB;AAAA,UACA;AAAA,QACF,CAAC;AAAA,MACH;AAEA,YAAM,mBACJ,UAAU,OAAO,WAAW,YAAY,uBAAuB,SAC1D,OAA2C,oBAC5C;AAEN,aAAO,IAAI,SAAS,KAAK,UAAU,oBAAoB,CAAC,CAAC,GAAG;AAAA,QAC1D,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAChD,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,YAAM,SAAS,gBAAgB,GAAG;AAClC,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,aAAO,IAAI,SAAS,KAAK,UAAU,EAAE,OAAO,QAAQ,CAAC,GAAG;AAAA,QACtD;AAAA,QACA,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAChD,CAAC;AAAA,IACH;AAAA,EACF;AACF;;;AC9JA,SAAS,oBAA+D;AAwBxE,SAAS,SAAS,KAAuC;AACvD,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,SAAmB,CAAC;AAC1B,QAAI,GAAG,QAAQ,CAAC,UAAU,OAAO,KAAK,OAAO,SAAS,KAAK,IAAI,QAAQ,OAAO,KAAK,KAAK,CAAC,CAAC;AAC1F,QAAI,GAAG,OAAO,MAAM,QAAQ,OAAO,OAAO,MAAM,CAAC,CAAC;AAClD,QAAI,GAAG,SAAS,MAAM;AAAA,EACxB,CAAC;AACH;AAEA,SAAS,SAAS,KAAqB,QAAgB,SAAwB;AAC7E,QAAM,OAAO,KAAK,UAAU,OAAO;AACnC,MAAI,UAAU,QAAQ;AAAA,IACpB,gBAAgB;AAAA,IAChB,kBAAkB,OAAO,WAAW,IAAI,EAAE,SAAS;AAAA,EACrD,CAAC;AACD,MAAI,IAAI,IAAI;AACd;AAEA,SAAS,iBAAiB,MAAsB;AAC9C,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,KAAK,SAAS,OAAO,CAAC;AAChD,WAAO,OAAO,cAAc,OAAO,SAAS,OAAO,UAAU,IAAI,OAAO,aAAa;AAAA,EACvF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,2BAA2B,MAAsB;AACxD,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,KAAK,SAAS,OAAO,CAAC;AAChD,QAAI,OAAO,WAAW,KAAM,QAAO,SAAS;AAC5C,WAAO,OAAO,KAAK,KAAK,UAAU,MAAM,CAAC;AAAA,EAC3C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,aAAa,SAAmD;AACpF,QAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAM,WAAW,QAAQ,mBAAmB,2BAA2B,QAAQ,QAAQ,EAAE;AACzF,QAAM,mBAAmB,OAAO,SAAS,QAAQ,gBAAgB,IAC7D,OAAO,QAAQ,gBAAgB,IAC/B;AACJ,QAAM,WAAW,sBAAsB,QAAQ,UAAU;AAEzD,QAAM,SAAS,aAAa,OAAO,KAAK,QAAQ;AAC9C,QAAI,IAAI,QAAQ,WAAW;AACzB,aAAO,SAAS,KAAK,KAAK;AAAA,QACxB,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,cAAc;AAAA,MAChB,CAAC;AAAA,IACH;AAEA,QAAI,IAAI,QAAQ,0BAA0B,IAAI,WAAW,QAAQ;AAC/D,aAAO,SAAS,KAAK,KAAK,EAAE,OAAO,YAAY,CAAC;AAAA,IAClD;AAEA,QAAI;AACF,YAAM,OAAO,MAAM,SAAS,GAAG;AAC/B,YAAM,aAAa,2BAA2B,IAAI;AAClD,YAAM,YAAY,iBAAiB,UAAU;AAE7C,YAAM,kBAAkB;AAAA,QACtB,KAAK,IAAI,KAAQ,KAAK,MAAM,mBAAmB,MAAY,YAAY,CAAC,CAAC;AAAA,MAC3E;AAEA,YAAM,cAAc,GAAG,OAAO;AAE9B,YAAM,UAAU,IAAI,QAAQ;AAC5B,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,OAAO,GAAG;AACtD,YAAI,OAAO,UAAU,SAAU;AAC/B,cAAM,QAAQ,IAAI,YAAY;AAC9B,YAAI,UAAU,UAAU,UAAU,oBAAoB,UAAU,aAAc;AAC9E,gBAAQ,IAAI,KAAK,KAAK;AAAA,MACxB;AACA,UAAI,CAAC,QAAQ,IAAI,cAAc,GAAG;AAChC,gBAAQ,IAAI,gBAAgB,kBAAkB;AAAA,MAChD;AAEA,YAAM,WAAW,MAAM;AAAA,QACrB;AAAA,QACA;AAAA,UACE,QAAQ;AAAA,UACR;AAAA,UACA,MAAM,IAAI,WAAW,UAAU;AAAA,QACjC;AAAA,QACA,EAAE,iBAAiB,gBAAgB;AAAA,MACrC;AAEA,YAAM,eAAe,MAAM,SAAS,KAAK;AACzC,UAAI,UAAU,SAAS,QAAQ;AAAA,QAC7B,gBAAgB,SAAS,QAAQ,IAAI,cAAc,KAAK;AAAA,MAC1D,CAAC;AACD,UAAI,IAAI,YAAY;AAAA,IACtB,SAAS,KAAK;AACZ,YAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,eAAS,KAAK,KAAK,EAAE,OAAO,IAAI,CAAC;AAAA,IACnC;AAAA,EACF,CAAC;AAED,QAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,WAAO,KAAK,SAAS,MAAM;AAC3B,WAAO,OAAO,MAAM,MAAM,MAAM;AAC9B,aAAO,eAAe,SAAS,MAAM;AACrC,cAAQ;AAAA,IACV,CAAC;AAAA,EACH,CAAC;AAED,QAAM,aAAc,OAAO,QAAQ,EAAkB;AACrD,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS,UAAU,IAAI,IAAI,UAAU;AAAA,IACrC,OAAO,MAAM,IAAI,QAAc,CAAC,SAAS,WAAW,OAAO,MAAM,CAAC,QAAS,MAAM,OAAO,GAAG,IAAI,QAAQ,CAAE,CAAC;AAAA,EAC5G;AACF;","names":[]}
|