@anuma/sdk 9.1.4 → 9.1.6
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/expo/{chunk-XW3LYSDA.mjs → chunk-WRZTH4OS.mjs} +38 -13
- package/dist/expo/{consolidate-4CQJYKRK.mjs → consolidate-HKL5EGZT.mjs} +1 -1
- package/dist/expo/index.cjs +201 -67
- package/dist/expo/index.d.mts +18 -5
- package/dist/expo/index.d.ts +18 -5
- package/dist/expo/index.mjs +164 -55
- package/dist/memory/context.d.mts +123 -0
- package/dist/memory/context.d.ts +123 -0
- package/dist/react/{chunk-7QX4J7KV.mjs → chunk-RSA3S6OA.mjs} +38 -13
- package/dist/react/{consolidate-WLDYJF6J.mjs → consolidate-WFTPBHDV.mjs} +1 -1
- package/dist/react/index.cjs +212 -77
- package/dist/react/index.d.mts +18 -5
- package/dist/react/index.d.ts +18 -5
- package/dist/react/index.mjs +180 -70
- package/dist/server/{chunk-AU3Z7NBC.mjs → chunk-QEROZ4VH.mjs} +38 -13
- package/dist/server/{consolidate-B6OSCZU4.mjs → consolidate-PYED62LW.mjs} +1 -1
- package/dist/server/index.cjs +198 -64
- package/dist/server/index.d.mts +18 -5
- package/dist/server/index.d.ts +18 -5
- package/dist/server/index.mjs +161 -52
- package/package.json +1 -1
|
@@ -183,6 +183,51 @@ interface EntityOperationsContext {
|
|
|
183
183
|
canWrite?: () => Promise<boolean>;
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
/**
|
|
187
|
+
* Class-B task types — the vocabulary of the `X-Anuma-Task-Type` header.
|
|
188
|
+
*
|
|
189
|
+
* A Class-B flow has ONE fixed purpose and no conversation: extract memories
|
|
190
|
+
* from a snippet, pick a topic, consolidate duplicates, classify a query. Today
|
|
191
|
+
* each of those builds its own system prompt on the client and sends it as a
|
|
192
|
+
* `role:system` message, which makes the prompt a suggestion rather than a
|
|
193
|
+
* contract — a caller can reword it into "you are a general assistant" and get
|
|
194
|
+
* general chat at internal-endpoint prices.
|
|
195
|
+
*
|
|
196
|
+
* The fix is that the SERVER owns the text and the client only names the task.
|
|
197
|
+
* This header is the name. The portal allowlists it against its own copy of this
|
|
198
|
+
* enum (ai-portal `internal/systemprompt/tasks.go`) and DROPS anything it does
|
|
199
|
+
* not recognise, so an unknown value degrades to "no task declared" rather than
|
|
200
|
+
* failing the request.
|
|
201
|
+
*
|
|
202
|
+
* ⚠ These string values are a wire contract with ai-portal. Adding one here
|
|
203
|
+
* before the portal knows it is harmless (it is dropped); renaming or removing
|
|
204
|
+
* one silently stops that flow being counted. Keep them in step with
|
|
205
|
+
* `AllTaskTypes` on the portal side, and see ai-portal
|
|
206
|
+
* `docs/BACKEND_OWNED_TASK_PROMPTS.md` for the rollout.
|
|
207
|
+
*
|
|
208
|
+
* ⚠ RELEASE ORDER. This union is a PUBLIC type, so a split is breaking in both
|
|
209
|
+
* directions and has to be sequenced:
|
|
210
|
+
*
|
|
211
|
+
* 1. merge + publish this package;
|
|
212
|
+
* 2. bump `@anuma/sdk` in the consumer and switch its call sites to the new
|
|
213
|
+
* names in the same commit.
|
|
214
|
+
*
|
|
215
|
+
* A consumer on the OLD version cannot compile the new names, and a consumer on
|
|
216
|
+
* the NEW version cannot compile the retired ones (`app_inspiration` is gone
|
|
217
|
+
* here, replaced by the three `app_inspiration_*` names). Both halves of that
|
|
218
|
+
* are type-only — the wire itself always degrades safely, because the portal
|
|
219
|
+
* drops any value it does not recognise. Concretely: ai-memoryless-client#5782
|
|
220
|
+
* is blocked on this publish for exactly that reason.
|
|
221
|
+
*
|
|
222
|
+
* Deliberately ABSENT: agent personas, `reflect()` when it answers the user, and
|
|
223
|
+
* the app-idea chat. Those carry an open conversation with the user, so a fixed
|
|
224
|
+
* server-owned prompt would be wrong rather than merely premature — the same
|
|
225
|
+
* boundary {@link INTERNAL_FLOW_MARKER} draws.
|
|
226
|
+
*
|
|
227
|
+
* @public
|
|
228
|
+
*/
|
|
229
|
+
type TaskType = "title" | "commit_message" | "memory_extract" | "memory_import_extract" | "memory_dedup" | "memory_decay" | "memory_injection_check" | "memory_verify_support" | "memory_autosort" | "memory_topic" | "memory_consolidate" | "memory_decompose" | "memory_profile_synth" | "memory_graph" | "memory_recall_embed" | "memory_retain_embed" | "folder_context" | "selection_memory" | "summarize" | "style_analysis" | "classify_search" | "app_inspiration_generate" | "app_inspiration_trends" | "app_inspiration_remix" | "spotlight_image" | "slide_image" | "slide_video" | "slide_music" | "slide_sfx" | "media_video" | "media_audio" | "media_bg_removal" | "media_image_preset" | "tool_app_builder" | "tool_document_builder" | "tool_slides" | "tool_slides_image_gen" | "connector_guidance" | "cf_task" | "xbot_intent" | "xbot_council" | "scheduled_task";
|
|
230
|
+
|
|
186
231
|
/**
|
|
187
232
|
* Auth for portal LLM calls (extraction, consolidation, decomposition,
|
|
188
233
|
* reflection). Mirrors `memoryEngine`'s `EmbeddingOptions` dual-auth:
|
|
@@ -202,6 +247,76 @@ interface PortalLlmAuth {
|
|
|
202
247
|
getToken?: () => Promise<string | null>;
|
|
203
248
|
}
|
|
204
249
|
|
|
250
|
+
/**
|
|
251
|
+
* Options for embedding generation
|
|
252
|
+
*
|
|
253
|
+
* Supports two auth methods:
|
|
254
|
+
* - `getToken`: For Privy identity tokens (uses Authorization: Bearer header)
|
|
255
|
+
* - `apiKey`: For direct API keys (uses X-API-Key header)
|
|
256
|
+
*
|
|
257
|
+
* At least one of `getToken` or `apiKey` must be provided.
|
|
258
|
+
*/
|
|
259
|
+
interface EmbeddingOptions {
|
|
260
|
+
taskType?: TaskType;
|
|
261
|
+
/** Function to get auth token (e.g., Privy's getIdentityToken). Uses Authorization: Bearer header. */
|
|
262
|
+
getToken?: () => Promise<string | null>;
|
|
263
|
+
/** Direct API key for server-side usage. Uses X-API-Key header. */
|
|
264
|
+
apiKey?: string;
|
|
265
|
+
/** Base URL for the API */
|
|
266
|
+
baseUrl?: string;
|
|
267
|
+
/** Embedding model to use */
|
|
268
|
+
model?: string;
|
|
269
|
+
/** Max texts per API call for batch embeddings (default: 100). Larger arrays are split into chunks. */
|
|
270
|
+
batchSize?: number;
|
|
271
|
+
/**
|
|
272
|
+
* Optional in-memory cache for embedding vectors. When provided, texts
|
|
273
|
+
* are looked up in this map before calling the API, and new embeddings
|
|
274
|
+
* are stored after generation. Useful when the same texts are embedded
|
|
275
|
+
* repeatedly (e.g., across eval iterations or re-indexing runs).
|
|
276
|
+
*
|
|
277
|
+
* Values are stored as `Float32Array` (the embedding model's native
|
|
278
|
+
* precision) rather than a float64 `number[]`, halving the resident RAM of
|
|
279
|
+
* the cache with no precision loss. `generateEmbedding(s)` still return
|
|
280
|
+
* `number[]` at the API boundary, so callers are unaffected.
|
|
281
|
+
*/
|
|
282
|
+
cache?: Map<string, Float32Array>;
|
|
283
|
+
/** Called after each embedding API call with the token usage from the response. */
|
|
284
|
+
onUsage?: (usage: {
|
|
285
|
+
promptTokens: number;
|
|
286
|
+
totalTokens: number;
|
|
287
|
+
}) => void;
|
|
288
|
+
/**
|
|
289
|
+
* Optional transform applied to each text immediately before it is sent to
|
|
290
|
+
* the embeddings endpoint (e.g. `PiiRedactor.maskText`). The cache and result
|
|
291
|
+
* ordering still key on the original text — only the API request body is
|
|
292
|
+
* transformed — so callers can keep storing/displaying the original value
|
|
293
|
+
* while real PII never reaches the server. Used when PII redaction is active.
|
|
294
|
+
*/
|
|
295
|
+
maskInput?: (text: string) => string;
|
|
296
|
+
/**
|
|
297
|
+
* Deadline, in ms, for EACH embeddings HTTP attempt (default 15000). An
|
|
298
|
+
* attempt that exceeds it is aborted and counts as a transient failure, so the
|
|
299
|
+
* bounded retry still applies. `0` disables the deadline.
|
|
300
|
+
*/
|
|
301
|
+
timeoutMs?: number;
|
|
302
|
+
/**
|
|
303
|
+
* Deadline, in ms, for the `getToken()` read that precedes a request (default
|
|
304
|
+
* 10000). A provider that never settles rejects instead of hanging the
|
|
305
|
+
* embedding — and the recall waiting on it. `0` disables the deadline.
|
|
306
|
+
*/
|
|
307
|
+
tokenTimeoutMs?: number;
|
|
308
|
+
/**
|
|
309
|
+
* Overall deadline, in ms, for one `generateEmbedding` or `generateEmbeddings` call — the token read,
|
|
310
|
+
* every retry attempt and the backoff between them. Unset (the default) means
|
|
311
|
+
* only the per-attempt deadlines apply, which is right for background/bulk
|
|
312
|
+
* embeds. The recall query path sets it (see
|
|
313
|
+
* `RecallOptions.queryEmbedTotalTimeoutMs`) so an outage degrades a turn to
|
|
314
|
+
* BM25 within a few seconds instead of ~4 x `timeoutMs`. Batch calls share
|
|
315
|
+
* one budget across authentication, all chunks, retries and backoff.
|
|
316
|
+
*/
|
|
317
|
+
totalTimeoutMs?: number;
|
|
318
|
+
}
|
|
319
|
+
|
|
205
320
|
interface RecencyOptions {
|
|
206
321
|
/** Override "now" — useful for deterministic tests and back-dated benchmarks. */
|
|
207
322
|
now?: Date;
|
|
@@ -213,6 +328,13 @@ interface RecencyOptions {
|
|
|
213
328
|
noDateMultiplier?: number;
|
|
214
329
|
}
|
|
215
330
|
|
|
331
|
+
type VaultCorpusCache = WeakMap<VaultMemoryOperationsContext, Map<string, Promise<StoredVaultMemory[]>>>;
|
|
332
|
+
type RowRequests = Map<EmbeddingOptions["maskInput"], Map<string, Promise<number[]>>>;
|
|
333
|
+
interface VaultPreparation {
|
|
334
|
+
corpora: VaultCorpusCache;
|
|
335
|
+
rows: WeakMap<VaultMemoryOperationsContext, RowRequests>;
|
|
336
|
+
}
|
|
337
|
+
|
|
216
338
|
/**
|
|
217
339
|
* How a fact's evidence has been evolving.
|
|
218
340
|
*
|
|
@@ -300,6 +422,7 @@ interface RankedMemory {
|
|
|
300
422
|
updatedAt: Date;
|
|
301
423
|
}
|
|
302
424
|
interface RecallOptions {
|
|
425
|
+
/** @internal */ vaultPreparation?: VaultPreparation;
|
|
303
426
|
/** Embedding cache shared with the rest of the turn, so the query is embedded once across lanes and the send. */
|
|
304
427
|
embeddingCache?: Map<string, Float32Array>;
|
|
305
428
|
/** Which kinds to search. Default: ['fact']. */
|
package/dist/memory/context.d.ts
CHANGED
|
@@ -183,6 +183,51 @@ interface EntityOperationsContext {
|
|
|
183
183
|
canWrite?: () => Promise<boolean>;
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
/**
|
|
187
|
+
* Class-B task types — the vocabulary of the `X-Anuma-Task-Type` header.
|
|
188
|
+
*
|
|
189
|
+
* A Class-B flow has ONE fixed purpose and no conversation: extract memories
|
|
190
|
+
* from a snippet, pick a topic, consolidate duplicates, classify a query. Today
|
|
191
|
+
* each of those builds its own system prompt on the client and sends it as a
|
|
192
|
+
* `role:system` message, which makes the prompt a suggestion rather than a
|
|
193
|
+
* contract — a caller can reword it into "you are a general assistant" and get
|
|
194
|
+
* general chat at internal-endpoint prices.
|
|
195
|
+
*
|
|
196
|
+
* The fix is that the SERVER owns the text and the client only names the task.
|
|
197
|
+
* This header is the name. The portal allowlists it against its own copy of this
|
|
198
|
+
* enum (ai-portal `internal/systemprompt/tasks.go`) and DROPS anything it does
|
|
199
|
+
* not recognise, so an unknown value degrades to "no task declared" rather than
|
|
200
|
+
* failing the request.
|
|
201
|
+
*
|
|
202
|
+
* ⚠ These string values are a wire contract with ai-portal. Adding one here
|
|
203
|
+
* before the portal knows it is harmless (it is dropped); renaming or removing
|
|
204
|
+
* one silently stops that flow being counted. Keep them in step with
|
|
205
|
+
* `AllTaskTypes` on the portal side, and see ai-portal
|
|
206
|
+
* `docs/BACKEND_OWNED_TASK_PROMPTS.md` for the rollout.
|
|
207
|
+
*
|
|
208
|
+
* ⚠ RELEASE ORDER. This union is a PUBLIC type, so a split is breaking in both
|
|
209
|
+
* directions and has to be sequenced:
|
|
210
|
+
*
|
|
211
|
+
* 1. merge + publish this package;
|
|
212
|
+
* 2. bump `@anuma/sdk` in the consumer and switch its call sites to the new
|
|
213
|
+
* names in the same commit.
|
|
214
|
+
*
|
|
215
|
+
* A consumer on the OLD version cannot compile the new names, and a consumer on
|
|
216
|
+
* the NEW version cannot compile the retired ones (`app_inspiration` is gone
|
|
217
|
+
* here, replaced by the three `app_inspiration_*` names). Both halves of that
|
|
218
|
+
* are type-only — the wire itself always degrades safely, because the portal
|
|
219
|
+
* drops any value it does not recognise. Concretely: ai-memoryless-client#5782
|
|
220
|
+
* is blocked on this publish for exactly that reason.
|
|
221
|
+
*
|
|
222
|
+
* Deliberately ABSENT: agent personas, `reflect()` when it answers the user, and
|
|
223
|
+
* the app-idea chat. Those carry an open conversation with the user, so a fixed
|
|
224
|
+
* server-owned prompt would be wrong rather than merely premature — the same
|
|
225
|
+
* boundary {@link INTERNAL_FLOW_MARKER} draws.
|
|
226
|
+
*
|
|
227
|
+
* @public
|
|
228
|
+
*/
|
|
229
|
+
type TaskType = "title" | "commit_message" | "memory_extract" | "memory_import_extract" | "memory_dedup" | "memory_decay" | "memory_injection_check" | "memory_verify_support" | "memory_autosort" | "memory_topic" | "memory_consolidate" | "memory_decompose" | "memory_profile_synth" | "memory_graph" | "memory_recall_embed" | "memory_retain_embed" | "folder_context" | "selection_memory" | "summarize" | "style_analysis" | "classify_search" | "app_inspiration_generate" | "app_inspiration_trends" | "app_inspiration_remix" | "spotlight_image" | "slide_image" | "slide_video" | "slide_music" | "slide_sfx" | "media_video" | "media_audio" | "media_bg_removal" | "media_image_preset" | "tool_app_builder" | "tool_document_builder" | "tool_slides" | "tool_slides_image_gen" | "connector_guidance" | "cf_task" | "xbot_intent" | "xbot_council" | "scheduled_task";
|
|
230
|
+
|
|
186
231
|
/**
|
|
187
232
|
* Auth for portal LLM calls (extraction, consolidation, decomposition,
|
|
188
233
|
* reflection). Mirrors `memoryEngine`'s `EmbeddingOptions` dual-auth:
|
|
@@ -202,6 +247,76 @@ interface PortalLlmAuth {
|
|
|
202
247
|
getToken?: () => Promise<string | null>;
|
|
203
248
|
}
|
|
204
249
|
|
|
250
|
+
/**
|
|
251
|
+
* Options for embedding generation
|
|
252
|
+
*
|
|
253
|
+
* Supports two auth methods:
|
|
254
|
+
* - `getToken`: For Privy identity tokens (uses Authorization: Bearer header)
|
|
255
|
+
* - `apiKey`: For direct API keys (uses X-API-Key header)
|
|
256
|
+
*
|
|
257
|
+
* At least one of `getToken` or `apiKey` must be provided.
|
|
258
|
+
*/
|
|
259
|
+
interface EmbeddingOptions {
|
|
260
|
+
taskType?: TaskType;
|
|
261
|
+
/** Function to get auth token (e.g., Privy's getIdentityToken). Uses Authorization: Bearer header. */
|
|
262
|
+
getToken?: () => Promise<string | null>;
|
|
263
|
+
/** Direct API key for server-side usage. Uses X-API-Key header. */
|
|
264
|
+
apiKey?: string;
|
|
265
|
+
/** Base URL for the API */
|
|
266
|
+
baseUrl?: string;
|
|
267
|
+
/** Embedding model to use */
|
|
268
|
+
model?: string;
|
|
269
|
+
/** Max texts per API call for batch embeddings (default: 100). Larger arrays are split into chunks. */
|
|
270
|
+
batchSize?: number;
|
|
271
|
+
/**
|
|
272
|
+
* Optional in-memory cache for embedding vectors. When provided, texts
|
|
273
|
+
* are looked up in this map before calling the API, and new embeddings
|
|
274
|
+
* are stored after generation. Useful when the same texts are embedded
|
|
275
|
+
* repeatedly (e.g., across eval iterations or re-indexing runs).
|
|
276
|
+
*
|
|
277
|
+
* Values are stored as `Float32Array` (the embedding model's native
|
|
278
|
+
* precision) rather than a float64 `number[]`, halving the resident RAM of
|
|
279
|
+
* the cache with no precision loss. `generateEmbedding(s)` still return
|
|
280
|
+
* `number[]` at the API boundary, so callers are unaffected.
|
|
281
|
+
*/
|
|
282
|
+
cache?: Map<string, Float32Array>;
|
|
283
|
+
/** Called after each embedding API call with the token usage from the response. */
|
|
284
|
+
onUsage?: (usage: {
|
|
285
|
+
promptTokens: number;
|
|
286
|
+
totalTokens: number;
|
|
287
|
+
}) => void;
|
|
288
|
+
/**
|
|
289
|
+
* Optional transform applied to each text immediately before it is sent to
|
|
290
|
+
* the embeddings endpoint (e.g. `PiiRedactor.maskText`). The cache and result
|
|
291
|
+
* ordering still key on the original text — only the API request body is
|
|
292
|
+
* transformed — so callers can keep storing/displaying the original value
|
|
293
|
+
* while real PII never reaches the server. Used when PII redaction is active.
|
|
294
|
+
*/
|
|
295
|
+
maskInput?: (text: string) => string;
|
|
296
|
+
/**
|
|
297
|
+
* Deadline, in ms, for EACH embeddings HTTP attempt (default 15000). An
|
|
298
|
+
* attempt that exceeds it is aborted and counts as a transient failure, so the
|
|
299
|
+
* bounded retry still applies. `0` disables the deadline.
|
|
300
|
+
*/
|
|
301
|
+
timeoutMs?: number;
|
|
302
|
+
/**
|
|
303
|
+
* Deadline, in ms, for the `getToken()` read that precedes a request (default
|
|
304
|
+
* 10000). A provider that never settles rejects instead of hanging the
|
|
305
|
+
* embedding — and the recall waiting on it. `0` disables the deadline.
|
|
306
|
+
*/
|
|
307
|
+
tokenTimeoutMs?: number;
|
|
308
|
+
/**
|
|
309
|
+
* Overall deadline, in ms, for one `generateEmbedding` or `generateEmbeddings` call — the token read,
|
|
310
|
+
* every retry attempt and the backoff between them. Unset (the default) means
|
|
311
|
+
* only the per-attempt deadlines apply, which is right for background/bulk
|
|
312
|
+
* embeds. The recall query path sets it (see
|
|
313
|
+
* `RecallOptions.queryEmbedTotalTimeoutMs`) so an outage degrades a turn to
|
|
314
|
+
* BM25 within a few seconds instead of ~4 x `timeoutMs`. Batch calls share
|
|
315
|
+
* one budget across authentication, all chunks, retries and backoff.
|
|
316
|
+
*/
|
|
317
|
+
totalTimeoutMs?: number;
|
|
318
|
+
}
|
|
319
|
+
|
|
205
320
|
interface RecencyOptions {
|
|
206
321
|
/** Override "now" — useful for deterministic tests and back-dated benchmarks. */
|
|
207
322
|
now?: Date;
|
|
@@ -213,6 +328,13 @@ interface RecencyOptions {
|
|
|
213
328
|
noDateMultiplier?: number;
|
|
214
329
|
}
|
|
215
330
|
|
|
331
|
+
type VaultCorpusCache = WeakMap<VaultMemoryOperationsContext, Map<string, Promise<StoredVaultMemory[]>>>;
|
|
332
|
+
type RowRequests = Map<EmbeddingOptions["maskInput"], Map<string, Promise<number[]>>>;
|
|
333
|
+
interface VaultPreparation {
|
|
334
|
+
corpora: VaultCorpusCache;
|
|
335
|
+
rows: WeakMap<VaultMemoryOperationsContext, RowRequests>;
|
|
336
|
+
}
|
|
337
|
+
|
|
216
338
|
/**
|
|
217
339
|
* How a fact's evidence has been evolving.
|
|
218
340
|
*
|
|
@@ -300,6 +422,7 @@ interface RankedMemory {
|
|
|
300
422
|
updatedAt: Date;
|
|
301
423
|
}
|
|
302
424
|
interface RecallOptions {
|
|
425
|
+
/** @internal */ vaultPreparation?: VaultPreparation;
|
|
303
426
|
/** Embedding cache shared with the rest of the turn, so the query is embedded once across lanes and the send. */
|
|
304
427
|
embeddingCache?: Map<string, Float32Array>;
|
|
305
428
|
/** Which kinds to search. Default: ['fact']. */
|
|
@@ -723,6 +723,12 @@ function assertTransportMatchesEndpoint(transport, endpoint) {
|
|
|
723
723
|
`endpointOverride "${endpoint}" is the other transport's endpoint, but transport is "${transport}". The request body is built for the transport, so this sends the wrong shape and 400s without retry.`
|
|
724
724
|
);
|
|
725
725
|
}
|
|
726
|
+
var TIME_BUDGET_EXHAUSTED = {
|
|
727
|
+
kind: "terminal",
|
|
728
|
+
code: "time-budget-exhausted",
|
|
729
|
+
reason: "time budget exhausted"
|
|
730
|
+
};
|
|
731
|
+
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
726
732
|
var RETRYABLE_HTTP = /* @__PURE__ */ new Set([408, 409, 425, 429]);
|
|
727
733
|
function isRetryableStatus(status) {
|
|
728
734
|
return status >= 500 || RETRYABLE_HTTP.has(status);
|
|
@@ -778,7 +784,8 @@ async function callPortalJsonCompletion(req) {
|
|
|
778
784
|
}
|
|
779
785
|
const maxAttempts = Math.max(1, req.maxAttempts ?? 3);
|
|
780
786
|
const startedAt = Date.now();
|
|
781
|
-
const
|
|
787
|
+
const deadlineAt = req.totalTimeoutMs === void 0 ? void 0 : startedAt + req.totalTimeoutMs;
|
|
788
|
+
const overBudget = () => deadlineAt !== void 0 && Date.now() >= deadlineAt;
|
|
782
789
|
let lastFailure;
|
|
783
790
|
let reinforce = false;
|
|
784
791
|
const giveUp = () => {
|
|
@@ -791,17 +798,11 @@ async function callPortalJsonCompletion(req) {
|
|
|
791
798
|
lastFailure = { reason: "time-budget-exhausted", attempts: attempt - 1 };
|
|
792
799
|
break;
|
|
793
800
|
}
|
|
794
|
-
const attemptReq = req
|
|
795
|
-
...req,
|
|
796
|
-
timeoutMs: Math.max(
|
|
797
|
-
1,
|
|
798
|
-
Math.min(req.timeoutMs ?? 6e4, req.totalTimeoutMs - (Date.now() - startedAt))
|
|
799
|
-
),
|
|
800
|
-
...reinforce && { reinforceJsonContract: true }
|
|
801
|
-
} : reinforce ? { ...req, reinforceJsonContract: true } : req;
|
|
801
|
+
const attemptReq = reinforce ? { ...req, reinforceJsonContract: true } : req;
|
|
802
802
|
const outcome = await attemptPortalJson(
|
|
803
803
|
{ ...attemptReq, transport },
|
|
804
|
-
endpoint
|
|
804
|
+
endpoint,
|
|
805
|
+
deadlineAt
|
|
805
806
|
);
|
|
806
807
|
if (outcome.kind === "ok") {
|
|
807
808
|
req.onAttempt?.({ attempt, ok: true });
|
|
@@ -841,13 +842,38 @@ async function callPortalJsonCompletion(req) {
|
|
|
841
842
|
}
|
|
842
843
|
return giveUp();
|
|
843
844
|
}
|
|
844
|
-
async function attemptPortalJson(req, endpoint) {
|
|
845
|
+
async function attemptPortalJson(req, endpoint, deadlineAt) {
|
|
846
|
+
const controller = new AbortController();
|
|
847
|
+
if (deadlineAt === void 0 || deadlineAt - Date.now() > MAX_TIMER_DELAY_MS) {
|
|
848
|
+
return requestPortalJson(req, endpoint, controller);
|
|
849
|
+
}
|
|
850
|
+
const remainingMs = deadlineAt - Date.now();
|
|
851
|
+
if (remainingMs <= 0) return TIME_BUDGET_EXHAUSTED;
|
|
852
|
+
let timer;
|
|
853
|
+
try {
|
|
854
|
+
const outcome = await Promise.race([
|
|
855
|
+
new Promise((resolve) => {
|
|
856
|
+
timer = setTimeout(() => {
|
|
857
|
+
resolve(TIME_BUDGET_EXHAUSTED);
|
|
858
|
+
controller.abort();
|
|
859
|
+
}, remainingMs);
|
|
860
|
+
}),
|
|
861
|
+
requestPortalJson(req, endpoint, controller, deadlineAt)
|
|
862
|
+
]);
|
|
863
|
+
return Date.now() >= deadlineAt ? TIME_BUDGET_EXHAUSTED : outcome;
|
|
864
|
+
} finally {
|
|
865
|
+
clearTimeout(timer);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
async function requestPortalJson(req, endpoint, controller, deadlineAt) {
|
|
845
869
|
const baseUrl = req.baseUrl ?? defaultBaseUrl();
|
|
846
870
|
const fetchImpl = req.fetchFn ?? fetch;
|
|
847
|
-
const timeoutMs = req.timeoutMs ?? 6e4;
|
|
848
871
|
const authHeaders = await resolvePortalAuthHeaders(req, req.tag);
|
|
872
|
+
const remainingMs = deadlineAt === void 0 ? Infinity : deadlineAt - Date.now();
|
|
873
|
+
if (controller.signal.aborted || remainingMs <= 0) return TIME_BUDGET_EXHAUSTED;
|
|
849
874
|
if (authHeaders === null)
|
|
850
875
|
return { kind: "terminal", code: "auth-unavailable", reason: "auth unavailable (no token)" };
|
|
876
|
+
const timeoutMs = Math.min(req.timeoutMs ?? 6e4, remainingMs);
|
|
851
877
|
const usesPrefill = req.transport !== "responses" && req.model.startsWith("anthropic/");
|
|
852
878
|
const prefill = usesPrefill ? "{" : "";
|
|
853
879
|
const messages = [
|
|
@@ -857,7 +883,6 @@ async function attemptPortalJson(req, endpoint) {
|
|
|
857
883
|
];
|
|
858
884
|
if (prefill) messages.push({ role: "assistant", content: prefill });
|
|
859
885
|
const supportsJsonObjectFormat = supportsResponseFormat(req.model);
|
|
860
|
-
const controller = new AbortController();
|
|
861
886
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
862
887
|
const requestBody = req.transport === "responses" ? {
|
|
863
888
|
model: req.model,
|