@agent-native/core 0.133.2 → 0.134.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/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +62 -0
- package/corpus/core/docs/content/observability.mdx +41 -1
- package/corpus/core/docs/content/tracking.mdx +25 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/run-manager.ts +7 -0
- package/corpus/core/src/client/agent-chat-adapter.ts +10 -0
- package/corpus/core/src/client/analytics.ts +105 -1
- package/corpus/core/src/client/use-action.ts +6 -0
- package/corpus/core/src/deploy/build.ts +38 -1
- package/corpus/core/src/observability/posthog-ai.ts +294 -0
- package/corpus/core/src/observability/routes.ts +29 -6
- package/corpus/core/src/observability/traces.ts +287 -12
- package/corpus/core/src/observability/types.ts +11 -0
- package/corpus/core/src/secrets/register-framework-secrets.ts +16 -0
- package/corpus/core/src/server/agent-run-context.ts +21 -0
- package/corpus/core/src/server/capture-error.ts +8 -0
- package/corpus/core/src/server/core-routes-plugin.ts +75 -4
- package/corpus/core/src/server/deploy-environment.ts +57 -0
- package/corpus/core/src/server/error-noise-filter.ts +268 -0
- package/corpus/core/src/server/posthog-config.ts +75 -0
- package/corpus/core/src/server/request-context.ts +6 -0
- package/corpus/core/src/server/sentry-config.ts +4 -8
- package/corpus/core/src/server/sentry-plugin.ts +13 -29
- package/corpus/core/src/server/sentry.ts +13 -156
- package/corpus/core/src/server/ssr-handler.ts +6 -1
- package/corpus/core/src/shared/mcp-embed-headers.ts +1 -1
- package/corpus/core/src/templates/workspace-core/.agents/skills/observability/SKILL.md +36 -10
- package/corpus/core/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +55 -1
- package/corpus/core/src/tracking/error-capture.ts +44 -109
- package/corpus/core/src/tracking/index.ts +11 -0
- package/corpus/core/src/tracking/posthog-exception.ts +305 -0
- package/corpus/core/src/tracking/providers.ts +91 -14
- package/corpus/core/src/tracking/redaction.ts +105 -0
- package/corpus/templates/content/actions/_batch-utils.ts +33 -0
- package/corpus/templates/content/actions/_builder-cms-read-client.ts +173 -81
- package/corpus/templates/content/actions/_content-files.ts +31 -21
- package/corpus/templates/content/actions/_database-source-utils.ts +653 -155
- package/corpus/templates/content/actions/_database-utils.ts +535 -41
- package/corpus/templates/content/actions/add-content-database-source-field-property.ts +291 -240
- package/corpus/templates/content/actions/add-database-item.ts +15 -1
- package/corpus/templates/content/actions/attach-content-database-source.ts +156 -58
- package/corpus/templates/content/actions/bind-content-database-source-field.ts +2 -2
- package/corpus/templates/content/actions/cancel-prepared-builder-source-update.ts +4 -1
- package/corpus/templates/content/actions/delete-database-items.ts +8 -2
- package/corpus/templates/content/actions/disconnect-content-database-source.ts +2 -2
- package/corpus/templates/content/actions/duplicate-database-item.ts +15 -1
- package/corpus/templates/content/actions/duplicate-database-items.ts +19 -1
- package/corpus/templates/content/actions/execute-builder-source-execution.ts +2 -1
- package/corpus/templates/content/actions/get-content-database.ts +12 -64
- package/corpus/templates/content/actions/move-database-item.ts +4 -1
- package/corpus/templates/content/actions/prepare-builder-source-execution.ts +1 -1
- package/corpus/templates/content/actions/prepare-builder-source-review.ts +4 -8
- package/corpus/templates/content/actions/preview-content-database-source-attach.ts +92 -0
- package/corpus/templates/content/actions/process-builder-body-hydration.ts +2 -1
- package/corpus/templates/content/actions/query-content-database-items.ts +64 -0
- package/corpus/templates/content/actions/refresh-content-database-source.ts +7 -0
- package/corpus/templates/content/actions/review-content-database-source-change-set.ts +1 -1
- package/corpus/templates/content/actions/set-content-database-source-write-mode.ts +1 -1
- package/corpus/templates/content/actions/stage-builder-revision.ts +1 -1
- package/corpus/templates/content/actions/update-content-database-personal-view.ts +29 -3
- package/corpus/templates/content/actions/update-content-database-view.ts +1 -1
- package/corpus/templates/content/actions/validate-builder-source-execution.ts +1 -1
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +3 -22
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +267 -105
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +82 -1
- package/corpus/templates/content/app/hooks/use-content-database.ts +285 -24
- package/corpus/templates/content/app/hooks/use-document-properties.ts +7 -6
- package/corpus/templates/content/app/i18n-data.ts +10 -0
- package/corpus/templates/content/changelog/2026-07-29-large-databases-keep-useful-rows-visible.md +6 -0
- package/corpus/templates/content/changelog/2026-07-30-builder-source-columns-now-appear-immediately-when-connected.md +6 -0
- package/corpus/templates/content/changelog/2026-07-30-large-builder-backed-tables-show-useful-rows-sooner-and-fini.md +6 -0
- package/corpus/templates/content/changelog/2026-08-01-large-builder-databases-now-show-rows-immediately-and-finish.md +6 -0
- package/corpus/templates/content/parity/matrix.md +2 -1
- package/corpus/templates/content/parity/matrix.ts +25 -0
- package/corpus/templates/content/shared/api.ts +54 -4
- package/corpus/templates/content/shared/database-query.ts +359 -0
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +7 -0
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +12 -0
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/analytics.d.ts +8 -0
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +95 -1
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/use-action.d.ts.map +1 -1
- package/dist/client/use-action.js +6 -0
- package/dist/client/use-action.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +38 -1
- package/dist/deploy/build.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/posthog-ai.d.ts +126 -0
- package/dist/observability/posthog-ai.d.ts.map +1 -0
- package/dist/observability/posthog-ai.js +190 -0
- package/dist/observability/posthog-ai.js.map +1 -0
- package/dist/observability/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts.map +1 -1
- package/dist/observability/routes.js +26 -6
- package/dist/observability/routes.js.map +1 -1
- package/dist/observability/traces.d.ts +9 -0
- package/dist/observability/traces.d.ts.map +1 -1
- package/dist/observability/traces.js +219 -10
- package/dist/observability/traces.js.map +1 -1
- package/dist/observability/types.d.ts +10 -0
- package/dist/observability/types.d.ts.map +1 -1
- package/dist/observability/types.js +1 -0
- package/dist/observability/types.js.map +1 -1
- package/dist/secrets/register-framework-secrets.d.ts.map +1 -1
- package/dist/secrets/register-framework-secrets.js +14 -0
- package/dist/secrets/register-framework-secrets.js.map +1 -1
- package/dist/server/agent-run-context.d.ts +8 -0
- package/dist/server/agent-run-context.d.ts.map +1 -1
- package/dist/server/agent-run-context.js +18 -0
- package/dist/server/agent-run-context.js.map +1 -1
- package/dist/server/capture-error.d.ts +8 -0
- package/dist/server/capture-error.d.ts.map +1 -1
- package/dist/server/capture-error.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts +0 -23
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +71 -7
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/deploy-environment.d.ts +17 -0
- package/dist/server/deploy-environment.d.ts.map +1 -0
- package/dist/server/deploy-environment.js +47 -0
- package/dist/server/deploy-environment.js.map +1 -0
- package/dist/server/error-noise-filter.d.ts +80 -0
- package/dist/server/error-noise-filter.d.ts.map +1 -0
- package/dist/server/error-noise-filter.js +173 -0
- package/dist/server/error-noise-filter.js.map +1 -0
- package/dist/server/posthog-config.d.ts +28 -0
- package/dist/server/posthog-config.d.ts.map +1 -0
- package/dist/server/posthog-config.js +53 -0
- package/dist/server/posthog-config.js.map +1 -0
- package/dist/server/request-context.d.ts +6 -0
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/request-context.js.map +1 -1
- package/dist/server/sentry-config.d.ts +1 -0
- package/dist/server/sentry-config.d.ts.map +1 -1
- package/dist/server/sentry-config.js +3 -1
- package/dist/server/sentry-config.js.map +1 -1
- package/dist/server/sentry-plugin.d.ts.map +1 -1
- package/dist/server/sentry-plugin.js +12 -27
- package/dist/server/sentry-plugin.js.map +1 -1
- package/dist/server/sentry.d.ts.map +1 -1
- package/dist/server/sentry.js +8 -124
- package/dist/server/sentry.js.map +1 -1
- package/dist/server/ssr-handler.d.ts.map +1 -1
- package/dist/server/ssr-handler.js +6 -1
- package/dist/server/ssr-handler.js.map +1 -1
- package/dist/shared/mcp-embed-headers.d.ts +1 -1
- package/dist/shared/mcp-embed-headers.d.ts.map +1 -1
- package/dist/shared/mcp-embed-headers.js +1 -1
- package/dist/shared/mcp-embed-headers.js.map +1 -1
- package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +36 -10
- package/dist/templates/workspace-core/.agents/skills/tracking/SKILL.md +55 -1
- package/dist/tracking/error-capture.d.ts +7 -0
- package/dist/tracking/error-capture.d.ts.map +1 -1
- package/dist/tracking/error-capture.js +10 -73
- package/dist/tracking/error-capture.js.map +1 -1
- package/dist/tracking/index.d.ts +1 -0
- package/dist/tracking/index.d.ts.map +1 -1
- package/dist/tracking/index.js +1 -0
- package/dist/tracking/index.js.map +1 -1
- package/dist/tracking/posthog-exception.d.ts +103 -0
- package/dist/tracking/posthog-exception.d.ts.map +1 -0
- package/dist/tracking/posthog-exception.js +181 -0
- package/dist/tracking/posthog-exception.js.map +1 -0
- package/dist/tracking/providers.d.ts +14 -0
- package/dist/tracking/providers.d.ts.map +1 -1
- package/dist/tracking/providers.js +63 -11
- package/dist/tracking/providers.js.map +1 -1
- package/dist/tracking/redaction.d.ts +25 -0
- package/dist/tracking/redaction.d.ts.map +1 -0
- package/dist/tracking/redaction.js +85 -0
- package/dist/tracking/redaction.js.map +1 -0
- package/docs/content/observability.mdx +41 -1
- package/docs/content/tracking.mdx +25 -0
- package/package.json +1 -1
- package/src/agent/run-manager.ts +7 -0
- package/src/client/agent-chat-adapter.ts +10 -0
- package/src/client/analytics.ts +105 -1
- package/src/client/use-action.ts +6 -0
- package/src/deploy/build.ts +38 -1
- package/src/observability/posthog-ai.ts +294 -0
- package/src/observability/routes.ts +29 -6
- package/src/observability/traces.ts +287 -12
- package/src/observability/types.ts +11 -0
- package/src/secrets/register-framework-secrets.ts +16 -0
- package/src/server/agent-run-context.ts +21 -0
- package/src/server/capture-error.ts +8 -0
- package/src/server/core-routes-plugin.ts +75 -4
- package/src/server/deploy-environment.ts +57 -0
- package/src/server/error-noise-filter.ts +268 -0
- package/src/server/posthog-config.ts +75 -0
- package/src/server/request-context.ts +6 -0
- package/src/server/sentry-config.ts +4 -8
- package/src/server/sentry-plugin.ts +13 -29
- package/src/server/sentry.ts +13 -156
- package/src/server/ssr-handler.ts +6 -1
- package/src/shared/mcp-embed-headers.ts +1 -1
- package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +36 -10
- package/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +55 -1
- package/src/tracking/error-capture.ts +44 -109
- package/src/tracking/index.ts +11 -0
- package/src/tracking/posthog-exception.ts +305 -0
- package/src/tracking/providers.ts +91 -14
- package/src/tracking/redaction.ts +105 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounding and redaction helpers shared by every exception emitter.
|
|
3
|
+
*
|
|
4
|
+
* Kept free of `process.env` and Node built-ins so the browser bundle can use
|
|
5
|
+
* the same rules — an exception shaped one way on the server and another way
|
|
6
|
+
* in the client is how a leak ships in only one of them.
|
|
7
|
+
*/
|
|
8
|
+
export const MAX_MESSAGE_LENGTH = 1000;
|
|
9
|
+
export const MAX_STACK_LENGTH = 8000;
|
|
10
|
+
export const MAX_TAGS = 30;
|
|
11
|
+
export const MAX_EXTRA_KEYS = 30;
|
|
12
|
+
export const MAX_EXTRA_VALUE_LENGTH = 1000;
|
|
13
|
+
const SECRET_RE = /\b(?:bearer|basic)\s+[^\s]+/gi;
|
|
14
|
+
export const SECRET_KEY_RE = /(?:authorization|cookie|set[-_]?cookie|token|secret|password|passwd|pwd|api[-_]?key|apikey|credential)/i;
|
|
15
|
+
export function redact(value) {
|
|
16
|
+
return value
|
|
17
|
+
.replace(SECRET_RE, (match) => `${match.split(/\s+/, 1)[0]} <redacted>`)
|
|
18
|
+
.replace(/([A-Za-z0-9_$.-]*(?:authorization|cookie|token|secret|password|passwd|pwd|api[-_]?key|apikey|credential)[A-Za-z0-9_$.-]*\s*[:=]\s*)([^\s,;}]+)/gi, "$1<redacted>");
|
|
19
|
+
}
|
|
20
|
+
export function boundedText(value, max) {
|
|
21
|
+
const text = typeof value === "string" ? value : String(value ?? "");
|
|
22
|
+
const safe = redact(text);
|
|
23
|
+
return safe.length > max ? safe.slice(0, max) : safe;
|
|
24
|
+
}
|
|
25
|
+
export function safeValue(value, depth = 2) {
|
|
26
|
+
if (value == null ||
|
|
27
|
+
typeof value === "boolean" ||
|
|
28
|
+
typeof value === "number") {
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
if (typeof value === "string")
|
|
32
|
+
return boundedText(value, MAX_EXTRA_VALUE_LENGTH);
|
|
33
|
+
if (depth <= 0)
|
|
34
|
+
return boundedText(value, MAX_EXTRA_VALUE_LENGTH);
|
|
35
|
+
if (Array.isArray(value)) {
|
|
36
|
+
return value.slice(0, 20).map((item) => safeValue(item, depth - 1));
|
|
37
|
+
}
|
|
38
|
+
if (typeof value === "object") {
|
|
39
|
+
const out = {};
|
|
40
|
+
for (const [key, child] of Object.entries(value)) {
|
|
41
|
+
if (Object.keys(out).length >= MAX_EXTRA_KEYS)
|
|
42
|
+
break;
|
|
43
|
+
const safeKey = boundedText(key, 100);
|
|
44
|
+
out[safeKey] = SECRET_KEY_RE.test(safeKey)
|
|
45
|
+
? "<redacted>"
|
|
46
|
+
: safeValue(child, depth - 1);
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
return boundedText(value, MAX_EXTRA_VALUE_LENGTH);
|
|
51
|
+
}
|
|
52
|
+
export function safeTags(tags) {
|
|
53
|
+
const out = {};
|
|
54
|
+
for (const [key, value] of Object.entries(tags ?? {})) {
|
|
55
|
+
if (Object.keys(out).length >= MAX_TAGS)
|
|
56
|
+
break;
|
|
57
|
+
// Skip, don't stop: callers build tag objects with optional entries
|
|
58
|
+
// (`{ ...tags, route, method, userAgent }`), so breaking on the first
|
|
59
|
+
// undefined dropped every tag after it depending on key order.
|
|
60
|
+
if (value == null)
|
|
61
|
+
continue;
|
|
62
|
+
const safeKey = boundedText(key, 100);
|
|
63
|
+
out[safeKey] = SECRET_KEY_RE.test(safeKey)
|
|
64
|
+
? "<redacted>"
|
|
65
|
+
: boundedText(value, 200);
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
/** Split an unknown thrown value into bounded, redacted type/message/stack. */
|
|
70
|
+
export function exceptionParts(error) {
|
|
71
|
+
if (error instanceof Error) {
|
|
72
|
+
return {
|
|
73
|
+
type: boundedText(error.name || "Error", 200),
|
|
74
|
+
message: boundedText(error.message || error.name || "Error", MAX_MESSAGE_LENGTH),
|
|
75
|
+
...(error.stack
|
|
76
|
+
? { stack: boundedText(error.stack, MAX_STACK_LENGTH) }
|
|
77
|
+
: {}),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
type: "Error",
|
|
82
|
+
message: boundedText(error, MAX_MESSAGE_LENGTH),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=redaction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redaction.js","sourceRoot":"","sources":["../../src/tracking/redaction.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Bounding and redaction helpers shared by every exception emitter.\n *\n * Kept free of `process.env` and Node built-ins so the browser bundle can use\n * the same rules — an exception shaped one way on the server and another way\n * in the client is how a leak ships in only one of them.\n */\n\nexport const MAX_MESSAGE_LENGTH = 1000;\nexport const MAX_STACK_LENGTH = 8000;\nexport const MAX_TAGS = 30;\nexport const MAX_EXTRA_KEYS = 30;\nexport const MAX_EXTRA_VALUE_LENGTH = 1000;\n\nconst SECRET_RE = /\\b(?:bearer|basic)\\s+[^\\s]+/gi;\n\nexport const SECRET_KEY_RE =\n /(?:authorization|cookie|set[-_]?cookie|token|secret|password|passwd|pwd|api[-_]?key|apikey|credential)/i;\n\nexport function redact(value: string): string {\n return value\n .replace(SECRET_RE, (match) => `${match.split(/\\s+/, 1)[0]} <redacted>`)\n .replace(\n /([A-Za-z0-9_$.-]*(?:authorization|cookie|token|secret|password|passwd|pwd|api[-_]?key|apikey|credential)[A-Za-z0-9_$.-]*\\s*[:=]\\s*)([^\\s,;}]+)/gi,\n \"$1<redacted>\",\n );\n}\n\nexport function boundedText(value: unknown, max: number): string {\n const text = typeof value === \"string\" ? value : String(value ?? \"\");\n const safe = redact(text);\n return safe.length > max ? safe.slice(0, max) : safe;\n}\n\nexport function safeValue(value: unknown, depth = 2): unknown {\n if (\n value == null ||\n typeof value === \"boolean\" ||\n typeof value === \"number\"\n ) {\n return value;\n }\n if (typeof value === \"string\")\n return boundedText(value, MAX_EXTRA_VALUE_LENGTH);\n if (depth <= 0) return boundedText(value, MAX_EXTRA_VALUE_LENGTH);\n if (Array.isArray(value)) {\n return value.slice(0, 20).map((item) => safeValue(item, depth - 1));\n }\n if (typeof value === \"object\") {\n const out: Record<string, unknown> = {};\n for (const [key, child] of Object.entries(value)) {\n if (Object.keys(out).length >= MAX_EXTRA_KEYS) break;\n const safeKey = boundedText(key, 100);\n out[safeKey] = SECRET_KEY_RE.test(safeKey)\n ? \"<redacted>\"\n : safeValue(child, depth - 1);\n }\n return out;\n }\n return boundedText(value, MAX_EXTRA_VALUE_LENGTH);\n}\n\nexport function safeTags(\n tags: Record<string, string | undefined> | undefined,\n): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [key, value] of Object.entries(tags ?? {})) {\n if (Object.keys(out).length >= MAX_TAGS) break;\n // Skip, don't stop: callers build tag objects with optional entries\n // (`{ ...tags, route, method, userAgent }`), so breaking on the first\n // undefined dropped every tag after it depending on key order.\n if (value == null) continue;\n const safeKey = boundedText(key, 100);\n out[safeKey] = SECRET_KEY_RE.test(safeKey)\n ? \"<redacted>\"\n : boundedText(value, 200);\n }\n return out;\n}\n\nexport interface ExceptionParts {\n type: string;\n message: string;\n stack?: string;\n}\n\n/** Split an unknown thrown value into bounded, redacted type/message/stack. */\nexport function exceptionParts(error: unknown): ExceptionParts {\n if (error instanceof Error) {\n return {\n type: boundedText(error.name || \"Error\", 200),\n message: boundedText(\n error.message || error.name || \"Error\",\n MAX_MESSAGE_LENGTH,\n ),\n ...(error.stack\n ? { stack: boundedText(error.stack, MAX_STACK_LENGTH) }\n : {}),\n };\n }\n return {\n type: \"Error\",\n message: boundedText(error, MAX_MESSAGE_LENGTH),\n };\n}\n"]}
|
|
@@ -256,6 +256,7 @@ All settings are stored in the `observability-config` key:
|
|
|
256
256
|
capturePrompts: false, // Store prompt content in traces
|
|
257
257
|
captureToolArgs: false, // Store action input arguments
|
|
258
258
|
captureToolResults: false, // Store action results
|
|
259
|
+
captureLlmSpans: true, // Emit one $ai_span per tool call to PostHog
|
|
259
260
|
evalSampleRate: 0, // 0-1, fraction of runs to LLM-judge
|
|
260
261
|
exporters: [] // OTLP export targets
|
|
261
262
|
}
|
|
@@ -291,6 +292,39 @@ All auto-mounted at `/_agent-native/observability/`:
|
|
|
291
292
|
|
|
292
293
|
All endpoints support `?since=N` (ms timestamp) and `?limit=N` query params.
|
|
293
294
|
|
|
295
|
+
## PostHog LLM analytics {#posthog}
|
|
296
|
+
|
|
297
|
+
When `POSTHOG_API_KEY` is set, every instrumented run is mirrored to PostHog's
|
|
298
|
+
LLM analytics as a full trace tree:
|
|
299
|
+
|
|
300
|
+
| Event | Emitted per | Carries |
|
|
301
|
+
| ---------------- | ----------- | ------------------------------------------------------- |
|
|
302
|
+
| `$ai_trace` | agent run | Latency, error state, token totals, cost |
|
|
303
|
+
| `$ai_generation` | model call | Model, provider, tokens, cost, tool definitions offered |
|
|
304
|
+
| `$ai_span` | tool call | Tool name, duration, error state |
|
|
305
|
+
|
|
306
|
+
Nodes share the run id as `$ai_trace_id`, so PostHog renders one tree per run.
|
|
307
|
+
`$ai_session_id` is the conversation thread; the browser session is sent
|
|
308
|
+
separately as `$session_id` so a trace links to its session replay.
|
|
309
|
+
|
|
310
|
+
Tool calls always appear (PostHog derives its tool tags from them), but their
|
|
311
|
+
**arguments** require `captureToolArgs` and message content requires
|
|
312
|
+
`capturePrompts`. When those are off the fields are omitted entirely rather than
|
|
313
|
+
sent empty, so an unrecorded prompt is never mistaken for an empty one.
|
|
314
|
+
|
|
315
|
+
Errors carry a structured `$ai_error` with the terminal code and whether the
|
|
316
|
+
failure was retryable, and exceptions captured during a run carry the run's
|
|
317
|
+
`$ai_trace_id` — so an issue in error tracking and the trace it came from link
|
|
318
|
+
to each other.
|
|
319
|
+
|
|
320
|
+
### Feedback in PostHog
|
|
321
|
+
|
|
322
|
+
Thumbs, category, and free-text feedback all emit `$ai_feedback`. PostHog's LLM
|
|
323
|
+
analytics feedback view reads a `survey sent` event instead, so set
|
|
324
|
+
`POSTHOG_AI_FEEDBACK_SURVEY_ID` (and `POSTHOG_AI_FEEDBACK_SURVEY_QUESTION_ID`
|
|
325
|
+
for multi-question surveys) to populate it. Without a survey id no survey event
|
|
326
|
+
is sent.
|
|
327
|
+
|
|
294
328
|
## Export to external platforms {#export}
|
|
295
329
|
|
|
296
330
|
Send traces to Langfuse, Datadog, Grafana, or any OTel-compatible backend:
|
|
@@ -331,10 +365,16 @@ The agent loop emits three span kinds:
|
|
|
331
365
|
|
|
332
366
|
Spans are finished with OK/ERROR status and record the error message on failure. Zero/sentinel attribute values are pruned so spans aren't cluttered with noise. This OTel layer is purely additive to the in-house `agent_trace_spans` / `agent_trace_summaries` tables that power the dashboard above — both are produced from the same run events.
|
|
333
367
|
|
|
334
|
-
## Error reporting (Sentry) {#sentry}
|
|
368
|
+
## Error reporting (Sentry or PostHog) {#sentry}
|
|
335
369
|
|
|
336
370
|
Server-side errors that escape Nitro route handlers are reported to Sentry when a DSN is configured. Without it the SDK silently no-ops, so it's safe to leave the env vars unset in dev. Browser and server events can go to the same Sentry project; split them into separate projects only when you want operational separation for ownership, volume, quotas, or alert routing.
|
|
337
371
|
|
|
372
|
+
Sentry is not required. Error reporting fans out to every configured backend, so
|
|
373
|
+
setting only `POSTHOG_API_KEY` gives full server-side error tracking with no
|
|
374
|
+
Sentry project at all — see [Tracking](/docs/tracking#posthog-error-tracking).
|
|
375
|
+
Configure both and each error goes to both. The noise-filtering rules below are
|
|
376
|
+
shared by every backend rather than being Sentry-specific.
|
|
377
|
+
|
|
338
378
|
| Surface | SDK | Env var | Notes |
|
|
339
379
|
| ------------------ | ----------------- | -------------------------------------------------------------- | --------------------------------------------------------------------- |
|
|
340
380
|
| Browser / SPA | `@sentry/browser` | `VITE_SENTRY_CLIENT_DSN`, `SENTRY_CLIENT_DSN`, or `SENTRY_DSN` | Captures unhandled errors and route-change breadcrumbs in the client. |
|
|
@@ -89,6 +89,31 @@ Set an env var and the provider auto-registers at server startup. No code change
|
|
|
89
89
|
|
|
90
90
|
Multiple providers can be active simultaneously. Every event goes to all of them.
|
|
91
91
|
|
|
92
|
+
## PostHog error tracking {#posthog-error-tracking}
|
|
93
|
+
|
|
94
|
+
With `POSTHOG_API_KEY` set, PostHog also receives server exceptions — no Sentry
|
|
95
|
+
DSN required. Server errors are captured automatically from the framework's
|
|
96
|
+
route error hook and attributed to the signed-in user, with production-tuned
|
|
97
|
+
noise filtering (expected 4xx responses, permission rejections, and serverless
|
|
98
|
+
socket churn are dropped rather than reported).
|
|
99
|
+
|
|
100
|
+
| Env var | Purpose |
|
|
101
|
+
| ------------------------ | ------------------------------------------------------------------- |
|
|
102
|
+
| `POSTHOG_ERROR_TRACKING` | Set to `false` to keep analytics but send exceptions somewhere else |
|
|
103
|
+
| `POSTHOG_PUBLIC_KEY` | Enables browser exception capture (also read as `VITE_POSTHOG_KEY`) |
|
|
104
|
+
| `POSTHOG_PUBLIC_HOST` | Browser ingest host (defaults to `POSTHOG_HOST`) |
|
|
105
|
+
|
|
106
|
+
`POSTHOG_PUBLIC_KEY` is separate from `POSTHOG_API_KEY` on purpose: the public
|
|
107
|
+
key is inlined into the HTML served to every visitor, so the server key is never
|
|
108
|
+
used as a fallback for it.
|
|
109
|
+
|
|
110
|
+
<Callout tone="info">
|
|
111
|
+
PostHog does not symbolicate stack traces without uploaded source maps, so
|
|
112
|
+
**minified browser stacks stay minified**. Server-side stacks are unaffected.
|
|
113
|
+
If you need symbolicated browser traces today, keep Sentry configured
|
|
114
|
+
alongside PostHog — both receive every captured error.
|
|
115
|
+
</Callout>
|
|
116
|
+
|
|
92
117
|
## API {#api}
|
|
93
118
|
|
|
94
119
|
### `track(name, properties?, meta?)` {#track}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.134.0",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
package/src/agent/run-manager.ts
CHANGED
|
@@ -735,6 +735,7 @@ export function startRun(
|
|
|
735
735
|
) => {
|
|
736
736
|
captureError(error, {
|
|
737
737
|
route: "/_agent-native/agent-chat",
|
|
738
|
+
aiTraceId: runId,
|
|
738
739
|
tags: {
|
|
739
740
|
source: "agent-run-manager",
|
|
740
741
|
phase,
|
|
@@ -1022,6 +1023,7 @@ export function startRun(
|
|
|
1022
1023
|
phase: "abort-check",
|
|
1023
1024
|
consecutiveFailures: String(consecutiveAbortCheckFailures),
|
|
1024
1025
|
},
|
|
1026
|
+
aiTraceId: runId,
|
|
1025
1027
|
extra: { runId, threadId },
|
|
1026
1028
|
});
|
|
1027
1029
|
if (!abort.signal.aborted) {
|
|
@@ -1065,6 +1067,7 @@ export function startRun(
|
|
|
1065
1067
|
phase: "heartbeat",
|
|
1066
1068
|
consecutiveFailures: String(consecutiveHeartbeatFailures),
|
|
1067
1069
|
},
|
|
1070
|
+
aiTraceId: runId,
|
|
1068
1071
|
extra: { runId, threadId },
|
|
1069
1072
|
});
|
|
1070
1073
|
}
|
|
@@ -1108,6 +1111,7 @@ export function startRun(
|
|
|
1108
1111
|
const errorCode = getRunErrorCode(error);
|
|
1109
1112
|
captureError(error, {
|
|
1110
1113
|
route: "/_agent-native/agent-chat",
|
|
1114
|
+
aiTraceId: runId,
|
|
1111
1115
|
tags: {
|
|
1112
1116
|
source: "agent-run-manager",
|
|
1113
1117
|
phase,
|
|
@@ -1686,6 +1690,7 @@ function subscribeFromSQL(
|
|
|
1686
1690
|
};
|
|
1687
1691
|
captureError(error, {
|
|
1688
1692
|
route: "/_agent-native/agent-chat/runs/:id/events",
|
|
1693
|
+
aiTraceId: runId,
|
|
1689
1694
|
tags: {
|
|
1690
1695
|
source: "agent-run-manager",
|
|
1691
1696
|
phase: "sql-subscription-poll",
|
|
@@ -2313,6 +2318,7 @@ export async function abortRunDurably(
|
|
|
2313
2318
|
// the request report the abort it did complete.
|
|
2314
2319
|
captureError(error, {
|
|
2315
2320
|
route: "/_agent-native/agent-chat/runs/:id/abort",
|
|
2321
|
+
aiTraceId: runId,
|
|
2316
2322
|
tags: {
|
|
2317
2323
|
source: "agent-run-manager",
|
|
2318
2324
|
phase: "abort-run",
|
|
@@ -2356,6 +2362,7 @@ export async function abortTurnDurably(
|
|
|
2356
2362
|
// visible rather than silent.
|
|
2357
2363
|
captureError(error, {
|
|
2358
2364
|
route: "/_agent-native/agent-chat/runs/:id/abort",
|
|
2365
|
+
aiTraceId: runId,
|
|
2359
2366
|
tags: { source: "agent-run-manager", phase: "abort-turn" },
|
|
2360
2367
|
extra: { runId, reason, ...ref },
|
|
2361
2368
|
});
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
clearActiveRun,
|
|
19
19
|
setPendingTurn,
|
|
20
20
|
} from "./active-run-state.js";
|
|
21
|
+
import { getOrCreateAnalyticsSessionId } from "./analytics-session.js";
|
|
21
22
|
import { captureError } from "./analytics.js";
|
|
22
23
|
import { agentNativePath } from "./api-path.js";
|
|
23
24
|
import { formatChatErrorText, normalizeChatError } from "./error-format.js";
|
|
@@ -2075,6 +2076,15 @@ export function createAgentChatAdapter(
|
|
|
2075
2076
|
} catch {
|
|
2076
2077
|
// Non-browser or Intl unavailable — tool calls will fall back to UTC.
|
|
2077
2078
|
}
|
|
2079
|
+
try {
|
|
2080
|
+
// Lets the run's `$ai_*` events carry PostHog's `$session_id`, so an
|
|
2081
|
+
// agent trace joins to the session replay it happened in.
|
|
2082
|
+
const sessionId = getOrCreateAnalyticsSessionId();
|
|
2083
|
+
if (sessionId) headers["x-agent-native-session-id"] = sessionId;
|
|
2084
|
+
// coercion-ok: replay linkage must not block sending the message
|
|
2085
|
+
} catch {
|
|
2086
|
+
// Analytics session unavailable — traces just lose replay linkage.
|
|
2087
|
+
}
|
|
2078
2088
|
// Surface hint — the server uses this to keep code-editing dev tools
|
|
2079
2089
|
// out of the app-rendered sidebar. The outer dev frame passes
|
|
2080
2090
|
// "dev-frame" explicitly; the reusable in-product chat defaults to
|
package/src/client/analytics.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
llmConnectionTrackingProperties,
|
|
6
6
|
type LlmConnectionStatus,
|
|
7
7
|
} from "../shared/llm-connection.js";
|
|
8
|
+
import { toPostHogExceptionProperties } from "../tracking/posthog-exception.js";
|
|
8
9
|
import {
|
|
9
10
|
getOrCreateAnalyticsAnonymousId,
|
|
10
11
|
getOrCreateAnalyticsSessionId,
|
|
@@ -54,6 +55,14 @@ declare global {
|
|
|
54
55
|
__AGENT_NATIVE_CONFIG__?: {
|
|
55
56
|
sentryDsn?: string;
|
|
56
57
|
sentryEnvironment?: string;
|
|
58
|
+
/**
|
|
59
|
+
* Public PostHog project key + host. Publishable and identical for every
|
|
60
|
+
* visitor, so it ships inside the CDN-cached SSR shell alongside the
|
|
61
|
+
* Sentry DSN. Absent when no public key is configured.
|
|
62
|
+
*/
|
|
63
|
+
posthogKey?: string;
|
|
64
|
+
posthogHost?: string;
|
|
65
|
+
posthogErrorTracking?: boolean;
|
|
57
66
|
/**
|
|
58
67
|
* Hosted Realtime Gateway config. Impersonal (same for every visitor),
|
|
59
68
|
* so it is safe inside the CDN-cached SSR shell — unlike the per-user
|
|
@@ -442,6 +451,15 @@ function applyTrackingIdentity(
|
|
|
442
451
|
return next;
|
|
443
452
|
}
|
|
444
453
|
|
|
454
|
+
/**
|
|
455
|
+
* The signed-in user id used to attribute browser events, or `undefined` when
|
|
456
|
+
* signed out. Same value the server attributes its events to (email, falling
|
|
457
|
+
* back to the auth user id), so a person is one person across both.
|
|
458
|
+
*/
|
|
459
|
+
function getTrackingUserId(): string | undefined {
|
|
460
|
+
return _trackingIdentity?.userId;
|
|
461
|
+
}
|
|
462
|
+
|
|
445
463
|
function getOrCreateAnonymousId(): string | undefined {
|
|
446
464
|
return getOrCreateAnalyticsAnonymousId();
|
|
447
465
|
}
|
|
@@ -1313,6 +1331,89 @@ function exceptionEventProperties(
|
|
|
1313
1331
|
};
|
|
1314
1332
|
}
|
|
1315
1333
|
|
|
1334
|
+
/**
|
|
1335
|
+
* Resolve browser PostHog config, or `undefined` when error capture should not
|
|
1336
|
+
* reach PostHog. Reads the SSR-injected shell config first, then Vite env for
|
|
1337
|
+
* static/SPA builds that never render through the SSR handler.
|
|
1338
|
+
*/
|
|
1339
|
+
function posthogErrorConfig(): { key: string; host: string } | undefined {
|
|
1340
|
+
const shell = window.__AGENT_NATIVE_CONFIG__;
|
|
1341
|
+
if (shell?.posthogErrorTracking === false) return undefined;
|
|
1342
|
+
const env = import.meta.env as Record<string, string | undefined>;
|
|
1343
|
+
// Static/SPA builds never render through the SSR handler, so they never see
|
|
1344
|
+
// the shell config that carries the server-side opt-out. Without this a
|
|
1345
|
+
// Vite-only deployment could not honour `POSTHOG_ERROR_TRACKING=false`
|
|
1346
|
+
// short of deleting the public key.
|
|
1347
|
+
if (env?.VITE_POSTHOG_ERROR_TRACKING?.trim().toLowerCase() === "false") {
|
|
1348
|
+
return undefined;
|
|
1349
|
+
}
|
|
1350
|
+
const key = shell?.posthogKey || env?.VITE_POSTHOG_KEY;
|
|
1351
|
+
if (!key) return undefined;
|
|
1352
|
+
const host = (
|
|
1353
|
+
shell?.posthogHost ||
|
|
1354
|
+
env?.VITE_POSTHOG_HOST ||
|
|
1355
|
+
"https://us.i.posthog.com"
|
|
1356
|
+
).replace(/\/+$/, "");
|
|
1357
|
+
return { key, host };
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
/**
|
|
1361
|
+
* Send the exception straight to PostHog's event endpoint.
|
|
1362
|
+
*
|
|
1363
|
+
* Not relayed through `/_agent-native/track`: that route requires a resolved
|
|
1364
|
+
* session, so every signed-out crash would be dropped without a trace.
|
|
1365
|
+
*
|
|
1366
|
+
* `distinct_id` uses the signed-in user id when we have one so browser events
|
|
1367
|
+
* join the server's events for the same person; otherwise the stable anonymous
|
|
1368
|
+
* id, which `$identify` later aliases on login.
|
|
1369
|
+
*/
|
|
1370
|
+
function sendPostHogExceptionEvent(event: CapturedExceptionEvent): void {
|
|
1371
|
+
const config = posthogErrorConfig();
|
|
1372
|
+
if (!config) return;
|
|
1373
|
+
|
|
1374
|
+
try {
|
|
1375
|
+
const session = errorCaptureSessionContext();
|
|
1376
|
+
const body = JSON.stringify({
|
|
1377
|
+
api_key: config.key,
|
|
1378
|
+
event: AGENT_NATIVE_EXCEPTION_EVENT_NAME,
|
|
1379
|
+
properties: {
|
|
1380
|
+
distinct_id: getTrackingUserId() || session.anonymousId || "anonymous",
|
|
1381
|
+
...toPostHogExceptionProperties({
|
|
1382
|
+
type: event.type,
|
|
1383
|
+
value: event.message,
|
|
1384
|
+
stack: event.stack,
|
|
1385
|
+
handled: event.handled,
|
|
1386
|
+
level: event.level,
|
|
1387
|
+
}),
|
|
1388
|
+
$current_url: event.url,
|
|
1389
|
+
$session_id: session.sessionId,
|
|
1390
|
+
...(session.replayId ? { $replay_id: session.replayId } : {}),
|
|
1391
|
+
...(event.release ? { release: event.release } : {}),
|
|
1392
|
+
...(event.environment ? { environment: event.environment } : {}),
|
|
1393
|
+
...(event.tags ? { exceptionTags: event.tags } : {}),
|
|
1394
|
+
source: "browser",
|
|
1395
|
+
},
|
|
1396
|
+
timestamp: event.occurredAt,
|
|
1397
|
+
});
|
|
1398
|
+
const endpoint = `${config.host}/i/v0/e/`;
|
|
1399
|
+
|
|
1400
|
+
if (navigator.sendBeacon) {
|
|
1401
|
+
// text/plain avoids a CORS preflight the page may not survive.
|
|
1402
|
+
const blob = new Blob([body], { type: "text/plain;charset=UTF-8" });
|
|
1403
|
+
if (navigator.sendBeacon(endpoint, blob)) return;
|
|
1404
|
+
}
|
|
1405
|
+
fetch(endpoint, {
|
|
1406
|
+
method: "POST",
|
|
1407
|
+
body,
|
|
1408
|
+
keepalive: true,
|
|
1409
|
+
headers: { "Content-Type": "text/plain;charset=UTF-8" },
|
|
1410
|
+
}).catch(() => {});
|
|
1411
|
+
// coercion-ok: throwing would replace the page's real error
|
|
1412
|
+
} catch {
|
|
1413
|
+
// Error reporting must never mask the original failure.
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1316
1417
|
function sendExceptionEvent(event: CapturedExceptionEvent): void {
|
|
1317
1418
|
// Route through the existing first-party analytics ingest as a dedicated
|
|
1318
1419
|
// `$exception` event. This reuses the public-key auth + sendBeacon/keepalive
|
|
@@ -1322,6 +1423,7 @@ function sendExceptionEvent(event: CapturedExceptionEvent): void {
|
|
|
1322
1423
|
AGENT_NATIVE_EXCEPTION_EVENT_NAME,
|
|
1323
1424
|
exceptionEventProperties(event),
|
|
1324
1425
|
);
|
|
1426
|
+
sendPostHogExceptionEvent(event);
|
|
1325
1427
|
}
|
|
1326
1428
|
|
|
1327
1429
|
function emitExceptionToReplay(event: CapturedExceptionEvent): void {
|
|
@@ -1339,7 +1441,9 @@ function errorCaptureAutoEnabled(): boolean {
|
|
|
1339
1441
|
_agentNativeAnalyticsPublicKey ||
|
|
1340
1442
|
(import.meta.env as Record<string, string | undefined>)
|
|
1341
1443
|
?.VITE_AGENT_NATIVE_ANALYTICS_PUBLIC_KEY;
|
|
1342
|
-
|
|
1444
|
+
// A PostHog public key is an equally explicit opt-in — an app running
|
|
1445
|
+
// PostHog and no Agent Native Analytics should still report browser crashes.
|
|
1446
|
+
return !!publicKey || !!posthogErrorConfig();
|
|
1343
1447
|
}
|
|
1344
1448
|
|
|
1345
1449
|
function maybeInstallErrorCapture(
|
package/src/client/use-action.ts
CHANGED
|
@@ -207,6 +207,12 @@ function appendActionQueryParam(
|
|
|
207
207
|
}
|
|
208
208
|
return;
|
|
209
209
|
}
|
|
210
|
+
if (typeof value === "object") {
|
|
211
|
+
// defineAction restores JSON strings when the schema expects an object.
|
|
212
|
+
// Preserve nested GET params instead of collapsing them to "[object Object]".
|
|
213
|
+
qs.append(key, JSON.stringify(value));
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
210
216
|
qs.append(key, String(value));
|
|
211
217
|
}
|
|
212
218
|
|
package/src/deploy/build.ts
CHANGED
|
@@ -1157,6 +1157,39 @@ function getSentryClientConfigScript() {
|
|
|
1157
1157
|
);
|
|
1158
1158
|
}
|
|
1159
1159
|
|
|
1160
|
+
function getPostHogClientConfigScript() {
|
|
1161
|
+
// MUST stay consistent with resolvePublicPostHogConfig in
|
|
1162
|
+
// server/posthog-config.ts (worker bundles a string copy; it can't import it).
|
|
1163
|
+
// Never falls back to POSTHOG_API_KEY — that key can be a private one and
|
|
1164
|
+
// this string is inlined into the public, CDN-cached HTML shell.
|
|
1165
|
+
const env = globalThis.process?.env || {};
|
|
1166
|
+
const posthogKey = firstNonEmpty(
|
|
1167
|
+
env.POSTHOG_PUBLIC_KEY,
|
|
1168
|
+
env.VITE_POSTHOG_KEY,
|
|
1169
|
+
env.VITE_POSTHOG_PUBLIC_KEY,
|
|
1170
|
+
);
|
|
1171
|
+
if (!posthogKey) return null;
|
|
1172
|
+
const posthogHost = (
|
|
1173
|
+
firstNonEmpty(
|
|
1174
|
+
env.POSTHOG_PUBLIC_HOST,
|
|
1175
|
+
env.VITE_POSTHOG_HOST,
|
|
1176
|
+
env.POSTHOG_HOST,
|
|
1177
|
+
) || "https://us.i.posthog.com"
|
|
1178
|
+
).replace(/\\/+$/, "");
|
|
1179
|
+
const config = {
|
|
1180
|
+
posthogKey,
|
|
1181
|
+
posthogHost,
|
|
1182
|
+
posthogErrorTracking:
|
|
1183
|
+
(env.POSTHOG_ERROR_TRACKING || "").trim().toLowerCase() !== "false",
|
|
1184
|
+
};
|
|
1185
|
+
return (
|
|
1186
|
+
'<script data-agent-native-posthog-config>' +
|
|
1187
|
+
'window.__AGENT_NATIVE_CONFIG__=Object.assign({},window.__AGENT_NATIVE_CONFIG__,' +
|
|
1188
|
+
JSON.stringify(config) +
|
|
1189
|
+
");</script>"
|
|
1190
|
+
);
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1160
1193
|
function getRealtimeClientConfigScript() {
|
|
1161
1194
|
// MUST stay byte-for-byte consistent with resolveRealtimeClientConfig in
|
|
1162
1195
|
// server/sentry-config.ts (worker bundles a string copy; it can't import it).
|
|
@@ -1330,7 +1363,11 @@ function applyImmutableAssetCacheHeaders(response, request) {
|
|
|
1330
1363
|
|
|
1331
1364
|
async function rewriteMountedResponse(response, basePath, pathname, request) {
|
|
1332
1365
|
const clientConfigScript =
|
|
1333
|
-
[
|
|
1366
|
+
[
|
|
1367
|
+
getSentryClientConfigScript(),
|
|
1368
|
+
getPostHogClientConfigScript(),
|
|
1369
|
+
getRealtimeClientConfigScript(),
|
|
1370
|
+
]
|
|
1334
1371
|
.filter(Boolean)
|
|
1335
1372
|
.join("") || null;
|
|
1336
1373
|
const headers = new Headers(response.headers);
|