@agent-native/core 0.49.27 → 0.51.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/dist/agent/harness/ai-sdk-adapter.d.ts +13 -0
- package/dist/agent/harness/ai-sdk-adapter.d.ts.map +1 -0
- package/dist/agent/harness/ai-sdk-adapter.js +232 -0
- package/dist/agent/harness/ai-sdk-adapter.js.map +1 -0
- package/dist/agent/harness/background.d.ts +15 -0
- package/dist/agent/harness/background.d.ts.map +1 -0
- package/dist/agent/harness/background.js +233 -0
- package/dist/agent/harness/background.js.map +1 -0
- package/dist/agent/harness/builtin.d.ts +2 -0
- package/dist/agent/harness/builtin.d.ts.map +1 -0
- package/dist/agent/harness/builtin.js +24 -0
- package/dist/agent/harness/builtin.js.map +1 -0
- package/dist/agent/harness/index.d.ts +9 -0
- package/dist/agent/harness/index.d.ts.map +1 -0
- package/dist/agent/harness/index.js +8 -0
- package/dist/agent/harness/index.js.map +1 -0
- package/dist/agent/harness/registry.d.ts +15 -0
- package/dist/agent/harness/registry.d.ts.map +1 -0
- package/dist/agent/harness/registry.js +70 -0
- package/dist/agent/harness/registry.js.map +1 -0
- package/dist/agent/harness/runner.d.ts +21 -0
- package/dist/agent/harness/runner.d.ts.map +1 -0
- package/dist/agent/harness/runner.js +86 -0
- package/dist/agent/harness/runner.js.map +1 -0
- package/dist/agent/harness/store.d.ts +46 -0
- package/dist/agent/harness/store.d.ts.map +1 -0
- package/dist/agent/harness/store.js +304 -0
- package/dist/agent/harness/store.js.map +1 -0
- package/dist/agent/harness/translate.d.ts +5 -0
- package/dist/agent/harness/translate.d.ts.map +1 -0
- package/dist/agent/harness/translate.js +120 -0
- package/dist/agent/harness/translate.js.map +1 -0
- package/dist/agent/harness/types.d.ts +127 -0
- package/dist/agent/harness/types.d.ts.map +1 -0
- package/dist/agent/harness/types.js +2 -0
- package/dist/agent/harness/types.js.map +1 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +2 -0
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +10 -1
- package/dist/cli/create.js.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.js +1 -1
- package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
- package/dist/cli/skills.d.ts +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +8 -1
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/blocks/library/wireframe.d.ts.map +1 -1
- package/dist/client/blocks/library/wireframe.js +0 -1
- package/dist/client/blocks/library/wireframe.js.map +1 -1
- package/dist/client/resources/use-resources.d.ts.map +1 -1
- package/dist/client/resources/use-resources.js +4 -2
- package/dist/client/resources/use-resources.js.map +1 -1
- package/dist/code-agents/background-run.d.ts +6 -4
- package/dist/code-agents/background-run.d.ts.map +1 -1
- package/dist/code-agents/background-run.js.map +1 -1
- package/dist/coding-tools/run-code.js +6 -4
- package/dist/coding-tools/run-code.js.map +1 -1
- package/dist/provider-api/index.d.ts +20 -12
- package/dist/provider-api/index.d.ts.map +1 -1
- package/dist/provider-api/index.js +157 -37
- package/dist/provider-api/index.js.map +1 -1
- package/dist/provider-api/quota-governor.d.ts +53 -0
- package/dist/provider-api/quota-governor.d.ts.map +1 -0
- package/dist/provider-api/quota-governor.js +395 -0
- package/dist/provider-api/quota-governor.js.map +1 -0
- package/dist/provider-api/staging.d.ts.map +1 -1
- package/dist/provider-api/staging.js +16 -0
- package/dist/provider-api/staging.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +34 -10
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/templates/workspace-core/.agents/skills/harness-agents/SKILL.md +98 -0
- package/dist/workspace-files/index.d.ts +0 -1
- package/dist/workspace-files/index.d.ts.map +1 -1
- package/dist/workspace-files/index.js +0 -1
- package/dist/workspace-files/index.js.map +1 -1
- package/dist/workspace-files/store.d.ts +14 -9
- package/dist/workspace-files/store.d.ts.map +1 -1
- package/dist/workspace-files/store.js +89 -164
- package/dist/workspace-files/store.js.map +1 -1
- package/dist/workspace-files/tool.d.ts +3 -4
- package/dist/workspace-files/tool.d.ts.map +1 -1
- package/dist/workspace-files/tool.js +8 -7
- package/dist/workspace-files/tool.js.map +1 -1
- package/docs/content/deployment.md +1 -1
- package/package.json +2 -1
- package/src/templates/workspace-core/.agents/skills/harness-agents/SKILL.md +98 -0
- package/dist/workspace-files/schema.d.ts +0 -195
- package/dist/workspace-files/schema.d.ts.map +0 -1
- package/dist/workspace-files/schema.js +0 -48
- package/dist/workspace-files/schema.js.map +0 -1
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { CredentialContext } from "../credentials/index.js";
|
|
2
|
+
export interface ProviderQuotaIdentityInput {
|
|
3
|
+
appId: string;
|
|
4
|
+
providerId: string;
|
|
5
|
+
ctx: CredentialContext;
|
|
6
|
+
credentialSources: readonly Record<string, unknown>[];
|
|
7
|
+
connectionId?: string | null;
|
|
8
|
+
accountId?: string | null;
|
|
9
|
+
}
|
|
10
|
+
export interface ProviderQuotaIdentity {
|
|
11
|
+
quotaKey: string;
|
|
12
|
+
providerId: string;
|
|
13
|
+
scopeKey: string;
|
|
14
|
+
credentialFingerprint: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ProviderQuotaRequest {
|
|
17
|
+
identity: ProviderQuotaIdentity;
|
|
18
|
+
method: string;
|
|
19
|
+
target: string;
|
|
20
|
+
requestKey?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface ProviderQuotaObservedResult {
|
|
23
|
+
status?: number;
|
|
24
|
+
headers?: Record<string, string>;
|
|
25
|
+
}
|
|
26
|
+
export interface ProviderQuotaExhaustedDetail {
|
|
27
|
+
providerId: string;
|
|
28
|
+
scopeKey: string;
|
|
29
|
+
quotaKey: string;
|
|
30
|
+
method: string;
|
|
31
|
+
target: string;
|
|
32
|
+
retryAfterMs: number;
|
|
33
|
+
retryAt: string;
|
|
34
|
+
reason: "cooldown" | "retry_after" | "max_attempts";
|
|
35
|
+
}
|
|
36
|
+
export interface ExecuteWithProviderQuotaOptions<T> {
|
|
37
|
+
request: ProviderQuotaRequest;
|
|
38
|
+
execute: () => Promise<T>;
|
|
39
|
+
inspect: (result: T) => ProviderQuotaObservedResult;
|
|
40
|
+
buildQuotaExhaustedResult: (detail: ProviderQuotaExhaustedDetail) => T;
|
|
41
|
+
maxAttempts?: number;
|
|
42
|
+
maxWaitMs?: number;
|
|
43
|
+
}
|
|
44
|
+
export declare function createProviderQuotaIdentity(input: ProviderQuotaIdentityInput): ProviderQuotaIdentity;
|
|
45
|
+
export declare function createProviderRequestDedupeKey(input: {
|
|
46
|
+
method: string;
|
|
47
|
+
url: string;
|
|
48
|
+
body?: BodyInit;
|
|
49
|
+
headers?: Record<string, string>;
|
|
50
|
+
}): string | undefined;
|
|
51
|
+
export declare function executeWithProviderQuota<T>(options: ExecuteWithProviderQuotaOptions<T>): Promise<T>;
|
|
52
|
+
export declare function resetProviderQuotaStateForTests(): void;
|
|
53
|
+
//# sourceMappingURL=quota-governor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quota-governor.d.ts","sourceRoot":"","sources":["../../src/provider-api/quota-governor.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAEjE,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,iBAAiB,CAAC;IACvB,iBAAiB,EAAE,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACtD,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,qBAAqB,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,2BAA2B;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,4BAA4B;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,UAAU,GAAG,aAAa,GAAG,cAAc,CAAC;CACrD;AAED,MAAM,WAAW,+BAA+B,CAAC,CAAC;IAChD,OAAO,EAAE,oBAAoB,CAAC;IAC9B,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC;IAC1B,OAAO,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,2BAA2B,CAAC;IACpD,yBAAyB,EAAE,CAAC,MAAM,EAAE,4BAA4B,KAAK,CAAC,CAAC;IACvE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAyCD,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,0BAA0B,GAChC,qBAAqB,CAiCvB;AAED,wBAAgB,8BAA8B,CAAC,KAAK,EAAE;IACpD,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC,GAAG,MAAM,GAAG,SAAS,CAcrB;AAED,wBAAsB,wBAAwB,CAAC,CAAC,EAC9C,OAAO,EAAE,+BAA+B,CAAC,CAAC,CAAC,GAC1C,OAAO,CAAC,CAAC,CAAC,CAuBZ;AAED,wBAAgB,+BAA+B,IAAI,IAAI,CAMtD"}
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { getDbExec, intType } from "../db/client.js";
|
|
3
|
+
const GLOBAL_KEY = "__agentNativeProviderApiQuotaGovernor";
|
|
4
|
+
const globalRef = globalThis;
|
|
5
|
+
const initialState = {
|
|
6
|
+
queues: new Map(),
|
|
7
|
+
inflight: new Map(),
|
|
8
|
+
cooldowns: new Map(),
|
|
9
|
+
};
|
|
10
|
+
const state = globalRef[GLOBAL_KEY] ?? (globalRef[GLOBAL_KEY] = initialState);
|
|
11
|
+
const DEFAULT_MAX_ATTEMPTS = 3;
|
|
12
|
+
const DEFAULT_MAX_WAIT_MS = 55_000;
|
|
13
|
+
const MAX_FALLBACK_BACKOFF_MS = 30_000;
|
|
14
|
+
const PERSISTENCE_RETRY_MS = 60_000;
|
|
15
|
+
export function createProviderQuotaIdentity(input) {
|
|
16
|
+
const scopeKey = input.ctx.orgId
|
|
17
|
+
? `org:${input.ctx.orgId}`
|
|
18
|
+
: input.ctx.userEmail
|
|
19
|
+
? `user:${input.ctx.userEmail}`
|
|
20
|
+
: "anonymous";
|
|
21
|
+
const credentialSources = input.credentialSources
|
|
22
|
+
.map((source) => ({
|
|
23
|
+
provider: String(source.provider ?? input.providerId),
|
|
24
|
+
key: String(source.key ?? ""),
|
|
25
|
+
source: String(source.source ?? ""),
|
|
26
|
+
connectionId: stringOrNull(source.connectionId) ?? input.connectionId,
|
|
27
|
+
accountId: stringOrNull(source.accountId) ?? input.accountId,
|
|
28
|
+
scope: stringOrNull(source.scope),
|
|
29
|
+
}))
|
|
30
|
+
.sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
|
|
31
|
+
const credentialFingerprint = hashStable({
|
|
32
|
+
providerId: input.providerId,
|
|
33
|
+
connectionId: input.connectionId ?? null,
|
|
34
|
+
accountId: input.accountId ?? null,
|
|
35
|
+
credentialSources,
|
|
36
|
+
});
|
|
37
|
+
return {
|
|
38
|
+
providerId: input.providerId,
|
|
39
|
+
scopeKey,
|
|
40
|
+
credentialFingerprint,
|
|
41
|
+
quotaKey: hashStable({
|
|
42
|
+
appId: input.appId,
|
|
43
|
+
providerId: input.providerId,
|
|
44
|
+
scopeKey,
|
|
45
|
+
credentialFingerprint,
|
|
46
|
+
}),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function createProviderRequestDedupeKey(input) {
|
|
50
|
+
const method = input.method.toUpperCase();
|
|
51
|
+
if (method !== "GET" && method !== "HEAD")
|
|
52
|
+
return undefined;
|
|
53
|
+
return hashStable({
|
|
54
|
+
method,
|
|
55
|
+
url: input.url,
|
|
56
|
+
headers: dedupeHeaders(input.headers),
|
|
57
|
+
body: typeof input.body === "string"
|
|
58
|
+
? input.body
|
|
59
|
+
: input.body == null
|
|
60
|
+
? null
|
|
61
|
+
: "[body]",
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
export async function executeWithProviderQuota(options) {
|
|
65
|
+
if (isQuotaGovernorDisabled())
|
|
66
|
+
return options.execute();
|
|
67
|
+
const dedupeKey = options.request.requestKey
|
|
68
|
+
? `${options.request.identity.quotaKey}:${options.request.requestKey}`
|
|
69
|
+
: null;
|
|
70
|
+
if (dedupeKey) {
|
|
71
|
+
const existing = state.inflight.get(dedupeKey);
|
|
72
|
+
if (existing)
|
|
73
|
+
return existing;
|
|
74
|
+
}
|
|
75
|
+
const run = runInQueue(options.request.identity.quotaKey, () => executeWithCooldown(options));
|
|
76
|
+
if (dedupeKey) {
|
|
77
|
+
state.inflight.set(dedupeKey, run);
|
|
78
|
+
run
|
|
79
|
+
.finally(() => {
|
|
80
|
+
state.inflight.delete(dedupeKey);
|
|
81
|
+
})
|
|
82
|
+
.catch(() => undefined);
|
|
83
|
+
}
|
|
84
|
+
return run;
|
|
85
|
+
}
|
|
86
|
+
export function resetProviderQuotaStateForTests() {
|
|
87
|
+
state.queues.clear();
|
|
88
|
+
state.inflight.clear();
|
|
89
|
+
state.cooldowns.clear();
|
|
90
|
+
state.initPromise = undefined;
|
|
91
|
+
state.persistenceUnavailableUntil = undefined;
|
|
92
|
+
}
|
|
93
|
+
async function executeWithCooldown(options) {
|
|
94
|
+
const maxAttempts = positiveIntFromEnv("AGENT_NATIVE_PROVIDER_API_MAX_ATTEMPTS", options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS);
|
|
95
|
+
const maxWaitMs = positiveIntFromEnv("AGENT_NATIVE_PROVIDER_API_MAX_WAIT_MS", options.maxWaitMs ?? DEFAULT_MAX_WAIT_MS);
|
|
96
|
+
const deadline = Date.now() + maxWaitMs;
|
|
97
|
+
for (let attempt = 0;; attempt++) {
|
|
98
|
+
const cooldownUntil = await getCooldownUntil(options.request.identity);
|
|
99
|
+
if (cooldownUntil > Date.now()) {
|
|
100
|
+
const waitMs = cooldownUntil - Date.now();
|
|
101
|
+
if (waitMs > Math.max(0, deadline - Date.now())) {
|
|
102
|
+
return options.buildQuotaExhaustedResult(quotaExhaustedDetail(options.request, waitMs, "cooldown"));
|
|
103
|
+
}
|
|
104
|
+
await sleepMs(waitMs);
|
|
105
|
+
}
|
|
106
|
+
const result = await options.execute();
|
|
107
|
+
const observed = options.inspect(result);
|
|
108
|
+
if (observed.status !== 429) {
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
const retryAfterMs = retryDelayMs(observed.headers, attempt);
|
|
112
|
+
await setCooldownUntil(options.request.identity, Date.now() + retryAfterMs, {
|
|
113
|
+
status: observed.status,
|
|
114
|
+
reason: "429",
|
|
115
|
+
});
|
|
116
|
+
if (attempt + 1 >= maxAttempts) {
|
|
117
|
+
return options.buildQuotaExhaustedResult(quotaExhaustedDetail(options.request, retryAfterMs, "max_attempts"));
|
|
118
|
+
}
|
|
119
|
+
if (retryAfterMs > Math.max(0, deadline - Date.now())) {
|
|
120
|
+
return options.buildQuotaExhaustedResult(quotaExhaustedDetail(options.request, retryAfterMs, "retry_after"));
|
|
121
|
+
}
|
|
122
|
+
await sleepMs(retryAfterMs);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async function runInQueue(quotaKey, fn) {
|
|
126
|
+
const queue = state.queues.get(quotaKey) ?? { tail: Promise.resolve() };
|
|
127
|
+
state.queues.set(quotaKey, queue);
|
|
128
|
+
const previous = queue.tail.catch(() => undefined);
|
|
129
|
+
let release = () => { };
|
|
130
|
+
const current = new Promise((resolve) => {
|
|
131
|
+
release = resolve;
|
|
132
|
+
});
|
|
133
|
+
const tail = previous.then(() => current);
|
|
134
|
+
queue.tail = tail;
|
|
135
|
+
await previous;
|
|
136
|
+
try {
|
|
137
|
+
return await fn();
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
release();
|
|
141
|
+
if (queue.tail === tail)
|
|
142
|
+
state.queues.delete(quotaKey);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async function getCooldownUntil(identity) {
|
|
146
|
+
const memory = state.cooldowns.get(identity.quotaKey);
|
|
147
|
+
const now = Date.now();
|
|
148
|
+
if (memory && memory.until <= now) {
|
|
149
|
+
state.cooldowns.delete(identity.quotaKey);
|
|
150
|
+
}
|
|
151
|
+
let until = memory && memory.until > now ? memory.until : 0;
|
|
152
|
+
const persisted = await readPersistedCooldown(identity.quotaKey);
|
|
153
|
+
if (persisted && persisted.until > now) {
|
|
154
|
+
state.cooldowns.set(identity.quotaKey, persisted);
|
|
155
|
+
until = Math.max(until, persisted.until);
|
|
156
|
+
}
|
|
157
|
+
return until;
|
|
158
|
+
}
|
|
159
|
+
async function setCooldownUntil(identity, until, options = {}) {
|
|
160
|
+
const entry = {
|
|
161
|
+
until,
|
|
162
|
+
providerId: identity.providerId,
|
|
163
|
+
scopeKey: identity.scopeKey,
|
|
164
|
+
status: options.status,
|
|
165
|
+
reason: options.reason,
|
|
166
|
+
};
|
|
167
|
+
state.cooldowns.set(identity.quotaKey, entry);
|
|
168
|
+
await writePersistedCooldown(identity.quotaKey, entry);
|
|
169
|
+
}
|
|
170
|
+
function quotaExhaustedDetail(request, retryAfterMs, reason) {
|
|
171
|
+
const retryAtMs = Date.now() + Math.max(0, retryAfterMs);
|
|
172
|
+
return {
|
|
173
|
+
providerId: request.identity.providerId,
|
|
174
|
+
scopeKey: request.identity.scopeKey,
|
|
175
|
+
quotaKey: request.identity.quotaKey,
|
|
176
|
+
method: request.method,
|
|
177
|
+
target: request.target,
|
|
178
|
+
retryAfterMs: Math.max(0, retryAfterMs),
|
|
179
|
+
retryAt: new Date(retryAtMs).toISOString(),
|
|
180
|
+
reason,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function retryDelayMs(headers, attempt) {
|
|
184
|
+
const retryAfter = retryAfterMs(headers);
|
|
185
|
+
if (retryAfter !== null)
|
|
186
|
+
return retryAfter;
|
|
187
|
+
const base = Math.min(1000 * 2 ** attempt, MAX_FALLBACK_BACKOFF_MS);
|
|
188
|
+
return Math.min(base + Math.floor(Math.random() * 250), MAX_FALLBACK_BACKOFF_MS);
|
|
189
|
+
}
|
|
190
|
+
function retryAfterMs(headers) {
|
|
191
|
+
const raw = headerValue(headers, "retry-after");
|
|
192
|
+
if (!raw)
|
|
193
|
+
return null;
|
|
194
|
+
const seconds = Number(raw);
|
|
195
|
+
if (Number.isFinite(seconds))
|
|
196
|
+
return Math.max(0, seconds * 1000);
|
|
197
|
+
const dateMs = Date.parse(raw);
|
|
198
|
+
if (Number.isFinite(dateMs))
|
|
199
|
+
return Math.max(0, dateMs - Date.now());
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
function headerValue(headers, name) {
|
|
203
|
+
if (!headers)
|
|
204
|
+
return undefined;
|
|
205
|
+
const lowerName = name.toLowerCase();
|
|
206
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
207
|
+
if (key.toLowerCase() === lowerName)
|
|
208
|
+
return value;
|
|
209
|
+
}
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
212
|
+
function sleepMs(ms) {
|
|
213
|
+
if (ms <= 0)
|
|
214
|
+
return Promise.resolve();
|
|
215
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
216
|
+
}
|
|
217
|
+
function positiveIntFromEnv(name, fallback) {
|
|
218
|
+
const raw = process.env[name];
|
|
219
|
+
const parsed = raw ? Number(raw) : Number.NaN;
|
|
220
|
+
if (Number.isFinite(parsed) && parsed > 0)
|
|
221
|
+
return Math.floor(parsed);
|
|
222
|
+
return fallback;
|
|
223
|
+
}
|
|
224
|
+
function isQuotaGovernorDisabled() {
|
|
225
|
+
const raw = process.env.AGENT_NATIVE_PROVIDER_API_QUOTA_GOVERNOR;
|
|
226
|
+
return raw === "0" || raw?.toLowerCase() === "false";
|
|
227
|
+
}
|
|
228
|
+
function shouldPersistCooldowns() {
|
|
229
|
+
const raw = process.env.AGENT_NATIVE_PROVIDER_API_PERSIST_COOLDOWNS;
|
|
230
|
+
if (raw === "0" || raw?.toLowerCase() === "false")
|
|
231
|
+
return false;
|
|
232
|
+
if (raw === "1" || raw?.toLowerCase() === "true")
|
|
233
|
+
return true;
|
|
234
|
+
if (process.env.NODE_ENV === "test" || process.env.VITEST)
|
|
235
|
+
return false;
|
|
236
|
+
return Boolean(process.env.DATABASE_URL ||
|
|
237
|
+
process.env.NETLIFY ||
|
|
238
|
+
process.env.VERCEL ||
|
|
239
|
+
process.env.AWS_LAMBDA_FUNCTION_NAME ||
|
|
240
|
+
process.env.APP_NAME);
|
|
241
|
+
}
|
|
242
|
+
function persistenceTemporarilyUnavailable() {
|
|
243
|
+
return Boolean(state.persistenceUnavailableUntil &&
|
|
244
|
+
state.persistenceUnavailableUntil > Date.now());
|
|
245
|
+
}
|
|
246
|
+
async function ensureCooldownTable() {
|
|
247
|
+
if (!shouldPersistCooldowns())
|
|
248
|
+
return;
|
|
249
|
+
if (persistenceTemporarilyUnavailable())
|
|
250
|
+
return;
|
|
251
|
+
if (!state.initPromise) {
|
|
252
|
+
state.initPromise = (async () => {
|
|
253
|
+
const db = getDbExec();
|
|
254
|
+
const integerType = intType();
|
|
255
|
+
await db.execute(`
|
|
256
|
+
CREATE TABLE IF NOT EXISTS provider_api_cooldowns (
|
|
257
|
+
quota_key TEXT NOT NULL,
|
|
258
|
+
provider_id TEXT NOT NULL,
|
|
259
|
+
scope_key TEXT NOT NULL,
|
|
260
|
+
cooldown_until ${integerType} NOT NULL,
|
|
261
|
+
status ${integerType},
|
|
262
|
+
reason TEXT,
|
|
263
|
+
updated_at ${integerType} NOT NULL,
|
|
264
|
+
PRIMARY KEY (quota_key)
|
|
265
|
+
)
|
|
266
|
+
`);
|
|
267
|
+
try {
|
|
268
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS provider_api_cooldowns_provider_idx ON provider_api_cooldowns (provider_id, cooldown_until)`);
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
// Index already exists or the backend rejected best-effort indexing.
|
|
272
|
+
}
|
|
273
|
+
})().catch((err) => {
|
|
274
|
+
state.initPromise = undefined;
|
|
275
|
+
state.persistenceUnavailableUntil = Date.now() + PERSISTENCE_RETRY_MS;
|
|
276
|
+
throw err;
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
await state.initPromise;
|
|
280
|
+
}
|
|
281
|
+
async function readPersistedCooldown(quotaKey) {
|
|
282
|
+
try {
|
|
283
|
+
if (!shouldPersistCooldowns() || persistenceTemporarilyUnavailable()) {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
await ensureCooldownTable();
|
|
287
|
+
if (!shouldPersistCooldowns() || persistenceTemporarilyUnavailable()) {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
const db = getDbExec();
|
|
291
|
+
const { rows } = await db.execute({
|
|
292
|
+
sql: `SELECT provider_id, scope_key, cooldown_until, status, reason FROM provider_api_cooldowns WHERE quota_key = ?`,
|
|
293
|
+
args: [quotaKey],
|
|
294
|
+
});
|
|
295
|
+
const row = rows[0];
|
|
296
|
+
if (!row)
|
|
297
|
+
return null;
|
|
298
|
+
const until = Number(row.cooldown_until);
|
|
299
|
+
if (!Number.isFinite(until) || until <= Date.now()) {
|
|
300
|
+
await db
|
|
301
|
+
.execute({
|
|
302
|
+
sql: `DELETE FROM provider_api_cooldowns WHERE quota_key = ?`,
|
|
303
|
+
args: [quotaKey],
|
|
304
|
+
})
|
|
305
|
+
.catch(() => undefined);
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
until,
|
|
310
|
+
providerId: String(row.provider_id ?? ""),
|
|
311
|
+
scopeKey: String(row.scope_key ?? ""),
|
|
312
|
+
status: row.status == null ? undefined : Number(row.status),
|
|
313
|
+
reason: row.reason == null ? undefined : String(row.reason),
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
state.persistenceUnavailableUntil = Date.now() + PERSISTENCE_RETRY_MS;
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
async function writePersistedCooldown(quotaKey, entry) {
|
|
322
|
+
try {
|
|
323
|
+
if (!shouldPersistCooldowns() || persistenceTemporarilyUnavailable()) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
await ensureCooldownTable();
|
|
327
|
+
if (!shouldPersistCooldowns() || persistenceTemporarilyUnavailable()) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
const db = getDbExec();
|
|
331
|
+
await db.execute({
|
|
332
|
+
sql: `
|
|
333
|
+
INSERT INTO provider_api_cooldowns
|
|
334
|
+
(quota_key, provider_id, scope_key, cooldown_until, status, reason, updated_at)
|
|
335
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
336
|
+
ON CONFLICT (quota_key) DO UPDATE SET
|
|
337
|
+
provider_id = excluded.provider_id,
|
|
338
|
+
scope_key = excluded.scope_key,
|
|
339
|
+
cooldown_until = excluded.cooldown_until,
|
|
340
|
+
status = excluded.status,
|
|
341
|
+
reason = excluded.reason,
|
|
342
|
+
updated_at = excluded.updated_at
|
|
343
|
+
`,
|
|
344
|
+
args: [
|
|
345
|
+
quotaKey,
|
|
346
|
+
entry.providerId,
|
|
347
|
+
entry.scopeKey,
|
|
348
|
+
Math.floor(entry.until),
|
|
349
|
+
entry.status ?? null,
|
|
350
|
+
entry.reason ?? null,
|
|
351
|
+
Date.now(),
|
|
352
|
+
],
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
catch {
|
|
356
|
+
state.persistenceUnavailableUntil = Date.now() + PERSISTENCE_RETRY_MS;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
function hashStable(value) {
|
|
360
|
+
return createHash("sha256")
|
|
361
|
+
.update(stableStringify(value))
|
|
362
|
+
.digest("hex")
|
|
363
|
+
.slice(0, 24);
|
|
364
|
+
}
|
|
365
|
+
function stableStringify(value) {
|
|
366
|
+
if (value === null || typeof value !== "object")
|
|
367
|
+
return JSON.stringify(value);
|
|
368
|
+
if (Array.isArray(value))
|
|
369
|
+
return `[${value.map(stableStringify).join(",")}]`;
|
|
370
|
+
const entries = Object.entries(value)
|
|
371
|
+
.filter(([, entry]) => entry !== undefined)
|
|
372
|
+
.sort(([a], [b]) => a.localeCompare(b));
|
|
373
|
+
return `{${entries
|
|
374
|
+
.map(([key, entry]) => `${JSON.stringify(key)}:${stableStringify(entry)}`)
|
|
375
|
+
.join(",")}}`;
|
|
376
|
+
}
|
|
377
|
+
function stringOrNull(value) {
|
|
378
|
+
return typeof value === "string" && value ? value : null;
|
|
379
|
+
}
|
|
380
|
+
function dedupeHeaders(headers) {
|
|
381
|
+
if (!headers)
|
|
382
|
+
return {};
|
|
383
|
+
const blocked = new Set([
|
|
384
|
+
"authorization",
|
|
385
|
+
"cookie",
|
|
386
|
+
"proxy-authorization",
|
|
387
|
+
"set-cookie",
|
|
388
|
+
"x-api-key",
|
|
389
|
+
"api-key",
|
|
390
|
+
]);
|
|
391
|
+
return Object.fromEntries(Object.entries(headers)
|
|
392
|
+
.filter(([key]) => !blocked.has(key.toLowerCase()))
|
|
393
|
+
.sort(([a], [b]) => a.localeCompare(b)));
|
|
394
|
+
}
|
|
395
|
+
//# sourceMappingURL=quota-governor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quota-governor.js","sourceRoot":"","sources":["../../src/provider-api/quota-governor.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\nimport { getDbExec, intType } from \"../db/client.js\";\nimport type { CredentialContext } from \"../credentials/index.js\";\n\nexport interface ProviderQuotaIdentityInput {\n appId: string;\n providerId: string;\n ctx: CredentialContext;\n credentialSources: readonly Record<string, unknown>[];\n connectionId?: string | null;\n accountId?: string | null;\n}\n\nexport interface ProviderQuotaIdentity {\n quotaKey: string;\n providerId: string;\n scopeKey: string;\n credentialFingerprint: string;\n}\n\nexport interface ProviderQuotaRequest {\n identity: ProviderQuotaIdentity;\n method: string;\n target: string;\n requestKey?: string;\n}\n\nexport interface ProviderQuotaObservedResult {\n status?: number;\n headers?: Record<string, string>;\n}\n\nexport interface ProviderQuotaExhaustedDetail {\n providerId: string;\n scopeKey: string;\n quotaKey: string;\n method: string;\n target: string;\n retryAfterMs: number;\n retryAt: string;\n reason: \"cooldown\" | \"retry_after\" | \"max_attempts\";\n}\n\nexport interface ExecuteWithProviderQuotaOptions<T> {\n request: ProviderQuotaRequest;\n execute: () => Promise<T>;\n inspect: (result: T) => ProviderQuotaObservedResult;\n buildQuotaExhaustedResult: (detail: ProviderQuotaExhaustedDetail) => T;\n maxAttempts?: number;\n maxWaitMs?: number;\n}\n\ninterface QueueState {\n tail: Promise<void>;\n}\n\ninterface CooldownEntry {\n until: number;\n providerId: string;\n scopeKey: string;\n status?: number;\n reason?: string;\n}\n\ninterface ProviderQuotaState {\n queues: Map<string, QueueState>;\n inflight: Map<string, Promise<unknown>>;\n cooldowns: Map<string, CooldownEntry>;\n initPromise?: Promise<void>;\n persistenceUnavailableUntil?: number;\n}\n\nconst GLOBAL_KEY = \"__agentNativeProviderApiQuotaGovernor\" as const;\ntype GlobalWithProviderQuota = typeof globalThis & {\n [GLOBAL_KEY]?: ProviderQuotaState;\n};\n\nconst globalRef = globalThis as GlobalWithProviderQuota;\nconst initialState: ProviderQuotaState = {\n queues: new Map(),\n inflight: new Map(),\n cooldowns: new Map(),\n};\nconst state: ProviderQuotaState =\n globalRef[GLOBAL_KEY] ?? (globalRef[GLOBAL_KEY] = initialState);\n\nconst DEFAULT_MAX_ATTEMPTS = 3;\nconst DEFAULT_MAX_WAIT_MS = 55_000;\nconst MAX_FALLBACK_BACKOFF_MS = 30_000;\nconst PERSISTENCE_RETRY_MS = 60_000;\n\nexport function createProviderQuotaIdentity(\n input: ProviderQuotaIdentityInput,\n): ProviderQuotaIdentity {\n const scopeKey = input.ctx.orgId\n ? `org:${input.ctx.orgId}`\n : input.ctx.userEmail\n ? `user:${input.ctx.userEmail}`\n : \"anonymous\";\n const credentialSources = input.credentialSources\n .map((source) => ({\n provider: String(source.provider ?? input.providerId),\n key: String(source.key ?? \"\"),\n source: String(source.source ?? \"\"),\n connectionId: stringOrNull(source.connectionId) ?? input.connectionId,\n accountId: stringOrNull(source.accountId) ?? input.accountId,\n scope: stringOrNull(source.scope),\n }))\n .sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));\n const credentialFingerprint = hashStable({\n providerId: input.providerId,\n connectionId: input.connectionId ?? null,\n accountId: input.accountId ?? null,\n credentialSources,\n });\n return {\n providerId: input.providerId,\n scopeKey,\n credentialFingerprint,\n quotaKey: hashStable({\n appId: input.appId,\n providerId: input.providerId,\n scopeKey,\n credentialFingerprint,\n }),\n };\n}\n\nexport function createProviderRequestDedupeKey(input: {\n method: string;\n url: string;\n body?: BodyInit;\n headers?: Record<string, string>;\n}): string | undefined {\n const method = input.method.toUpperCase();\n if (method !== \"GET\" && method !== \"HEAD\") return undefined;\n return hashStable({\n method,\n url: input.url,\n headers: dedupeHeaders(input.headers),\n body:\n typeof input.body === \"string\"\n ? input.body\n : input.body == null\n ? null\n : \"[body]\",\n });\n}\n\nexport async function executeWithProviderQuota<T>(\n options: ExecuteWithProviderQuotaOptions<T>,\n): Promise<T> {\n if (isQuotaGovernorDisabled()) return options.execute();\n\n const dedupeKey = options.request.requestKey\n ? `${options.request.identity.quotaKey}:${options.request.requestKey}`\n : null;\n if (dedupeKey) {\n const existing = state.inflight.get(dedupeKey);\n if (existing) return existing as Promise<T>;\n }\n\n const run = runInQueue(options.request.identity.quotaKey, () =>\n executeWithCooldown(options),\n );\n if (dedupeKey) {\n state.inflight.set(dedupeKey, run);\n run\n .finally(() => {\n state.inflight.delete(dedupeKey);\n })\n .catch(() => undefined);\n }\n return run;\n}\n\nexport function resetProviderQuotaStateForTests(): void {\n state.queues.clear();\n state.inflight.clear();\n state.cooldowns.clear();\n state.initPromise = undefined;\n state.persistenceUnavailableUntil = undefined;\n}\n\nasync function executeWithCooldown<T>(\n options: ExecuteWithProviderQuotaOptions<T>,\n): Promise<T> {\n const maxAttempts = positiveIntFromEnv(\n \"AGENT_NATIVE_PROVIDER_API_MAX_ATTEMPTS\",\n options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS,\n );\n const maxWaitMs = positiveIntFromEnv(\n \"AGENT_NATIVE_PROVIDER_API_MAX_WAIT_MS\",\n options.maxWaitMs ?? DEFAULT_MAX_WAIT_MS,\n );\n const deadline = Date.now() + maxWaitMs;\n\n for (let attempt = 0; ; attempt++) {\n const cooldownUntil = await getCooldownUntil(options.request.identity);\n if (cooldownUntil > Date.now()) {\n const waitMs = cooldownUntil - Date.now();\n if (waitMs > Math.max(0, deadline - Date.now())) {\n return options.buildQuotaExhaustedResult(\n quotaExhaustedDetail(options.request, waitMs, \"cooldown\"),\n );\n }\n await sleepMs(waitMs);\n }\n\n const result = await options.execute();\n const observed = options.inspect(result);\n if (observed.status !== 429) {\n return result;\n }\n\n const retryAfterMs = retryDelayMs(observed.headers, attempt);\n await setCooldownUntil(\n options.request.identity,\n Date.now() + retryAfterMs,\n {\n status: observed.status,\n reason: \"429\",\n },\n );\n\n if (attempt + 1 >= maxAttempts) {\n return options.buildQuotaExhaustedResult(\n quotaExhaustedDetail(options.request, retryAfterMs, \"max_attempts\"),\n );\n }\n\n if (retryAfterMs > Math.max(0, deadline - Date.now())) {\n return options.buildQuotaExhaustedResult(\n quotaExhaustedDetail(options.request, retryAfterMs, \"retry_after\"),\n );\n }\n\n await sleepMs(retryAfterMs);\n }\n}\n\nasync function runInQueue<T>(\n quotaKey: string,\n fn: () => Promise<T>,\n): Promise<T> {\n const queue = state.queues.get(quotaKey) ?? { tail: Promise.resolve() };\n state.queues.set(quotaKey, queue);\n\n const previous = queue.tail.catch(() => undefined);\n let release: () => void = () => {};\n const current = new Promise<void>((resolve) => {\n release = resolve;\n });\n const tail = previous.then(() => current);\n queue.tail = tail;\n\n await previous;\n try {\n return await fn();\n } finally {\n release();\n if (queue.tail === tail) state.queues.delete(quotaKey);\n }\n}\n\nasync function getCooldownUntil(\n identity: ProviderQuotaIdentity,\n): Promise<number> {\n const memory = state.cooldowns.get(identity.quotaKey);\n const now = Date.now();\n if (memory && memory.until <= now) {\n state.cooldowns.delete(identity.quotaKey);\n }\n let until = memory && memory.until > now ? memory.until : 0;\n\n const persisted = await readPersistedCooldown(identity.quotaKey);\n if (persisted && persisted.until > now) {\n state.cooldowns.set(identity.quotaKey, persisted);\n until = Math.max(until, persisted.until);\n }\n return until;\n}\n\nasync function setCooldownUntil(\n identity: ProviderQuotaIdentity,\n until: number,\n options: { status?: number; reason?: string } = {},\n): Promise<void> {\n const entry: CooldownEntry = {\n until,\n providerId: identity.providerId,\n scopeKey: identity.scopeKey,\n status: options.status,\n reason: options.reason,\n };\n state.cooldowns.set(identity.quotaKey, entry);\n await writePersistedCooldown(identity.quotaKey, entry);\n}\n\nfunction quotaExhaustedDetail(\n request: ProviderQuotaRequest,\n retryAfterMs: number,\n reason: ProviderQuotaExhaustedDetail[\"reason\"],\n): ProviderQuotaExhaustedDetail {\n const retryAtMs = Date.now() + Math.max(0, retryAfterMs);\n return {\n providerId: request.identity.providerId,\n scopeKey: request.identity.scopeKey,\n quotaKey: request.identity.quotaKey,\n method: request.method,\n target: request.target,\n retryAfterMs: Math.max(0, retryAfterMs),\n retryAt: new Date(retryAtMs).toISOString(),\n reason,\n };\n}\n\nfunction retryDelayMs(\n headers: Record<string, string> | undefined,\n attempt: number,\n): number {\n const retryAfter = retryAfterMs(headers);\n if (retryAfter !== null) return retryAfter;\n const base = Math.min(1000 * 2 ** attempt, MAX_FALLBACK_BACKOFF_MS);\n return Math.min(\n base + Math.floor(Math.random() * 250),\n MAX_FALLBACK_BACKOFF_MS,\n );\n}\n\nfunction retryAfterMs(\n headers: Record<string, string> | undefined,\n): number | null {\n const raw = headerValue(headers, \"retry-after\");\n if (!raw) return null;\n const seconds = Number(raw);\n if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000);\n const dateMs = Date.parse(raw);\n if (Number.isFinite(dateMs)) return Math.max(0, dateMs - Date.now());\n return null;\n}\n\nfunction headerValue(\n headers: Record<string, string> | undefined,\n name: string,\n): string | undefined {\n if (!headers) return undefined;\n const lowerName = name.toLowerCase();\n for (const [key, value] of Object.entries(headers)) {\n if (key.toLowerCase() === lowerName) return value;\n }\n return undefined;\n}\n\nfunction sleepMs(ms: number): Promise<void> {\n if (ms <= 0) return Promise.resolve();\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction positiveIntFromEnv(name: string, fallback: number): number {\n const raw = process.env[name];\n const parsed = raw ? Number(raw) : Number.NaN;\n if (Number.isFinite(parsed) && parsed > 0) return Math.floor(parsed);\n return fallback;\n}\n\nfunction isQuotaGovernorDisabled(): boolean {\n const raw = process.env.AGENT_NATIVE_PROVIDER_API_QUOTA_GOVERNOR;\n return raw === \"0\" || raw?.toLowerCase() === \"false\";\n}\n\nfunction shouldPersistCooldowns(): boolean {\n const raw = process.env.AGENT_NATIVE_PROVIDER_API_PERSIST_COOLDOWNS;\n if (raw === \"0\" || raw?.toLowerCase() === \"false\") return false;\n if (raw === \"1\" || raw?.toLowerCase() === \"true\") return true;\n if (process.env.NODE_ENV === \"test\" || process.env.VITEST) return false;\n return Boolean(\n process.env.DATABASE_URL ||\n process.env.NETLIFY ||\n process.env.VERCEL ||\n process.env.AWS_LAMBDA_FUNCTION_NAME ||\n process.env.APP_NAME,\n );\n}\n\nfunction persistenceTemporarilyUnavailable(): boolean {\n return Boolean(\n state.persistenceUnavailableUntil &&\n state.persistenceUnavailableUntil > Date.now(),\n );\n}\n\nasync function ensureCooldownTable(): Promise<void> {\n if (!shouldPersistCooldowns()) return;\n if (persistenceTemporarilyUnavailable()) return;\n if (!state.initPromise) {\n state.initPromise = (async () => {\n const db = getDbExec();\n const integerType = intType();\n await db.execute(`\n CREATE TABLE IF NOT EXISTS provider_api_cooldowns (\n quota_key TEXT NOT NULL,\n provider_id TEXT NOT NULL,\n scope_key TEXT NOT NULL,\n cooldown_until ${integerType} NOT NULL,\n status ${integerType},\n reason TEXT,\n updated_at ${integerType} NOT NULL,\n PRIMARY KEY (quota_key)\n )\n `);\n try {\n await db.execute(\n `CREATE INDEX IF NOT EXISTS provider_api_cooldowns_provider_idx ON provider_api_cooldowns (provider_id, cooldown_until)`,\n );\n } catch {\n // Index already exists or the backend rejected best-effort indexing.\n }\n })().catch((err) => {\n state.initPromise = undefined;\n state.persistenceUnavailableUntil = Date.now() + PERSISTENCE_RETRY_MS;\n throw err;\n });\n }\n await state.initPromise;\n}\n\nasync function readPersistedCooldown(\n quotaKey: string,\n): Promise<CooldownEntry | null> {\n try {\n if (!shouldPersistCooldowns() || persistenceTemporarilyUnavailable()) {\n return null;\n }\n await ensureCooldownTable();\n if (!shouldPersistCooldowns() || persistenceTemporarilyUnavailable()) {\n return null;\n }\n const db = getDbExec();\n const { rows } = await db.execute({\n sql: `SELECT provider_id, scope_key, cooldown_until, status, reason FROM provider_api_cooldowns WHERE quota_key = ?`,\n args: [quotaKey],\n });\n const row = rows[0];\n if (!row) return null;\n const until = Number(row.cooldown_until);\n if (!Number.isFinite(until) || until <= Date.now()) {\n await db\n .execute({\n sql: `DELETE FROM provider_api_cooldowns WHERE quota_key = ?`,\n args: [quotaKey],\n })\n .catch(() => undefined);\n return null;\n }\n return {\n until,\n providerId: String(row.provider_id ?? \"\"),\n scopeKey: String(row.scope_key ?? \"\"),\n status: row.status == null ? undefined : Number(row.status),\n reason: row.reason == null ? undefined : String(row.reason),\n };\n } catch {\n state.persistenceUnavailableUntil = Date.now() + PERSISTENCE_RETRY_MS;\n return null;\n }\n}\n\nasync function writePersistedCooldown(\n quotaKey: string,\n entry: CooldownEntry,\n): Promise<void> {\n try {\n if (!shouldPersistCooldowns() || persistenceTemporarilyUnavailable()) {\n return;\n }\n await ensureCooldownTable();\n if (!shouldPersistCooldowns() || persistenceTemporarilyUnavailable()) {\n return;\n }\n const db = getDbExec();\n await db.execute({\n sql: `\n INSERT INTO provider_api_cooldowns\n (quota_key, provider_id, scope_key, cooldown_until, status, reason, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (quota_key) DO UPDATE SET\n provider_id = excluded.provider_id,\n scope_key = excluded.scope_key,\n cooldown_until = excluded.cooldown_until,\n status = excluded.status,\n reason = excluded.reason,\n updated_at = excluded.updated_at\n `,\n args: [\n quotaKey,\n entry.providerId,\n entry.scopeKey,\n Math.floor(entry.until),\n entry.status ?? null,\n entry.reason ?? null,\n Date.now(),\n ],\n });\n } catch {\n state.persistenceUnavailableUntil = Date.now() + PERSISTENCE_RETRY_MS;\n }\n}\n\nfunction hashStable(value: unknown): string {\n return createHash(\"sha256\")\n .update(stableStringify(value))\n .digest(\"hex\")\n .slice(0, 24);\n}\n\nfunction stableStringify(value: unknown): string {\n if (value === null || typeof value !== \"object\") return JSON.stringify(value);\n if (Array.isArray(value)) return `[${value.map(stableStringify).join(\",\")}]`;\n const entries = Object.entries(value as Record<string, unknown>)\n .filter(([, entry]) => entry !== undefined)\n .sort(([a], [b]) => a.localeCompare(b));\n return `{${entries\n .map(([key, entry]) => `${JSON.stringify(key)}:${stableStringify(entry)}`)\n .join(\",\")}}`;\n}\n\nfunction stringOrNull(value: unknown): string | null {\n return typeof value === \"string\" && value ? value : null;\n}\n\nfunction dedupeHeaders(\n headers: Record<string, string> | undefined,\n): Record<string, string> {\n if (!headers) return {};\n const blocked = new Set([\n \"authorization\",\n \"cookie\",\n \"proxy-authorization\",\n \"set-cookie\",\n \"x-api-key\",\n \"api-key\",\n ]);\n return Object.fromEntries(\n Object.entries(headers)\n .filter(([key]) => !blocked.has(key.toLowerCase()))\n .sort(([a], [b]) => a.localeCompare(b)),\n );\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"staging.d.ts","sourceRoot":"","sources":["../../src/provider-api/staging.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAWzD,0EAA0E;AAC1E,MAAM,MAAM,SAAS,GACjB,MAAM,GACN,MAAM,GACN,SAAS,GACT,OAAO,GACP,SAAS,GACT,MAAM,GACN,MAAM,CAAC;AAEX,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC/B;AAED,MAAM,WAAW,kBAAmB,SAAQ,sBAAsB;IAChE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC/B;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE;QACP,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,EAAE,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;KACvC,CAAC;IACF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAMD,oEAAoE;AACpE,MAAM,MAAM,mBAAmB,GAAG,CAChC,IAAI,EAAE,sBAAsB,KACzB,OAAO,CAAC,OAAO,CAAC,CAAC;AAEtB,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAsBD,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,OAAO,EACb,SAAS,GAAE,SAAkB,GAC5B,OAAO,EAAE,CA0BX;
|
|
1
|
+
{"version":3,"file":"staging.d.ts","sourceRoot":"","sources":["../../src/provider-api/staging.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAWzD,0EAA0E;AAC1E,MAAM,MAAM,SAAS,GACjB,MAAM,GACN,MAAM,GACN,SAAS,GACT,OAAO,GACP,SAAS,GACT,MAAM,GACN,MAAM,CAAC;AAEX,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC/B;AAED,MAAM,WAAW,kBAAmB,SAAQ,sBAAsB;IAChE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC/B;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE;QACP,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,EAAE,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;KACvC,CAAC;IACF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAMD,oEAAoE;AACpE,MAAM,MAAM,mBAAmB,GAAG,CAChC,IAAI,EAAE,sBAAsB,KACzB,OAAO,CAAC,OAAO,CAAC,CAAC;AAEtB,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAsBD,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,OAAO,EACb,SAAS,GAAE,SAAkB,GAC5B,OAAO,EAAE,CA0BX;AAqED;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,mBAAmB,EAC5B,GAAG,EAAE,qBAAqB,GACzB,OAAO,CAAC,aAAa,CAAC,CAuNxB"}
|
|
@@ -110,6 +110,15 @@ function getRetryAfterMs(responseHeaders, attempt) {
|
|
|
110
110
|
// Exponential back-off: 1s, 2s, 4s, 8s, cap 30s
|
|
111
111
|
return Math.min(1000 * Math.pow(2, attempt), 30_000);
|
|
112
112
|
}
|
|
113
|
+
function isProviderQuotaCooldown(response) {
|
|
114
|
+
const headers = response.headers;
|
|
115
|
+
const quotaHeader = headers?.["x-agent-native-provider-quota"] ??
|
|
116
|
+
headers?.["X-Agent-Native-Provider-Quota"];
|
|
117
|
+
if (quotaHeader === "exhausted")
|
|
118
|
+
return true;
|
|
119
|
+
const json = response.json;
|
|
120
|
+
return json?.error === "provider_quota_exhausted";
|
|
121
|
+
}
|
|
113
122
|
// ---------------------------------------------------------------------------
|
|
114
123
|
// Core staging executor
|
|
115
124
|
// ---------------------------------------------------------------------------
|
|
@@ -197,6 +206,13 @@ export async function stagingExecuteRequest(args, execute, ctx) {
|
|
|
197
206
|
const raw = (await execute(currentArgs));
|
|
198
207
|
const response = raw.response;
|
|
199
208
|
if (response?.status === 429) {
|
|
209
|
+
if (isProviderQuotaCooldown(response)) {
|
|
210
|
+
const json = response.json;
|
|
211
|
+
const retryAt = typeof json?.retryAt === "string"
|
|
212
|
+
? ` Retry after ${json.retryAt}.`
|
|
213
|
+
: "";
|
|
214
|
+
throw new Error(`Provider API quota exhausted (429).${retryAt}`);
|
|
215
|
+
}
|
|
200
216
|
if (attempt >= 5) {
|
|
201
217
|
throw new Error(`Provider returned 429 Too Many Requests after ${attempt + 1} attempts. ` +
|
|
202
218
|
`Try again later or reduce the page count.`);
|