@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
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* automatically by the core-routes plugin).
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
import { reshapeTrackedExceptionProperties } from "./posthog-exception.js";
|
|
16
17
|
import { registerTrackingProvider } from "./registry.js";
|
|
17
18
|
import type { TrackingProvider, TrackingEvent } from "./types.js";
|
|
18
19
|
|
|
@@ -162,27 +163,61 @@ function isPostHogAiObservabilityEvent(eventName: string): boolean {
|
|
|
162
163
|
return eventName.startsWith("$ai_");
|
|
163
164
|
}
|
|
164
165
|
|
|
165
|
-
|
|
166
|
+
/**
|
|
167
|
+
* `$ai_*` and `$exception` are ingested through PostHog's dedicated endpoint
|
|
168
|
+
* rather than `/capture/`, and `$exception` additionally has to carry
|
|
169
|
+
* `$exception_list` — the framework's own `captureException()` emits camelCase
|
|
170
|
+
* fields that PostHog would otherwise render as an empty, ungroupable issue.
|
|
171
|
+
*/
|
|
172
|
+
function createPostHogProvider(
|
|
173
|
+
apiKey: string,
|
|
174
|
+
host: string,
|
|
175
|
+
errorTracking: boolean,
|
|
176
|
+
): TrackingProvider {
|
|
177
|
+
const sendToEventsEndpoint = (
|
|
178
|
+
event: TrackingEvent,
|
|
179
|
+
properties: Record<string, unknown> | undefined,
|
|
180
|
+
distinctId: string,
|
|
181
|
+
): void => {
|
|
182
|
+
enqueue(
|
|
183
|
+
`${host}/i/v0/e/`,
|
|
184
|
+
JSON.stringify({
|
|
185
|
+
api_key: apiKey,
|
|
186
|
+
event: event.name,
|
|
187
|
+
properties: {
|
|
188
|
+
distinct_id: distinctId,
|
|
189
|
+
...properties,
|
|
190
|
+
timestamp: event.timestamp,
|
|
191
|
+
},
|
|
192
|
+
}),
|
|
193
|
+
);
|
|
194
|
+
};
|
|
195
|
+
|
|
166
196
|
return {
|
|
167
197
|
name: "posthog",
|
|
168
198
|
track(event: TrackingEvent) {
|
|
169
199
|
const distinctId = event.userId || "anonymous";
|
|
170
200
|
if (isPostHogAiObservabilityEvent(event.name)) {
|
|
171
|
-
|
|
172
|
-
`${host}/i/v0/e/`,
|
|
173
|
-
JSON.stringify({
|
|
174
|
-
api_key: apiKey,
|
|
175
|
-
event: event.name,
|
|
176
|
-
properties: {
|
|
177
|
-
distinct_id: distinctId,
|
|
178
|
-
...event.properties,
|
|
179
|
-
timestamp: event.timestamp,
|
|
180
|
-
},
|
|
181
|
-
}),
|
|
182
|
-
);
|
|
201
|
+
sendToEventsEndpoint(event, event.properties, distinctId);
|
|
183
202
|
return;
|
|
184
203
|
}
|
|
185
204
|
|
|
205
|
+
if (event.name === "$exception") {
|
|
206
|
+
// `POSTHOG_ERROR_TRACKING=false` keeps product analytics flowing while
|
|
207
|
+
// another backend owns crashes. Drop rather than downgrade to
|
|
208
|
+
// `/capture/`: a malformed exception is what this branch exists to
|
|
209
|
+
// prevent.
|
|
210
|
+
if (!errorTracking) return;
|
|
211
|
+
const reshaped = reshapeTrackedExceptionProperties(event.properties);
|
|
212
|
+
if (reshaped) {
|
|
213
|
+
sendToEventsEndpoint(event, reshaped, distinctId);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
// No recognizable exception fields. Fall through to `/capture/` so the
|
|
217
|
+
// event is still recorded as-is rather than becoming an issue with
|
|
218
|
+
// nothing in it.
|
|
219
|
+
}
|
|
220
|
+
|
|
186
221
|
enqueue(
|
|
187
222
|
`${host}/capture/`,
|
|
188
223
|
JSON.stringify({
|
|
@@ -213,6 +248,42 @@ function createPostHogProvider(apiKey: string, host: string): TrackingProvider {
|
|
|
213
248
|
};
|
|
214
249
|
}
|
|
215
250
|
|
|
251
|
+
/**
|
|
252
|
+
* Send one event to PostHog only, bypassing the provider fan-out.
|
|
253
|
+
*
|
|
254
|
+
* For payloads whose *shape* is PostHog-specific and whose *content* other
|
|
255
|
+
* backends must not receive. `track()` broadcasts to every configured provider,
|
|
256
|
+
* so a PostHog-only integration (e.g. enabling a survey id) would otherwise
|
|
257
|
+
* start exporting that integration's content — including user-authored text —
|
|
258
|
+
* to Mixpanel, Amplitude, webhooks, and Agent Native Analytics as a side
|
|
259
|
+
* effect nobody opted into.
|
|
260
|
+
*
|
|
261
|
+
* Returns `false` when PostHog is not configured, so callers can tell "not
|
|
262
|
+
* sent" from "sent".
|
|
263
|
+
*/
|
|
264
|
+
export function sendPostHogEvent(
|
|
265
|
+
name: string,
|
|
266
|
+
properties: Record<string, unknown>,
|
|
267
|
+
distinctId: string,
|
|
268
|
+
): boolean {
|
|
269
|
+
const apiKey = process.env.POSTHOG_API_KEY;
|
|
270
|
+
if (!apiKey) return false;
|
|
271
|
+
const host = (process.env.POSTHOG_HOST || POSTHOG_DEFAULT_HOST).replace(
|
|
272
|
+
/\/+$/,
|
|
273
|
+
"",
|
|
274
|
+
);
|
|
275
|
+
enqueue(
|
|
276
|
+
`${host}/capture/`,
|
|
277
|
+
JSON.stringify({
|
|
278
|
+
api_key: apiKey,
|
|
279
|
+
event: name,
|
|
280
|
+
distinct_id: distinctId,
|
|
281
|
+
properties: { ...properties, timestamp: new Date().toISOString() },
|
|
282
|
+
}),
|
|
283
|
+
);
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
|
|
216
287
|
// ─── Mixpanel ──────────────────────────────────────────────────────────────
|
|
217
288
|
|
|
218
289
|
function createMixpanelProvider(token: string): TrackingProvider {
|
|
@@ -383,7 +454,13 @@ export function registerBuiltinProviders(): void {
|
|
|
383
454
|
/\/+$/,
|
|
384
455
|
"",
|
|
385
456
|
);
|
|
386
|
-
registerTrackingProvider(
|
|
457
|
+
registerTrackingProvider(
|
|
458
|
+
createPostHogProvider(
|
|
459
|
+
posthogKey,
|
|
460
|
+
host,
|
|
461
|
+
process.env.POSTHOG_ERROR_TRACKING?.trim().toLowerCase() !== "false",
|
|
462
|
+
),
|
|
463
|
+
);
|
|
387
464
|
}
|
|
388
465
|
|
|
389
466
|
const mixpanelToken = process.env.MIXPANEL_TOKEN;
|
|
@@ -0,0 +1,105 @@
|
|
|
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
|
+
|
|
9
|
+
export const MAX_MESSAGE_LENGTH = 1000;
|
|
10
|
+
export const MAX_STACK_LENGTH = 8000;
|
|
11
|
+
export const MAX_TAGS = 30;
|
|
12
|
+
export const MAX_EXTRA_KEYS = 30;
|
|
13
|
+
export const MAX_EXTRA_VALUE_LENGTH = 1000;
|
|
14
|
+
|
|
15
|
+
const SECRET_RE = /\b(?:bearer|basic)\s+[^\s]+/gi;
|
|
16
|
+
|
|
17
|
+
export const SECRET_KEY_RE =
|
|
18
|
+
/(?:authorization|cookie|set[-_]?cookie|token|secret|password|passwd|pwd|api[-_]?key|apikey|credential)/i;
|
|
19
|
+
|
|
20
|
+
export function redact(value: string): string {
|
|
21
|
+
return value
|
|
22
|
+
.replace(SECRET_RE, (match) => `${match.split(/\s+/, 1)[0]} <redacted>`)
|
|
23
|
+
.replace(
|
|
24
|
+
/([A-Za-z0-9_$.-]*(?:authorization|cookie|token|secret|password|passwd|pwd|api[-_]?key|apikey|credential)[A-Za-z0-9_$.-]*\s*[:=]\s*)([^\s,;}]+)/gi,
|
|
25
|
+
"$1<redacted>",
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function boundedText(value: unknown, max: number): string {
|
|
30
|
+
const text = typeof value === "string" ? value : String(value ?? "");
|
|
31
|
+
const safe = redact(text);
|
|
32
|
+
return safe.length > max ? safe.slice(0, max) : safe;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function safeValue(value: unknown, depth = 2): unknown {
|
|
36
|
+
if (
|
|
37
|
+
value == null ||
|
|
38
|
+
typeof value === "boolean" ||
|
|
39
|
+
typeof value === "number"
|
|
40
|
+
) {
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
if (typeof value === "string")
|
|
44
|
+
return boundedText(value, MAX_EXTRA_VALUE_LENGTH);
|
|
45
|
+
if (depth <= 0) return boundedText(value, MAX_EXTRA_VALUE_LENGTH);
|
|
46
|
+
if (Array.isArray(value)) {
|
|
47
|
+
return value.slice(0, 20).map((item) => safeValue(item, depth - 1));
|
|
48
|
+
}
|
|
49
|
+
if (typeof value === "object") {
|
|
50
|
+
const out: Record<string, unknown> = {};
|
|
51
|
+
for (const [key, child] of Object.entries(value)) {
|
|
52
|
+
if (Object.keys(out).length >= MAX_EXTRA_KEYS) break;
|
|
53
|
+
const safeKey = boundedText(key, 100);
|
|
54
|
+
out[safeKey] = SECRET_KEY_RE.test(safeKey)
|
|
55
|
+
? "<redacted>"
|
|
56
|
+
: safeValue(child, depth - 1);
|
|
57
|
+
}
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
return boundedText(value, MAX_EXTRA_VALUE_LENGTH);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function safeTags(
|
|
64
|
+
tags: Record<string, string | undefined> | undefined,
|
|
65
|
+
): Record<string, string> {
|
|
66
|
+
const out: Record<string, string> = {};
|
|
67
|
+
for (const [key, value] of Object.entries(tags ?? {})) {
|
|
68
|
+
if (Object.keys(out).length >= MAX_TAGS) break;
|
|
69
|
+
// Skip, don't stop: callers build tag objects with optional entries
|
|
70
|
+
// (`{ ...tags, route, method, userAgent }`), so breaking on the first
|
|
71
|
+
// undefined dropped every tag after it depending on key order.
|
|
72
|
+
if (value == null) continue;
|
|
73
|
+
const safeKey = boundedText(key, 100);
|
|
74
|
+
out[safeKey] = SECRET_KEY_RE.test(safeKey)
|
|
75
|
+
? "<redacted>"
|
|
76
|
+
: boundedText(value, 200);
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface ExceptionParts {
|
|
82
|
+
type: string;
|
|
83
|
+
message: string;
|
|
84
|
+
stack?: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Split an unknown thrown value into bounded, redacted type/message/stack. */
|
|
88
|
+
export function exceptionParts(error: unknown): ExceptionParts {
|
|
89
|
+
if (error instanceof Error) {
|
|
90
|
+
return {
|
|
91
|
+
type: boundedText(error.name || "Error", 200),
|
|
92
|
+
message: boundedText(
|
|
93
|
+
error.message || error.name || "Error",
|
|
94
|
+
MAX_MESSAGE_LENGTH,
|
|
95
|
+
),
|
|
96
|
+
...(error.stack
|
|
97
|
+
? { stack: boundedText(error.stack, MAX_STACK_LENGTH) }
|
|
98
|
+
: {}),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
type: "Error",
|
|
103
|
+
message: boundedText(error, MAX_MESSAGE_LENGTH),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { getDialect, type Dialect } from "@agent-native/core/db";
|
|
2
|
+
|
|
1
3
|
export function chunks<T>(items: T[], size: number): T[][] {
|
|
2
4
|
if (items.length === 0) return [];
|
|
3
5
|
const out: T[][] = [];
|
|
@@ -6,3 +8,34 @@ export function chunks<T>(items: T[], size: number): T[][] {
|
|
|
6
8
|
}
|
|
7
9
|
return out;
|
|
8
10
|
}
|
|
11
|
+
|
|
12
|
+
export function bulkChunkSizeForColumnCount(
|
|
13
|
+
columnCount: number,
|
|
14
|
+
dialect: Dialect = getDialect(),
|
|
15
|
+
) {
|
|
16
|
+
// D1 rejects statements with more than 100 bound params, so derive every
|
|
17
|
+
// bulk chunk from the statement's column count instead of a fixed row count.
|
|
18
|
+
const budget = dialect === "d1" ? 90 : 900;
|
|
19
|
+
return Math.max(1, Math.floor(budget / Math.max(1, columnCount)));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function processWithConcurrency<T>(
|
|
23
|
+
items: readonly T[],
|
|
24
|
+
concurrency: number,
|
|
25
|
+
worker: (item: T) => Promise<void>,
|
|
26
|
+
) {
|
|
27
|
+
let nextIndex = 0;
|
|
28
|
+
const workerCount = Math.min(
|
|
29
|
+
items.length,
|
|
30
|
+
Math.max(1, Math.floor(concurrency)),
|
|
31
|
+
);
|
|
32
|
+
await Promise.all(
|
|
33
|
+
Array.from({ length: workerCount }, async () => {
|
|
34
|
+
while (nextIndex < items.length) {
|
|
35
|
+
const item = items[nextIndex];
|
|
36
|
+
nextIndex += 1;
|
|
37
|
+
if (item !== undefined) await worker(item);
|
|
38
|
+
}
|
|
39
|
+
}),
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -163,6 +163,7 @@ type BuilderMcpToolResult = {
|
|
|
163
163
|
const BUILDER_CMS_DEFAULT_READ_LIMIT = 500;
|
|
164
164
|
const BUILDER_CMS_MAX_READ_LIMIT = 10_000;
|
|
165
165
|
const BUILDER_CMS_PAGE_SIZE = 100;
|
|
166
|
+
const BUILDER_CMS_PROJECTED_READ_CONCURRENCY = 8;
|
|
166
167
|
const BUILDER_CMS_READ_RETRIES = 2;
|
|
167
168
|
const BUILDER_CMS_METADATA_ENTRY_FIELD_PATHS = [
|
|
168
169
|
"id",
|
|
@@ -192,6 +193,10 @@ const BUILDER_CMS_HEAVY_BODY_FIELD_PATHS = [
|
|
|
192
193
|
const BUILDER_CMS_FIELD_PATH_PATTERN =
|
|
193
194
|
/^[A-Za-z0-9_$-]+(?:\.[A-Za-z0-9_$-]+)*$/;
|
|
194
195
|
|
|
196
|
+
type BuilderCmsContentPageResult =
|
|
197
|
+
| { pageLimit: number; pageEntries: BuilderCmsSourceEntry[] }
|
|
198
|
+
| { pageLimit: number; error: string };
|
|
199
|
+
|
|
195
200
|
function normalizeBuilderCmsListFieldPath(fieldPath: string) {
|
|
196
201
|
const trimmed = fieldPath.trim();
|
|
197
202
|
if (!trimmed || !BUILDER_CMS_FIELD_PATH_PATTERN.test(trimmed)) return null;
|
|
@@ -218,6 +223,36 @@ function normalizeBuilderCmsListFieldPath(fieldPath: string) {
|
|
|
218
223
|
return normalized;
|
|
219
224
|
}
|
|
220
225
|
|
|
226
|
+
function preserveProjectedBuilderFieldAbsence(
|
|
227
|
+
read: BuilderCmsReadResult,
|
|
228
|
+
fieldPaths: readonly string[] | undefined,
|
|
229
|
+
rawData: boolean | undefined,
|
|
230
|
+
): BuilderCmsReadResult {
|
|
231
|
+
if (read.state !== "live" || rawData === true || !fieldPaths?.length) {
|
|
232
|
+
return read;
|
|
233
|
+
}
|
|
234
|
+
const projectedFieldPaths = [
|
|
235
|
+
...new Set(
|
|
236
|
+
fieldPaths
|
|
237
|
+
.map(normalizeBuilderCmsListFieldPath)
|
|
238
|
+
.filter((fieldPath): fieldPath is string => fieldPath !== null),
|
|
239
|
+
),
|
|
240
|
+
];
|
|
241
|
+
if (projectedFieldPaths.length === 0) return read;
|
|
242
|
+
return {
|
|
243
|
+
...read,
|
|
244
|
+
entries: read.entries.map((entry) => {
|
|
245
|
+
const sourceValues = { ...entry.sourceValues };
|
|
246
|
+
for (const fieldPath of projectedFieldPaths) {
|
|
247
|
+
if (!Object.prototype.hasOwnProperty.call(sourceValues, fieldPath)) {
|
|
248
|
+
sourceValues[fieldPath] = null;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return { ...entry, sourceValues };
|
|
252
|
+
}),
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
221
256
|
export function builderCmsListEntryFields(fieldPaths: readonly string[] = []) {
|
|
222
257
|
const fields = new Map<string, string>();
|
|
223
258
|
for (const fieldPath of [
|
|
@@ -752,6 +787,7 @@ async function initializeBuilderMcp(args: {
|
|
|
752
787
|
async function readBuilderCmsContentEntriesViaMcp(args: {
|
|
753
788
|
model: string;
|
|
754
789
|
fieldPaths?: readonly string[];
|
|
790
|
+
includeBodies?: boolean;
|
|
755
791
|
rawData?: boolean;
|
|
756
792
|
limit?: number;
|
|
757
793
|
maxPages?: number;
|
|
@@ -775,7 +811,9 @@ async function readBuilderCmsContentEntriesViaMcp(args: {
|
|
|
775
811
|
const contentEntries: BuilderCmsSourceEntry[] = [];
|
|
776
812
|
const fields = args.rawData
|
|
777
813
|
? undefined
|
|
778
|
-
:
|
|
814
|
+
: args.includeBodies
|
|
815
|
+
? `${builderCmsListEntryFields(args.fieldPaths)},${BUILDER_CMS_HEAVY_BODY_FIELD_PATHS.join(",")}`
|
|
816
|
+
: builderCmsListEntryFields(args.fieldPaths);
|
|
779
817
|
const seenContentIds = new Set<string>();
|
|
780
818
|
let pagesRead = 0;
|
|
781
819
|
let hasMore = false;
|
|
@@ -941,6 +979,8 @@ async function readBuilderCmsContentEntriesViaMcp(args: {
|
|
|
941
979
|
async function readBuilderCmsContentEntriesViaContentApi(args: {
|
|
942
980
|
model: string;
|
|
943
981
|
fieldPaths?: readonly string[];
|
|
982
|
+
includeBodies?: boolean;
|
|
983
|
+
allowCached?: boolean;
|
|
944
984
|
rawData?: boolean;
|
|
945
985
|
limit?: number;
|
|
946
986
|
maxPages?: number;
|
|
@@ -959,14 +999,21 @@ async function readBuilderCmsContentEntriesViaContentApi(args: {
|
|
|
959
999
|
// Fidelity reads intentionally do neither: Builder's raw data object is the
|
|
960
1000
|
// clone contract, including unresolved references and every nested field.
|
|
961
1001
|
url.searchParams.set("enrich", args.rawData === true ? "false" : "true");
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1002
|
+
if (args.allowCached !== true || args.rawData === true) {
|
|
1003
|
+
url.searchParams.set("noCache", "true");
|
|
1004
|
+
url.searchParams.set(
|
|
1005
|
+
"cachebust",
|
|
1006
|
+
args.rawData === true ? String(Date.now()) : "true",
|
|
1007
|
+
);
|
|
1008
|
+
}
|
|
967
1009
|
url.searchParams.set("includeUnpublished", "true");
|
|
968
1010
|
if (args.rawData !== true) {
|
|
969
|
-
url.searchParams.set(
|
|
1011
|
+
url.searchParams.set(
|
|
1012
|
+
"fields",
|
|
1013
|
+
args.includeBodies
|
|
1014
|
+
? `${builderCmsListEntryFields(args.fieldPaths)},${BUILDER_CMS_HEAVY_BODY_FIELD_PATHS.join(",")}`
|
|
1015
|
+
: builderCmsListEntryFields(args.fieldPaths),
|
|
1016
|
+
);
|
|
970
1017
|
}
|
|
971
1018
|
|
|
972
1019
|
const limit = readLimit(args.limit);
|
|
@@ -978,73 +1025,103 @@ async function readBuilderCmsContentEntriesViaContentApi(args: {
|
|
|
978
1025
|
const seenIds = new Set<string>();
|
|
979
1026
|
let pagesRead = 0;
|
|
980
1027
|
let hasMore = false;
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
1028
|
+
const concurrency =
|
|
1029
|
+
args.rawData === true ? 1 : BUILDER_CMS_PROJECTED_READ_CONCURRENCY;
|
|
1030
|
+
const errorResult = (message: string): BuilderCmsReadResult => ({
|
|
1031
|
+
state: "error",
|
|
1032
|
+
entries: [],
|
|
1033
|
+
fetchedAt,
|
|
1034
|
+
message,
|
|
1035
|
+
progress: {
|
|
1036
|
+
requestedLimit: limit,
|
|
1037
|
+
pageSize: BUILDER_CMS_PAGE_SIZE,
|
|
1038
|
+
startOffset,
|
|
1039
|
+
nextOffset: startOffset + entries.length,
|
|
1040
|
+
fetchedEntryCount: startOffset + entries.length,
|
|
1041
|
+
hasMore,
|
|
1042
|
+
partial: Boolean(args.maxPages) && hasMore,
|
|
1043
|
+
readMode: "builder-api",
|
|
1044
|
+
},
|
|
1045
|
+
});
|
|
990
1046
|
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1047
|
+
while (entries.length < limit) {
|
|
1048
|
+
const pagesRemaining = args.maxPages
|
|
1049
|
+
? args.maxPages - pagesRead
|
|
1050
|
+
: Number.POSITIVE_INFINITY;
|
|
1051
|
+
if (pagesRemaining <= 0) break;
|
|
1052
|
+
const pageCount = Math.min(
|
|
1053
|
+
pagesRead === 0 ? 1 : concurrency,
|
|
1054
|
+
pagesRemaining,
|
|
1055
|
+
Math.ceil((limit - entries.length) / BUILDER_CMS_PAGE_SIZE),
|
|
1056
|
+
);
|
|
1057
|
+
const pageRequests = Array.from({ length: pageCount }, (_, index) => {
|
|
1058
|
+
const pageUrl = new URL(url);
|
|
1059
|
+
const pageLimit = readPageLimit(
|
|
1060
|
+
limit - entries.length - index * BUILDER_CMS_PAGE_SIZE,
|
|
1061
|
+
);
|
|
1062
|
+
pageUrl.searchParams.set("limit", String(pageLimit));
|
|
1063
|
+
pageUrl.searchParams.set(
|
|
1064
|
+
"offset",
|
|
1065
|
+
String(startOffset + (pagesRead + index) * BUILDER_CMS_PAGE_SIZE),
|
|
1066
|
+
);
|
|
1067
|
+
return { pageLimit, pageUrl };
|
|
1068
|
+
});
|
|
1069
|
+
const pageResults = await Promise.all(
|
|
1070
|
+
pageRequests.map(
|
|
1071
|
+
async ({
|
|
1072
|
+
pageLimit,
|
|
1073
|
+
pageUrl,
|
|
1074
|
+
}): Promise<BuilderCmsContentPageResult> => {
|
|
1075
|
+
try {
|
|
1076
|
+
const response = await fetchBuilderContentPage({
|
|
1077
|
+
fetchImpl: args.fetchImpl,
|
|
1078
|
+
url: pageUrl,
|
|
1079
|
+
privateKey: args.privateKey,
|
|
1080
|
+
});
|
|
1081
|
+
if (!response.ok) {
|
|
1082
|
+
return {
|
|
1083
|
+
pageLimit,
|
|
1084
|
+
error: `Builder CMS read failed with HTTP ${response.status}.`,
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
const json = (await response.json()) as unknown;
|
|
1088
|
+
const pageEntries = entryArrayFromResponse(json)
|
|
1089
|
+
.map((entry) => normalizeBuilderCmsApiEntry(entry, args.model))
|
|
1090
|
+
.filter((entry): entry is BuilderCmsSourceEntry =>
|
|
1091
|
+
Boolean(entry),
|
|
1092
|
+
);
|
|
1093
|
+
return { pageLimit, pageEntries };
|
|
1094
|
+
} catch (error) {
|
|
1095
|
+
return {
|
|
1096
|
+
pageLimit,
|
|
1097
|
+
error:
|
|
1098
|
+
error instanceof Error
|
|
1099
|
+
? `Builder CMS read failed: ${error.message}`
|
|
1100
|
+
: "Builder CMS read failed.",
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1016
1103
|
},
|
|
1017
|
-
|
|
1018
|
-
|
|
1104
|
+
),
|
|
1105
|
+
);
|
|
1019
1106
|
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
};
|
|
1107
|
+
let stoppedOnShortPage = false;
|
|
1108
|
+
for (const pageResult of pageResults) {
|
|
1109
|
+
if ("error" in pageResult) return errorResult(pageResult.error);
|
|
1110
|
+
const appended = appendUniqueBuilderEntries(
|
|
1111
|
+
entries,
|
|
1112
|
+
seenIds,
|
|
1113
|
+
pageResult.pageEntries,
|
|
1114
|
+
);
|
|
1115
|
+
pagesRead += 1;
|
|
1116
|
+
hasMore =
|
|
1117
|
+
pageResult.pageEntries.length >= pageResult.pageLimit && appended > 0;
|
|
1118
|
+
if (args.maxPages && pagesRead >= args.maxPages) break;
|
|
1119
|
+
if (!hasMore) {
|
|
1120
|
+
stoppedOnShortPage = true;
|
|
1121
|
+
break;
|
|
1122
|
+
}
|
|
1037
1123
|
}
|
|
1038
|
-
|
|
1039
|
-
const json = (await response.json()) as unknown;
|
|
1040
|
-
const pageEntries = entryArrayFromResponse(json)
|
|
1041
|
-
.map((entry) => normalizeBuilderCmsApiEntry(entry, args.model))
|
|
1042
|
-
.filter((entry): entry is BuilderCmsSourceEntry => Boolean(entry));
|
|
1043
|
-
const appended = appendUniqueBuilderEntries(entries, seenIds, pageEntries);
|
|
1044
|
-
pagesRead += 1;
|
|
1045
|
-
hasMore = pageEntries.length >= pageLimit && appended > 0;
|
|
1046
|
-
if (args.maxPages && pagesRead >= args.maxPages) break;
|
|
1047
|
-
if (!hasMore) break;
|
|
1124
|
+
if (stoppedOnShortPage || !hasMore) break;
|
|
1048
1125
|
}
|
|
1049
1126
|
|
|
1050
1127
|
return {
|
|
@@ -1231,6 +1308,8 @@ export async function readBuilderCmsModelFields(args: {
|
|
|
1231
1308
|
export async function readBuilderCmsContentEntries(args: {
|
|
1232
1309
|
model: string;
|
|
1233
1310
|
fieldPaths?: readonly string[];
|
|
1311
|
+
includeBodies?: boolean;
|
|
1312
|
+
allowCached?: boolean;
|
|
1234
1313
|
rawData?: boolean;
|
|
1235
1314
|
requirePrivateKey?: boolean;
|
|
1236
1315
|
limit?: number;
|
|
@@ -1265,6 +1344,8 @@ export async function readBuilderCmsContentEntries(args: {
|
|
|
1265
1344
|
const contentApiRead = await readBuilderCmsContentEntriesViaContentApi({
|
|
1266
1345
|
model: args.model,
|
|
1267
1346
|
fieldPaths: args.fieldPaths,
|
|
1347
|
+
includeBodies: args.includeBodies,
|
|
1348
|
+
allowCached: args.allowCached,
|
|
1268
1349
|
rawData: args.rawData,
|
|
1269
1350
|
limit: args.limit,
|
|
1270
1351
|
maxPages: args.maxPages,
|
|
@@ -1273,22 +1354,33 @@ export async function readBuilderCmsContentEntries(args: {
|
|
|
1273
1354
|
publicKey,
|
|
1274
1355
|
privateKey: privateKey ?? undefined,
|
|
1275
1356
|
});
|
|
1276
|
-
if (contentApiRead.state === "live")
|
|
1357
|
+
if (contentApiRead.state === "live") {
|
|
1358
|
+
return preserveProjectedBuilderFieldAbsence(
|
|
1359
|
+
contentApiRead,
|
|
1360
|
+
args.fieldPaths,
|
|
1361
|
+
args.rawData,
|
|
1362
|
+
);
|
|
1363
|
+
}
|
|
1277
1364
|
if (!privateKey) return contentApiRead;
|
|
1278
1365
|
}
|
|
1279
1366
|
|
|
1280
1367
|
if (privateKey) {
|
|
1281
1368
|
try {
|
|
1282
|
-
return
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1369
|
+
return preserveProjectedBuilderFieldAbsence(
|
|
1370
|
+
await readBuilderCmsContentEntriesViaMcp({
|
|
1371
|
+
model: args.model,
|
|
1372
|
+
fieldPaths: args.fieldPaths,
|
|
1373
|
+
includeBodies: args.includeBodies,
|
|
1374
|
+
rawData: args.rawData,
|
|
1375
|
+
limit: args.limit,
|
|
1376
|
+
maxPages: args.maxPages,
|
|
1377
|
+
offset: args.offset,
|
|
1378
|
+
fetchImpl,
|
|
1379
|
+
privateKey,
|
|
1380
|
+
}),
|
|
1381
|
+
args.fieldPaths,
|
|
1382
|
+
args.rawData,
|
|
1383
|
+
);
|
|
1292
1384
|
} catch (error) {
|
|
1293
1385
|
return {
|
|
1294
1386
|
state: "error",
|