@tempoxyz/patron 0.0.16 → 0.0.18

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,117 +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
14
  loadIssuer,
8
15
  needsRefill,
9
- normalizePayee,
10
- refillSize,
11
- resolveSettings,
12
- settingsFor,
13
- spend,
14
- startRefill
15
- } from "./chunk-4Z6ZIXMK.js";
16
+ redeem,
17
+ refillSize
18
+ } from "./chunk-XJOIDBXC.js";
19
+ import {
20
+ clampTokensPerPage,
21
+ settingsFor
22
+ } from "./chunk-CF4LJZLC.js";
16
23
  import {
17
24
  readConfig,
18
25
  readUsage,
19
26
  updateUsage,
20
27
  withLock,
21
28
  writePrivateFile
22
- } from "./chunk-FXE4CGY7.js";
29
+ } from "./chunk-RDEPJ6XD.js";
30
+ import "./chunk-GTMLTZRN.js";
23
31
 
24
32
  // src/hook.ts
25
33
  import { homedir } from "node:os";
26
34
 
27
- // src/hook-input.ts
28
- var MAX_DOMAINS_PER_HOOK = 10;
29
- var MAX_DEPTH = 6;
30
- var MAX_URLS = 50;
31
- var FETCH_TOOLS = /* @__PURE__ */ new Set(["WebFetch", "read_web_page"]);
32
- var SEARCH_TOOLS = /* @__PURE__ */ new Set(["WebSearch", "web_search"]);
33
- function hookUrls(payload) {
34
- if (!isRecord(payload) || typeof payload.tool_name !== "string") return [];
35
- const tool = payload.tool_name;
36
- const response = "tool_response" in payload ? payload.tool_response : jsonValue(payload.tool_output);
37
- if (FETCH_TOOLS.has(tool)) {
38
- const input = payload.tool_input;
39
- if (isRecord(input) && typeof input.url === "string") return [input.url];
40
- const urls2 = [];
41
- collectUrls(response, urls2, 0);
42
- return urls2.slice(0, 1);
43
- }
44
- if (!SEARCH_TOOLS.has(tool)) return [];
45
- const urls = [];
46
- collectUrls(response, urls, 0);
47
- if (urls.length === 0 && tool === "web_search") collectTextUrls(response, urls, 0);
48
- return urls;
49
- }
50
- function hookDomains(payload) {
51
- const domains = [];
52
- for (const url of hookUrls(payload)) {
53
- const domain = normalizePayee(url);
54
- if (domain && !domains.includes(domain)) domains.push(domain);
55
- if (domains.length >= MAX_DOMAINS_PER_HOOK) break;
56
- }
57
- return domains;
58
- }
59
- function turnEnd(payload) {
60
- if (!isRecord(payload)) return void 0;
61
- if (payload.hook_event_name === "Stop" && typeof payload.turn_id === "string" && typeof payload.transcript_path === "string")
62
- return { transcriptPath: payload.transcript_path };
63
- if (payload.type === "agent-turn-complete" && typeof payload["thread-id"] === "string")
64
- return { codexThreadId: payload["thread-id"] };
65
- return void 0;
66
- }
67
- function isCursorPayload(payload) {
68
- return isRecord(payload) && typeof payload.cursor_version === "string";
69
- }
70
- function parseHookInput(raw) {
71
- try {
72
- return JSON.parse(raw);
73
- } catch {
74
- return void 0;
75
- }
76
- }
77
- function jsonValue(value) {
78
- if (typeof value !== "string") return value;
79
- try {
80
- return JSON.parse(value);
81
- } catch {
82
- return value;
83
- }
84
- }
85
- function collectUrls(value, urls, depth) {
86
- if (depth > MAX_DEPTH || urls.length >= MAX_URLS) return;
87
- if (Array.isArray(value)) {
88
- for (const item of value) collectUrls(item, urls, depth + 1);
89
- return;
90
- }
91
- if (!isRecord(value)) return;
92
- for (const [key, item] of Object.entries(value)) {
93
- if (key === "url" && typeof item === "string") urls.push(item);
94
- else collectUrls(item, urls, depth + 1);
95
- }
96
- }
97
- function collectTextUrls(value, urls, depth) {
98
- if (depth > MAX_DEPTH || urls.length >= MAX_URLS) return;
99
- if (typeof value === "string") {
100
- for (const match of value.matchAll(/https?:\/\/[^\s"'<>()[\]`]+/g)) {
101
- if (urls.length >= MAX_URLS) return;
102
- urls.push(match[0].replace(/[.,;:!?]+$/, ""));
103
- }
104
- return;
105
- }
106
- if (Array.isArray(value))
107
- for (const item of value) collectTextUrls(item, urls, depth + 1);
108
- else if (isRecord(value))
109
- for (const item of Object.values(value)) collectTextUrls(item, urls, depth + 1);
110
- }
111
- function isRecord(value) {
112
- return typeof value === "object" && value !== null && !Array.isArray(value);
113
- }
114
-
115
35
  // src/transcript.ts
116
36
  import { createHash } from "node:crypto";
117
37
  import { mkdir, open, readdir, readFile, rm, stat } from "node:fs/promises";
@@ -134,7 +54,7 @@ function codexRolloutUrls(lines) {
134
54
  } catch {
135
55
  continue;
136
56
  }
137
- if (!isRecord2(record) || !isRecord2(record.payload)) continue;
57
+ if (!isRecord(record) || !isRecord(record.payload)) continue;
138
58
  const payload = record.payload;
139
59
  if (record.type === "response_item") responseItemUrls(payload, urls);
140
60
  else if (record.type === "event_msg") eventUrls(payload, urls);
@@ -162,7 +82,7 @@ function responseItemUrls(item, urls) {
162
82
  }
163
83
  function eventUrls(event, urls) {
164
84
  const search = event.type === "web_search_end" ? event : event.type === "item_completed" && field(event.item, "type") === "WebSearch" ? event.item : void 0;
165
- if (!isRecord2(search)) return;
85
+ if (!isRecord(search)) return;
166
86
  pushUrl(urls, field(search.action, "url"));
167
87
  for (const result of asArray(search.results)) pushUrl(urls, field(result, "url"));
168
88
  }
@@ -179,7 +99,7 @@ async function takeTranscriptDomains(home, transcriptPath2, options) {
179
99
  const known = /* @__PURE__ */ new Set([...state.paid, ...state.pending]);
180
100
  const pending = [...state.pending];
181
101
  for (const url of codexRolloutUrls(lines)) {
182
- const domain = normalizePayee(url);
102
+ const domain = payableDomain(url);
183
103
  if (domain && !known.has(domain)) {
184
104
  known.add(domain);
185
105
  pending.push(domain);
@@ -257,7 +177,7 @@ async function readState(path) {
257
177
  } catch {
258
178
  return void 0;
259
179
  }
260
- if (!isRecord2(value) || typeof value.offset !== "number") return void 0;
180
+ if (!isRecord(value) || typeof value.offset !== "number") return void 0;
261
181
  const strings = (list) => asArray(list).filter((item) => typeof item === "string");
262
182
  return {
263
183
  offset: value.offset,
@@ -278,17 +198,17 @@ function pushUrl(urls, value) {
278
198
  if (typeof value === "string" && /^https?:\/\//i.test(value)) urls.push(value);
279
199
  }
280
200
  function field(value, key) {
281
- return isRecord2(value) ? value[key] : void 0;
201
+ return isRecord(value) ? value[key] : void 0;
282
202
  }
283
203
  function asArray(value) {
284
204
  return Array.isArray(value) ? value : [];
285
205
  }
286
- function isRecord2(value) {
206
+ function isRecord(value) {
287
207
  return typeof value === "object" && value !== null && !Array.isArray(value);
288
208
  }
289
209
 
290
210
  // src/hook.ts
291
- var HOOK_TIMEOUT_MS = 2e3;
211
+ var PAY_TIMEOUT_MS = 2e4;
292
212
  var ABORT_MARGIN_MS = 500;
293
213
  async function runHook(dependencies, timeoutMs = HOOK_TIMEOUT_MS) {
294
214
  let timer;
@@ -333,31 +253,20 @@ async function payReads(dependencies, deadline) {
333
253
  const issuer = await loadIssuer(settings.home, settings.gatewayUrl, {
334
254
  fetch: boundedFetch
335
255
  });
336
- const paid = [];
337
- let empty = false;
338
256
  const perPage = clampTokensPerPage(config.tokensPerPage);
339
- let pending = domains.slice();
340
- rounds: for (let round = 0; round < perPage && pending.length > 0; round++) {
341
- const next = [];
342
- for (const domain of pending) {
343
- if (Date.now() >= deadline) break rounds;
344
- const result = await spend({
345
- domain,
346
- fetch: boundedFetch,
347
- issuer,
348
- relayUrl: settings.relayUrl,
349
- store
350
- }).catch(() => null);
351
- if (result === void 0) {
352
- empty = true;
353
- break rounds;
354
- }
355
- if (!result?.ok) continue;
356
- if (round === 0) paid.push(result.domain || domain);
357
- next.push(domain);
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
358
267
  }
359
- pending = next;
360
- }
268
+ );
269
+ await store.add(unspent);
361
270
  const now = dependencies.now?.() ?? /* @__PURE__ */ new Date();
362
271
  if (paid.length > 0)
363
272
  await updateUsage(
@@ -369,7 +278,7 @@ async function payReads(dependencies, deadline) {
369
278
  },
370
279
  now
371
280
  );
372
- 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;
373
282
  if (refill) dependencies.startRefill();
374
283
  return {
375
284
  paid,
@@ -377,6 +286,38 @@ async function payReads(dependencies, deadline) {
377
286
  ...empty && paid.length === 0 ? { skipped: "no-tokens" } : {}
378
287
  };
379
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
+ }
380
321
  function withDeadline(fetchFn, deadline) {
381
322
  return (input, init) => fetchFn(input, {
382
323
  ...init,
@@ -387,43 +328,7 @@ function transcriptPath(turn, agents = { env: process.env, home: homedir() }) {
387
328
  if ("transcriptPath" in turn) return Promise.resolve(turn.transcriptPath);
388
329
  return findCodexRollout(codexHome(agents.env, agents.home), turn.codexThreadId);
389
330
  }
390
-
391
- // src/hook-process.ts
392
- var MAX_INPUT_BYTES = 8 * 1024 * 1024;
393
- async function runHookProcess(argument = process.argv[3]) {
394
- setTimeout(() => process.exit(0), HOOK_TIMEOUT_MS + 500).unref();
395
- const raw = argument ?? await readStdin(HOOK_TIMEOUT_MS / 2);
396
- if (isCursorPayload(parseHookInput(raw))) process.stdout.write("{}\n");
397
- await runHook({
398
- readInput: async () => raw,
399
- settings: resolveSettings(),
400
- startRefill: () => startRefill()
401
- });
402
- }
403
- function readStdin(timeoutMs) {
404
- return new Promise((resolve) => {
405
- const chunks = [];
406
- let size = 0;
407
- const finish = () => {
408
- clearTimeout(timer);
409
- process.stdin.removeAllListeners();
410
- process.stdin.pause();
411
- resolve(Buffer.concat(chunks).toString("utf8"));
412
- };
413
- const timer = setTimeout(finish, timeoutMs);
414
- process.stdin.on("data", (chunk) => {
415
- size += chunk.length;
416
- if (size > MAX_INPUT_BYTES) {
417
- chunks.length = 0;
418
- finish();
419
- return;
420
- }
421
- chunks.push(chunk);
422
- });
423
- process.stdin.on("end", finish);
424
- process.stdin.on("error", finish);
425
- });
426
- }
427
331
  export {
428
- runHookProcess
332
+ PAY_TIMEOUT_MS,
333
+ runHook
429
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-CCXQ4T4L.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-CCXQ4T4L.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.16",
3
+ "version": "0.0.18",
4
4
  "description": "Pay the websites your AI agents read, privately",
5
5
  "type": "module",
6
6
  "bin": {