agent-accelerator 0.5.9 → 0.6.5
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 +64 -16
- package/examples/prompts/SYSTEM_PROMPT.md +2 -0
- package/examples/prompts/SYSTEM_PROMPT_AGENT.md +4 -0
- package/examples/prompts/SYSTEM_PROMPT_TOOLS.md +1 -0
- package/package.json +4 -1
- package/src/agent/agent.ts +447 -172
- package/src/agent/architecture.md +16 -12
- package/src/agent/config-blocks.ts +39 -12
- package/src/agent/context.ts +9 -3
- package/src/agent/defaults.ts +88 -0
- package/src/agent/delegation.ts +86 -28
- package/src/agent/loop.ts +474 -374
- package/src/agent/subagent.ts +1 -3
- package/src/agent/subagents-tool.ts +50 -19
- package/src/agent/turns.ts +3 -1
- package/src/agent/usage-limit.ts +92 -0
- package/src/agent/worker.ts +73 -30
- package/src/agent/workers.ts +126 -21
- package/src/architecture.md +17 -15
- package/src/index.ts +43 -19
- package/src/models/architecture.md +4 -3
- package/src/models/catalog-cache.ts +6 -5
- package/src/models/catalog.ts +39 -30
- package/src/prompts/additional-context.md +2 -0
- package/src/prompts/architecture.md +19 -0
- package/src/prompts/prompts.ts +81 -0
- package/src/prompts/reasoning-directive.md +5 -0
- package/src/{scheduler/scheduled-actions.md → prompts/scheduled-actions-tool.md} +4 -0
- package/src/prompts/structured-output.md +2 -0
- package/src/{agent/subagents.md → prompts/subagents-tool.md} +3 -0
- package/src/prompts/tool-bridge.md +1 -0
- package/src/prompts/tool-interrupted.md +1 -0
- package/src/prompts/tool-not-found.md +1 -0
- package/src/prompts/tool-repeat-blocked.md +1 -0
- package/src/prompts/tool-schema-error.md +1 -0
- package/src/prompts/tool-truncated-arguments.md +1 -0
- package/src/prompts/worker-failed.md +1 -0
- package/src/prompts/worker-replied.md +2 -0
- package/src/providers/architecture.md +10 -8
- package/src/providers/google.ts +228 -69
- package/src/providers/openai-compat.ts +74 -56
- package/src/providers/openai.ts +64 -41
- package/src/providers/openrouter.ts +270 -48
- package/src/providers/registry.ts +6 -21
- package/src/providers/shared.ts +87 -33
- package/src/providers.ts +115 -124
- package/src/scheduler/architecture.md +8 -7
- package/src/scheduler/scheduler.ts +226 -60
- package/src/scheduler/store.ts +23 -10
- package/src/scheduler/time.ts +17 -3
- package/src/scheduler/tool.ts +38 -16
- package/src/session/architecture.md +7 -6
- package/src/session/journal.ts +39 -12
- package/src/session/store.ts +126 -61
- package/src/session/usage.ts +58 -9
- package/src/streaming/architecture.md +4 -3
- package/src/streaming/event-stream.ts +26 -12
- package/src/telemetry.ts +6 -2
- package/src/tools/architecture.md +9 -7
- package/src/tools/executor.ts +109 -62
- package/src/tools/schema.ts +10 -6
- package/src/tools/subagent-result.ts +6 -1
- package/src/tools/tool-content.ts +32 -0
- package/src/tools/tool.ts +9 -11
- package/src/types/agent.ts +37 -22
- package/src/types/architecture.md +11 -10
- package/src/types/core.ts +5 -9
- package/src/types/errors.ts +26 -4
- package/src/types/message.ts +7 -0
- package/src/types/model.ts +4 -5
- package/src/types/scheduler.ts +4 -4
- package/src/types/tool.ts +14 -2
- package/src/update-models.ts +5 -5
- package/src/utils/architecture.md +16 -15
- package/src/utils/cache.ts +1 -3
- package/src/utils/durable.ts +17 -5
- package/src/utils/env.ts +9 -7
- package/src/utils/errors.ts +9 -17
- package/src/utils/headers.ts +3 -10
- package/src/utils/media.ts +23 -8
- package/src/utils/raw-capture.ts +9 -2
- package/src/utils/retry.ts +15 -3
- package/src/utils/safe-io.ts +29 -7
- package/src/utils/serialization.ts +1 -4
- package/src/utils/session.ts +1 -2
package/src/utils/errors.ts
CHANGED
|
@@ -21,7 +21,9 @@ export function isTransientError(error: unknown): boolean {
|
|
|
21
21
|
NaN
|
|
22
22
|
);
|
|
23
23
|
if ([408, 425, 429, 500, 502, 503, 504].includes(status)) return true;
|
|
24
|
-
const code = String(
|
|
24
|
+
const code = String(
|
|
25
|
+
(err["providerCode"] as string | undefined) ?? (err["code"] as string | undefined) ?? ""
|
|
26
|
+
).toUpperCase();
|
|
25
27
|
if (["ECONNRESET", "ECONNREFUSED", "EAI_AGAIN", "UND_ERR_CONNECT_TIMEOUT", "EPIPE", "ETIMEDOUT"].includes(code)) {
|
|
26
28
|
return true;
|
|
27
29
|
}
|
|
@@ -114,9 +116,7 @@ export function toConciseProviderError(error: unknown, providerId: string, model
|
|
|
114
116
|
// Forwarded when present (e.g. OpenRouter's canonical `error_type`,
|
|
115
117
|
// which disambiguates lossy native codes). Survives re-wraps so retry
|
|
116
118
|
// helpers never strip it.
|
|
117
|
-
...(typeof err?.errorType === "string"
|
|
118
|
-
? { errorType: { value: err.errorType, enumerable: false } }
|
|
119
|
-
: {}),
|
|
119
|
+
...(typeof err?.errorType === "string" ? { errorType: { value: err.errorType, enumerable: false } } : {}),
|
|
120
120
|
// The provider's own error code (e.g. Google's `not_found` chain-eviction code, which carries no HTTP status on
|
|
121
121
|
// SSE error frames); `code` itself is the SDK kind (`"provider"`). Survives re-wraps so chain detectors never lose it.
|
|
122
122
|
...((typeof err?.code === "string" || typeof err?.code === "number") && err.code !== ""
|
|
@@ -148,11 +148,7 @@ const PART_TO_MODALITY: Record<string, "image" | "audio" | "video" | "pdf"> = {
|
|
|
148
148
|
* models (custom providers, dynamic routers) are skipped: the provider
|
|
149
149
|
* verdict stands and surfaces via toConciseProviderError instead. Throws a `ValidationError`.
|
|
150
150
|
*/
|
|
151
|
-
export function assertModalitiesSupported(
|
|
152
|
-
context: ProviderContext,
|
|
153
|
-
providerId: string,
|
|
154
|
-
modelId: string
|
|
155
|
-
): void {
|
|
151
|
+
export function assertModalitiesSupported(context: ProviderContext, providerId: string, modelId: string): void {
|
|
156
152
|
const needed = new Set<string>();
|
|
157
153
|
for (const msg of context.messages) {
|
|
158
154
|
if (!Array.isArray((msg as any)?.content)) continue;
|
|
@@ -185,17 +181,13 @@ export function assertModalitiesSupported(
|
|
|
185
181
|
* That wire has no video shape — only `input_text`/`input_image`/`input_file`
|
|
186
182
|
* exist in either vendor's Responses docs — so a video part sent as
|
|
187
183
|
* `input_file` lands in the router's document parser and 400s confusingly
|
|
188
|
-
* (`Failed to parse the file ... Provide a PDF document`, observed live
|
|
189
|
-
*
|
|
190
|
-
*
|
|
184
|
+
* (`Failed to parse the file ... Provide a PDF document`, observed live under
|
|
185
|
+
* the old OpenRouter Responses skin despite the catalog `video` flag, which is
|
|
186
|
+
* chat-transport oriented). Fail fast with
|
|
191
187
|
* a clear one-liner instead; video stays Gemini-only, and OpenRouter Chat
|
|
192
188
|
* Completions carries `video_url` natively (no guard there). Throws a `ValidationError`.
|
|
193
189
|
*/
|
|
194
|
-
export function assertNoVideoPartsOnResponses(
|
|
195
|
-
context: ProviderContext,
|
|
196
|
-
providerId: string,
|
|
197
|
-
modelId: string
|
|
198
|
-
): void {
|
|
190
|
+
export function assertNoVideoPartsOnResponses(context: ProviderContext, providerId: string, modelId: string): void {
|
|
199
191
|
let hasVideo = false;
|
|
200
192
|
for (const msg of context.messages) {
|
|
201
193
|
if (!Array.isArray((msg as any)?.content)) continue;
|
package/src/utils/headers.ts
CHANGED
|
@@ -24,7 +24,7 @@ export function getPackageVersion(): string {
|
|
|
24
24
|
const v = (globalThis as any).process?.env?.npm_package_version;
|
|
25
25
|
if (typeof v === "string" && v) return v;
|
|
26
26
|
} catch {}
|
|
27
|
-
return "0.5
|
|
27
|
+
return "0.6.5";
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
// User-Agent string with version and platform; in: AGENT_ACCEL_USER_AGENT, out: a string.
|
|
@@ -93,11 +93,7 @@ function findHeaderKey(headers: Record<string, string>, name: string): string |
|
|
|
93
93
|
* coexist on the wire.
|
|
94
94
|
* @example `setHeaderCaseInsensitive(headers, "Authorization", "Bearer k");`
|
|
95
95
|
*/
|
|
96
|
-
export function setHeaderCaseInsensitive(
|
|
97
|
-
headers: Record<string, string>,
|
|
98
|
-
name: string,
|
|
99
|
-
value: string
|
|
100
|
-
): void {
|
|
96
|
+
export function setHeaderCaseInsensitive(headers: Record<string, string>, name: string, value: string): void {
|
|
101
97
|
const existing = findHeaderKey(headers, name);
|
|
102
98
|
if (existing !== undefined && existing !== name) delete headers[existing];
|
|
103
99
|
headers[name] = value;
|
|
@@ -111,10 +107,7 @@ export function setHeaderCaseInsensitive(
|
|
|
111
107
|
* Returns true when a stored signature may be echoed on `currentProvider`:
|
|
112
108
|
* untagged (legacy) echoes everywhere, tagged echoes only on a match.
|
|
113
109
|
*/
|
|
114
|
-
export function shouldEchoSignature(
|
|
115
|
-
storedProvider: unknown,
|
|
116
|
-
currentProvider: string
|
|
117
|
-
): boolean {
|
|
110
|
+
export function shouldEchoSignature(storedProvider: unknown, currentProvider: string): boolean {
|
|
118
111
|
if (!storedProvider) return true;
|
|
119
112
|
return storedProvider === currentProvider;
|
|
120
113
|
}
|
package/src/utils/media.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// paths are loaded via a lazy dynamic import so browsers never resolve it.
|
|
3
3
|
import { bytesToBase64, base64ToBytes } from "./base64.ts";
|
|
4
4
|
import { fetchRemoteBytes } from "./safe-io.ts";
|
|
5
|
+
import { ValidationError } from "../types/errors.ts";
|
|
5
6
|
|
|
6
7
|
export interface NormalizedMedia {
|
|
7
8
|
mimeType: string;
|
|
@@ -63,7 +64,12 @@ export function sniffMimeType(bytes: Uint8Array): string | undefined {
|
|
|
63
64
|
if (ascii(bytes, 0, "FORM") && ascii(bytes, 8, "AIF")) return "audio/aiff";
|
|
64
65
|
if (bytes[0] === 0xff && ((bytes[1] ?? 0) & 0xf6) === 0xf0) return "audio/aac"; // ADTS: sync bits plus layer 00 (MP3 frames have a non-zero layer)
|
|
65
66
|
if (ascii(bytes, 0, "ID3") || (bytes[0] === 0xff && ((bytes[1] ?? 0) & 0xe0) === 0xe0)) return "audio/mp3";
|
|
66
|
-
if (ascii(bytes, 4, "ftyp"))
|
|
67
|
+
if (ascii(bytes, 4, "ftyp"))
|
|
68
|
+
return ascii(bytes, 8, "M4A") || ascii(bytes, 8, "M4B")
|
|
69
|
+
? "audio/m4a"
|
|
70
|
+
: ascii(bytes, 8, "qt")
|
|
71
|
+
? "video/quicktime"
|
|
72
|
+
: "video/mp4";
|
|
67
73
|
if (bytes[0] === 0 && bytes[1] === 0 && bytes[2] === 1 && bytes[3] === 0xba) return "video/mpeg"; // MPEG program stream
|
|
68
74
|
if (bytes[0] === 0x1a && bytes[1] === 0x45 && bytes[2] === 0xdf && bytes[3] === 0xa3) return "video/webm";
|
|
69
75
|
return undefined;
|
|
@@ -72,9 +78,15 @@ export function sniffMimeType(bytes: Uint8Array): string | undefined {
|
|
|
72
78
|
const GENERIC_MIME = new Set(["", "application/octet-stream", "binary/octet-stream"]);
|
|
73
79
|
|
|
74
80
|
/** Picks the best MIME type: explicit hint, then a specific candidate, then magic bytes, then the fallback. */
|
|
75
|
-
function resolveMime(
|
|
81
|
+
function resolveMime(
|
|
82
|
+
explicit: string | undefined,
|
|
83
|
+
candidate: string | null | undefined,
|
|
84
|
+
bytes: Uint8Array,
|
|
85
|
+
fallback: string
|
|
86
|
+
): string {
|
|
76
87
|
if (explicit) return explicit;
|
|
77
|
-
const specific =
|
|
88
|
+
const specific =
|
|
89
|
+
candidate && !GENERIC_MIME.has(candidate.split(";")[0]!.trim().toLowerCase()) ? candidate : undefined;
|
|
78
90
|
return specific || sniffMimeType(bytes) || candidate || fallback;
|
|
79
91
|
}
|
|
80
92
|
|
|
@@ -130,7 +142,7 @@ export async function normalizeMediaInput(
|
|
|
130
142
|
// but redirects are re-checked and the download is size-capped.
|
|
131
143
|
const response = await fetchRemoteBytes(input, { allowPrivateNetwork: true });
|
|
132
144
|
if (!response.ok) {
|
|
133
|
-
throw new
|
|
145
|
+
throw new ValidationError(`Failed to fetch media from URL: ${input} (${response.statusText})`);
|
|
134
146
|
}
|
|
135
147
|
const buffer = response.bytes;
|
|
136
148
|
const mimeType = resolveMime(explicitMimeType, response.contentType, buffer, inferMimeType(input));
|
|
@@ -175,10 +187,11 @@ export async function normalizeMediaInput(
|
|
|
175
187
|
base64Data,
|
|
176
188
|
dataUrl: `data:${mimeType};base64,${base64Data}`,
|
|
177
189
|
};
|
|
178
|
-
} catch
|
|
190
|
+
} catch {
|
|
179
191
|
// S11: don't silently return path string as base64 (would 400). Re-check if it's actually base64 with "/"
|
|
180
192
|
const trimmed = input.replace(/\s/g, "");
|
|
181
|
-
const isMaybeBase64 =
|
|
193
|
+
const isMaybeBase64 =
|
|
194
|
+
trimmed.length > 100 && /^[A-Za-z0-9+/=\n\r]+$/.test(trimmed.slice(0, 500)) && trimmed.length % 4 === 0;
|
|
182
195
|
if (isMaybeBase64) {
|
|
183
196
|
const mimeType = explicitMimeType || sniffBase64(trimmed) || "image/jpeg";
|
|
184
197
|
return {
|
|
@@ -187,9 +200,11 @@ export async function normalizeMediaInput(
|
|
|
187
200
|
dataUrl: `data:${mimeType};base64,${trimmed}`,
|
|
188
201
|
};
|
|
189
202
|
}
|
|
190
|
-
throw new
|
|
203
|
+
throw new ValidationError(
|
|
204
|
+
`Failed to normalize media input: not a valid file path, data URL, http URL, or base64 string: ${input.slice(0, 80)}`
|
|
205
|
+
);
|
|
191
206
|
}
|
|
192
207
|
}
|
|
193
208
|
|
|
194
|
-
throw new
|
|
209
|
+
throw new ValidationError(`Unsupported media input type: ${typeof input}`);
|
|
195
210
|
}
|
package/src/utils/raw-capture.ts
CHANGED
|
@@ -16,7 +16,11 @@ function byteLength(value: unknown): number {
|
|
|
16
16
|
function toolNames(tools: unknown): string[] | undefined {
|
|
17
17
|
if (!Array.isArray(tools)) return undefined;
|
|
18
18
|
const names = tools
|
|
19
|
-
.map(
|
|
19
|
+
.map(
|
|
20
|
+
(t) =>
|
|
21
|
+
(t as { name?: unknown; function?: { name?: unknown } })?.name ??
|
|
22
|
+
(t as { function?: { name?: unknown } })?.function?.name
|
|
23
|
+
)
|
|
20
24
|
.filter((n): n is string => typeof n === "string");
|
|
21
25
|
return names.length > 0 ? names : undefined;
|
|
22
26
|
}
|
|
@@ -56,7 +60,10 @@ function summarizeResponseBody(body: unknown): unknown {
|
|
|
56
60
|
* model output never leak through `response.raw` / `toJSON()` into logs.
|
|
57
61
|
* - `false`: keep only URL, method and HTTP status.
|
|
58
62
|
*/
|
|
59
|
-
export function applyCaptureRaw(
|
|
63
|
+
export function applyCaptureRaw(
|
|
64
|
+
raw: ProviderRawData | undefined,
|
|
65
|
+
mode: CaptureRawMode | undefined
|
|
66
|
+
): ProviderRawData | undefined {
|
|
60
67
|
if (!raw || mode === true) return raw;
|
|
61
68
|
const redactedMode = mode !== false; // undefined -> default "redacted"
|
|
62
69
|
const request = raw.request
|
package/src/utils/retry.ts
CHANGED
|
@@ -13,7 +13,18 @@ export function isRetryableRun(error: unknown): boolean {
|
|
|
13
13
|
if (!error || isAbortError(error)) return false;
|
|
14
14
|
const name = (error as { name?: unknown }).name;
|
|
15
15
|
if (name === "TimeoutError") return true; // the loop's own request / first-byte / idle timeouts
|
|
16
|
-
if (
|
|
16
|
+
if (
|
|
17
|
+
[
|
|
18
|
+
"BudgetExceededError",
|
|
19
|
+
"ConfigError",
|
|
20
|
+
"ValidationError",
|
|
21
|
+
"SecurityPolicyError",
|
|
22
|
+
"SessionLoadError",
|
|
23
|
+
"ThinkingLevelError",
|
|
24
|
+
"ToolError",
|
|
25
|
+
].includes(String(name))
|
|
26
|
+
)
|
|
27
|
+
return false;
|
|
17
28
|
return isTransientError(error);
|
|
18
29
|
}
|
|
19
30
|
|
|
@@ -24,8 +35,9 @@ export function readRetryAfterMs(error: unknown): number | undefined {
|
|
|
24
35
|
const direct = err["retryAfterMs"];
|
|
25
36
|
if (typeof direct === "number" && Number.isFinite(direct) && direct >= 0) return Math.min(60_000, direct);
|
|
26
37
|
const raw =
|
|
27
|
-
((err["response"] as Record<string, unknown> | undefined)?.["headers"] as Record<string, unknown> | undefined)?.[
|
|
28
|
-
|
|
38
|
+
((err["response"] as Record<string, unknown> | undefined)?.["headers"] as Record<string, unknown> | undefined)?.[
|
|
39
|
+
"retry-after"
|
|
40
|
+
] ?? (err["headers"] as Record<string, unknown> | undefined)?.["retry-after"];
|
|
29
41
|
if (raw === undefined) return undefined;
|
|
30
42
|
const secs = Number(Array.isArray(raw) ? raw[0] : raw);
|
|
31
43
|
if (!Number.isFinite(secs) || secs < 0) return undefined;
|
package/src/utils/safe-io.ts
CHANGED
|
@@ -139,19 +139,31 @@ function isPrivateHostname(host: string): boolean {
|
|
|
139
139
|
// Throws RemoteFetchError for a non-http(s) URL or, unless private hosts are allowed, a private host (also by DNS result); in: fetchRemoteBytes on every hop, out: nothing or a thrown error.
|
|
140
140
|
async function assertRemoteUrlAllowed(url: URL, allowPrivateNetwork: boolean): Promise<void> {
|
|
141
141
|
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
142
|
-
throw new RemoteFetchError(
|
|
142
|
+
throw new RemoteFetchError(
|
|
143
|
+
"blocked",
|
|
144
|
+
`[Agent Accelerator] Only http(s) URLs are allowed (got ${url.protocol}).`,
|
|
145
|
+
url.href
|
|
146
|
+
);
|
|
143
147
|
}
|
|
144
148
|
if (allowPrivateNetwork) return;
|
|
145
149
|
const host = url.hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
146
150
|
if (isPrivateHostname(host)) {
|
|
147
|
-
throw new RemoteFetchError(
|
|
151
|
+
throw new RemoteFetchError(
|
|
152
|
+
"blocked",
|
|
153
|
+
`[Agent Accelerator] Refusing to fetch private/internal host "${host}".`,
|
|
154
|
+
url.href
|
|
155
|
+
);
|
|
148
156
|
}
|
|
149
157
|
if (/^[\d.]+$/.test(host) || host.includes(":")) return; // IP literal already checked
|
|
150
158
|
try {
|
|
151
159
|
const dns = await import("node:dns");
|
|
152
160
|
const addrs = await dns.promises.lookup(host, { all: true });
|
|
153
161
|
if (addrs.some((a) => isPrivateAddress(a.address))) {
|
|
154
|
-
throw new RemoteFetchError(
|
|
162
|
+
throw new RemoteFetchError(
|
|
163
|
+
"blocked",
|
|
164
|
+
`[Agent Accelerator] Host "${host}" resolves to a private/internal address.`,
|
|
165
|
+
url.href
|
|
166
|
+
);
|
|
155
167
|
}
|
|
156
168
|
} catch (err) {
|
|
157
169
|
if (err instanceof RemoteFetchError) throw err;
|
|
@@ -169,7 +181,9 @@ async function readCapped(res: Response, maxBytes: number, url: string): Promise
|
|
|
169
181
|
);
|
|
170
182
|
const declared = Number(res.headers.get("content-length"));
|
|
171
183
|
if (Number.isFinite(declared) && declared > maxBytes) {
|
|
172
|
-
try {
|
|
184
|
+
try {
|
|
185
|
+
await res.body?.cancel();
|
|
186
|
+
} catch {}
|
|
173
187
|
throw tooLarge();
|
|
174
188
|
}
|
|
175
189
|
if (!res.body) {
|
|
@@ -185,7 +199,9 @@ async function readCapped(res: Response, maxBytes: number, url: string): Promise
|
|
|
185
199
|
if (done) break;
|
|
186
200
|
total += value.byteLength;
|
|
187
201
|
if (total > maxBytes) {
|
|
188
|
-
try {
|
|
202
|
+
try {
|
|
203
|
+
await reader.cancel();
|
|
204
|
+
} catch {}
|
|
189
205
|
throw tooLarge();
|
|
190
206
|
}
|
|
191
207
|
chunks.push(value);
|
|
@@ -227,7 +243,11 @@ export async function fetchRemoteBytes(
|
|
|
227
243
|
if ((res as { type?: string }).type === "opaqueredirect") {
|
|
228
244
|
// Browser runtimes hide the Location header: only trusted callers may follow blindly.
|
|
229
245
|
if (!allowPrivate) {
|
|
230
|
-
throw new RemoteFetchError(
|
|
246
|
+
throw new RemoteFetchError(
|
|
247
|
+
"blocked",
|
|
248
|
+
"[Agent Accelerator] Redirects cannot be validated in this runtime.",
|
|
249
|
+
current.href
|
|
250
|
+
);
|
|
231
251
|
}
|
|
232
252
|
const followed = await fetch(current.href, { signal: opts.signal });
|
|
233
253
|
return {
|
|
@@ -240,7 +260,9 @@ export async function fetchRemoteBytes(
|
|
|
240
260
|
}
|
|
241
261
|
const location = res.status >= 300 && res.status < 400 ? res.headers.get("location") : null;
|
|
242
262
|
if (location) {
|
|
243
|
-
try {
|
|
263
|
+
try {
|
|
264
|
+
await res.body?.cancel();
|
|
265
|
+
} catch {}
|
|
244
266
|
current = new URL(location, current);
|
|
245
267
|
continue;
|
|
246
268
|
}
|
|
@@ -53,10 +53,7 @@ export function toJsonSafe(value: unknown, seen = new WeakSet<object>()): unknow
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
if (value instanceof Map) {
|
|
56
|
-
return Array.from(value.entries()).map(([key, entry]) => [
|
|
57
|
-
toJsonSafe(key, seen),
|
|
58
|
-
toJsonSafe(entry, seen),
|
|
59
|
-
]);
|
|
56
|
+
return Array.from(value.entries()).map(([key, entry]) => [toJsonSafe(key, seen), toJsonSafe(entry, seen)]);
|
|
60
57
|
}
|
|
61
58
|
|
|
62
59
|
if (value instanceof Set) {
|
package/src/utils/session.ts
CHANGED
|
@@ -15,8 +15,7 @@ export function createSessionId(prefix?: string): string {
|
|
|
15
15
|
const head = prefix ? `${prefix}-` : "";
|
|
16
16
|
let id: string;
|
|
17
17
|
try {
|
|
18
|
-
const webCrypto =
|
|
19
|
-
(globalThis as any)?.crypto ?? (typeof crypto !== "undefined" ? crypto : undefined);
|
|
18
|
+
const webCrypto = (globalThis as any)?.crypto ?? (typeof crypto !== "undefined" ? crypto : undefined);
|
|
20
19
|
if (webCrypto && typeof webCrypto.randomUUID === "function") {
|
|
21
20
|
id = `${head}${webCrypto.randomUUID()}`;
|
|
22
21
|
} else {
|