@tempoxyz/patron 0.0.17 → 0.0.19

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.
@@ -1,128 +1,37 @@
1
1
  import { createRequire as __patronCreateRequire } from 'node:module'
2
2
  const require = __patronCreateRequire(import.meta.url)
3
+ import {
4
+ HOOK_TIMEOUT_MS,
5
+ MAX_DOMAINS_PER_HOOK,
6
+ hookDomains,
7
+ parseHookInput,
8
+ payableDomain,
9
+ turnEnd
10
+ } from "./chunk-RJRJWNSQ.js";
3
11
  import {
4
12
  FileTokenStore,
5
- clampTokensPerPage,
6
13
  codexHome,
7
- isExcludedByDefault,
8
14
  loadIssuer,
9
15
  needsRefill,
10
- normalizePayee,
11
- refillSize,
12
- resolveSettings,
13
- settingsFor,
14
- spend,
15
- startPayment,
16
- startRefill
17
- } from "./chunk-GYL4GCNU.js";
16
+ redeem,
17
+ refillSize
18
+ } from "./chunk-WG4BTHH7.js";
19
+ import {
20
+ clampTokensPerPage,
21
+ settingsFor
22
+ } from "./chunk-CF4LJZLC.js";
18
23
  import {
19
24
  readConfig,
20
25
  readUsage,
21
26
  updateUsage,
22
27
  withLock,
23
28
  writePrivateFile
24
- } from "./chunk-FXE4CGY7.js";
29
+ } from "./chunk-RDEPJ6XD.js";
30
+ import "./chunk-GTMLTZRN.js";
25
31
 
26
32
  // src/hook.ts
27
33
  import { homedir } from "node:os";
28
34
 
29
- // src/hook-input.ts
30
- var MAX_DOMAINS_PER_HOOK = 10;
31
- var MAX_DEPTH = 6;
32
- var MAX_URLS = 50;
33
- var FETCH_TOOLS = /* @__PURE__ */ new Set(["WebFetch", "read_web_page"]);
34
- var SEARCH_TOOLS = /* @__PURE__ */ new Set(["WebSearch", "web_search"]);
35
- function hookUrls(payload) {
36
- if (!isRecord(payload) || typeof payload.tool_name !== "string") return [];
37
- const tool = payload.tool_name;
38
- const response = "tool_response" in payload ? payload.tool_response : jsonValue(payload.tool_output);
39
- if (FETCH_TOOLS.has(tool)) {
40
- if (!succeeded(response)) return [];
41
- const input = payload.tool_input;
42
- if (isRecord(input) && typeof input.url === "string") return [input.url];
43
- const urls2 = [];
44
- collectUrls(response, urls2, 0);
45
- return urls2.slice(0, 1);
46
- }
47
- if (!SEARCH_TOOLS.has(tool)) return [];
48
- const urls = [];
49
- collectUrls(response, urls, 0);
50
- if (urls.length === 0 && tool === "web_search") collectTextUrls(response, urls, 0);
51
- return urls;
52
- }
53
- function hookDomains(payload) {
54
- const domains = [];
55
- for (const url of hookUrls(payload)) {
56
- const domain = payableDomain(url);
57
- if (domain && !domains.includes(domain)) domains.push(domain);
58
- if (domains.length >= MAX_DOMAINS_PER_HOOK) break;
59
- }
60
- return domains;
61
- }
62
- function payableDomain(url) {
63
- return isExcludedByDefault(url) ? null : normalizePayee(url);
64
- }
65
- function succeeded(response) {
66
- if (!isRecord(response)) return true;
67
- const status = response.code ?? response.status ?? response.statusCode;
68
- return typeof status !== "number" || status >= 200 && status < 300;
69
- }
70
- function turnEnd(payload) {
71
- if (!isRecord(payload)) return void 0;
72
- if (payload.hook_event_name === "Stop" && typeof payload.turn_id === "string" && typeof payload.transcript_path === "string")
73
- return { transcriptPath: payload.transcript_path };
74
- if (payload.type === "agent-turn-complete" && typeof payload["thread-id"] === "string")
75
- return { codexThreadId: payload["thread-id"] };
76
- return void 0;
77
- }
78
- function isCursorPayload(payload) {
79
- return isRecord(payload) && typeof payload.cursor_version === "string";
80
- }
81
- function parseHookInput(raw) {
82
- try {
83
- return JSON.parse(raw);
84
- } catch {
85
- return void 0;
86
- }
87
- }
88
- function jsonValue(value) {
89
- if (typeof value !== "string") return value;
90
- try {
91
- return JSON.parse(value);
92
- } catch {
93
- return value;
94
- }
95
- }
96
- function collectUrls(value, urls, depth) {
97
- if (depth > MAX_DEPTH || urls.length >= MAX_URLS) return;
98
- if (Array.isArray(value)) {
99
- for (const item of value) collectUrls(item, urls, depth + 1);
100
- return;
101
- }
102
- if (!isRecord(value)) return;
103
- for (const [key, item] of Object.entries(value)) {
104
- if (key === "url" && typeof item === "string") urls.push(item);
105
- else collectUrls(item, urls, depth + 1);
106
- }
107
- }
108
- function collectTextUrls(value, urls, depth) {
109
- if (depth > MAX_DEPTH || urls.length >= MAX_URLS) return;
110
- if (typeof value === "string") {
111
- for (const match of value.matchAll(/https?:\/\/[^\s"'<>()[\]`]+/g)) {
112
- if (urls.length >= MAX_URLS) return;
113
- urls.push(match[0].replace(/[.,;:!?]+$/, ""));
114
- }
115
- return;
116
- }
117
- if (Array.isArray(value))
118
- for (const item of value) collectTextUrls(item, urls, depth + 1);
119
- else if (isRecord(value))
120
- for (const item of Object.values(value)) collectTextUrls(item, urls, depth + 1);
121
- }
122
- function isRecord(value) {
123
- return typeof value === "object" && value !== null && !Array.isArray(value);
124
- }
125
-
126
35
  // src/transcript.ts
127
36
  import { createHash } from "node:crypto";
128
37
  import { mkdir, open, readdir, readFile, rm, stat } from "node:fs/promises";
@@ -145,7 +54,7 @@ function codexRolloutUrls(lines) {
145
54
  } catch {
146
55
  continue;
147
56
  }
148
- if (!isRecord2(record) || !isRecord2(record.payload)) continue;
57
+ if (!isRecord(record) || !isRecord(record.payload)) continue;
149
58
  const payload = record.payload;
150
59
  if (record.type === "response_item") responseItemUrls(payload, urls);
151
60
  else if (record.type === "event_msg") eventUrls(payload, urls);
@@ -173,7 +82,7 @@ function responseItemUrls(item, urls) {
173
82
  }
174
83
  function eventUrls(event, urls) {
175
84
  const search = event.type === "web_search_end" ? event : event.type === "item_completed" && field(event.item, "type") === "WebSearch" ? event.item : void 0;
176
- if (!isRecord2(search)) return;
85
+ if (!isRecord(search)) return;
177
86
  pushUrl(urls, field(search.action, "url"));
178
87
  for (const result of asArray(search.results)) pushUrl(urls, field(result, "url"));
179
88
  }
@@ -268,7 +177,7 @@ async function readState(path) {
268
177
  } catch {
269
178
  return void 0;
270
179
  }
271
- if (!isRecord2(value) || typeof value.offset !== "number") return void 0;
180
+ if (!isRecord(value) || typeof value.offset !== "number") return void 0;
272
181
  const strings = (list) => asArray(list).filter((item) => typeof item === "string");
273
182
  return {
274
183
  offset: value.offset,
@@ -289,17 +198,16 @@ function pushUrl(urls, value) {
289
198
  if (typeof value === "string" && /^https?:\/\//i.test(value)) urls.push(value);
290
199
  }
291
200
  function field(value, key) {
292
- return isRecord2(value) ? value[key] : void 0;
201
+ return isRecord(value) ? value[key] : void 0;
293
202
  }
294
203
  function asArray(value) {
295
204
  return Array.isArray(value) ? value : [];
296
205
  }
297
- function isRecord2(value) {
206
+ function isRecord(value) {
298
207
  return typeof value === "object" && value !== null && !Array.isArray(value);
299
208
  }
300
209
 
301
210
  // src/hook.ts
302
- var HOOK_TIMEOUT_MS = 2e3;
303
211
  var PAY_TIMEOUT_MS = 2e4;
304
212
  var ABORT_MARGIN_MS = 500;
305
213
  async function runHook(dependencies, timeoutMs = HOOK_TIMEOUT_MS) {
@@ -345,34 +253,20 @@ async function payReads(dependencies, deadline) {
345
253
  const issuer = await loadIssuer(settings.home, settings.gatewayUrl, {
346
254
  fetch: boundedFetch
347
255
  });
348
- const paid = [];
349
- let empty = false;
350
256
  const perPage = clampTokensPerPage(config.tokensPerPage);
351
- let pending = domains.slice();
352
- for (let round = 0; round < perPage && pending.length > 0; round++) {
353
- if (Date.now() >= deadline) break;
354
- const results = await Promise.all(
355
- pending.map(
356
- (domain) => spend({
357
- domain,
358
- fetch: boundedFetch,
359
- issuer,
360
- relayUrl: settings.relayUrl,
361
- store
362
- }).catch(() => null)
363
- )
364
- );
365
- const next = [];
366
- results.forEach((result, index) => {
367
- const domain = pending[index];
368
- if (result === void 0) empty = true;
369
- if (!result?.ok) return;
370
- if (round === 0) paid.push(result.domain || domain);
371
- next.push(domain);
372
- });
373
- if (empty) break;
374
- pending = next;
375
- }
257
+ const taken = await store.takeWithRemaining(domains.length * perPage, domains.length);
258
+ const { empty, paid, unspent } = await redeemRounds(
259
+ taken.tokens,
260
+ domains,
261
+ perPage,
262
+ deadline,
263
+ {
264
+ fetch: boundedFetch,
265
+ issuer,
266
+ relayUrl: settings.relayUrl
267
+ }
268
+ );
269
+ await store.add(unspent);
376
270
  const now = dependencies.now?.() ?? /* @__PURE__ */ new Date();
377
271
  if (paid.length > 0)
378
272
  await updateUsage(
@@ -384,7 +278,7 @@ async function payReads(dependencies, deadline) {
384
278
  },
385
279
  now
386
280
  );
387
- const refill = needsRefill(await store.count()) && refillSize(config, await readUsage(settings.home, now)) > 0;
281
+ const refill = needsRefill(taken.remaining + unspent.length) && refillSize(config, await readUsage(settings.home, now)) > 0;
388
282
  if (refill) dependencies.startRefill();
389
283
  return {
390
284
  paid,
@@ -392,6 +286,38 @@ async function payReads(dependencies, deadline) {
392
286
  ...empty && paid.length === 0 ? { skipped: "no-tokens" } : {}
393
287
  };
394
288
  }
289
+ async function redeemRounds(tokens, domains, perPage, deadline, context) {
290
+ const paid = [];
291
+ const failed2 = [];
292
+ let used = 0;
293
+ let pending = domains.slice();
294
+ let empty = tokens.length === 0;
295
+ for (let round = 0; round < perPage && pending.length > 0 && !empty; round++) {
296
+ if (Date.now() >= deadline) break;
297
+ const batch = pending.slice(0, tokens.length - used);
298
+ if (batch.length < pending.length) empty = true;
299
+ const start = used;
300
+ used += batch.length;
301
+ const results = await Promise.all(
302
+ batch.map(async (domain, index) => {
303
+ const token = tokens[start + index];
304
+ try {
305
+ return await redeem({ ...context, domain, token });
306
+ } catch {
307
+ failed2.push(token);
308
+ return void 0;
309
+ }
310
+ })
311
+ );
312
+ pending = batch.filter((domain, index) => {
313
+ const result = results[index];
314
+ if (!result?.ok) return false;
315
+ if (round === 0) paid.push(result.domain || domain);
316
+ return true;
317
+ });
318
+ }
319
+ return { empty, paid, unspent: [...failed2, ...tokens.slice(used)] };
320
+ }
395
321
  function withDeadline(fetchFn, deadline) {
396
322
  return (input, init) => fetchFn(input, {
397
323
  ...init,
@@ -402,59 +328,7 @@ function transcriptPath(turn, agents = { env: process.env, home: homedir() }) {
402
328
  if ("transcriptPath" in turn) return Promise.resolve(turn.transcriptPath);
403
329
  return findCodexRollout(codexHome(agents.env, agents.home), turn.codexThreadId);
404
330
  }
405
-
406
- // src/hook-process.ts
407
- var MAX_INPUT_BYTES = 8 * 1024 * 1024;
408
- async function runHookProcess(argument = process.argv[3]) {
409
- setTimeout(() => process.exit(0), HOOK_TIMEOUT_MS + 500).unref();
410
- const raw = argument ?? await readStdin(HOOK_TIMEOUT_MS / 2);
411
- const payload = parseHookInput(raw);
412
- if (isCursorPayload(payload)) process.stdout.write("{}\n");
413
- if (!turnEnd(payload) && hookDomains(payload).length === 0) return;
414
- if (await startPayment(raw)) return;
415
- await runHook({
416
- readInput: async () => raw,
417
- settings: resolveSettings(),
418
- startRefill: () => startRefill()
419
- });
420
- }
421
- async function runPayProcess() {
422
- setTimeout(() => process.exit(0), PAY_TIMEOUT_MS + 1e3).unref();
423
- const raw = await readStdin(HOOK_TIMEOUT_MS);
424
- await runHook(
425
- {
426
- readInput: async () => raw,
427
- settings: resolveSettings(),
428
- startRefill: () => startRefill()
429
- },
430
- PAY_TIMEOUT_MS
431
- );
432
- }
433
- function readStdin(timeoutMs) {
434
- return new Promise((resolve) => {
435
- const chunks = [];
436
- let size = 0;
437
- const finish = () => {
438
- clearTimeout(timer);
439
- process.stdin.removeAllListeners();
440
- process.stdin.pause();
441
- resolve(Buffer.concat(chunks).toString("utf8"));
442
- };
443
- const timer = setTimeout(finish, timeoutMs);
444
- process.stdin.on("data", (chunk) => {
445
- size += chunk.length;
446
- if (size > MAX_INPUT_BYTES) {
447
- chunks.length = 0;
448
- finish();
449
- return;
450
- }
451
- chunks.push(chunk);
452
- });
453
- process.stdin.on("end", finish);
454
- process.stdin.on("error", finish);
455
- });
456
- }
457
331
  export {
458
- runHookProcess,
459
- runPayProcess
332
+ PAY_TIMEOUT_MS,
333
+ runHook
460
334
  };
@@ -0,0 +1,75 @@
1
+ import { createRequire as __patronCreateRequire } from 'node:module'
2
+ const require = __patronCreateRequire(import.meta.url)
3
+ import {
4
+ HOOK_TIMEOUT_MS,
5
+ hookDomains,
6
+ isCursorPayload,
7
+ parseHookInput,
8
+ turnEnd
9
+ } from "./chunk-RJRJWNSQ.js";
10
+ import {
11
+ startPayment,
12
+ startRefill
13
+ } from "./chunk-CV2WYKM7.js";
14
+ import {
15
+ resolveSettings
16
+ } from "./chunk-CF4LJZLC.js";
17
+ import "./chunk-GTMLTZRN.js";
18
+
19
+ // src/hook-process.ts
20
+ var MAX_INPUT_BYTES = 8 * 1024 * 1024;
21
+ async function runHookProcess(argument = process.argv[3]) {
22
+ setTimeout(() => process.exit(0), HOOK_TIMEOUT_MS + 500).unref();
23
+ const raw = argument ?? await readStdin(HOOK_TIMEOUT_MS / 2);
24
+ const payload = parseHookInput(raw);
25
+ if (isCursorPayload(payload)) process.stdout.write("{}\n");
26
+ if (!turnEnd(payload) && hookDomains(payload).length === 0) return;
27
+ if (await startPayment(raw)) return;
28
+ const { runHook } = await import("./hook-6DRXW2XN.js");
29
+ await runHook({
30
+ readInput: async () => raw,
31
+ settings: resolveSettings(),
32
+ startRefill: () => startRefill()
33
+ });
34
+ }
35
+ async function runPayProcess() {
36
+ const { PAY_TIMEOUT_MS, runHook } = await import("./hook-6DRXW2XN.js");
37
+ setTimeout(() => process.exit(0), PAY_TIMEOUT_MS + 1e3).unref();
38
+ const raw = await readStdin(HOOK_TIMEOUT_MS);
39
+ await runHook(
40
+ {
41
+ readInput: async () => raw,
42
+ settings: resolveSettings(),
43
+ startRefill: () => startRefill()
44
+ },
45
+ PAY_TIMEOUT_MS
46
+ );
47
+ }
48
+ function readStdin(timeoutMs) {
49
+ return new Promise((resolve) => {
50
+ const chunks = [];
51
+ let size = 0;
52
+ const finish = () => {
53
+ clearTimeout(timer);
54
+ process.stdin.removeAllListeners();
55
+ process.stdin.pause();
56
+ resolve(Buffer.concat(chunks).toString("utf8"));
57
+ };
58
+ const timer = setTimeout(finish, timeoutMs);
59
+ process.stdin.on("data", (chunk) => {
60
+ size += chunk.length;
61
+ if (size > MAX_INPUT_BYTES) {
62
+ chunks.length = 0;
63
+ finish();
64
+ return;
65
+ }
66
+ chunks.push(chunk);
67
+ });
68
+ process.stdin.on("end", finish);
69
+ process.stdin.on("error", finish);
70
+ });
71
+ }
72
+ export {
73
+ runHookProcess,
74
+ runPayProcess
75
+ };
@@ -2,7 +2,8 @@ import { createRequire as __patronCreateRequire } from 'node:module'
2
2
  const require = __patronCreateRequire(import.meta.url)
3
3
  import {
4
4
  statePaths
5
- } from "./chunk-FXE4CGY7.js";
5
+ } from "./chunk-RDEPJ6XD.js";
6
+ import "./chunk-GTMLTZRN.js";
6
7
 
7
8
  // src/wallet.ts
8
9
  import { Store } from "accounts";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tempoxyz/patron",
3
- "version": "0.0.17",
3
+ "version": "0.0.19",
4
4
  "description": "Pay the websites your AI agents read, privately",
5
5
  "type": "module",
6
6
  "bin": {