hippo-memory 1.57.0 → 1.58.0
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/README.md +11 -0
- package/dist/agent-memories/claude-code.js +1 -1
- package/dist/agent-memories/gemini.js +1 -1
- package/dist/api-errors.d.ts +27 -0
- package/dist/api-errors.js +37 -0
- package/dist/api.d.ts +5 -5
- package/dist/api.js +40 -47
- package/dist/audit.d.ts +4 -0
- package/dist/audit.js +11 -0
- package/dist/autolearn.d.ts +1 -1
- package/dist/autolearn.js +7 -5
- package/dist/capture-contract.d.ts +47 -0
- package/dist/capture-contract.js +49 -0
- package/dist/capture-error.js +2 -1
- package/dist/capture.d.ts +0 -13
- package/dist/capture.js +5 -66
- package/dist/cli/shared.js +10 -6
- package/dist/cli.js +100 -39
- package/dist/client.js +9 -0
- package/dist/codex-patch.js +1 -1
- package/dist/compaction-record.d.ts +1 -1
- package/dist/compaction-record.js +3 -2
- package/dist/config.d.ts +5 -0
- package/dist/config.js +17 -0
- package/dist/connectors/github/dlq.js +5 -2
- package/dist/connectors/github/octokit-client.js +4 -2
- package/dist/connectors/slack/dlq.js +6 -2
- package/dist/connectors/slack/web-client.js +7 -5
- package/dist/consolidate.d.ts +10 -0
- package/dist/consolidate.js +36 -34
- package/dist/customer-notes.js +14 -13
- package/dist/dag.js +3 -2
- package/dist/dashboard.js +1 -1
- package/dist/db.d.ts +12 -0
- package/dist/db.js +62 -1
- package/dist/decisions.js +9 -8
- package/dist/doctor.js +5 -0
- package/dist/embedding-provider.js +3 -3
- package/dist/embeddings.d.ts +4 -4
- package/dist/embeddings.js +72 -16
- package/dist/extract.js +3 -2
- package/dist/http-retry.d.ts +21 -0
- package/dist/http-retry.js +50 -0
- package/dist/http-util.d.ts +8 -0
- package/dist/http-util.js +10 -0
- package/dist/importers.d.ts +2 -0
- package/dist/importers.js +16 -5
- package/dist/incidents.js +11 -10
- package/dist/judgment.js +10 -17
- package/dist/log.d.ts +25 -0
- package/dist/log.js +48 -0
- package/dist/mcp/server.js +52 -24
- package/dist/mcp/tool-args.d.ts +21 -0
- package/dist/mcp/tool-args.js +80 -0
- package/dist/memory.js +3 -2
- package/dist/overlap-index.d.ts +7 -0
- package/dist/overlap-index.js +38 -0
- package/dist/pilot-arm.d.ts +9 -0
- package/dist/pilot-arm.js +47 -0
- package/dist/policies.js +12 -11
- package/dist/predictions.js +9 -8
- package/dist/processes.js +14 -13
- package/dist/project-briefs.js +16 -15
- package/dist/project-identity.d.ts +1 -1
- package/dist/project-identity.js +25 -1
- package/dist/raw-archive.js +7 -6
- package/dist/recall-scope.d.ts +2 -1
- package/dist/recall-scope.js +2 -1
- package/dist/refine-llm.js +3 -2
- package/dist/reject-flow.js +6 -9
- package/dist/rejection.d.ts +2 -1
- package/dist/rejection.js +2 -1
- package/dist/search.js +14 -2
- package/dist/secret-detect.d.ts +13 -1
- package/dist/secret-detect.js +33 -1
- package/dist/server.d.ts +3 -1
- package/dist/server.js +180 -409
- package/dist/session-digest.js +2 -1
- package/dist/shared.js +7 -6
- package/dist/skills.js +15 -14
- package/dist/store.js +4 -4
- package/dist/token-ledger.d.ts +4 -2
- package/dist/token-ledger.js +2 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
- package/extensions/openclaw-plugin/package.json +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/dist/connectors/slack/ratelimit.d.ts +0 -9
- package/dist/connectors/slack/ratelimit.js +0 -18
package/dist/session-digest.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import { createHash } from 'crypto';
|
|
3
3
|
import * as fs from 'fs';
|
|
4
4
|
import * as path from 'path';
|
|
5
|
-
import { collectSessionTurns, errorMessage,
|
|
5
|
+
import { collectSessionTurns, errorMessage, readTranscriptTail, splitSentences, } from './capture.js';
|
|
6
|
+
import { isObjectLike, isStringValue } from './capture-contract.js';
|
|
6
7
|
import { PATCH_SUCCESS_LINE, patchPaths, shellPatch } from './codex-patch.js';
|
|
7
8
|
import { loadConfig } from './config.js';
|
|
8
9
|
import { createMemory, Layer } from './memory.js';
|
package/dist/shared.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Resolution: $HIPPO_HOME > $XDG_DATA_HOME/hippo > ~/.hippo/
|
|
5
5
|
* Local .hippo/ stores are per-project.
|
|
6
6
|
*/
|
|
7
|
+
import { BadRequestError, NotFoundError } from './api-errors.js';
|
|
7
8
|
import * as fs from 'fs';
|
|
8
9
|
import * as path from 'path';
|
|
9
10
|
import { generateId, COMPACTION_MEMORY_TAG } from './memory.js';
|
|
@@ -48,16 +49,16 @@ export function initGlobal() {
|
|
|
48
49
|
export function promoteToGlobal(localRoot, id, opts) {
|
|
49
50
|
const entry = readEntry(localRoot, id, opts?.tenantId);
|
|
50
51
|
if (!entry)
|
|
51
|
-
throw new
|
|
52
|
+
throw new NotFoundError(`Memory not found: ${id}`);
|
|
52
53
|
// CD5: same veto as shareMemory; a promoted copy would have no quarantine record to review.
|
|
53
54
|
if (isQuarantineScope(entry.scope)) {
|
|
54
|
-
throw new
|
|
55
|
+
throw new BadRequestError(`Refusing to promote ${id}: it is quarantined pending review. Approve it first via 'hippo quarantine approve ${id}'.`);
|
|
55
56
|
}
|
|
56
57
|
// v39 S4 producer veto: promote is a producer path to the global store
|
|
57
58
|
// exactly like shareMemory - same hard rule (codex gating review P2).
|
|
58
59
|
const promoteSecret = detectSecret(entry);
|
|
59
60
|
if (promoteSecret.flagged) {
|
|
60
|
-
throw new
|
|
61
|
+
throw new BadRequestError(`Refusing to promote ${id} to the global store: content matches secret material (${promoteSecret.reason}). ` +
|
|
61
62
|
`Secrets stay in their owning project's store.`);
|
|
62
63
|
}
|
|
63
64
|
initGlobal();
|
|
@@ -273,18 +274,18 @@ export function shareMemory(localRoot, id, options = {}) {
|
|
|
273
274
|
// when tenantId is provided.
|
|
274
275
|
const entry = readEntry(localRoot, id, options.tenantId);
|
|
275
276
|
if (!entry)
|
|
276
|
-
throw new
|
|
277
|
+
throw new NotFoundError(`Memory not found: ${id}`);
|
|
277
278
|
// v39 S4 producer veto: secrets never go to the global store, not even
|
|
278
279
|
// with --force. Explicit and loud - a silent null would read as "low
|
|
279
280
|
// transfer score" and invite retries.
|
|
280
281
|
const secret = detectSecret(entry);
|
|
281
282
|
if (secret.flagged) {
|
|
282
|
-
throw new
|
|
283
|
+
throw new BadRequestError(`Refusing to share ${id} to the global store: content matches secret material (${secret.reason}). ` +
|
|
283
284
|
`Secrets stay in their owning project's store.`);
|
|
284
285
|
}
|
|
285
286
|
// CD5: a quarantined row is unreviewed input, not a lesson; sharing it would spread poison globally.
|
|
286
287
|
if (isQuarantineScope(entry.scope)) {
|
|
287
|
-
throw new
|
|
288
|
+
throw new BadRequestError(`Refusing to share ${id}: it is quarantined pending review. Approve it first via 'hippo quarantine approve ${id}'.`);
|
|
288
289
|
}
|
|
289
290
|
const score = transferScore(entry);
|
|
290
291
|
if (score < 0.3 && !options.force)
|
package/dist/skills.js
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
* Lifecycle: active -> superseded (a newer version replaces it) or active ->
|
|
22
22
|
* closed (retired). Export renders ACTIVE skills only.
|
|
23
23
|
*/
|
|
24
|
+
import { BadRequestError, ConflictError, NotFoundError } from './api-errors.js';
|
|
24
25
|
import { openHippoDb, closeHippoDb } from './db.js';
|
|
25
26
|
import { writeEntry } from './store.js';
|
|
26
27
|
import { assertTenantId } from './tenant.js';
|
|
@@ -49,28 +50,28 @@ export const MAX_EXPORT_SKILLS = 1000;
|
|
|
49
50
|
function validateSkillFields(skillName, instructions, trigger) {
|
|
50
51
|
const name = (skillName ?? '').trim();
|
|
51
52
|
if (name.length === 0)
|
|
52
|
-
throw new
|
|
53
|
+
throw new BadRequestError('saveSkill: skillName is required');
|
|
53
54
|
if (/[\r\n]/.test(name))
|
|
54
|
-
throw new
|
|
55
|
+
throw new BadRequestError('saveSkill: skillName must be a single line (no newlines)');
|
|
55
56
|
if (name.length > MAX_SKILL_NAME_LEN) {
|
|
56
|
-
throw new
|
|
57
|
+
throw new BadRequestError(`saveSkill: skillName exceeds the ${MAX_SKILL_NAME_LEN}-char cap`);
|
|
57
58
|
}
|
|
58
59
|
if (!instructions || instructions.trim().length === 0) {
|
|
59
|
-
throw new
|
|
60
|
+
throw new BadRequestError('saveSkill: instructions are required');
|
|
60
61
|
}
|
|
61
62
|
if (instructions.length > MAX_SKILL_INSTRUCTIONS_LEN) {
|
|
62
|
-
throw new
|
|
63
|
+
throw new BadRequestError(`saveSkill: instructions exceed the ${MAX_SKILL_INSTRUCTIONS_LEN}-char cap`);
|
|
63
64
|
}
|
|
64
65
|
let triggerVal = null;
|
|
65
66
|
if (trigger !== undefined && trigger !== null && trigger.trim().length > 0) {
|
|
66
67
|
if (trigger.length > MAX_SKILL_TRIGGER_LEN) {
|
|
67
|
-
throw new
|
|
68
|
+
throw new BadRequestError(`saveSkill: trigger exceeds the ${MAX_SKILL_TRIGGER_LEN}-char cap`);
|
|
68
69
|
}
|
|
69
70
|
// Single-line, like skill_name: a trigger is a short "when to apply" phrase,
|
|
70
71
|
// and a newline would let it forge a heading inside the export **When:** line
|
|
71
72
|
// (independent-review 2026-05-30). Reject rather than emit a multi-line trigger.
|
|
72
73
|
if (/[\r\n]/.test(trigger)) {
|
|
73
|
-
throw new
|
|
74
|
+
throw new BadRequestError('saveSkill: trigger must be a single line (no newlines)');
|
|
74
75
|
}
|
|
75
76
|
triggerVal = trigger;
|
|
76
77
|
}
|
|
@@ -148,10 +149,10 @@ export function saveSkill(hippoRoot, tenantId, opts, actor = 'cli') {
|
|
|
148
149
|
// SAFETY: row shape matches the `status, version` columns named in the SELECT below.
|
|
149
150
|
const pred = db.prepare(`SELECT status, version FROM skills WHERE id = ? AND tenant_id = ?`).get(opts.supersedesSkillId, tenantId);
|
|
150
151
|
if (!pred) {
|
|
151
|
-
throw new
|
|
152
|
+
throw new NotFoundError(`saveSkill: skill ${opts.supersedesSkillId} to supersede not found for tenant ${tenantId}`);
|
|
152
153
|
}
|
|
153
154
|
if (pred.status !== 'active') {
|
|
154
|
-
throw new
|
|
155
|
+
throw new ConflictError(`saveSkill: skill ${opts.supersedesSkillId} is not active (status='${pred.status}'); only active skills can be superseded.`);
|
|
155
156
|
}
|
|
156
157
|
version = pred.version + 1;
|
|
157
158
|
}
|
|
@@ -169,7 +170,7 @@ export function saveSkill(hippoRoot, tenantId, opts, actor = 'cli') {
|
|
|
169
170
|
WHERE id = ? AND tenant_id = ? AND status = 'active' AND id != ?
|
|
170
171
|
`).run(skillId, now, opts.supersedesSkillId, tenantId, skillId);
|
|
171
172
|
if (sup.changes === 0) {
|
|
172
|
-
throw new
|
|
173
|
+
throw new ConflictError(`saveSkill: skill ${opts.supersedesSkillId} could not be superseded (no longer active or self-reference).`);
|
|
173
174
|
}
|
|
174
175
|
appendAuditEvent(db, {
|
|
175
176
|
tenantId,
|
|
@@ -228,15 +229,15 @@ export function closeSkill(hippoRoot, tenantId, id, actor = 'cli') {
|
|
|
228
229
|
// SAFETY: row shape matches the single `status` column named in the SELECT above.
|
|
229
230
|
const existing = db.prepare(`SELECT status FROM skills WHERE id = ? AND tenant_id = ?`).get(id, tenantId);
|
|
230
231
|
if (!existing) {
|
|
231
|
-
throw new
|
|
232
|
+
throw new NotFoundError(`closeSkill: skill ${id} not found for tenant ${tenantId}`);
|
|
232
233
|
}
|
|
233
|
-
throw new
|
|
234
|
+
throw new ConflictError(`closeSkill: skill ${id} is not active (status='${existing.status}'); only active skills can be closed.`);
|
|
234
235
|
}
|
|
235
236
|
// SAFETY: row's shape matches the columns named in SKILL_COLS above.
|
|
236
237
|
const row = db.prepare(`SELECT ${SKILL_COLS} FROM skills WHERE id = ? AND tenant_id = ?`)
|
|
237
238
|
.get(id, tenantId);
|
|
238
239
|
if (!row)
|
|
239
|
-
throw new
|
|
240
|
+
throw new NotFoundError(`closeSkill: skill ${id} not found after UPDATE`);
|
|
240
241
|
appendAuditEvent(db, {
|
|
241
242
|
tenantId,
|
|
242
243
|
actor,
|
|
@@ -282,7 +283,7 @@ export function loadSkills(hippoRoot, tenantId, opts = {}) {
|
|
|
282
283
|
let rows;
|
|
283
284
|
if (opts.status) {
|
|
284
285
|
if (!VALID_SKILL_STATES.has(opts.status)) {
|
|
285
|
-
throw new
|
|
286
|
+
throw new BadRequestError(`loadSkills: status must be one of ${Array.from(VALID_SKILL_STATES).join('|')}; got ${opts.status}`);
|
|
286
287
|
}
|
|
287
288
|
// SAFETY: rows' shape matches the columns named in SKILL_COLS above.
|
|
288
289
|
rows = db.prepare(`
|
package/dist/store.js
CHANGED
|
@@ -15,7 +15,7 @@ import { RECALL_DEFAULT_DENY_SCOPES } from './recall-scope.js';
|
|
|
15
15
|
import { assertTenantId } from './tenant.js';
|
|
16
16
|
import { isRecallBoostAblated } from './ablation.js';
|
|
17
17
|
import { rarestPromptTerms, RAREST_TERM_COUNT } from './prompt-recall.js';
|
|
18
|
-
import { appendAuditEvent } from './audit.js';
|
|
18
|
+
import { appendAuditEvent, reportAuditWriteFailure } from './audit.js';
|
|
19
19
|
import { resolveTenantId } from './tenant.js';
|
|
20
20
|
import { redactSecretsStrict } from './secret-detect.js';
|
|
21
21
|
import { deriveOriginProject, originFromSource, findHippoStoreDir, realpathOrResolve } from './project-identity.js';
|
|
@@ -42,9 +42,9 @@ function audit(db, op, targetId, metadata, actor = 'cli', tenantId) {
|
|
|
42
42
|
metadata,
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
|
-
catch {
|
|
46
|
-
//
|
|
47
|
-
|
|
45
|
+
catch (error) {
|
|
46
|
+
// The mutation has already succeeded; a broken audit table must not undo it.
|
|
47
|
+
reportAuditWriteFailure(op, String(error), targetId);
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
/**
|
package/dist/token-ledger.d.ts
CHANGED
|
@@ -7,8 +7,9 @@ import type { DatabaseSyncLike } from './db.js';
|
|
|
7
7
|
* - `context`, `recall`: the CLI commands.
|
|
8
8
|
* - `mcp_recall`, `mcp_context`: the MCP tools.
|
|
9
9
|
* - `http_recall`, `http_context`, `http_assemble`: the HTTP API.
|
|
10
|
+
* - `pilot`: the session's pilot arm row (src/pilot-arm.ts); not a send, so it is in no surface list.
|
|
10
11
|
*/
|
|
11
|
-
export type TokenSurface = 'hook' | 'hook_recall' | 'compact_resume' | 'context' | 'recall' | 'mcp_recall' | 'mcp_context' | 'http_recall' | 'http_context' | 'http_assemble';
|
|
12
|
+
export type TokenSurface = 'hook' | 'hook_recall' | 'compact_resume' | 'context' | 'recall' | 'mcp_recall' | 'mcp_context' | 'http_recall' | 'http_context' | 'http_assemble' | 'pilot';
|
|
12
13
|
/** All surfaces, in report order. */
|
|
13
14
|
export declare const TOKEN_SURFACES: readonly TokenSurface[];
|
|
14
15
|
/** Surfaces whose re-reads are counted: only a hook payload tells a sub-agent's block from its parent's, as both carry one session id. */
|
|
@@ -19,8 +20,9 @@ export declare const REREAD_SURFACES: readonly TokenSurface[];
|
|
|
19
20
|
* - `skip`: identical to the session's last injected block, so not sent again.
|
|
20
21
|
* - `reset`: the host compacted its context, so the next block must be sent.
|
|
21
22
|
* - `reread`: tokens later calls read again, booked at session end as one row per session, hook surface and UTC day of the calls.
|
|
23
|
+
* - `arm`: the pilot assignment; `block_hash` is `hippo` or `holdout`, `items` the holdout rate in basis points.
|
|
22
24
|
*/
|
|
23
|
-
export type TokenEvent = 'inject' | 'skip' | 'reset' | 'reread';
|
|
25
|
+
export type TokenEvent = 'inject' | 'skip' | 'reset' | 'reread' | 'arm';
|
|
24
26
|
/** Rows older than this are pruned on write. */
|
|
25
27
|
export declare const TOKEN_LEDGER_RETENTION_DAYS = 90;
|
|
26
28
|
/**
|
package/dist/token-ledger.js
CHANGED
|
@@ -125,7 +125,7 @@ export function summarizeTokenUse(db, tenantId, sinceIso) {
|
|
|
125
125
|
COUNT(DISTINCT CASE WHEN event = 'reread' THEN session_id END) AS reread_sessions,
|
|
126
126
|
SUM(CASE WHEN event = 'inject' THEN tokens ELSE 0 END) AS tokens
|
|
127
127
|
FROM token_ledger
|
|
128
|
-
WHERE tenant_id = ? AND ts >= ? AND session_id IS NOT NULL`).get(...REREAD_SURFACES, tenantId, sinceIso);
|
|
128
|
+
WHERE tenant_id = ? AND ts >= ? AND session_id IS NOT NULL AND event <> 'arm'`).get(...REREAD_SURFACES, tenantId, sinceIso);
|
|
129
129
|
const sessionCount = Number(perSession?.sessions ?? 0);
|
|
130
130
|
const sessionTokens = Number(perSession?.tokens ?? 0);
|
|
131
131
|
return {
|
|
@@ -197,7 +197,7 @@ export function tokensBySession(db, tenantId, sinceIso) {
|
|
|
197
197
|
SUM(CASE WHEN event = 'skip' THEN tokens ELSE 0 END) AS skipped,
|
|
198
198
|
SUM(CASE WHEN event = 'inject' THEN 1 ELSE 0 END) AS injections
|
|
199
199
|
FROM token_ledger
|
|
200
|
-
WHERE tenant_id = ? AND ts >= ? AND session_id IS NOT NULL
|
|
200
|
+
WHERE tenant_id = ? AND ts >= ? AND session_id IS NOT NULL AND event <> 'arm'
|
|
201
201
|
GROUP BY session_id`).all(tenantId, sinceIso);
|
|
202
202
|
return rows.map((r) => ({
|
|
203
203
|
sessionId: r.session_id,
|
package/dist/version.d.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* an ESM `import` can resolve cleanly, and a hardcoded constant survives
|
|
17
17
|
* any packager that drops .json files.
|
|
18
18
|
*/
|
|
19
|
-
export declare const PACKAGE_VERSION = "1.
|
|
19
|
+
export declare const PACKAGE_VERSION = "1.58.0";
|
|
20
20
|
/** Compares plain x.y.z versions, positive if a > b; tags throw so the rollback guard never misfires silently. */
|
|
21
21
|
export declare function compareSemver(a: string, b: string): number;
|
|
22
22
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* an ESM `import` can resolve cleanly, and a hardcoded constant survives
|
|
17
17
|
* any packager that drops .json files.
|
|
18
18
|
*/
|
|
19
|
-
export const PACKAGE_VERSION = '1.
|
|
19
|
+
export const PACKAGE_VERSION = '1.58.0';
|
|
20
20
|
/** Compares plain x.y.z versions, positive if a > b; tags throw so the rollback guard never misfires silently. */
|
|
21
21
|
export function compareSemver(a, b) {
|
|
22
22
|
const parse = (v) => {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "hippo-memory",
|
|
3
3
|
"name": "Hippo Memory",
|
|
4
4
|
"description": "Memory for AI agents that learns what is wrong and ranks it down. Injects context at session start and captures errors.",
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.58.0",
|
|
6
6
|
|
|
7
7
|
"configSchema": {
|
|
8
8
|
"type": "object",
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "hippo-memory",
|
|
3
3
|
"name": "Hippo Memory",
|
|
4
4
|
"description": "Memory for AI agents that learns what is wrong and ranks it down. Injects context at session start and captures errors.",
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.58.0",
|
|
6
6
|
"configSchema": {
|
|
7
7
|
"type": "object",
|
|
8
8
|
"additionalProperties": false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hippo-memory",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.58.0",
|
|
4
4
|
"description": "Memory for AI agents that learns what is wrong and ranks it down. MCP server, hooks for Claude Code, OpenCode and Codex, AGENTS.md instructions for Codex, Cursor, OpenClaw and Pi. SQLite, zero runtime deps.",
|
|
5
5
|
"mcpName": "io.github.kitfunso/hippo-memory",
|
|
6
6
|
"type": "module",
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export interface RetryOpts {
|
|
2
|
-
url: string;
|
|
3
|
-
init?: RequestInit;
|
|
4
|
-
fetchImpl?: typeof fetch;
|
|
5
|
-
sleep?: (ms: number) => Promise<void>;
|
|
6
|
-
maxRetries?: number;
|
|
7
|
-
}
|
|
8
|
-
export declare function fetchWithRetry(opts: RetryOpts): Promise<Response>;
|
|
9
|
-
//# sourceMappingURL=ratelimit.d.ts.map
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
export async function fetchWithRetry(opts) {
|
|
2
|
-
const f = opts.fetchImpl ?? fetch;
|
|
3
|
-
const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
4
|
-
const max = opts.maxRetries ?? 3;
|
|
5
|
-
let attempt = 0;
|
|
6
|
-
while (true) {
|
|
7
|
-
const r = await f(opts.url, opts.init);
|
|
8
|
-
if (r.status !== 429)
|
|
9
|
-
return r;
|
|
10
|
-
if (attempt >= max)
|
|
11
|
-
throw new Error(`rate-limited after ${attempt + 1} attempts: ${opts.url}`);
|
|
12
|
-
const ra = r.headers.get('retry-after');
|
|
13
|
-
const delaySec = ra ? Number(ra) : Math.pow(2, attempt);
|
|
14
|
-
await sleep(Math.max(0, delaySec) * 1000);
|
|
15
|
-
attempt++;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
//# sourceMappingURL=ratelimit.js.map
|